diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..a186cd2079e78c66fca5e037bafa0917818116df
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[compose.yaml]
+indent_size = 4
diff --git a/.env.docker b/.env.docker
new file mode 100644
index 0000000000000000000000000000000000000000..48c7ceb1f1f8a49740d8e664b3ad2c4b1277d2b7
--- /dev/null
+++ b/.env.docker
@@ -0,0 +1,36 @@
+APP_NAME=AILIXIR
+APP_ENV=production
+APP_KEY=base64:CHANGEME_GENERATE_ON_FIRST_BOOT=
+APP_DEBUG=false
+APP_URL=http://localhost:8080
+
+LOG_CHANNEL=stderr
+LOG_LEVEL=info
+
+DB_CONNECTION=sqlite
+DB_DATABASE=/var/www/html/database/ailixir.sqlite
+
+SESSION_DRIVER=database
+SESSION_LIFETIME=120
+
+CACHE_STORE=database
+QUEUE_CONNECTION=database
+
+# AI microservices (Docker internal DNS)
+GENERATION_SERVICE_URL=http://generation:8000
+CHEMICAL_AI_URL=http://chemical-rag:5000
+ADMET_AI_URL=http://admet:8000
+AI_ADMET_SERVICE_URL=http://admet:8000
+DRUG_REPURPOSING_URL=http://drug-repurposing:8000
+AI_SERVICE_URL=http://drug-repurposing:8000
+
+AI_INTEGRATION_ROUTES_ENABLED=true
+
+MAIL_MAILER=smtp
+MAIL_HOST=smtp.gmail.com
+MAIL_PORT=587
+MAIL_USERNAME=yasmeensaleh747@gmail.com
+MAIL_PASSWORD="fghu ijct vhfu xnud"
+MAIL_ENCRYPTION=tls
+MAIL_FROM_ADDRESS=yasmeensaleh747@gmail.com
+MAIL_FROM_NAME="AILIXIR"
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..e4e969c8759e129be71e72fdfd365cf7c7420bb4
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,16 @@
+# AILIXIR API Test Configuration
+# Copy this to .env and fill in your values
+
+# API URLs
+BASE_URL=https://america-hyperlipemic-grazyna.ngrok-free.dev/api
+AUTH_BASE_URL=https://america-hyperlipemic-grazyna.ngrok-free.dev/api
+
+# Test User Credentials
+TEST_EMAIL=your_email@example.com
+TEST_PASSWORD=your_password
+TEST_NAME=Your Name
+
+# Database (SQLite)
+DB_CONNECTION=sqlite
+DB_DATABASE=database/ailixir.sqlite
+MD_SIMULATION_URL=http://localhost:5005
\ No newline at end of file
diff --git a/.env.hf b/.env.hf
new file mode 100644
index 0000000000000000000000000000000000000000..eba9418e60f6ea3a6ee3dfec6874f844072b0e1b
--- /dev/null
+++ b/.env.hf
@@ -0,0 +1,37 @@
+# AILIXIR Hugging Face Spaces Environment
+# Copy the relevant values to HF Space secrets/settings
+
+APP_NAME=AILIXIR
+APP_ENV=production
+APP_DEBUG=false
+APP_URL=https://Ailixir-AI-Team-ailixir-api.hf.space
+
+LOG_CHANNEL=stderr
+LOG_LEVEL=info
+
+DB_CONNECTION=sqlite
+DB_DATABASE=/var/www/html/database/ailixir.sqlite
+
+SESSION_DRIVER=database
+SESSION_LIFETIME=120
+CACHE_STORE=database
+QUEUE_CONNECTION=database
+
+# AI Microservice URLs (HF Spaces)
+GENERATION_SERVICE_URL=https://shdwRow-ailixir-generation.hf.space
+CHEMICAL_AI_URL=https://RottenShadow-ailixir-chemical-rag.hf.space
+ADMET_AI_URL=https://shdwRow-ailixir-admet.hf.space
+AI_ADMET_SERVICE_URL=https://shdwRow-ailixir-admet.hf.space
+DRUG_REPURPOSING_URL=https://RottenShadow-ailixir-drug-repurposing.hf.space
+AI_SERVICE_URL=https://RottenShadow-ailixir-drug-repurposing.hf.space
+
+AI_INTEGRATION_ROUTES_ENABLED=true
+MAIL_MAILER=log
+
+CLOUDINARY_URL=cloudinary://dummy_key:dummy_secret@dummy_cloud
+CLOUDINARY_CLOUD_NAME=dummy_cloud
+CLOUDINARY_API_KEY=dummy_key
+CLOUDINARY_API_SECRET=dummy_secret
+
+# Hugging Face Spaces mode (triggers supervisord)
+RUN_MODE=hf
diff --git a/.gitattributes b/.gitattributes
index a6344aac8c09253b3b630fb776ae94478aa0275b..fcb21d396d657f597ef8b6729f73d89b0a871c9b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,35 +1,11 @@
-*.7z filter=lfs diff=lfs merge=lfs -text
-*.arrow filter=lfs diff=lfs merge=lfs -text
-*.bin filter=lfs diff=lfs merge=lfs -text
-*.bz2 filter=lfs diff=lfs merge=lfs -text
-*.ckpt filter=lfs diff=lfs merge=lfs -text
-*.ftz filter=lfs diff=lfs merge=lfs -text
-*.gz filter=lfs diff=lfs merge=lfs -text
-*.h5 filter=lfs diff=lfs merge=lfs -text
-*.joblib filter=lfs diff=lfs merge=lfs -text
-*.lfs.* filter=lfs diff=lfs merge=lfs -text
-*.mlmodel filter=lfs diff=lfs merge=lfs -text
-*.model filter=lfs diff=lfs merge=lfs -text
-*.msgpack filter=lfs diff=lfs merge=lfs -text
-*.npy filter=lfs diff=lfs merge=lfs -text
-*.npz filter=lfs diff=lfs merge=lfs -text
-*.onnx filter=lfs diff=lfs merge=lfs -text
-*.ot filter=lfs diff=lfs merge=lfs -text
-*.parquet filter=lfs diff=lfs merge=lfs -text
-*.pb filter=lfs diff=lfs merge=lfs -text
-*.pickle filter=lfs diff=lfs merge=lfs -text
-*.pkl filter=lfs diff=lfs merge=lfs -text
-*.pt filter=lfs diff=lfs merge=lfs -text
-*.pth filter=lfs diff=lfs merge=lfs -text
-*.rar filter=lfs diff=lfs merge=lfs -text
-*.safetensors filter=lfs diff=lfs merge=lfs -text
-saved_model/**/* filter=lfs diff=lfs merge=lfs -text
-*.tar.* filter=lfs diff=lfs merge=lfs -text
-*.tar filter=lfs diff=lfs merge=lfs -text
-*.tflite filter=lfs diff=lfs merge=lfs -text
-*.tgz filter=lfs diff=lfs merge=lfs -text
-*.wasm filter=lfs diff=lfs merge=lfs -text
-*.xz filter=lfs diff=lfs merge=lfs -text
-*.zip filter=lfs diff=lfs merge=lfs -text
-*.zst filter=lfs diff=lfs merge=lfs -text
-*tfevents* filter=lfs diff=lfs merge=lfs -text
+* text=auto eol=lf
+
+*.blade.php diff=html
+*.css diff=css
+*.html diff=html
+*.md diff=markdown
+*.php diff=php
+
+/.github export-ignore
+CHANGELOG.md export-ignore
+.styleci.yml export-ignore
diff --git a/.github/workflows/api-tests-hf.yml b/.github/workflows/api-tests-hf.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0f6030320fa22a5b88e9f366e5ca7ffff2513858
--- /dev/null
+++ b/.github/workflows/api-tests-hf.yml
@@ -0,0 +1,63 @@
+name: AILIXIR API Tests (HF Spaces)
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "0 6 * * *"
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install test dependencies
+ run: pip install -r requirements.txt
+
+ - name: Wake up HF Space
+ run: |
+ echo "Waiting for ailixir-api HF Space to be ready..."
+ for i in $(seq 1 60); do
+ if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then
+ echo "ailixir-api is up"
+ sleep 5
+ exit 0
+ fi
+ echo "Attempt $i/60: not ready yet..."
+ sleep 5
+ done
+ echo "ailixir-api failed to become healthy in time"
+ exit 1
+
+ - name: Run API tests
+ env:
+ BASE_URL: https://Ailixir-AI-Team-ailixir-api.hf.space/api
+ TEST_EMAIL: test@example.com
+ TEST_PASSWORD: password123
+ TEST_NAME: testuser
+ run: |
+ python -m pytest tests/ -v --tb=short --junitxml=junit.xml || true
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results-hf
+ path: junit.xml
+
+ - name: Test Summary
+ uses: test-summary/action@v2
+ with:
+ paths: junit.xml
+ if: always()
diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f931f34b13e1a3d1ce7451adcfd7f0234c0025ee
--- /dev/null
+++ b/.github/workflows/api-tests.yml
@@ -0,0 +1,135 @@
+name: AILIXIR API Tests (Docker)
+
+on:
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Pull and verify Git LFS files
+ run: |
+ git lfs version
+ git lfs pull
+ git lfs checkout
+ git lfs ls-files
+
+ python - <<'PY'
+ from pathlib import Path
+
+ paths = [
+ "ai_apps/generation/bundle/models/affinity/config.pkl",
+ "ai_apps/generation/bundle/models/affinity/model.pt",
+ "ai_apps/generation/bundle/models/generator/egfr_generator.chkpt",
+ "ai_apps/generation/bundle/models/generator/reinvent.prior",
+ ]
+
+ for p in paths:
+ path = Path(p)
+ if not path.exists():
+ raise SystemExit(f"Missing required artifact: {p}")
+
+ data = path.read_bytes()[:80]
+ size = path.stat().st_size
+
+ print(f"{p}: {size} bytes")
+
+ if data.startswith(b"version https://git-lfs.github.com"):
+ raise SystemExit(f"{p} is still a Git LFS pointer file")
+ PY
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Create .env file for Docker
+ run: |
+ cp docker/laravel.env .env
+
+ - name: Start Docker containers
+ run: |
+ docker compose up -d --build
+
+ - name: Wait for services to be healthy
+ run: |
+ echo "Waiting for Laravel API to be ready..."
+ for i in {1..60}; do
+ if docker exec ailixir-laravel curl -f http://127.0.0.1:8000 > /dev/null 2>&1; then
+ echo "Laravel API is ready"
+ break
+ fi
+ echo "Attempt $i/60: Laravel API not ready yet..."
+ sleep 3
+ done
+
+ echo "Waiting for ADMET service to be healthy..."
+ for i in {1..120}; do
+ if docker exec ailixir-admet python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" > /dev/null 2>&1; then
+ echo "ADMET service is healthy internally inside container"
+ echo "Waiting additional 10 seconds for service to fully initialize..."
+ sleep 10
+ break
+ fi
+ echo "Attempt $i/120: ADMET service not ready yet..."
+ sleep 5
+ done
+
+ echo "All services are up and running!"
+ docker compose ps
+
+ - name: Run database migrations
+ run: |
+ docker exec ailixir-laravel php artisan migrate --force
+
+ - name: Run API tests
+ env:
+ BASE_URL: http://localhost:8080/api
+ TEST_EMAIL: test@example.com
+ TEST_PASSWORD: password123
+ TEST_NAME: testuser
+ run: |
+ docker exec ailixir-laravel \
+ python -m pytest tests/ -v --tb=short --junitxml=junit.xml || true
+
+ - name: Copy test results from container
+ if: always()
+ run: |
+ docker cp ailixir-laravel:/var/www/html/junit.xml ./junit.xml || true
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results
+ path: junit.xml
+
+ - name: Test Summary
+ uses: test-summary/action@v2
+ with:
+ paths: "junit.xml"
+ if: always()
+
+ - name: Collect Docker logs
+ if: failure()
+ run: |
+ mkdir -p logs
+ docker compose logs laravel > logs/laravel.log
+ docker compose logs admet > logs/admet.log
+ docker compose logs drug-repurposing > logs/drug-repurposing.log
+ docker compose logs chemical-rag > logs/chemical-rag.log
+
+ - name: Upload Docker logs
+ uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: docker-logs
+ path: logs/
+
+ - name: Cleanup Docker containers
+ if: always()
+ run: |
+ docker compose down -v
diff --git a/.github/workflows/e2e-md-test.yml b/.github/workflows/e2e-md-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7cc2613bcb3b69e77a524270d89b888cc7c5c85f
--- /dev/null
+++ b/.github/workflows/e2e-md-test.yml
@@ -0,0 +1,139 @@
+name: MD Simulation E2E Test
+
+on:
+ push:
+ branches: [main, develop, MD-integration]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "0 6 * * *"
+ workflow_dispatch:
+
+concurrency:
+ group: e2e-md-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ e2e-md-simulation:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build Laravel image
+ run: docker build -t ailixir-laravel .
+
+ - name: Build MD simulation image
+ run: docker build -t md-simulation ai_apps/Protein-Ligand-MD-Simulation/
+
+ - name: Create Docker network
+ run: docker network create ailixir-md
+
+ - name: Start Redis
+ run: |
+ docker run -d --name md-redis \
+ --network ailixir-md \
+ --network-alias md-redis \
+ redis:7-alpine
+
+ - name: Start MD Simulation API
+ run: |
+ docker run -d --name md-api \
+ --network ailixir-md \
+ -p 5005:5005 \
+ -e REDIS_HOST=md-redis \
+ -e REDIS_PORT=6379 \
+ -e USE_REDIS=true \
+ -e OPENMM_PLATFORM=CPU \
+ -e FLASK_HOST=0.0.0.0 \
+ -e FLASK_PORT=5005 \
+ -e MD_DATA_DIR=/app/data \
+ -e LOG_LEVEL=INFO \
+ md-simulation
+
+ - name: Start Laravel API
+ run: |
+ docker run -d --name ailixir-laravel \
+ --network ailixir-md \
+ -p 8000:8000 \
+ --env-file docker/laravel.env \
+ -e MD_SIMULATION_URL=http://md-api:5005 \
+ -e QUEUE_CONNECTION=sync \
+ -e APP_KEY=base64:l+P8vPcH6w+2qV6vYC3h8K9xN0mR4sT5uW6xY7zA8bB= \
+ ailixir-laravel
+
+ - name: Wait for Laravel API to be ready
+ run: |
+ echo "Waiting for Laravel API to be ready..."
+ for i in $(seq 1 30); do
+ if curl -sf http://localhost:8000/ > /dev/null 2>&1; then
+ echo "Laravel API is ready"
+ exit 0
+ fi
+ echo "Waiting for Laravel ($i/30)..."
+ sleep 2
+ done
+ docker logs ailixir-laravel
+ exit 1
+
+ - name: Wait for MD Simulation API to be healthy
+ run: |
+ echo "Waiting for MD Simulation API to be healthy..."
+ for i in $(seq 1 60); do
+ if curl -sf http://localhost:5005/health > /dev/null 2>&1; then
+ echo "MD Simulation API is healthy"
+ exit 0
+ fi
+ echo "Waiting for MD API ($i/60)..."
+ sleep 5
+ done
+ docker logs md-api
+ exit 1
+
+ - name: Run MD Simulation E2E tests
+ run: |
+ docker exec \
+ -e MD_E2E_BASE_URL=http://localhost:8000/api \
+ ailixir-laravel \
+ ./vendor/bin/pest --filter=MdSimulationE2ETest --log-junit=/var/www/html/junit.xml
+
+ - name: Copy test results from container
+ if: always()
+ run: |
+ docker cp ailixir-laravel:/var/www/html/junit.xml ./junit.xml || true
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: e2e-md-test-results
+ path: junit.xml
+
+ - name: Test Summary
+ uses: test-summary/action@v2
+ if: always()
+ with:
+ paths: junit.xml
+
+ - name: Dump logs on failure
+ if: failure()
+ run: |
+ echo "=== Laravel logs ==="
+ docker logs ailixir-laravel || true
+ echo "=== MD API logs ==="
+ docker logs md-api || true
+ echo "=== Redis logs ==="
+ docker logs md-redis || true
+
+ - name: Cleanup
+ if: always()
+ run: |
+ docker rm -f ailixir-laravel md-api md-redis 2>/dev/null || true
+ docker network rm ailixir-md 2>/dev/null || true
diff --git a/.github/workflows/integration-test-hf.yml b/.github/workflows/integration-test-hf.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8637b333e49b8cb713d33262b265c095ad067a4e
--- /dev/null
+++ b/.github/workflows/integration-test-hf.yml
@@ -0,0 +1,492 @@
+name: HF Spaces Integration Test
+
+on:
+ push:
+ branches: [main, develop, "feature/**"]
+ pull_request:
+ branches: [main, develop]
+ workflow_dispatch:
+
+concurrency:
+ group: hf-integration-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ core-ai-proxy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Warm up ailixir-api Space
+ run: |
+ echo "Waiting for ailixir-api HF Space..."
+ for i in $(seq 1 60); do
+ if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then
+ echo "ailixir-api is up"
+ sleep 5
+ exit 0
+ fi
+ echo "Attempt $i/60..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Warm up ailixir-drug-repurposing Space
+ run: |
+ echo "Waiting for ailixir-drug-repurposing HF Space..."
+ for i in $(seq 1 60); do
+ if curl -sf https://RottenShadow-ailixir-drug-repurposing.hf.space/health > /dev/null 2>&1; then
+ echo "ailixir-drug-repurposing is up"
+ sleep 3
+ exit 0
+ fi
+ echo "Attempt $i/60..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Warm up ailixir-admet Space
+ run: |
+ echo "Waiting for ailixir-admet HF Space..."
+ for i in $(seq 1 60); do
+ if curl -sf https://shdwRow-ailixir-admet.hf.space/health > /dev/null 2>&1; then
+ echo "ailixir-admet is up"
+ sleep 3
+ exit 0
+ fi
+ echo "Attempt $i/60..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Warm up ailixir-chemical-rag Space
+ run: |
+ echo "Waiting for ailixir-chemical-rag HF Space..."
+ for i in $(seq 1 60); do
+ if curl -sf https://RottenShadow-ailixir-chemical-rag.hf.space/health > /dev/null 2>&1; then
+ echo "ailixir-chemical-rag is up"
+ sleep 3
+ exit 0
+ fi
+ echo "Attempt $i/60..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Login and get Bearer token
+ run: |
+ echo "POST /api/user/login"
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat login.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "Login failed with HTTP $HTTP_CODE"
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ try:
+ data = json.load(open("login.json"))
+ except json.JSONDecodeError as e:
+ print("Invalid JSON response:", e)
+ with open("login.json") as f:
+ print("Raw response:", f.read())
+ sys.exit(1)
+
+ if not data.get("success"):
+ print("Login failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ token = data.get("data", {}).get("token")
+ if not token:
+ print("No token in response:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ with open("token.txt", "w") as f:
+ f.write(token)
+ print("Login OK - token saved")
+ PY
+
+ - name: Test aggregated AI health via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "GET /api/ai-services/health"
+ HTTP_CODE=$(curl -s -o health.json -w "%{http_code}" https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/health \
+ -H "Authorization: Bearer $TOKEN")
+ echo "HTTP Status: $HTTP_CODE"
+ cat health.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("health.json"))
+ if not data.get("success"):
+ print("AI services health check failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("All AI services healthy via Laravel proxy")
+ PY
+
+ - name: Test ADMET prediction via Laravel proxy
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/admet/predict"
+ HTTP_CODE=$(curl -s -o admet.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/admet/predict \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles": "c1ccccc1, CCO, CCC"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat admet.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "ADMET prediction failed with HTTP $HTTP_CODE"
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ try:
+ data = json.load(open("admet.json"))
+ except json.JSONDecodeError as e:
+ print("Invalid JSON response:", e)
+ with open("admet.json") as f:
+ print("Raw response:", f.read())
+ sys.exit(1)
+
+ if not data.get("success"):
+ print("ADMET prediction test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ results = data.get("data", {}).get("results", [])
+ if len(results) < 3:
+ print(f"Expected at least 3 results, got {len(results)}")
+ sys.exit(1)
+
+ print(f"ADMET prediction OK - {len(results)} compounds processed")
+ PY
+
+ - name: Test Chemical RAG proxy via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/ai-services/test/chemical-search"
+ HTTP_CODE=$(curl -s -o chemical.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/test/chemical-search \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "malaria drug"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat chemical.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("chemical.json"))
+ if not data.get("success"):
+ print("Chemical RAG proxy test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("Chemical RAG proxy OK")
+ PY
+
+ - name: Test Drug Repurposing proxy via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "GET /api/ai-services/test/drug-repurposing"
+ HTTP_CODE=$(curl -s -o drug.json -w "%{http_code}" https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/test/drug-repurposing \
+ -H "Authorization: Bearer $TOKEN")
+ echo "HTTP Status: $HTTP_CODE"
+ cat drug.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("drug.json"))
+ if not data.get("success"):
+ print("Drug Repurposing proxy test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("Drug Repurposing proxy OK")
+ PY
+
+ chemical-search:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Warm up ailixir-api Space
+ run: |
+ for i in $(seq 1 60); do
+ if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then
+ echo "ailixir-api is up"
+ sleep 5
+ exit 0
+ fi
+ echo "Waiting ($i/60)..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Login and get Bearer token
+ run: |
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ if [ "$HTTP_CODE" != "200" ]; then exit 1; fi
+ python3 - <<'PY'
+ import json
+ with open('login.json') as f: data = json.load(f)
+ token = data.get('data', {}).get('token')
+ if not token: exit(1)
+ with open('token.txt','w') as f: f.write(token)
+ print('Login OK')
+ PY
+
+ - name: Test retrieval-only chemical search
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/chemical-search (retrieval-only)"
+ HTTP_CODE=$(curl -s -o search.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/chemical-search \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles":"CC(=O)O"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat search.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("search.json"))
+ if not data.get("success"):
+ print("Chemical search (retrieval-only) failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ if not isinstance(data.get("compounds"), list):
+ print("Missing 'compounds' array in response")
+ sys.exit(1)
+ if data.get("metadata", {}).get("source") != "retrieval":
+ print("Expected source='retrieval', got:", data.get("metadata", {}).get("source"))
+ sys.exit(1)
+ print("Chemical search (retrieval-only) OK - found", len(data["compounds"]), "compounds")
+ PY
+
+ - name: Test full RAG chemical search
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/chemical-search/full-rag"
+ HTTP_CODE=$(curl -s -o rag.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/chemical-search/full-rag \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles":"CC(=O)O"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat rag.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("rag.json"))
+ if not data.get("success"):
+ print("Chemical search (full RAG) failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ if not isinstance(data.get("compounds"), list):
+ print("Missing 'compounds' array in response")
+ sys.exit(1)
+ if data.get("metadata", {}).get("source") != "full_rag":
+ print("Expected source='full_rag', got:", data.get("metadata", {}).get("source"))
+ sys.exit(1)
+ for compound in data["compounds"]:
+ if not compound.get("explanation"):
+ print("Missing explanation for compound:", compound.get("name"))
+ sys.exit(1)
+ print("Chemical search (full RAG) OK - found", len(data["compounds"]), "compounds with explanations")
+ PY
+
+ drug-repurposing-integration:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Warm up ailixir-api Space
+ run: |
+ for i in $(seq 1 60); do
+ if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then
+ echo "ailixir-api is up"
+ sleep 5
+ exit 0
+ fi
+ echo "Waiting ($i/60)..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Login and get Bearer token
+ run: |
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ if [ "$HTTP_CODE" != "200" ]; then exit 1; fi
+ python3 - <<'PY'
+ import json
+ with open('login.json') as f: data = json.load(f)
+ token = data.get('data', {}).get('token')
+ if not token: exit(1)
+ with open('token.txt','w') as f: f.write(token)
+ print('Login OK')
+ PY
+
+ - name: Test unauthenticated DR request is rejected
+ run: |
+ echo "POST /api/drug-repurposing/targets (no auth)"
+ HTTP_CODE=$(curl -s -o dr_unauth.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \
+ -H "Content-Type: application/json" \
+ -d '{"disease_name":"Type 2 Diabetes"}')
+ if [ "$HTTP_CODE" != "401" ]; then
+ echo "Expected 401, got $HTTP_CODE"
+ exit 1
+ fi
+ python3 - <<'PY'
+ import json
+ with open('dr_unauth.json') as f: data = json.load(f)
+ assert not data.get('success', True), 'Expected success=false'
+ print('Unauthenticated DR request correctly rejected')
+ PY
+
+ - name: Test DR target lookup requires disease_name
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/drug-repurposing/targets (missing disease_name)"
+ HTTP_CODE=$(curl -s -o dr_validation.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"top_n": 2}')
+ if [ "$HTTP_CODE" != "422" ]; then
+ echo "Expected 422, got $HTTP_CODE"
+ exit 1
+ fi
+ echo "Missing disease_name correctly rejected"
+
+ - name: Test DR target lookup via Laravel proxy
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/drug-repurposing/targets"
+ HTTP_CODE=$(curl -s -o dr_targets.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"disease_name":"Type 2 Diabetes","top_n":2}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat dr_targets.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "DR target lookup failed with HTTP $HTTP_CODE"
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("dr_targets.json"))
+ if not data.get("success"):
+ print("DR target lookup failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ job_id = data.get("data", {}).get("job_id")
+ status = data.get("data", {}).get("status")
+ print(f"DR target lookup OK - job_id={job_id}, status={status}")
+
+ # Poll for completion
+ import time, urllib.request
+ base = "https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets"
+ with open("token.txt") as f: token = f.read().strip()
+ for i in range(30):
+ req = urllib.request.Request(f"{base}/{job_id}")
+ req.add_header("Authorization", f"Bearer {token}")
+ resp = urllib.request.urlopen(req)
+ status_data = json.loads(resp.read())
+ s = status_data.get("data", {}).get("status")
+ if s == "completed":
+ output = status_data.get("data", {}).get("output", {})
+ assert len(output.get("targets", [])) > 0, "No targets found"
+ print(f"DR targets completed - {len(output['targets'])} targets found")
+ sys.exit(0)
+ elif s == "failed":
+ print("DR target lookup failed:", json.dumps(status_data, indent=2))
+ sys.exit(1)
+ print(f" Polling... attempt {i+1}/30, status={s}")
+ time.sleep(5)
+ print("DR target lookup did not complete in time")
+ sys.exit(1)
+ PY
+
+ docking-integration:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Warm up ailixir-api Space
+ run: |
+ for i in $(seq 1 60); do
+ if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then
+ echo "ailixir-api is up"
+ sleep 5
+ exit 0
+ fi
+ echo "Waiting ($i/60)..."
+ sleep 5
+ done
+ exit 1
+
+ - name: Login and get Bearer token
+ run: |
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ if [ "$HTTP_CODE" != "200" ]; then exit 1; fi
+ python3 - <<'PY'
+ import json
+ with open('login.json') as f: data = json.load(f)
+ token = data.get('data', {}).get('token')
+ if not token: exit(1)
+ with open('token.txt','w') as f: f.write(token)
+ print('Login OK')
+ PY
+
+ - name: Test docking submit
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/docking/submit"
+ HTTP_CODE=$(curl -s -o docking.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/docking/submit \
+ -H "Authorization: Bearer $TOKEN" \
+ -F "protein_name=EGFR" \
+ -F "protein_file=@tests/Fixtures/docking/egfr_clean.pdbqt" \
+ -F "ligand_name=Erlotinib" \
+ -F "ligand_file=@tests/Fixtures/docking/ligand.pdbqt" \
+ -F "center_x=10.0" \
+ -F "center_y=15.0" \
+ -F "center_z=20.0" \
+ -F "box_size_x=25.0" \
+ -F "box_size_y=25.0" \
+ -F "box_size_z=25.0" \
+ -F "exhaustiveness=8")
+ echo "HTTP Status: $HTTP_CODE"
+ cat docking.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "Docking submit failed with HTTP $HTTP_CODE"
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("docking.json"))
+ if not data.get("success"):
+ print("Docking submit failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("Docking submit OK - job created")
+ PY
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..419f7bad4dbc8896b96f8d51b330ce144f8ddbc7
--- /dev/null
+++ b/.github/workflows/integration-test.yml
@@ -0,0 +1,553 @@
+name: Docker Integration Test
+
+on:
+ workflow_dispatch:
+
+concurrency:
+ group: docker-integration-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ laravel-to-ai-services:
+ runs-on: ubuntu-latest
+ timeout-minutes: 90
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ - name: Pull and verify Git LFS files
+ run: |
+ git lfs version
+ git lfs pull
+ git lfs checkout
+ git lfs ls-files
+
+ python - <<'PY'
+ from pathlib import Path
+
+ paths = [
+ "ai_apps/generation/bundle/models/affinity/config.pkl",
+ "ai_apps/generation/bundle/models/affinity/model.pt",
+ "ai_apps/generation/bundle/models/generator/egfr_generator.chkpt",
+ "ai_apps/generation/bundle/models/generator/reinvent.prior",
+ ]
+
+ for p in paths:
+ path = Path(p)
+ if not path.exists():
+ raise SystemExit(f"Missing required artifact: {p}")
+
+ data = path.read_bytes()[:80]
+ size = path.stat().st_size
+
+ print(f"{p}: {size} bytes")
+
+ if data.startswith(b"version https://git-lfs.github.com"):
+ raise SystemExit(f"{p} is still a Git LFS pointer file")
+ PY
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and start stack
+ run: |
+ # Clean up volumes from previous runs
+ docker compose down -v 2>/dev/null || true
+
+ docker builder prune -af || true
+ docker system df
+
+ COMPOSE_PARALLEL_LIMIT=1 docker compose build
+ docker system df
+
+ docker compose up -d --wait
+
+
+ # Run fresh migrations to ensure clean database state
+ docker compose exec -T laravel php artisan migrate:fresh --force
+
+ - name: Seed verified test user
+ run: |
+ echo "=== Creating verified test user via PHP script ==="
+ docker compose exec -T laravel php -r "
+ require 'vendor/autoload.php';
+ \$app = require_once 'bootstrap/app.php';
+ \$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
+ \$kernel->bootstrap();
+
+ use App\Models\User;
+ use Illuminate\Support\Facades\Hash;
+
+ \$user = User::firstOrCreate(
+ ['email' => 'salehyasmeen080@gmail.com'],
+ [
+ 'name' => 'Test User',
+ 'password' => Hash::make('123456789'),
+ 'role' => 'normal',
+ 'is_verified' => true,
+ 'email_verified_at' => now(),
+ 'created_at' => now(),
+ 'updated_at' => now()
+ ]
+ );
+ echo 'User ID: ' . \$user->id . PHP_EOL;
+ echo 'Email: ' . \$user->email . PHP_EOL;
+ echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
+ "
+
+ - name: Login and get Bearer token
+ run: |
+ echo "POST /api/user/login"
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat login.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "Login failed with HTTP $HTTP_CODE"
+ docker compose logs laravel
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ try:
+ data = json.load(open("login.json"))
+ except json.JSONDecodeError as e:
+ print("Invalid JSON response:", e)
+ with open("login.json") as f:
+ print("Raw response:", f.read())
+ sys.exit(1)
+
+ if not data.get("success"):
+ print("Login failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ token = data.get("data", {}).get("token")
+ if not token:
+ print("No token in response:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ with open("token.txt", "w") as f:
+ f.write(token)
+ print("Login OK - token saved")
+ PY
+
+ - name: Show service status
+ if: always()
+ run: docker compose ps -a
+
+ - name: Wait for Laravel API
+ run: |
+ for i in $(seq 1 60); do
+ if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
+ echo "Laravel is up"
+ exit 0
+ fi
+ echo "Waiting for Laravel ($i/60)..."
+ sleep 5
+ done
+ docker compose logs laravel
+ exit 1
+
+ - name: Wait for ADMET Service to be Healthy
+ run: |
+ echo "Waiting for ADMET service to be healthy..."
+ for i in $(seq 1 120); do
+ if curl -sf http://localhost:8002/health > /dev/null 2>&1; then
+ echo "ADMET service is healthy and responding on port 8002"
+ echo "Waiting additional 10 seconds for service to fully initialize..."
+ sleep 10
+ exit 0
+ fi
+ if docker exec ailixir-admet python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" > /dev/null 2>&1; then
+ echo "ADMET service is healthy internally inside container"
+ echo "Waiting additional 10 seconds for service to fully initialize..."
+ sleep 10
+ exit 0
+ fi
+ echo "Attempt $i/120: ADMET service not ready yet..."
+ sleep 5
+ done
+ echo "ADMET service failed to become healthy in time"
+ docker compose logs admet
+ exit 1
+
+ - name: Test aggregated AI health via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "GET /api/ai-services/health"
+ curl -sf http://localhost:8080/api/ai-services/health \
+ -H "Authorization: Bearer $TOKEN" | tee health.json
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("health.json"))
+ if not data.get("success"):
+ print("AI services health check failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("All AI services healthy via Laravel")
+ PY
+
+ - name: Test ADMET prediction via
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/admet/predict"
+ HTTP_CODE=$(curl -s -o admet.json -w "%{http_code}" -X POST http://localhost:8080/api/admet/predict \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles": "c1ccccc1, CCO, CCC"}')
+
+ echo "HTTP Status: $HTTP_CODE"
+ cat admet.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "ADMET prediction request failed with HTTP $HTTP_CODE"
+ docker compose logs laravel
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ try:
+ data = json.load(open("admet.json"))
+ except json.JSONDecodeError as e:
+ print("Invalid JSON response:", e)
+ with open("admet.json") as f:
+ print("Raw response:", f.read())
+ sys.exit(1)
+
+ if not data.get("success"):
+ print("ADMET prediction test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ results = data.get("data", {}).get("results", [])
+ if len(results) < 3:
+ print(f"Expected at least 3 results, got {len(results)}")
+ sys.exit(1)
+
+ print(f"ADMET prediction OK - {len(results)} compounds processed")
+ PY
+
+ - name: Test Chemical RAG proxy via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/ai-services/test/chemical-search"
+ HTTP_CODE=$(curl -s -o chemical.json -w "%{http_code}" -X POST http://localhost:8080/api/ai-services/test/chemical-search \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "malaria drug"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat chemical.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("chemical.json"))
+ if not data.get("success"):
+ print("Chemical RAG proxy test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("Chemical RAG proxy OK")
+ PY
+
+ - name: Test Drug Repurposing proxy via Laravel
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "GET /api/ai-services/test/drug-repurposing"
+ HTTP_CODE=$(curl -s -o drug.json -w "%{http_code}" http://localhost:8080/api/ai-services/test/drug-repurposing \
+ -H "Authorization: Bearer $TOKEN")
+ echo "HTTP Status: $HTTP_CODE"
+ cat drug.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("drug.json"))
+ if not data.get("success"):
+ print("Drug Repurposing proxy test failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ print("Drug Repurposing proxy OK")
+ PY
+
+ - name: Dump logs on failure
+ if: failure()
+ run: |
+ docker compose logs --no-color
+
+ - name: Tear down
+ if: always()
+ run: docker compose down -v
+
+ drug-repurposing-integration-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and start stack
+
+ run: |
+ docker compose down -v 2>/dev/null || true
+ docker builder prune -af || true
+ docker system df
+
+ COMPOSE_PARALLEL_LIMIT=1 docker compose build
+ docker system df
+
+ docker compose up -d --wait
+
+ - name: Verify drug-repurposing reachable from Laravel container
+ run: |
+ echo "=== Laravel → drug-repurposing DNS ==="
+ docker compose exec -T laravel getent hosts drug-repurposing
+
+ echo "=== Health check from Laravel container ==="
+ docker compose exec -T laravel curl -sf http://drug-repurposing:8000/health | tee /tmp/drug-repurposing-health.json
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("/tmp/drug-repurposing-health.json"))
+ if data.get("status") != "healthy":
+ print("Expected healthy status:", data)
+ sys.exit(1)
+ print("drug-repurposing reachable from Laravel:", data.get("service"))
+ PY
+
+ echo "=== DRUG_REPURPOSING_URL in Laravel env ==="
+ docker compose exec -T laravel php -r 'echo "DRUG_REPURPOSING_URL=" . getenv("DRUG_REPURPOSING_URL") . PHP_EOL;'
+
+ - name: Run drug repurposing integration tests
+ run: |
+ # Match phpunit.xml: sync queue + sqlite so jobs run inside the test process
+ docker compose exec -T \
+ -e QUEUE_CONNECTION=sync \
+ laravel php artisan test --filter=DrugRepurposingIntegrationTest
+
+
+ - name: Dump logs on failure
+ if: failure()
+ run: |
+ docker compose logs --no-color laravel drug-repurposing queue
+
+ - name: Tear down
+ if: always()
+ run: docker compose down -v
+
+ docking-integration-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and start stack
+ run: |
+ docker compose down -v 2>/dev/null || true
+ docker compose build --build-arg COMPOSER_DEV="" laravel
+ docker compose up -d laravel
+
+ - name: Verify RDKit, Vina, and Open Babel in container
+ run: |
+ echo "Verifying python3 import rdkit"
+ docker compose exec -T laravel python3 -c "import rdkit; print(rdkit.__version__)"
+ echo "Verifying vina --help"
+ docker compose exec -T laravel vina --help
+ echo "Verifying obabel -V"
+ docker compose exec -T laravel obabel -V
+
+ - name: Run Laravel Feature Tests
+ run: |
+ docker compose exec -T laravel php artisan test --filter=DockingSubmitTest
+
+ - name: Tear down
+ if: always()
+ run: docker compose down -v
+
+ rag-integration-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 90
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and start stack
+ run: |
+ docker compose down -v 2>/dev/null || true
+ docker builder prune -af || true
+ docker system df
+
+ COMPOSE_PARALLEL_LIMIT=1 docker compose build
+ docker system df
+
+ docker compose up -d --wait
+
+ # Run fresh migrations to ensure clean database state
+ docker compose exec -T laravel php artisan migrate:fresh --force
+
+ - name: Seed verified test user
+ run: |
+ echo "=== Creating verified test user via PHP script ==="
+ docker compose exec -T laravel php -r "
+ require 'vendor/autoload.php';
+ \$app = require_once 'bootstrap/app.php';
+ \$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
+ \$kernel->bootstrap();
+
+ use App\Models\User;
+ use Illuminate\Support\Facades\Hash;
+
+ \$user = User::firstOrCreate(
+ ['email' => 'salehyasmeen080@gmail.com'],
+ [
+ 'name' => 'Test User',
+ 'password' => Hash::make('123456789'),
+ 'role' => 'normal',
+ 'is_verified' => true,
+ 'email_verified_at' => now(),
+ 'created_at' => now(),
+ 'updated_at' => now()
+ ]
+ );
+ echo 'User ID: ' . \$user->id . PHP_EOL;
+ echo 'Email: ' . \$user->email . PHP_EOL;
+ echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
+ "
+
+ - name: Login and get Bearer token
+ run: |
+ echo "POST /api/user/login"
+ HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/api/user/login \
+ -H "Content-Type: application/json" \
+ -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat login.json
+ echo ""
+
+ if [ "$HTTP_CODE" != "200" ]; then
+ echo "Login failed with HTTP $HTTP_CODE"
+ docker compose logs laravel
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json, sys
+ try:
+ data = json.load(open("login.json"))
+ except json.JSONDecodeError as e:
+ print("Invalid JSON response:", e)
+ with open("login.json") as f:
+ print("Raw response:", f.read())
+ sys.exit(1)
+
+ if not data.get("success"):
+ print("Login failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ token = data.get("data", {}).get("token")
+ if not token:
+ print("No token in response:", json.dumps(data, indent=2))
+ sys.exit(1)
+
+ with open("token.txt", "w") as f:
+ f.write(token)
+ print("Login OK - token saved")
+ PY
+
+ - name: Show service status
+ if: always()
+ run: docker compose ps -a
+
+ - name: Wait for Laravel API
+ run: |
+ for i in $(seq 1 60); do
+ if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
+ echo "Laravel is up"
+ exit 0
+ fi
+ echo "Waiting for Laravel ($i/60)..."
+ sleep 5
+ done
+ docker compose logs laravel
+ exit 1
+
+ - name: Test retrieval-only chemical search
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/chemical-search (retrieval-only)"
+ HTTP_CODE=$(curl -s -o chemical_search.json -w "%{http_code}" -X POST http://localhost:8080/api/chemical-search \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles":"CC(=O)O"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat chemical_search.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("chemical_search.json"))
+ if not data.get("success"):
+ print("Chemical search (retrieval-only) failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ if not isinstance(data.get("compounds"), list):
+ print("Missing 'compounds' array in response")
+ sys.exit(1)
+ if data.get("metadata", {}).get("source") != "retrieval":
+ print("Expected source='retrieval', got:", data.get("metadata", {}).get("source"))
+ sys.exit(1)
+ print("Chemical search (retrieval-only) OK - found", len(data["compounds"]), "compounds")
+ PY
+
+ - name: Test full RAG chemical search
+ run: |
+ TOKEN=$(cat token.txt)
+ echo "POST /api/chemical-search/full-rag"
+ HTTP_CODE=$(curl -s -o chemical_rag.json -w "%{http_code}" -X POST http://localhost:8080/api/chemical-search/full-rag \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles":"CC(=O)O"}')
+ echo "HTTP Status: $HTTP_CODE"
+ cat chemical_rag.json
+ echo ""
+ python3 - <<'PY'
+ import json, sys
+ data = json.load(open("chemical_rag.json"))
+ if not data.get("success"):
+ print("Chemical search (full RAG) failed:", json.dumps(data, indent=2))
+ sys.exit(1)
+ if not isinstance(data.get("compounds"), list):
+ print("Missing 'compounds' array in response")
+ sys.exit(1)
+ if data.get("metadata", {}).get("source") != "full_rag":
+ print("Expected source='full_rag', got:", data.get("metadata", {}).get("source"))
+ sys.exit(1)
+ # Verify explanations exist for full RAG
+ for compound in data["compounds"]:
+ if not compound.get("explanation"):
+ print("Missing explanation for compound:", compound.get("name"))
+ sys.exit(1)
+ print("Chemical search (full RAG) OK - found", len(data["compounds"]), "compounds with explanations")
+ PY
+
+ - name: Dump logs on failure
+ if: failure()
+ run: |
+ docker compose logs --no-color
+
+ - name: Tear down
+ if: always()
+ run: docker compose down -v
diff --git a/.github/workflows/keep-hf-alive.yml b/.github/workflows/keep-hf-alive.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7641830ff1427fa91e7fe3fdba02e88c3dd7d8f0
--- /dev/null
+++ b/.github/workflows/keep-hf-alive.yml
@@ -0,0 +1,21 @@
+name: Keep HF Alive
+
+on:
+ schedule:
+ - cron: "0 */24 * * *"
+ workflow_dispatch:
+
+jobs:
+ keep_hf_alive_job:
+ name: keep hf alive job
+ runs-on: ubuntu-latest
+ steps:
+ - name: Hit main API health endpoint
+ run: |
+ curl -s -o /dev/null -w "%{http_code}" \
+ "https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/health"
+
+ - name: Hit generation health endpoint
+ run: |
+ curl -s -o /dev/null -w "%{http_code}" \
+ "https://shdwrow-ailixir-generation.hf.space/health"
diff --git a/.github/workflows/md-simulation-health.yml b/.github/workflows/md-simulation-health.yml
new file mode 100644
index 0000000000000000000000000000000000000000..892a1c9af803cafcb9916bf4ed020293b362977f
--- /dev/null
+++ b/.github/workflows/md-simulation-health.yml
@@ -0,0 +1,117 @@
+name: MD Simulation Health Check
+
+on:
+ push:
+ branches: [MD-integration]
+ workflow_dispatch:
+
+concurrency:
+ group: md-simulation-health-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ md-simulation-health-check:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build Laravel image
+ run: docker build -t ailixir-laravel .
+
+ - name: Build MD simulation image
+ run: docker build -t md-simulation ai_apps/Protein-Ligand-MD-Simulation/
+
+ - name: Create Docker network
+ run: docker network create ailixir-md
+
+ - name: Start Laravel API
+ run: |
+ docker run -d --name ailixir-laravel \
+ --network ailixir-md \
+ -p 8000:8000 \
+ --env-file docker/laravel.env \
+ -e MD_SIMULATION_URL=http://md-api:5005 \
+ ailixir-laravel
+
+ - name: Start Redis
+ run: |
+ docker run -d --name md-redis \
+ --network ailixir-md \
+ --network-alias md-redis \
+ redis:7-alpine
+
+ - name: Start MD Simulation API
+ run: |
+ docker run -d --name md-api \
+ --network ailixir-md \
+ -p 5005:5005 \
+ -e REDIS_HOST=md-redis \
+ -e REDIS_PORT=6379 \
+ -e USE_REDIS=true \
+ -e OPENMM_PLATFORM=CPU \
+ -e FLASK_HOST=0.0.0.0 \
+ -e FLASK_PORT=5005 \
+ -e MD_DATA_DIR=/app/data \
+ -e LOG_LEVEL=INFO \
+ md-simulation
+
+ - name: Wait for Laravel API to be ready
+ run: |
+ echo "Waiting for Laravel API to be ready..."
+ for i in $(seq 1 30); do
+ if curl -sf http://localhost:8000/ > /dev/null 2>&1; then
+ echo "Laravel API is ready"
+ exit 0
+ fi
+ echo "Waiting for Laravel ($i/30)..."
+ sleep 2
+ done
+ docker logs ailixir-laravel
+ exit 1
+
+ - name: Wait for MD Simulation API to be healthy
+ run: |
+ echo "Waiting for MD Simulation API to be healthy..."
+ for i in $(seq 1 60); do
+ if curl -sf http://localhost:5005/health > /dev/null 2>&1; then
+ echo "MD Simulation API is healthy"
+ exit 0
+ fi
+ echo "Waiting for MD API ($i/60)..."
+ sleep 5
+ done
+ docker logs md-api
+ exit 1
+
+ - name: Test MD simulation health via Laravel proxy
+ run: |
+ echo "GET /api/md-simulation/health via Laravel proxy"
+ RESPONSE=$(curl -sf http://localhost:8000/api/md-simulation/health)
+ echo "$RESPONSE"
+ STATUS=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status'))")
+ if [ "$STATUS" != "ok" ]; then
+ echo "Expected status 'ok', got '$STATUS'"
+ exit 1
+ fi
+ echo "MD Simulation service is healthy via Laravel proxy!"
+
+ - name: Dump logs on failure
+ if: failure()
+ run: |
+ docker logs ailixir-laravel || true
+ docker logs md-api || true
+ docker logs md-redis || true
+
+ - name: Cleanup
+ if: always()
+ run: |
+ docker rm -f ailixir-laravel md-api md-redis 2>/dev/null || true
+ docker network rm ailixir-md 2>/dev/null || true
diff --git a/.github/workflows/push-models-to-hf.yml b/.github/workflows/push-models-to-hf.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25068c0490e83184bf5d765f56a1f7ea41cce94c
--- /dev/null
+++ b/.github/workflows/push-models-to-hf.yml
@@ -0,0 +1,31 @@
+name: Push Models to HF Generation Space
+
+on:
+ workflow_dispatch:
+
+jobs:
+ push-models:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Upload models to HF dataset
+ run: |
+ pip install huggingface_hub
+ python -c "
+ from huggingface_hub import HfApi
+ api = HfApi()
+ api.upload_folder(
+ repo_id='shdwRow/ailixir-generation-models',
+ folder_path='ai_apps/generation/bundle/models',
+ repo_type='dataset',
+ token='${{ secrets.HF_TOKEN_SHDWROW }}'
+ )
+ print('Models uploaded successfully')
+ "
+
+ - name: Rebuild HF Space with latest models
+ run: |
+ curl -X POST \
+ "https://huggingface.co/api/spaces/shdwRow/ailixir-generation/restart" \
+ -H "Authorization: Bearer ${{ secrets.HF_TOKEN_SHDWROW }}"
diff --git a/.github/workflows/sync-hf-all.yml b/.github/workflows/sync-hf-all.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25834db72767e9c6b36199fbdcc3da28c529b419
--- /dev/null
+++ b/.github/workflows/sync-hf-all.yml
@@ -0,0 +1,232 @@
+name: Sync to Hugging Face Spaces
+
+# Automatically syncs code from GitHub main branch to all 5 Hugging Face Spaces.
+# Each job syncs a different subset of the repo to a specific HF Space.
+#
+# Two HF accounts are used:
+# - RottenShadow: ailixir-drug-repurposing, ailixir-chemical-rag
+# - shdwRow (secondary): ailixir-admet, ailixir-generation
+# - Ailixir-AI-Team: ailixir-api
+#
+# Required GitHub secrets:
+# HF_TOKEN — HF access token for RottenShadow account (read+write permissions)
+# HF_TOKEN_AILIXIR — HF access token for Ailixir-AI-Team account (read+write permissions)
+# HF_TOKEN_SHDWROW — HF access token for shdwRow account (read+write permissions)
+# GOOGLE_CLIENT_ID — Google OAuth client ID
+# GOOGLE_CLIENT_SECRET — Google OAuth client secret
+
+on:
+ push:
+ branches: [main, feature/hugging-face-deploy]
+ workflow_dispatch:
+
+jobs:
+ # ── Backend API (Laravel) ──────────────────────────────────────
+ # Syncs the full repo minus AI app directories to Ailixir-AI-Team/ailixir-api.
+ # The live SQLite database (database/ailixir.sqlite) is preserved across syncs:
+ # - Excluded from rsync so it's never overwritten
+ # - Restored from HEAD via git checkout so live data isn't committed
+ backend:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Sync ailixir-api
+ env:
+ HF_TOKEN_AILIXIR: ${{ secrets.HF_TOKEN_AILIXIR }}
+ run: |
+ SPACE="Ailixir-AI-Team/ailixir-api"
+ REPO_URL="https://Ailixir-AI-Team:${HF_TOKEN_AILIXIR}@huggingface.co/spaces/${SPACE}"
+ git clone --depth 1 "$REPO_URL" /tmp/hf-space
+ cd /tmp/hf-space
+ # Sync files (new, modified, deleted) but preserve the live database
+ rsync -av --delete --exclude=ai_apps --exclude=.git --exclude=.env --exclude=.dockerignore --exclude=public/imgs --exclude=storage --exclude=Diagrams --exclude=database/ailixir.sqlite "$GITHUB_WORKSPACE/" .
+ cp "$GITHUB_WORKSPACE/README_HF.md" README.md 2>/dev/null || true
+ # Write .env with all required environment variables for HF Spaces
+ printf '%s\n' \
+ 'RUN_MODE=hf' \
+ 'APP_KEY=base64:T/MgQZht16genNJN3aZ5flCuvfe7Zfs4yFhuAFO2itU=' \
+ 'AI_INTEGRATION_ROUTES_ENABLED=true' \
+ 'APP_URL=https://Ailixir-AI-Team-ailixir-api.hf.space' \
+ 'APP_DEBUG=true' \
+ 'ADMET_AI_URL=https://shdwRow-ailixir-admet.hf.space' \
+ 'CHEMICAL_AI_URL=https://RottenShadow-ailixir-chemical-rag.hf.space' \
+ 'DRUG_REPURPOSING_URL=https://RottenShadow-ailixir-drug-repurposing.hf.space' \
+ 'GENERATION_SERVICE_URL=https://shdwRow-ailixir-generation.hf.space' \
+ 'GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}' \
+ 'GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}' \
+ 'GOOGLE_REDIRECT_URI=https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/auth/google' \
+ 'CLOUDINARY_CLOUD_NAME=dummy_cloud' \
+ 'CLOUDINARY_API_KEY=dummy_key' \
+ 'CLOUDINARY_API_SECRET=dummy_secret' \
+ 'QUEUE_CONNECTION=sync' \
+ > .env
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+ # Prevent the live database from being tracked/committed
+ git checkout HEAD -- database/ailixir.sqlite 2>/dev/null || true
+ git add -A
+ if git diff --cached --quiet; then echo " No changes"; else git commit -m "Auto-sync: $GITHUB_SHA"; git push; fi
+
+ # ── Drug Repurposing (FastAPI) ─────────────────────────────────
+ # Syncs ai_apps/Drug Reporposing/ to RottenShadow/ailixir-drug-repurposing.
+ # Renames Dockerfile.hf → Dockerfile if present.
+ # Can run in mock mode for testing (set USE_MOCK_MODEL=true in HF secrets).
+ drug-repurposing:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Sync ailixir-drug-repurposing
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ SPACE="RottenShadow/ailixir-drug-repurposing"
+ REPO_URL="https://RottenShadow:${HF_TOKEN}@huggingface.co/spaces/${SPACE}"
+ git clone --depth 1 "$REPO_URL" /tmp/hf-space
+ cd /tmp/hf-space
+ rsync -av --delete --exclude=.git --exclude=__pycache__ --exclude='*.pyc' --exclude=digrams --exclude=docs --exclude=docker "$GITHUB_WORKSPACE/ai_apps/Drug Reporposing/" .
+ cp "$GITHUB_WORKSPACE/ai_apps/Drug Reporposing/README_HF.md" README.md 2>/dev/null || true
+ if [ -f Dockerfile.hf ]; then mv Dockerfile.hf Dockerfile; fi
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+ git add -A
+ if git diff --cached --quiet; then echo " No changes"; else git commit -m "Auto-sync: $GITHUB_SHA"; git push; fi
+
+ # ── ADMET Prediction (FastAPI + PyTorch) ───────────────────────
+ # Syncs ai_apps/ADMIT/admet_inference/ to shdwRow/ailixir-admet.
+ # Uses HF Xet for binary .ckpt model files.
+ # Requires HF_TOKEN_SHDWROW secret (shdwRow account).
+ admet:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install git-xet for binary file support
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/huggingface/xet-core/refs/heads/main/git_xet/install.sh | sh
+ git xet install
+ - name: Sync ailixir-admet
+ env:
+ HF_TOKEN_SHDWROW: ${{ secrets.HF_TOKEN_SHDWROW }}
+ run: |
+ SPACE="shdwRow/ailixir-admet"
+ REPO_URL="https://shdwRow:${HF_TOKEN_SHDWROW}@huggingface.co/spaces/${SPACE}"
+ git clone --depth 1 "$REPO_URL" /tmp/hf-space
+ cd /tmp/hf-space
+ rsync -av --delete --exclude=.git --exclude=datasets.rar "$GITHUB_WORKSPACE/ai_apps/ADMIT/admet_inference/" .
+ cp README_HF.md README.md 2>/dev/null || true
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+ git config lfs.allowincompletepush true
+ git lfs install
+ git lfs track '*.ckpt'
+ git add -A
+ if git diff --cached --quiet; then echo " No changes"; else git commit -m "Auto-sync: $GITHUB_SHA"; git push; fi
+
+ # ── Chemical RAG (FastAPI + ChromaDB) ─────────────────────────
+ # Syncs ai_apps/chemical-rag-system/chemical-rag-system/ to RottenShadow/ailixir-chemical-rag.
+ chemical-rag:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Sync ailixir-chemical-rag
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ SPACE="RottenShadow/ailixir-chemical-rag"
+ REPO_URL="https://RottenShadow:${HF_TOKEN}@huggingface.co/spaces/${SPACE}"
+ git clone --depth 1 "$REPO_URL" /tmp/hf-space
+ cd /tmp/hf-space
+ rsync -av --delete --exclude=.git --exclude=digrams "$GITHUB_WORKSPACE/ai_apps/chemical-rag-system/chemical-rag-system/" .
+ cp README_HF.md README.md 2>/dev/null || true
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+ git add -A
+ if git diff --cached --quiet; then echo " No changes"; else git commit -m "Auto-sync: $GITHUB_SHA"; git push; fi
+
+ # ── Molecular Generation (FastAPI + REINVENT4) ────────────────
+ # Syncs ai_apps/generation/ to shdwRow/ailixir-generation.
+ # Renames Dockerfile.hf → Dockerfile (or Dockerfile.api → Dockerfile as fallback).
+ # Uses HF Xet for binary .ckpt, .pt, .pkl model files.
+ # Requires HF_TOKEN_SHDWROW secret (shdwRow account).
+ generation:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install git-xet for binary file support
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/huggingface/xet-core/refs/heads/main/git_xet/install.sh | sh
+ git xet install
+ - name: Sync ailixir-generation
+ env:
+ HF_TOKEN_SHDWROW: ${{ secrets.HF_TOKEN_SHDWROW }}
+ run: |
+ SPACE="shdwRow/ailixir-generation"
+ REPO_URL="https://shdwRow:${HF_TOKEN_SHDWROW}@huggingface.co/spaces/${SPACE}"
+ git clone --depth 1 "$REPO_URL" /tmp/hf-space
+ cd /tmp/hf-space
+ rsync -av --delete --exclude=.git --exclude=bundle/models "$GITHUB_WORKSPACE/ai_apps/generation/" .
+ cp "$GITHUB_WORKSPACE/ai_apps/generation/README_HF.md" README.md 2>/dev/null || true
+ if [ -f Dockerfile.hf ]; then mv Dockerfile.hf Dockerfile; elif [ -f Dockerfile.api ]; then mv Dockerfile.api Dockerfile; fi
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+ git add -A
+ if git diff --cached --quiet; then echo " No changes"; else git commit -m "Auto-sync: $GITHUB_SHA"; git push; fi
+
+ chat-bot:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install Hugging Face CLI
+ run: pip install -U "huggingface_hub[cli]"
+
+ - name: Add Gemini key to Hugging Face Space secrets
+ env:
+ HF_TOKEN_SHDWROW: ${{ secrets.HF_TOKEN_SHDWROW }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+ run: |
+ hf spaces secrets add shdwRow/ailixir-chat-bot \
+ -s GEMINI_API_KEY="$GEMINI_API_KEY" \
+ --token "$HF_TOKEN_SHDWROW"
+
+ - name: Sync ailixir-chat-bot
+ env:
+ HF_TOKEN_SHDWROW: ${{ secrets.HF_TOKEN_SHDWROW }}
+ run: |
+ SPACE="shdwRow/ailixir-chat-bot"
+ REPO_URL="https://shdwRow:${HF_TOKEN_SHDWROW}@huggingface.co/spaces/${SPACE}"
+
+ git clone --depth 1 "$REPO_URL" /tmp/hf-chat-bot
+ cd /tmp/hf-chat-bot
+
+ rsync -av --delete \
+ --exclude=.git \
+ --exclude=.env \
+ --exclude=__pycache__ \
+ --exclude='*.pyc' \
+ --exclude='*.db' \
+ --exclude='*.db-shm' \
+ --exclude='*.db-wal' \
+ "$GITHUB_WORKSPACE/ai_apps/chem_agent_api/" .
+
+ git config user.name "AILIXIR Bot"
+ git config user.email "bot@ailixir.app"
+
+ git add -A
+ if git diff --cached --quiet; then
+ echo "No changes"
+ else
+ git commit -m "Auto-sync chat bot: $GITHUB_SHA"
+ git push
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1a3a0a344e912c748101f5315b3c63a5f963ed9b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+*.log
+.DS_Store
+.env
+.env.backup
+.env.production
+.phpactor.json
+.phpunit.result.cache
+/.fleet
+/.idea
+/.nova
+/.phpunit.cache
+/.vscode
+/.zed
+/auth.json
+/node_modules
+/public/build
+/public/hot
+/public/storage
+/storage/*.key
+/storage/pail
+/vendor
+Homestead.json
+Homestead.yaml
+Thumbs.db
+.env
+__pycache__/
+*.pyc
+*.sqlite
+*.sqlite-wal
+*.sqlite-shm
+
+ai_apps/chem_agent_api/.env
+ai_apps/chem_agent_api/_pycache_/
+ai_apps/chem_agent_api/pycache/
+ai_apps/chem_agent_api/*.db
+ai_apps/chem_agent_api/*.db-shm
+ai_apps/chem_agent_api/*.db-wal
diff --git a/.scribe/.filehashes b/.scribe/.filehashes
new file mode 100644
index 0000000000000000000000000000000000000000..d22a6d7c6dec31700aab97176f9ebce4f49b89e6
--- /dev/null
+++ b/.scribe/.filehashes
@@ -0,0 +1,4 @@
+# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
+# Scribe uses this file to know when you change something manually in your docs.
+.scribe/intro.md=ac7eb5bb370d74c7b07631610e041687
+.scribe/auth.md=9bee2b1ef8a238b2e58613fa636d5f39
\ No newline at end of file
diff --git a/.scribe/auth.md b/.scribe/auth.md
new file mode 100644
index 0000000000000000000000000000000000000000..82903629df7255f5709a9a943bf20af5ba61aede
--- /dev/null
+++ b/.scribe/auth.md
@@ -0,0 +1,3 @@
+# Authenticating requests
+
+This API is not authenticated.
diff --git a/.scribe/endpoints.cache/00.yaml b/.scribe/endpoints.cache/00.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..12a5d693361277885125dc5d8cbe8de6304821b0
--- /dev/null
+++ b/.scribe/endpoints.cache/00.yaml
@@ -0,0 +1,1503 @@
+## Autogenerated by Scribe. DO NOT MODIFY.
+
+name: Endpoints
+description: ''
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/awards
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/awards/{id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the award.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/awards/{id}/scientists'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the award.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards\/consequatur\/scientists could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/scientists
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/scientists/{id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the scientist.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/scientists/{id}/awards'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the scientist.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists\/consequatur\/awards could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/register
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Auth ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ name:
+ custom: []
+ name: name
+ description: 'Must not be greater than 255 characters.'
+ required: true
+ example: vmqeopfuudtdsufvyvddq
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address.'
+ required: true
+ example: kunde.eloisa@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: 'Must be at least 6 characters.'
+ required: true
+ example: '4[*UyPJ"}6'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ name: vmqeopfuudtdsufvyvddq
+ email: kunde.eloisa@example.com
+ password: '4[*UyPJ"}6'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/login
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: ''
+ required: true
+ example: 'O[2UZ5ij-e/dl4m{o,'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ password: 'O[2UZ5ij-e/dl4m{o,'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/verify-email
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Email Verification ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ otp:
+ custom: []
+ name: otp
+ description: 'Must be 6 digits.'
+ required: true
+ example: '810798'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ otp: '810798'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/resend-otp
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/forgot-password
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Password Reset ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/reset-password
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ otp:
+ custom: []
+ name: otp
+ description: 'Must be 6 digits.'
+ required: true
+ example: '810798'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: 'Must be at least 6 characters.'
+ required: true
+ example: '[2UZ5ij-e/dl4'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ otp: '810798'
+ password: '[2UZ5ij-e/dl4'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/resend-reset-password-otp
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/auth/google
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Google OAuth ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ access_token:
+ custom: []
+ name: access_token
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ access_token: consequatur
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/user/profile
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Profile ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/user\/profile could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/logout
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/refresh
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/refresh could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/clear
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/clear could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/categories
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/categories could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: 'api/news/{articleId}/share'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ articleId:
+ custom: []
+ name: articleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ articleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: 'api/news/{articleId}/save'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ articleId:
+ custom: []
+ name: articleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ articleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/saved
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/saved could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/news/saved/{savedArticleId}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ savedArticleId:
+ custom: []
+ name: savedArticleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ savedArticleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/ai/run
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ num_smiles:
+ custom: []
+ name: num_smiles
+ description: ''
+ required: true
+ example: 17
+ type: integer
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ top_k:
+ custom: []
+ name: top_k
+ description: ''
+ required: true
+ example: 17
+ type: integer
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ Docking:
+ custom: []
+ name: Docking
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ DeepPurpose_Affinity:
+ custom: []
+ name: DeepPurpose_Affinity
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ SA:
+ custom: []
+ name: SA
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ TPSA:
+ custom: []
+ name: TPSA
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ MW:
+ custom: []
+ name: MW
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ num_smiles: 17
+ top_k: 17
+ Docking: 11613.31890586
+ DeepPurpose_Affinity: 11613.31890586
+ SA: 11613.31890586
+ TPSA: 11613.31890586
+ MW: 11613.31890586
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/status/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/status\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/preview/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/preview\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/download/top/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/download\/top\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/download/full/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/download\/full\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/ai/history
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/history could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/upload-file
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/.scribe/endpoints/00.yaml b/.scribe/endpoints/00.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fcb0008fa0e9433bd563663c3ec3dc0400ac90ea
--- /dev/null
+++ b/.scribe/endpoints/00.yaml
@@ -0,0 +1,1501 @@
+name: Endpoints
+description: ''
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/awards
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/awards/{id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the award.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/awards/{id}/scientists'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the award.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/awards\/consequatur\/scientists could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/scientists
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/scientists/{id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the scientist.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/scientists/{id}/awards'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The ID of the scientist.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/scientists\/consequatur\/awards could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/register
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Auth ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ name:
+ custom: []
+ name: name
+ description: 'Must not be greater than 255 characters.'
+ required: true
+ example: vmqeopfuudtdsufvyvddq
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address.'
+ required: true
+ example: kunde.eloisa@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: 'Must be at least 6 characters.'
+ required: true
+ example: '4[*UyPJ"}6'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ name: vmqeopfuudtdsufvyvddq
+ email: kunde.eloisa@example.com
+ password: '4[*UyPJ"}6'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/login
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: ''
+ required: true
+ example: 'O[2UZ5ij-e/dl4m{o,'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ password: 'O[2UZ5ij-e/dl4m{o,'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/verify-email
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Email Verification ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ otp:
+ custom: []
+ name: otp
+ description: 'Must be 6 digits.'
+ required: true
+ example: '810798'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ otp: '810798'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/resend-otp
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/forgot-password
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Password Reset ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/reset-password
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ otp:
+ custom: []
+ name: otp
+ description: 'Must be 6 digits.'
+ required: true
+ example: '810798'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ password:
+ custom: []
+ name: password
+ description: 'Must be at least 6 characters.'
+ required: true
+ example: '[2UZ5ij-e/dl4'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ otp: '810798'
+ password: '[2UZ5ij-e/dl4'
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/resend-reset-password-otp
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ email:
+ custom: []
+ name: email
+ description: 'Must be a valid email address. The email of an existing record in the users table.'
+ required: true
+ example: qkunze@example.com
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ email: qkunze@example.com
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/auth/google
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Google OAuth ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ access_token:
+ custom: []
+ name: access_token
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ access_token: consequatur
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/user/profile
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: '---------------- Profile ----------------'
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/user\/profile could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/user/logout
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/refresh
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/refresh could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/clear
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/clear could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/categories
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/categories could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: 'api/news/{articleId}/share'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ articleId:
+ custom: []
+ name: articleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ articleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: 'api/news/{articleId}/save'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ articleId:
+ custom: []
+ name: articleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ articleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/news/saved
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/news\/saved could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/news/saved/{savedArticleId}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ savedArticleId:
+ custom: []
+ name: savedArticleId
+ description: ''
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ savedArticleId: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/ai/run
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ num_smiles:
+ custom: []
+ name: num_smiles
+ description: ''
+ required: true
+ example: 17
+ type: integer
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ top_k:
+ custom: []
+ name: top_k
+ description: ''
+ required: true
+ example: 17
+ type: integer
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ Docking:
+ custom: []
+ name: Docking
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ DeepPurpose_Affinity:
+ custom: []
+ name: DeepPurpose_Affinity
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ SA:
+ custom: []
+ name: SA
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ TPSA:
+ custom: []
+ name: TPSA
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ MW:
+ custom: []
+ name: MW
+ description: ''
+ required: true
+ example: 11613.31890586
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ num_smiles: 17
+ top_k: 17
+ Docking: 11613.31890586
+ DeepPurpose_Affinity: 11613.31890586
+ SA: 11613.31890586
+ TPSA: 11613.31890586
+ MW: 11613.31890586
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/status/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/status\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/preview/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/preview\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/download/top/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/download\/top\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/ai/download/full/{job_job_id}'
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ job_job_id:
+ custom: []
+ name: job_job_id
+ description: 'The ID of the job job.'
+ required: true
+ example: consequatur
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ job_job_id: consequatur
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/download\/full\/consequatur could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/ai/history
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 404
+ content: '{"success":false,"message":"The route api\/ai\/history could not be found."}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/upload-file
+ metadata:
+ custom: []
+ groupName: Endpoints
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: ''
+ description: ''
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses: []
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/.scribe/endpoints/custom.0.yaml b/.scribe/endpoints/custom.0.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4b0235215fdb344d2c423510187e6a27cff17829
--- /dev/null
+++ b/.scribe/endpoints/custom.0.yaml
@@ -0,0 +1,53 @@
+# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package),
+# you can define it in a custom.*.yaml file, like this one.
+# Each custom file should contain an array of endpoints. Here's an example:
+# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options
+
+#- httpMethods:
+# - POST
+# uri: api/doSomething/{param}
+# metadata:
+# groupName: The group the endpoint belongs to. Can be a new group or an existing group.
+# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough.
+# subgroup: You can add a subgroup, too.
+# title: Do something
+# description: 'This endpoint allows you to do something.'
+# authenticated: false
+# headers:
+# Content-Type: application/json
+# Accept: application/json
+# urlParameters:
+# param:
+# name: param
+# description: A URL param for no reason.
+# required: true
+# example: 2
+# type: integer
+# queryParameters:
+# speed:
+# name: speed
+# description: How fast the thing should be done. Can be `slow` or `fast`.
+# required: false
+# example: fast
+# type: string
+# bodyParameters:
+# something:
+# name: something
+# description: The things we should do.
+# required: true
+# example:
+# - string 1
+# - string 2
+# type: 'string[]'
+# responses:
+# - status: 200
+# description: 'When the thing was done smoothly.'
+# content: # Your response content can be an object, an array, a string or empty.
+# {
+# "hey": "ho ho ho"
+# }
+# responseFields:
+# hey:
+# name: hey
+# description: Who knows?
+# type: string # This is optional
diff --git a/.scribe/intro.md b/.scribe/intro.md
new file mode 100644
index 0000000000000000000000000000000000000000..3dca3157d8b8bac9b2bde900ebca16eebefc40c7
--- /dev/null
+++ b/.scribe/intro.md
@@ -0,0 +1,13 @@
+# Introduction
+
+
+
+
+
+ This documentation aims to provide all the information you need to work with our API.
+
+
+
diff --git a/API.md b/API.md
new file mode 100644
index 0000000000000000000000000000000000000000..f9e7a1900629d9f69327513762c4514c72b7b50e
--- /dev/null
+++ b/API.md
@@ -0,0 +1,4222 @@
+content = '''# 📡 AILIXIR API Reference
+
+**Version:** 2.1 | **Last Updated:** May 29, 2026 | **Status:** Production Ready
+
+Complete API specification for AILIXIR-Backend, including all endpoints, authentication, request/response schemas, error handling, and integration examples.
+
+---
+
+## 📋 Table of Contents
+
+- [Overview](#overview)
+- [Authentication](#authentication)
+- [Base URLs](#base-urls)
+- [Common Patterns](#common-patterns)
+- [Health & Status](#health--status)
+- [Authentication Endpoints](#authentication-endpoints)
+- [AI Integration Endpoints](#ai-integration-endpoints)
+- [AI Service Endpoints](#ai-service-endpoints)
+- [AI Agent Endpoints (Chemistry AI)](#ai-agent-endpoints-chemistry-ai)
+- [Chemical Search Endpoints](#chemical-search-endpoints)
+- [Docking API](#docking-api)
+- [Drug Repurposing API](#drug-repurposing-api)
+- [Convert SMILES API](#convert-smiles-api)
+- [User Management Endpoints](#user-management-endpoints)
+- [Awards And Scientists API](#awards-and-scientists-api)
+- [News API](#news-api)
+- [Admet Prediction API](#admet-prediction-api)
+- [AI Generation API](#ai-generation-api)
+- [MD Simulation API](#md-simulation-api)
+- [Error Handling](#error-handling)
+- [Rate Limiting](#rate-limiting)
+- [Integration Examples](#integration-examples)
+- [Support](#support)
+
+---
+
+## Overview
+
+AILIXIR provides a RESTful API for accessing all drug discovery services. The API is built with:
+
+- **Framework:** Laravel 11 with Sanctum authentication
+- **Response Format:** JSON
+- **Authentication:** JWT Bearer tokens
+- **Rate Limiting:** In place (contact admin for limits)
+- **Versioning:** URL-based (`/api/v1/...`)
+
+**Key Features:**
+
+- Asynchronous job processing (long-running AI tasks)
+- Real-time health monitoring
+- User authentication and authorization
+- Result caching and versioning
+- File upload/download support
+- AI-powered chemistry analysis agent with conversation memory
+
+---
+
+## Authentication
+
+### Overview
+
+AILIXIR uses **Laravel Sanctum** for API authentication. Most endpoints require a valid JWT bearer token.
+
+### Types of Tokens
+
+| Token Type | Purpose | Duration |
+| ----------------- | ---------------------------- | ------------- |
+| **Access Token** | Query user data, submit jobs | Session-based |
+| **API Token** | Long-lived access (optional) | 365 days |
+| **Refresh Token** | Renew access after expiry | 14 days |
+
+### Authentication Header
+
+All authenticated requests must include:
+
+```http
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+### Token Generation
+
+Tokens are issued after successful login or registration. Store securely (never in localStorage for sensitive apps).
+
+---
+
+## Base URLs
+
+### Production
+
+```
+https://ailixir.pharmaai.io/api
+```
+
+### Development (Local)
+
+```
+http://localhost:8080/api
+```
+
+### Service-Specific Discovery
+
+Each microservice exposes its own API discovery endpoint:
+
+```
+http://localhost:8002/docs # ADMET Service (FastAPI)
+http://localhost:8001/docs # Drug Repurposing (FastAPI)
+http://localhost:5000/docs # Chemical RAG (FastAPI)
+http://localhost:8080/docs # Laravel API (Swagger)
+```
+
+---
+
+## Common Patterns
+
+### Pagination
+
+List endpoints support pagination:
+
+```query
+?page=1&per_page=20
+```
+
+**Response Field:** `meta.pagination`
+
+```json
+{
+ "data": [...],
+ "meta": {
+ "pagination": {
+ "current_page": 1,
+ "per_page": 20,
+ "total": 100,
+ "last_page": 5
+ }
+ }
+}
+```
+
+### Filtering
+
+Endpoints support filtering (details per endpoint):
+
+```query
+?filter[status]=completed&filter[created_after]=2026-05-01
+```
+
+### Sorting
+
+```query
+?sort=created_at&sort_order=desc
+```
+
+### Asynchronous Jobs
+
+Long-running operations return `202 Accepted` with job tracking URL:
+
+```json
+{
+ "success": true,
+ "job_id": "abc-123-def",
+ "status": "pending",
+ "check_url": "/api/ai/status/abc-123-def"
+}
+```
+
+**Check Status:**
+
+```
+GET /api/ai/status/{job_id}
+```
+
+---
+
+## Health & Status
+
+### System Health
+
+Check overall system status:
+
+```http
+GET /health
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "status": "healthy",
+ "timestamp": "2026-05-29T12:30:45Z",
+ "version": "2.0.0",
+ "uptime_seconds": 876543
+}
+```
+
+### All Services Health
+
+Check all AI microservices:
+
+```http
+GET /ai-services/health
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "services": {
+ "admet": {
+ "status": "healthy",
+ "http_status": 200,
+ "body": {
+ "status": "ready",
+ "model_version": "MPNN_CNN_BindingDB"
+ }
+ },
+ "drug_repurposing": {
+ "status": "healthy",
+ "http_status": 200,
+ "body": {
+ "status": "ready",
+ "model": "DeepPurpose v0.1.5"
+ }
+ },
+ "chemical_rag": {
+ "status": "healthy",
+ "http_status": 200,
+ "body": {
+ "status": "ready",
+ "index_size": 1000000
+ }
+ }
+ }
+}
+```
+
+**Response (503 Service Unavailable):** One or more services is down
+
+---
+
+## Authentication Endpoints
+
+### Register User
+
+```http
+POST /user/register
+Content-Type: application/json
+
+{
+ "name": "Yasmeen Ahmed",
+ "email": "yasmeen@test.com",
+ "password": "password123",
+ "password_confirmation": "password123"
+}
+```
+
+**Response (201 Created):**
+
+```json
+{
+ "success": true,
+ "message": "Registered successfully. Please check your email for OTP verification code.",
+ "data": {
+ "email": "yasmeen@test.com"
+ }
+}
+```
+
+---
+
+### Login
+
+```http
+POST /user/login
+Content-Type: application/json
+
+{
+ "email": "yasmeen@test.com",
+ "password": "password123"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Login successful",
+ "data": {
+ "token": "55|F4V06Yis3FcP35oGoEE2cwmNv3yTiVCpMEx9vYjya891dcb2",
+ "user": {
+ "id": 80,
+ "name": "yasmeen564",
+ "email": "salehyasmeen080@gmail.com",
+ "email_verified_at": null,
+ "last_otp_sent_at": null,
+ "role": "normal",
+ "created_at": "2026-05-25T00:21:51.000000Z",
+ "updated_at": "2026-05-30T00:02:06.000000Z",
+ "email_verification_otp_expires_at": null,
+ "is_verified": true,
+ "password_reset_otp_expires_at": null
+ }
+ }
+}
+```
+
+---
+
+### Verify Email
+
+After account creation, verify email to activate account:
+
+```http
+POST /user/verify-email
+Content-Type: application/json
+
+{
+ "email": "yasmeen@test.com",
+ "otp": "123456"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Email verified successfully",
+ "data": {
+ "token": "54|dxcTq6KRZQfOESfbKcqEoXV4znbkU8IKCND1nX2H085c2309",
+ "user": {
+ "id": 80,
+ "name": "yasmeen564",
+ "email": "salehyasmeen080@gmail.com",
+ "email_verified_at": null,
+ "last_otp_sent_at": null,
+ "role": "normal",
+ "created_at": "2026-05-25T00:21:51.000000Z",
+ "updated_at": "2026-05-30T00:02:06.000000Z",
+ "email_verification_otp_expires_at": null,
+ "is_verified": true,
+ "password_reset_otp_expires_at": null
+ }
+ }
+}
+```
+
+---
+
+### Resend Verification Email
+
+```http
+POST /user/resend-verification
+Content-Type: application/json
+
+{
+ "email": "yasmeen@test.com"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "OTP resent successfully",
+ "data": {
+ "email": "yasmeen111@example.com"
+ }
+}
+```
+
+---
+
+### Forgot Password
+
+```http
+POST /user/forgot-password
+Content-Type: application/json
+
+{
+ "email": "yasmeen@test.com"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "OTP sent successfully",
+ "data": {
+ "email": "salehyasmeen080@gmail.com"
+ }
+}
+```
+
+---
+
+### Reset Password
+
+```http
+POST /user/reset-password
+Content-Type: application/json
+
+{
+ "email": "yasmeen@test.com",
+ "otp": "123456",
+ "password": "new_password123",
+ "password_confirmation": "new_password123"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Password reset successfully"
+}
+```
+
+---
+
+### Logout
+
+```http
+POST /user/logout
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Logged out successfully"
+}
+```
+
+## '''
+
+## AI Integration Endpoints
+
+These endpoints are for testing and monitoring AI microservices. Available only if `AI_INTEGRATION_ROUTES_ENABLED=true`.
+
+### Test ADMET Prediction
+
+Quick test of ADMET service:
+
+```http
+POST /ai-services/test/admet
+Content-Type: application/json
+
+{
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "batch_size": 32
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "service": "admet",
+ "upstream_status": 200,
+ "data": {
+ "status": "success",
+ "predictions": {
+ "absorption": 0.78,
+ "distribution": 0.65,
+ "metabolism": 0.45,
+ "excretion": 0.82,
+ "toxicity": 0.12
+ },
+ "processing_time_ms": 234
+ }
+}
+```
+
+### Test Chemical Search
+
+Quick test of Chemical RAG service:
+
+```http
+POST /ai-services/test/chemical-search
+Content-Type: application/json
+
+{
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "top_k": 5
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "service": "chemical_rag",
+ "upstream_status": 200,
+ "data": {
+ "query": {
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O"
+ },
+ "results": [
+ {
+ "rank": 1,
+ "cid": 2244,
+ "name": "Aspirin",
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "similarity": 1.0,
+ "image_url": "https://..."
+ }
+ ],
+ "processing_time_ms": 145
+ }
+}
+```
+
+### Test Drug Repurposing
+
+Quick test of Drug Repurposing service:
+
+```http
+GET /ai-services/test/drug-repurposing
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "service": "drug_repurposing",
+ "health": {
+ "status": 200,
+ "body": {
+ "status": "healthy",
+ "model": "DeepPurpose",
+ "version": "0.1.5"
+ }
+ },
+ "model_status": {
+ "status": 200,
+ "body": {
+ "available_models": ["MPNN_CNN_BindingDB"]
+ }
+ }
+}
+```
+
+---
+
+## AI Service Endpoints
+
+### Submit AI Job
+
+Submit a drug discovery analysis job:
+
+```http
+POST /ai/run
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "job_type": "admet_prediction",
+ "parameters": {
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "batch_size": 32
+ },
+ "notification_email": "john@pharmaai.io"
+}
+```
+
+**Response (202 Accepted):**
+
+```json
+{
+ "success": true,
+ "job_id": "job-abc-123-def",
+ "status": "pending",
+ "created_at": "2026-05-29T12:30:45Z",
+ "check_url": "/api/ai/status/job-abc-123-def"
+}
+```
+
+### Get Job Status
+
+```http
+GET /ai/status/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK - Pending):**
+
+```json
+{
+ "success": true,
+ "job_id": "job-abc-123-def",
+ "status": "processing",
+ "progress": 45,
+ "estimated_completion": "2026-05-29T12:35:00Z"
+}
+```
+
+**Response (200 OK - Completed):**
+
+```json
+{
+ "success": true,
+ "job_id": "job-abc-123-def",
+ "status": "completed",
+ "results": {
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "predictions": {
+ "absorption": 0.78,
+ "distribution": 0.65,
+ "metabolism": 0.45,
+ "excretion": 0.82,
+ "toxicity": 0.12
+ }
+ },
+ "completed_at": "2026-05-29T12:32:15Z"
+}
+```
+
+**Response (200 OK - Failed):**
+
+```json
+{
+ "success": false,
+ "job_id": "job-abc-123-def",
+ "status": "failed",
+ "error": "Invalid SMILES string",
+ "failed_at": "2026-05-29T12:30:50Z"
+}
+```
+
+### Preview Results
+
+Get a preview of results while job is processing:
+
+```http
+GET /ai/preview/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "job_id": "job-abc-123-def",
+ "status": "processing",
+ "preview": {
+ "processed_compounds": 50,
+ "total_compounds": 100,
+ "average_prediction_time_ms": 234
+ }
+}
+```
+
+### Download Results (Top)
+
+Download first N results (useful for large datasets):
+
+```http
+GET /ai/download/top/{job_id}?limit=100
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```
+Content-Type: text/csv
+Content-Disposition: attachment; filename="results-top-100.csv"
+
+smiles,absorption,distribution,metabolism,excretion,toxicity
+CC(=O)Oc1ccccc1C(=O)O,0.78,0.65,0.45,0.82,0.12
+...
+```
+
+### Download Full Results
+
+Download complete results as CSV:
+
+```http
+GET /ai/download/full/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```
+Content-Type: application/zip
+Content-Disposition: attachment; filename="results-full.zip"
+
+# Contains: predictions.csv, metadata.json, summary.txt
+```
+
+### Job History
+
+Get all jobs submitted by user:
+
+```http
+GET /ai/history?page=1&per_page=20&filter[status]=completed&sort=created_at&sort_order=desc
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "job-abc-123-def",
+ "job_type": "admet_prediction",
+ "status": "completed",
+ "created_at": "2026-05-29T12:00:00Z",
+ "completed_at": "2026-05-29T12:30:00Z",
+ "result_summary": {
+ "total_compounds": 100,
+ "average_absorption": 0.72
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "current_page": 1,
+ "per_page": 20,
+ "total": 42,
+ "last_page": 3
+ }
+ }
+}
+```
+
+---
+
+## AI Agent Endpoints (Chemistry AI)
+
+AI-powered chemistry analysis agent with conversation memory. All endpoints require authentication.
+
+**Base Path:** `/api/chemistry`
+
+**Capabilities:**
+
+- SMILES validation and property calculation
+- Drug-likeness classification (Lipinski Ro5, Veber, Lead-likeness)
+- ADMET profiling with structural toxicity alerts
+- Molecular docking result ranking and recommendation
+- Multi-molecule side-by-side comparison
+- Async CSV batch processing
+- Conversation context via thread_id
+
+---
+
+### Create Conversation Thread
+
+Start a new isolated chemistry conversation thread.
+
+```http
+POST /api/chemistry/thread
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "thread_id": "3a148307-b97f-4910-88db-03eca6477419",
+ "id": 2,
+ "created_at": "2026-05-25T18:34:15.000000Z"
+ }
+}
+```
+
+**Notes:**
+
+- One thread_id per user session
+- Do not share thread IDs across different users
+- Pass the same thread_id across multiple calls to maintain conversation history
+
+---
+
+### List User Threads
+
+Get all active threads for the authenticated user.
+
+```http
+GET /api/chemistry/threads
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": 2,
+ "thread_id": "3a148307-b97f-4910-88db-03eca6477419",
+ "title": "New Conversation",
+ "last_used_at": "2026-05-25T18:34:15.000000Z",
+ "created_at": "2026-05-25T18:34:15.000000Z"
+ },
+ {
+ "id": 1,
+ "thread_id": "0455ef45-faa0-4e82-b808-db1c574ad175",
+ "title": "New Conversation",
+ "last_used_at": "2026-05-25T18:21:23.000000Z",
+ "created_at": "2026-05-24T14:54:10.000000Z"
+ }
+ ]
+}
+```
+
+---
+
+### Send Chat Message
+
+Send a natural language chemistry question to the AI agent.
+
+```http
+POST /api/chemistry/chat
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "message": "Is CC(=O)Oc1ccccc1C(=O)O a good drug candidate?",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "reply": "Based on the analysis of CC(=O)Oc1ccccc1C(=O)O (Aspirin)...",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
+ "processing_time_ms": 11888
+ }
+}
+```
+
+**Example Messages:**
+
+- `"Is CC(=O)Oc1ccccc1C(=O)O a good drug candidate?"`
+- `"Compare aspirin and ibuprofen CC(C)Cc1ccc(cc1)C(C)C(=O)O"`
+- `"What are the toxicity concerns for this molecule?"` (follow-up)
+- `"Which molecule we discussed has the best CNS penetration?"` (follow-up)
+
+**Notes:**
+
+- `thread_id` is optional. Omit to start a fresh conversation automatically.
+- The agent remembers all prior messages on the same thread.
+- The agent automatically handles: SMILES validation, molecular property calculation, drug-likeness rules, ADMET profiling, docking ranking, and multi-molecule comparison.
+
+---
+
+### Analyze Single SMILES
+
+Full molecular analysis pipeline in one call.
+
+```http
+POST /api/chemistry/analyze/smiles
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "reply": "Complete analysis text...",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
+ "processing_time_ms": 5234
+ }
+}
+```
+
+**Pipeline Steps:**
+
+1. Validate SMILES
+2. Compute molecular properties (MW, LogP, HBD, HBA, TPSA, QED, Fsp3)
+3. Drug-likeness classification (Lipinski Ro5, Veber, Lead-likeness)
+4. ADMET profile with structural toxicity alerts
+
+**Notes:**
+
+- `smiles` field is required in JSON body
+- URL-encode special characters if sending via query string: `CC%28%3DO%29Oc1ccccc1C%28%3DO%29O`
+
+---
+
+### Compare Multiple Molecules
+
+Side-by-side comparison of 2 or more molecules with recommendation.
+
+```http
+POST /api/chemistry/analyze/compare
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "smiles": [
+ "CC(=O)Oc1ccccc1C(=O)O",
+ "CC(C)Cc1ccc(cc1)C(C)C(=O)O",
+ "Cn1cnc2c1c(=O)n(c(=O)n2C)C"
+ ],
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "reply": "Side-by-side property table and recommendation...",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
+ "processing_time_ms": 23543
+ }
+}
+```
+
+**Agent Returns:**
+
+- Side-by-side property table (MW, LogP, HBD, HBA, TPSA, QED, Fsp3)
+- Drug-likeness pass/fail for each molecule
+- Named recommendation with justification
+
+---
+
+### Analyze Docking Results
+
+Rank and recommend best docking candidates.
+
+```http
+POST /api/chemistry/analyze/docking
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "docking_data": "CC(=O)Oc1ccccc1C(=O)O | -7.2 | 1.1 | H-bond to Ser195\\nCC(C)Cc1ccc(cc1)C(C)C(=O)O | -8.9 | 0.8 | deep pocket binding\\nCn1cnc2c1c(=O)n(c(=O)n2C)C | -6.1 | 1.9 |",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
+}
+```
+
+**Docking Data Format:**
+
+```
+SMILES | binding_affinity_kcal_mol | rmsd_angstrom | optional_notes
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "reply": "Top Pick: CC(C)Cc1ccc(cc1)C(C)C(=O)O...",
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
+ "processing_time_ms": 5392
+ }
+}
+```
+
+**Interpretation Rules:**
+
+- ΔG more negative = stronger binding
+- RMSD < 2 Å = reliable pose; > 2 Å = uncertain binding mode
+- Best candidate = strongest binder that also passes Lipinski Ro5
+
+---
+
+### Upload CSV for Batch Analysis
+
+Process multiple molecules asynchronously.
+
+```http
+POST /api/chemistry/csv/upload
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: multipart/form-data
+
+file: [Choose File] molecules.csv
+analysis_type: full
+```
+
+**Analysis Types:**
+
+| Type | What It Runs | Speed |
+| ---------- | ---------------------------------- | ------ |
+| `full` | Properties + drug-likeness + ADMET | Slow |
+| `quick` | Lipinski pass/fail + QED only | Fast |
+| `admet` | ADMET profile only | Medium |
+| `classify` | Drug-likeness classification only | Fast |
+
+**Required CSV Columns:**
+
+- `smiles` — SMILES string (required)
+- `name` — compound name or ID (optional, auto-generated if missing)
+
+**Example CSV:**
+
+```csv
+name,smiles
+Aspirin,CC(=O)Oc1ccccc1C(=O)O
+Ibuprofen,CC(C)Cc1ccc(cc1)C(C)C(=O)O
+Caffeine,Cn1cnc2c1c(=O)n(c(=O)n2C)C
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
+ "id": 1,
+ "status": "queued",
+ "total_rows": 4
+ }
+}
+```
+
+**Limits:** Maximum 100 rows per upload. Split larger datasets into batches.
+
+---
+
+### Check CSV Job Status
+
+Poll for processing progress.
+
+```http
+GET /api/chemistry/csv/status/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK - Running):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
+ "status": "running",
+ "total": 4,
+ "completed": 3,
+ "failed_rows": 2,
+ "progress_percent": 75
+ }
+}
+```
+
+**Status Values:**
+| Value | Meaning |
+|-------|---------|
+| `queued` | Job is waiting to start |
+| `running` | Actively processing rows |
+| `done` | All rows finished — results are ready |
+| `failed` | Job-level error (row errors are inside results) |
+
+**Polling:** Poll every 5–10 seconds. Once status == "done", call GET /csv/results/{job_id}.
+
+---
+
+### Download CSV Results
+
+Get completed analysis as CSV file download.
+
+```http
+GET /api/chemistry/csv/results/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```
+Content-Type: text/csv
+Content-Disposition: attachment; filename="results_{job_id}.csv"
+
+row,name,smiles,status,error,analysis
+1,Aspirin,CC(=O)Oc1ccccc1C(=O)O,success,,"Full analysis text..."
+2,Ibuprofen,CC(C)Cc1ccc(cc1)C(C)C(=O)O,failed,"Quota exceeded",...
+```
+
+**Output CSV Columns:**
+
+- `row` — original row number in the uploaded file
+- `name` — compound name
+- `smiles` — input SMILES string
+- `status` — success or failed
+- `analysis` — full agent analysis text
+- `error` — error message (empty if status is success)
+
+---
+
+### List User CSV Jobs
+
+```http
+GET /api/chemistry/csv/jobs
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": 1,
+ "job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
+ "filename": "molecules.csv",
+ "analysis_type": "full",
+ "status": "done",
+ "total_rows": 4,
+ "completed_rows": 4,
+ "failed_rows": 3,
+ "progress_percent": 100,
+ "created_at": "2026-05-29T12:00:00Z"
+ }
+ ]
+}
+```
+
+---
+
+### Delete CSV Job
+
+Remove completed job and free storage.
+
+```http
+DELETE /api/chemistry/csv/jobs/{job_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Job deleted successfully"
+}
+```
+
+**Notes:**
+
+- Call this after successfully downloading results to keep memory usage low
+- Trying to delete a non-existent job returns 404
+
+---
+
+### Get User Analysis History
+
+Retrieve all past analyses with pagination.
+
+```http
+GET /api/chemistry/history?type=smiles
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Description |
+| --------- | ------- | ---------------------------------------------- |
+| `type` | string | Filter: `smiles`, `compare`, `docking`, `chat` |
+| `page` | integer | Page number |
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "current_page": 1,
+ "data": [
+ {
+ "id": 1,
+ "type": "smiles",
+ "input_data": "CC(=O)Oc1ccccc1C(=O)O",
+ "response": "Analysis text...",
+ "status": "success",
+ "created_at": "2026-05-29T12:00:00Z",
+ "thread": {
+ "id": 1,
+ "thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
+ "title": "New Conversation"
+ }
+ }
+ ],
+ "per_page": 20,
+ "total": 42
+ }
+}
+```
+
+---
+
+## Chemical Search Endpoints
+
+### Retrieval-Only Search
+
+Query the chemical database for similar compounds (no explanations):
+
+```http
+POST /chemical-search
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "top_k": 10
+}
+```
+
+**Response (202 Accepted):**
+
+```json
+{
+ "success": true,
+ "query": {
+ "smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)",
+ "top_k": 3
+ },
+ "compounds": [
+ {
+ "rank": 1,
+ "smiles": "CN1C=NC2=C1C(=O)NC(=O)N2C",
+ "name": "Compound_5429",
+ "cid": "5429",
+ "similarity": 1,
+ "explanation": null,
+ "image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/6096554304416497818.png"
+ },
+ {
+ "rank": 2,
+ "smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
+ "name": "Compound_2519",
+ "cid": "2519",
+ "similarity": 0.9922,
+ "explanation": null,
+ "image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/3925926930136341582.png"
+ },
+ {
+ "rank": 3,
+ "smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2)C",
+ "name": "Compound_4687",
+ "cid": "4687",
+ "similarity": 0.9922,
+ "explanation": null,
+ "image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/5189886293630140187.png"
+ }
+ ],
+ "metadata": {
+ "total_results": 3,
+ "search_time_ms": 443.08,
+ "similarity_metric": "Tanimoto",
+ "fingerprint": "Morgan (2048, radius=2)",
+ "source": "retrieval"
+ }
+}
+```
+
+### Full RAG Search
+
+Query with LLM-generated explanations:
+
+```http
+POST /chemical-search/full-rag
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "smiles": "CC(=O)Oc1ccccc1C(=O)O",
+ "top_k": 5
+}
+```
+
+**Response (202 Accepted):**
+
+```json
+{
+ "success": true,
+ "query": {
+ "smiles": "C1=CC=C(C(=C1)C(=O)O)",
+ "top_k": 2
+ },
+ "compounds": [
+ {
+ "rank": 1,
+ "smiles": "C1=CC=C(C=C1)C(=O)O",
+ "name": "Compound_243",
+ "cid": "243",
+ "similarity": 1,
+ "explanation": "These compounds are structurally identical, as indicated by the similarity score of 1.000. Both share the same core scaffold of a benzene ring (C1=CC=C(C=C1)) with a carboxylic acid group (C(=O)O) attached to it. The molecular formulas (C7H6O2) and heavy atom counts (9) are identical, confirming no atom substitutions or differences in structure. The only apparent difference is the SMILES representation, which is",
+ "image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/2500971538592982398.png"
+ },
+ {
+ "rank": 2,
+ "smiles": "C1=CC=C(C=C1)[13C](=O)O",
+ "name": "Compound_19759",
+ "cid": "19759",
+ "similarity": 1,
+ "explanation": "These compounds are structurally identical, as indicated by the similarity score of 1.000. Both share the same core scaffold of a benzene ring (C1=CC=C(C=C1)) with a carboxylic acid group (C(=O)O) attached. The only difference is that the match compound has a ^13C isotope substitution at the carboxylic acid carbon, which does not alter the molecular structure or connectivity. Both compounds have the same molecular formula (C7H6O",
+ "image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/8561718417946188610.png"
+ }
+ ],
+ "metadata": {
+ "total_results": 2,
+ "search_time_ms": 7975.89,
+ "similarity_metric": "Tanimoto",
+ "fingerprint": "Morgan (2048, radius=2)",
+ "source": "full_rag"
+ }
+}
+```
+
+## Docking API
+
+### POST `/api/docking/submit`
+
+- Auth required
+- Content type: `multipart/form-data`
+- Required fields:
+ - `protein_name` (string)
+ - `protein_file` (file)
+ - `center_x` (numeric)
+ - `center_y` (numeric)
+ - `center_z` (numeric)
+ - `box_size_x` (numeric)
+ - `box_size_y` (numeric)
+ - `box_size_z` (numeric)
+
+### VERY IMPORTANT
+
+### Optional fields:
+
+- `ligand_name` (string)
+- `exhaustiveness` (integer)
+- `n_poses` (integer)
+
+### Must include exactly one of:
+
+- `ligand_file` (file)
+- `ligand_smiles` (string)
+
+#### Example curl request
+
+```bash
+curl -X POST "{base_url}/api/docking/submit" \\
+ -H "Authorization: Bearer {token}" \\
+ -F "protein_name=EGFR" \\
+ -F "protein_file=@protein.pdbqt" \\
+ -F "ligand_name=Erlotinib" \\
+ -F "ligand_smiles=CC1=CC(=O)NC2=C1C=CC=C2" \\
+ -F "center_x=10.0" \\
+ -F "center_y=15.0" \\
+ -F "center_z=20.0" \\
+ -F "box_size_x=25.0" \\
+ -F "box_size_y=25.0" \\
+ -F "box_size_z=25.0" \\
+ -F "exhaustiveness=8" \\
+ -F "n_poses=5"
+```
+
+#### Success response
+
+```json
+{
+ "success": true,
+ "message": "Docking Job Successfully Queued",
+ "data": {
+ "job_id": 123,
+ "status": "pending"
+ }
+}
+```
+
+---
+
+### GET `/api/docking/{id}`
+
+- Auth required
+- Path parameter:
+ - `id` (integer)
+
+#### Response (completed)
+
+```json
+{
+ "success": true,
+ "message": "Job details retrieved successfully",
+ "data": {
+ "id": 5,
+ "status": "completed",
+ "protein": "EGFR",
+ "ligand": "Erlotinib",
+ "created_at": "2026-06-17T05:45:31+00:00",
+ "download_url": "{base_url}/api/docking/download/5",
+ "scores": [
+ {
+ "affinity": 0,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ },
+ {
+ "affinity": 0,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ },
+ {
+ "affinity": 0.001,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ }
+ ],
+ "error": null
+ }
+}
+```
+
+If the job is not completed, `scores` will be an empty array.
+
+#### Response (failed)
+
+```json
+{
+ "success": true,
+ "message": "Job details retrieved successfully",
+ "data": {
+ "id": 7,
+ "status": "failed",
+ "protein": "EGFR",
+ "ligand": "Erlotinib",
+ "created_at": "2026-06-17T05:45:31+00:00",
+ "download_url": "{base_url}/api/docking/download/7",
+ "scores": [],
+ "error": "\n\nPDBQT parsing error: Unknown or inappropriate tag found in flex residue or ligand.\n > ATOM 1 N UNL 1 8.304 191.693 26.328 0.00 0.00 +0.000 N \n"
+ }
+}
+```
+
+#### Error response (not found)
+
+```json
+{
+ "success": false,
+ "message": "Docking job not found or unauthorized",
+ "data": null
+}
+```
+
+---
+
+### GET `/api/docking/history`
+
+- Auth required
+- Query parameters:
+ - `per_page` (integer, optional, default 15)
+
+#### Response
+
+```json
+{
+ "success": true,
+ "message": "Docking history retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 5,
+ "status": "completed",
+ "protein": "EGFR",
+ "ligand": "Erlotinib",
+ "created_at": "2026-06-17T05:45:31+00:00",
+ "download_url": "{base_url}/api/docking/download/5",
+ "scores": [
+ {
+ "affinity": 0,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ },
+ {
+ "affinity": 0,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ },
+ {
+ "affinity": 0.001,
+ "inter": 0,
+ "intra": -2.031,
+ "torsions": 0,
+ "unbound": -2.031
+ }
+ ],
+ "error": null
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 2,
+ "totalResults": 3,
+ "perPage": 2,
+ "hasNextPage": true,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+### GET `/api/docking/download/{id}`
+
+- Path parameter:
+ - `id` (integer)
+- Query parameter (instead of Bearer header for browser link clicks):
+ - `token` (string, required) — Sanctum token
+- Accepts `Authorization: Bearer` header or `?token=` query parameter.
+- Returns a file download for the completed docking result (multi-model PDBQT with all poses).
+- Content disposition filename: `docking_result_{id}.pdbqt`
+
+---
+
+## Drug Repurposing API
+
+### POST `/api/drug-repurposing/targets`
+
+- Auth required
+- Content type: `application/json`
+- Request body:
+ - `disease_name` (string, required)
+ - `top_n` (integer, optional, default 10, range 1–100)
+
+#### Example curl request
+
+```bash
+curl -X POST "{base_url}/api/drug-repurposing/targets" \\
+ -H "Authorization: Bearer {token}" \\
+ -H "Content-Type: application/json" \\
+ -d '{"disease_name": "Type 2 Diabetes", "top_n": 10}'
+```
+
+#### Success response
+
+```json
+{
+ "success": true,
+ "message": "Target lookup queued successfully",
+ "data": {
+ "job_id": 15,
+ "status": "pending"
+ }
+}
+```
+
+---
+
+### GET `/api/drug-repurposing/targets/{id}`
+
+- Auth required
+- Path parameter:
+ - `id` (integer)
+
+#### Success response (completed)
+
+```json
+{
+ "success": true,
+ "message": "Target lookup history retrieved successfully",
+ "data": {
+ "id": 4,
+ "input": {
+ "disease_name": "Type 2 Diabetes",
+ "top_n": 10
+ },
+ "output": {
+ "disease": "Type 2 Diabetes",
+ "disease_id": "EFO_0001360",
+ "total_targets": 10,
+ "targets": [
+ {
+ "symbol": "KCNJ11",
+ "name": "potassium inwardly rectifying channel subfamily J member 11",
+ "score": 0.8651,
+ "sequence": null,
+ "uniprot_id": "Q14654",
+ "pdb_ids": ["2UKM", "2UGY", "2UUG"]
+ },
+ {
+ "symbol": "ABCC8",
+ "name": "ATP binding cassette subfamily C member 8",
+ "score": 0.8648,
+ "sequence": null,
+ "uniprot_id": "Q09428",
+ "pdb_ids": []
+ },
+ {
+ "symbol": "GCK",
+ "name": "glucokinase",
+ "score": 0.8612,
+ "sequence": null,
+ "uniprot_id": "P35557",
+ "pdb_ids": ["1V4S", "3F9M", "4ISE"]
+ },
+ {
+ "symbol": "PPARG",
+ "name": "peroxisome proliferator activated receptor gamma",
+ "score": 0.8486,
+ "sequence": null,
+ "uniprot_id": "P37231",
+ "pdb_ids": ["7AEX", "7AEW", "7AEV"]
+ },
+ {
+ "symbol": "INSR",
+ "name": "insulin receptor",
+ "score": 0.7887,
+ "sequence": null,
+ "uniprot_id": "P06213",
+ "pdb_ids": ["2HR7", "3EKN", "4IBM"]
+ },
+ {
+ "symbol": "HNF1B",
+ "name": "HNF1 homeobox B",
+ "score": 0.7846,
+ "sequence": null,
+ "uniprot_id": "P35680",
+ "pdb_ids": []
+ },
+ {
+ "symbol": "HNF1A",
+ "name": "HNF1 homeobox A",
+ "score": 0.7796,
+ "sequence": null,
+ "uniprot_id": "P20823",
+ "pdb_ids": []
+ },
+ {
+ "symbol": "HNF4A",
+ "name": "hepatocyte nuclear factor 4 alpha",
+ "score": 0.7763,
+ "sequence": null,
+ "uniprot_id": "P41235",
+ "pdb_ids": ["7D1C", "7D1D"]
+ },
+ {
+ "symbol": "WFS1",
+ "name": "wolframin ER transmembrane glycoprotein",
+ "score": 0.7695,
+ "sequence": null,
+ "uniprot_id": "O76024",
+ "pdb_ids": []
+ },
+ {
+ "symbol": "GLP1R",
+ "name": "glucagon like peptide 1 receptor",
+ "score": 0.7667,
+ "sequence": null,
+ "uniprot_id": "P43220",
+ "pdb_ids": []
+ }
+ ]
+ },
+ "status": "completed"
+ }
+}
+```
+
+If the job is not completed, the `output` field will be `null`.
+
+---
+
+### GET `/api/drug-repurposing/targets/history`
+
+- Auth required
+- Query parameters:
+ - `per_page` (integer, optional, default 15)
+
+#### Response
+
+```json
+{
+ "success": true,
+ "message": "Target lookup history retrieved successfully",
+ "data": {
+ "data": [
+ {
+ "id": 4,
+ "input": {
+ "disease_name": "Type 2 Diabetes",
+ "top_n": 10
+ },
+ "output": {
+ "disease": "Type 2 Diabetes",
+ "disease_id": "EFO_0001360",
+ "total_targets": 10,
+ "targets": [
+ {
+ "symbol": "KCNJ11",
+ "name": "potassium inwardly rectifying channel subfamily J member 11",
+ "score": 0.8651,
+ "sequence": null,
+ "uniprot_id": "Q14654",
+ "pdb_ids": ["2UKM", "2UGY", "2UUG"]
+ },
+ {
+ "symbol": "GCK",
+ "name": "glucokinase",
+ "score": 0.8612,
+ "sequence": null,
+ "uniprot_id": "P35557",
+ "pdb_ids": ["1V4S", "3F9M", "4ISE"]
+ }
+ ]
+ },
+ "status": "completed",
+ "created_at": "2026-06-03T07:31:42.000000Z"
+ }
+ ],
+ "pagination": {
+ "current_page": 1,
+ "per_page": 15,
+ "total": 3,
+ "last_page": 1,
+ "has_more": false
+ }
+ }
+}
+```
+
+---
+
+### POST `/api/drug-repurposing/screen`
+
+- Auth required
+- Content type: `application/json`
+- Request body:
+ - `disease_name` (string, required)
+ - `known_drugs` (array of strings, optional)
+ - `min_score` (numeric, optional, range 0–1)
+ - `top_n_targets` (integer, optional, range 1–100)
+
+#### Example curl request
+
+```bash
+curl -X POST "{base_url}/api/drug-repurposing/screen" \\
+ -H "Authorization: Bearer {token}" \\
+ -H "Content-Type: application/json" \\
+ -d '{
+ "disease_name": "Type 2 Diabetes",
+ "known_drugs": ["Metformin", "Insulin"],
+ "min_score": 0.5,
+ "top_n_targets": 10
+ }'
+```
+
+#### Success response
+
+```json
+{
+ "success": true,
+ "message": "Screening queued successfully",
+ "data": {
+ "job_id": 22,
+ "status": "pending"
+ }
+}
+```
+
+---
+
+### GET `/api/drug-repurposing/screen/{id}`
+
+- Auth required
+- Path parameter:
+ - `id` (integer)
+
+#### Success response (completed)
+
+```json
+{
+ "success": true,
+ "message": "Screening history retrieved successfully",
+ "data": {
+ "id": 22,
+ "input": {
+ "disease_name": "Type 2 Diabetes",
+ "min_score": 0.5,
+ "top_n_targets": 10,
+ "known_drugs": ["Metformin", "Insulin"]
+ },
+ "output": {
+ "disease_name": "Type 2 Diabetes",
+ "total_targets_found": 10,
+ "total_drugs_screened": 200,
+ "total_pairs_evaluated": 2000,
+ "top_candidates": [
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "KCNJ11",
+ "uniprot_id": "Q14654",
+ "binding_score": 0.9821,
+ "rank": 1,
+ "status": "Potential Discovery"
+ },
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "ABCC8",
+ "uniprot_id": "Q09428",
+ "binding_score": 0.9765,
+ "rank": 2,
+ "status": "Potential Discovery"
+ },
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "GCK",
+ "uniprot_id": "P35557",
+ "binding_score": 0.9712,
+ "rank": 3,
+ "status": "Potential Discovery"
+ },
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "PPARG",
+ "uniprot_id": "P37231",
+ "binding_score": 0.9689,
+ "rank": 4,
+ "status": "Potential Discovery"
+ },
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "INSR",
+ "uniprot_id": "P06213",
+ "binding_score": 0.9634,
+ "rank": 5,
+ "status": "Potential Discovery"
+ }
+ ],
+ "warnings": []
+ },
+ "status": "completed"
+ }
+}
+```
+
+If the job is not completed, the `output` field will be `null`.
+
+---
+
+### GET `/api/drug-repurposing/screen/history`
+
+- Auth required
+- Query parameters:
+ - `per_page` (integer, optional, default 15)
+
+#### Response
+
+```json
+{
+ "success": true,
+ "message": "Screening history retrieved successfully",
+ "data": {
+ "data": [
+ {
+ "id": 22,
+ "input": {
+ "disease_name": "Type 2 Diabetes",
+ "min_score": 0.5,
+ "top_n_targets": 10,
+ "known_drugs": ["Metformin", "Insulin"]
+ },
+ "output": {
+ "disease_name": "Type 2 Diabetes",
+ "total_targets_found": 10,
+ "total_drugs_screened": 200,
+ "total_pairs_evaluated": 2000,
+ "top_candidates": [
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "KCNJ11",
+ "uniprot_id": "Q14654",
+ "binding_score": 0.9821,
+ "rank": 1,
+ "status": "Potential Discovery"
+ },
+ {
+ "drug_name": "Drug_CHEMBL1754",
+ "smiles": "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)Cl)Cl",
+ "target_symbol": "ABCC8",
+ "uniprot_id": "Q09428",
+ "binding_score": 0.9765,
+ "rank": 2,
+ "status": "Potential Discovery"
+ }
+ ],
+ "warnings": []
+ },
+ "status": "completed",
+ "created_at": "2026-06-02T21:35:24.000000Z"
+ }
+ ],
+ "pagination": {
+ "current_page": 1,
+ "per_page": 15,
+ "total": 5,
+ "last_page": 1,
+ "has_more": false
+ }
+ }
+}
+```
+
+---
+
+## Convert SMILES API
+
+### GET `/api/convert-smiles/history`
+
+- Auth required
+- Query parameters:
+ - `per_page` (integer, optional, default 15)
+
+#### Response
+
+```json
+{
+ "success": true,
+ "message": "Conversion history retrieved successfully",
+ "data": {
+ "items": [
+ {
+ "job_id": 16,
+ "status": "completed",
+ "smiles": "CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0",
+ "created_at": "2026-04-21T18:43:58+00:00",
+ "results": {
+ "download_url": "{base_url}/api/convert-smiles/download/{job_id}"
+ }
+ }
+ ],
+ "pagination": {
+ "current_page": 1,
+ "per_page": 3,
+ "total": 13,
+ "last_page": 5,
+ "has_more": true
+ }
+ }
+}
+```
+
+---
+
+### POST `/api/convert-smiles/convert`
+
+- Auth required
+- Content type: `application/json`
+- Request body:
+ - `ligand_smiles` (string, required)
+
+#### Example curl request
+
+```bash
+curl -X POST "{base_url}/api/convert-smiles/convert" \\
+ -H "Authorization: Bearer {token}" \\
+ -H "Content-Type: application/json" \\
+ -d '{"ligand_smiles":"CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0"}'
+```
+
+#### Success response
+
+```json
+{
+ "success": true,
+ "message": "SMILES converted to PDBQT successfully",
+ "data": {
+ "job_id": 16,
+ "download_url": "{base_url}/api/convert-smiles/download/{job_id}",
+ "smiles": "CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0"
+ }
+}
+```
+
+---
+
+### GET `/api/convert-smiles/download/{id}`
+
+- Auth required
+- Path parameter:
+ - `id` (integer)
+- Returns a file download for the converted PDBQT.
+- Content disposition filename: `converted_ligand_{id}.pdbqt`
+
+---
+
+## User Management Endpoints
+
+### Get Profile
+
+```http
+GET /user/profile
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "user": {
+ "id": 1,
+ "name": "John Researcher",
+ "email": "john@pharmaai.io",
+ "email_verified_at": "2026-05-29T10:00:00Z",
+ "profile": {
+ "institution": "MIT",
+ "research_focus": "drug_discovery",
+ "bio": "Computational chemist..."
+ },
+ "created_at": "2026-05-01T00:00:00Z"
+ }
+}
+```
+
+### Update Profile
+
+```http
+POST /user/update-profile
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "name": "John Researcher, PhD",
+ "profile": {
+ "institution": "Stanford",
+ "research_focus": "drug_discovery",
+ "bio": "Senior researcher..."
+ }
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Profile updated successfully",
+ "user": {...}
+}
+```
+
+---
+
+## Awards And Scientists API
+
+### List Awards
+
+```http
+GET /awards?page=1&per_page=10
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Awards retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 1,
+ "name": "Nobel Prize in Physiology or Medicine",
+ "category": "Medicine",
+ "images": ["https://..."],
+ "short_description": "The Nobel Prize in Physiology or Medicine is the world's most prestigious award...",
+ "scientists_count": 9,
+ "scientists": [
+ {
+ "id": 14,
+ "name": "Alexander Fleming",
+ "nationality": "British",
+ "birth_year": null,
+ "death_year": null,
+ "field": "Microbiology",
+ "images": ["https://..."],
+ "bio": null,
+ "short_bio": null,
+ "impact": null
+ }
+ ]
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 1,
+ "totalResults": 10,
+ "perPage": 10,
+ "hasNextPage": false,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+### Get Award Details
+
+```http
+GET /awards/{award_id}
+```
+
+**Example:** `GET /awards/2`
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Award retrieved successfully",
+ "data": {
+ "id": 2,
+ "name": "Lasker Award",
+ "category": "Medical Research",
+ "images": ["https://..."],
+ "short_description": "The Lasker Award is often called the 'American Nobel Prize'...",
+ "scientists": [
+ {
+ "id": 17,
+ "name": "Tu Youyou",
+ "nationality": "Chinese",
+ "birth_year": null,
+ "death_year": null,
+ "field": "Pharmaceutical Chemistry",
+ "images": ["https://..."],
+ "bio": null,
+ "short_bio": null,
+ "impact": null
+ }
+ ]
+ }
+}
+```
+
+---
+
+### Get Award Scientists
+
+```http
+GET /awards/{award_id}/scientists
+```
+
+**Example:** `GET /awards/2/scientists`
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Scientists retrieved successfully",
+ "data": [
+ {
+ "id": 17,
+ "name": "Tu Youyou",
+ "nationality": "Chinese",
+ "birth_year": null,
+ "death_year": null,
+ "field": "Pharmaceutical Chemistry",
+ "images": ["https://..."],
+ "bio": null,
+ "short_bio": null,
+ "impact": null
+ }
+ ]
+}
+```
+
+---
+
+### List Scientists
+
+```http
+GET /scientists?page=1&per_page=50
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Scientists retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 14,
+ "name": "Alexander Fleming",
+ "nationality": "British",
+ "birth_year": null,
+ "death_year": null,
+ "field": "Microbiology",
+ "images": ["https://..."],
+ "bio": null,
+ "short_bio": null,
+ "impact": null
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 1,
+ "totalResults": 24,
+ "perPage": 50,
+ "hasNextPage": false,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+### Get Scientist Details
+
+```http
+GET /scientists/{scientist_id}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Scientist retrieved successfully",
+ "data": {
+ "id": 14,
+ "name": "Alexander Fleming",
+ "nationality": "British",
+ "birth_year": null,
+ "death_year": null,
+ "field": "Microbiology",
+ "images": ["https://..."],
+ "bio": "Full biography text...",
+ "short_bio": "Short biography...",
+ "impact": "Scientific impact description...",
+ "awards": [
+ {
+ "id": 1,
+ "name": "Nobel Prize in Physiology or Medicine",
+ "year": 1945
+ }
+ ]
+ }
+}
+```
+
+---
+
+### Get Scientist Awards
+
+```http
+GET /scientists/{scientist_id}/awards
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Awards retrieved successfully",
+ "data": [
+ {
+ "id": 1,
+ "name": "Nobel Prize in Physiology or Medicine",
+ "category": "Medicine",
+ "year": 1945,
+ "images": ["https://..."]
+ }
+ ]
+}
+```
+
+---
+
+## News API
+
+### Get News Feed
+
+```http
+GET /news?page=1&per_page=10
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Articles retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 13,
+ "title": "AAPS National Biotechnology Conference",
+ "summary": "11 May 2026 - 14 May 2026 - All Day Sheraton San Diego...",
+ "source": "European Pharmaceutical Review",
+ "url": "https://...",
+ "published_at": "2026-05-11T07:00:00+00:00"
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 44,
+ "totalResults": 434,
+ "perPage": 10,
+ "hasNextPage": true,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+### Get News with Pagination
+
+```http
+GET /news?page=24&per_page=10
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Articles retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 21,
+ "title": "EU kicks off one-year pilot to expedite multinational trials",
+ "summary": "The European Union unveiled details of a pilot project...",
+ "source": "Endpoints News",
+ "url": "https://...",
+ "published_at": "2026-01-23T19:24:15+00:00"
+ }
+ ],
+ "pagination": {
+ "currentPage": 24,
+ "totalPages": 44,
+ "totalResults": 434,
+ "perPage": 10,
+ "hasNextPage": true,
+ "hasPrevPage": true
+ }
+ }
+}
+```
+
+---
+
+### Refresh News
+
+```http
+GET /news/refresh
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "News refreshed successfully",
+ "data": {
+ "new_articles": 5,
+ "total_articles": 439
+ }
+}
+```
+
+---
+
+### Get News Categories
+
+```http
+GET /news/categories
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Categories retrieved successfully",
+ "data": [
+ "chemistry",
+ "pharma",
+ "biotech",
+ "medicine",
+ "research",
+ "clinical_trials"
+ ]
+}
+```
+
+---
+
+### Save Article
+
+```http
+POST /news/{article_id}/save
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Article saved successfully",
+ "data": {
+ "saved_article_id": 123,
+ "article_id": 13,
+ "title": "AAPS National Biotechnology Conference",
+ "saved_at": "2026-05-29T12:00:00Z"
+ }
+}
+```
+
+---
+
+### Share Article
+
+```http
+POST /news/{article_id}/share
+Authorization: Bearer YOUR_ACCESS_TOKEN
+Content-Type: application/json
+
+{
+ "share_with": "colleague@pharmaai.io",
+ "message": "Check out this interesting article!"
+}
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Article shared successfully",
+ "data": {
+ "share_id": 456,
+ "article_id": 13,
+ "shared_with": "colleague@pharmaai.io",
+ "shared_at": "2026-05-29T12:00:00Z"
+ }
+}
+```
+
+---
+
+### Get Saved Articles
+
+```http
+GET /news/saved
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Saved articles retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 123,
+ "article_id": 13,
+ "title": "AAPS National Biotechnology Conference",
+ "summary": "11 May 2026 - 14 May 2026...",
+ "source": "European Pharmaceutical Review",
+ "saved_at": "2026-05-29T12:00:00Z"
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 1,
+ "totalResults": 5,
+ "perPage": 10,
+ "hasNextPage": false,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+### Unsave Article
+
+```http
+DELETE /news/saved/{saved_article_id}
+Authorization: Bearer YOUR_ACCESS_TOKEN
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Article unsaved successfully"
+}
+```
+
+'''
+
+## Admet Prediction API
+
+### POST `/api/admet/predict`
+
+Predict ADMET properties for chemical compounds from SMILES strings or file uploads.
+
+**Authentication:** Bearer token required
+
+**Input Options:**
+
+| Option | Type | Description |
+| -------- | ------ | ------------------------------------------ |
+| `smiles` | string | Comma-separated SMILES strings (max 6) |
+| `file` | file | CSV or TXT file with SMILES (max 100 rows) |
+
+**Example 1: JSON Input**
+
+```bash
+curl -X POST /api/admet/predict \\
+ -H "Authorization: Bearer {token}" \\
+ -H "Content-Type: application/json" \\
+ -d '{"smiles": "c1ccccc1, CCO, CCC"}'
+```
+
+**Example 2: File Upload Input (CSV or TXT)**
+
+```bash
+curl -X POST /api/admet/predict \\
+ -H "Authorization: Bearer {token}" \\
+ -F "file=@/path/to/file.csv"
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "ADMET predictions generated successfully",
+ "data": [
+ {
+ "smiles": "c1ccccc1",
+ "absorption": -2.9907076358795166,
+ "distribution": 0.8750113248825073,
+ "metabolism": -0.11453431844711304,
+ "excretion": 7.360866546630859,
+ "toxicity": 0.8492187261581421
+ },
+ {
+ "smiles": "CCO",
+ "absorption": -3.7663559913635254,
+ "distribution": 0.8921501040458679,
+ "metabolism": -0.2787337601184845,
+ "excretion": 20.92142677307129,
+ "toxicity": -0.6471661329269409
+ },
+ {
+ "smiles": "CCC",
+ "absorption": -3.09336519241333,
+ "distribution": 1.2096238136291504,
+ "metabolism": -0.17122718691825867,
+ "excretion": 20.524127960205078,
+ "toxicity": -0.25303196907043457
+ }
+ ]
+}
+```
+
+'''
+
+## AI Generation API
+
+### POST `/api/ai/generation/run`
+
+**Authentication:** Bearer token required
+
+**Input Options:**
+
+| Option | Type | Description |
+| --------------- | ------- | --------------------------------------------------------------------------- |
+| `num_molecules` | integer | Number of molecules to generate |
+| `return_top_k` | integer | Number of top molecules to return |
+| `docking_mode` | string | Docking mode: "all" or "off" or "top_k" |
+| `dock_top_k` | integer | Number of top molecules to dock is only required when docking mode is top_k |
+
+**Example:**
+
+```bash
+curl -X POST /api/ai/generation/run \\
+ -H "Authorization: Bearer {token}" \\
+ -H "Content-Type: application/json" \\
+ -d '{"num_molecules": 5, "return_top_k": 5, "docking_mode": "all", "dock_top_k": 5}'
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Generation job started successfully",
+ "data": {
+ "job_id": "gen_20260627_192907_58c7f5",
+ "status": "running",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "off",
+ "dock_top_k": 0,
+ "created_at": "2026-06-27 19:29:05"
+ }
+}
+```
+
+---
+
+**GET `/api/ai/generation/status/{job_id}`**
+
+**Authentication:** Bearer token required
+
+**Example:**
+
+```bash
+curl -X GET /api/ai/generation/status/gen_20260602_174612_90c1b1 \\
+ -H "Authorization: Bearer {token}"
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Generation job status",
+ "data": {
+ "job_id": "gen_20260627_192907_58c7f5",
+ "status": "completed",
+ "stage": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "off",
+ "dock_top_k": 0,
+ "summary": {
+ "num_requested": 5,
+ "num_generated": 4,
+ "num_valid": 4,
+ "num_returned": 4,
+ "num_docked": 0
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "download_url": "https://shdwrow-ailixir-generation.hf.space/files/jobs/gen_20260627_192907_58c7f5/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "download_url": "https://shdwrow-ailixir-generation.hf.space/files/jobs/gen_20260627_192907_58c7f5/generated_results.json"
+ }
+ },
+ "ligands": [
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 4.57,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5234216428527144,
+ "sa_score": 2.7645745083533857,
+ "pred_pAff_mean": 10.726751327514648,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 1
+ },
+ {
+ "SMILES": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "SMILES_state": 1,
+ "NLL": 5.39,
+ "valid": true,
+ "canonical_smiles": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "mw": 427.5120000000002,
+ "logp": 2.8160000000000007,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5240018720180243,
+ "sa_score": 2.567665997715085,
+ "pred_pAff_mean": 10.247249603271484,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 2
+ },
+ {
+ "SMILES": "O=C(CCc1ccncc1)Nc1ccc2nncc(-c3ccc4nncn4c3)c2c1",
+ "SMILES_state": 1,
+ "NLL": 12.24,
+ "valid": true,
+ "canonical_smiles": "O=C(CCc1ccncc1)Nc1ccc2nncc(-c3ccc4nncn4c3)c2c1",
+ "mw": 395.4260000000001,
+ "logp": 3.3058000000000014,
+ "tpsa": 97.96,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.4902723648859505,
+ "sa_score": 2.652044473533349,
+ "pred_pAff_mean": 8.516275405883789,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 3
+ },
+ {
+ "SMILES": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCOCC4)cc23)c1",
+ "SMILES_state": 1,
+ "NLL": 6.54,
+ "valid": true,
+ "canonical_smiles": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCOCC4)cc23)c1",
+ "mw": 433.51200000000034,
+ "logp": 3.3061000000000016,
+ "tpsa": 96.45,
+ "hbd": 2,
+ "hba": 6,
+ "rot_bonds": 7,
+ "qed": 0.593505953472874,
+ "sa_score": 2.37134883917013,
+ "pred_pAff_mean": 7.571469783782959,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 4
+ }
+ ],
+ "created_at": "2026-06-27 19:29:05"
+ }
+}
+```
+
+---
+
+**GET `/api/ai/generation/jobs/{job_id}/results`**
+
+**Authentication:** Bearer token required
+
+**Example:**
+
+```bash
+curl -X GET /api/ai/generation/jobs/gen_20260602_174612_90c1b1/results \\
+ -H "Authorization: Bearer {token}"
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Generation job results",
+ "data": {
+ "job_id": "gen_20260627_192907_58c7f5",
+ "status": "completed",
+ "stage": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "off",
+ "dock_top_k": 0,
+ "summary": {
+ "num_requested": 5,
+ "num_generated": 4,
+ "num_valid": 4,
+ "num_returned": 4,
+ "num_docked": 0
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "download_url": "https://shdwrow-ailixir-generation.hf.space/files/jobs/gen_20260627_192907_58c7f5/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "download_url": "https://shdwrow-ailixir-generation.hf.space/files/jobs/gen_20260627_192907_58c7f5/generated_results.json"
+ }
+ },
+ "ligands": [
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 4.57,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5234216428527144,
+ "sa_score": 2.7645745083533857,
+ "pred_pAff_mean": 10.726751327514648,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 1
+ },
+ {
+ "SMILES": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "SMILES_state": 1,
+ "NLL": 5.39,
+ "valid": true,
+ "canonical_smiles": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "mw": 427.5120000000002,
+ "logp": 2.8160000000000007,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5240018720180243,
+ "sa_score": 2.567665997715085,
+ "pred_pAff_mean": 10.247249603271484,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 2
+ },
+ {
+ "SMILES": "O=C(CCc1ccncc1)Nc1ccc2nncc(-c3ccc4nncn4c3)c2c1",
+ "SMILES_state": 1,
+ "NLL": 12.24,
+ "valid": true,
+ "canonical_smiles": "O=C(CCc1ccncc1)Nc1ccc2nncc(-c3ccc4nncn4c3)c2c1",
+ "mw": 395.4260000000001,
+ "logp": 3.3058000000000014,
+ "tpsa": 97.96,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.4902723648859505,
+ "sa_score": 2.652044473533349,
+ "pred_pAff_mean": 8.516275405883789,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 3
+ },
+ {
+ "SMILES": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCOCC4)cc23)c1",
+ "SMILES_state": 1,
+ "NLL": 6.54,
+ "valid": true,
+ "canonical_smiles": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCOCC4)cc23)c1",
+ "mw": 433.51200000000034,
+ "logp": 3.3061000000000016,
+ "tpsa": 96.45,
+ "hbd": 2,
+ "hba": 6,
+ "rot_bonds": 7,
+ "qed": 0.593505953472874,
+ "sa_score": 2.37134883917013,
+ "pred_pAff_mean": 7.571469783782959,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 4
+ }
+ ],
+ "created_at": "2026-06-27 19:29:05"
+ }
+}
+```
+
+---
+
+### POST `/api/ai/generation/jobs/{job_id}/cancel`
+
+**Authentication:** Bearer token required
+
+**Example:**
+
+```bash
+curl -X POST http://localhost:8080/api/ai/generation/jobs/{job_id}/cancel \
+ -H "Authorization: Bearer {token}"
+```
+
+**Response: Status:Completed**
+
+```json
+{
+ "success": false,
+ "message": "Cannot cancel a completed job",
+ "data": null
+}
+```
+
+**Response: Status:No Completed**
+
+```json
+{
+ "success": true,
+ "message": "Job cancelled successfully",
+ "data": {
+ "job_id": "gen_20260627_193543_01b810",
+ "status": "cancelled"
+ }
+}
+```
+
+---
+
+### GET `/api/ai/generation/history`
+
+**Authentication:** Bearer token required
+
+**Example:**
+
+```bash
+curl -X GET http://localhost:8080/api/ai/generation/history \
+ -H "Authorization: Bearer {token}"
+```
+
+**Response:**
+
+```json
+{
+ "success": true,
+ "message": "Generation job history retrieved successfully",
+ "data": {
+ "results": [
+ {
+ "id": 5,
+ "user_id": 1,
+ "job_id": "gen_20260602_185419_15fc7b",
+ "status": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 3,
+ "return_top_k": 3,
+ "docking_mode": "all",
+ "dock_top_k": 3,
+ "summary": {
+ "num_requested": 3,
+ "num_generated": 3,
+ "num_valid": 3,
+ "num_returned": 3,
+ "num_docked": 3
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "relative_url": "/files/jobs/gen_20260602_185419_15fc7b/generated_results.csv",
+ "download_url": "https://superplausibly-nonflowering-keiko.ngrok-free.dev/files/jobs/gen_20260602_185419_15fc7b/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "relative_url": "/files/jobs/gen_20260602_185419_15fc7b/generated_results.json",
+ "download_url": "https://superplausibly-nonflowering-keiko.ngrok-free.dev/files/jobs/gen_20260602_185419_15fc7b/generated_results.json"
+ }
+ },
+ "created_at": "2026-06-02T18:54:16.000000Z",
+ "updated_at": "2026-06-02T18:56:01.000000Z",
+ "ligands": [
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 4.14,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5234216428527144,
+ "sa_score": 2.7645745083533857,
+ "pred_pAff_mean": 10.726750373840332,
+ "docking_score": -9.16,
+ "docking_status": "completed",
+ "rank": 1
+ },
+ {
+ "SMILES": "O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1",
+ "SMILES_state": 1,
+ "NLL": 6.06,
+ "valid": true,
+ "canonical_smiles": "O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1",
+ "mw": 395.89400000000006,
+ "logp": 4.451200000000003,
+ "tpsa": 70.15,
+ "hbd": 2,
+ "hba": 5,
+ "rot_bonds": 6,
+ "qed": 0.6447750051881624,
+ "sa_score": 2.11252799494409,
+ "pred_pAff_mean": 8.844084739685059,
+ "docking_score": -8.23,
+ "docking_status": "completed",
+ "rank": 2
+ },
+ {
+ "SMILES": "CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1",
+ "SMILES_state": 1,
+ "NLL": 22.74,
+ "valid": true,
+ "canonical_smiles": "CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1",
+ "mw": 468.5650000000001,
+ "logp": 3.4236000000000013,
+ "tpsa": 99.17,
+ "hbd": 2,
+ "hba": 8,
+ "rot_bonds": 6,
+ "qed": 0.4441562846144389,
+ "sa_score": 3.462932738106919,
+ "pred_pAff_mean": 8.693540573120117,
+ "docking_score": -9.2,
+ "docking_status": "completed",
+ "rank": 3
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "user_id": 1,
+ "job_id": "gen_20260602_185402_e9b887",
+ "status": "running",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "all",
+ "dock_top_k": 5,
+ "summary": null,
+ "files": null,
+ "created_at": "2026-06-02T18:54:00.000000Z",
+ "updated_at": "2026-06-02T18:54:00.000000Z",
+ "ligands": null
+ },
+ {
+ "id": 3,
+ "user_id": 1,
+ "job_id": "gen_20260602_174612_90c1b1",
+ "status": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "all",
+ "dock_top_k": 5,
+ "summary": {
+ "num_requested": 5,
+ "num_generated": 5,
+ "num_valid": 5,
+ "num_returned": 5,
+ "num_docked": 5
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "relative_url": "/files/jobs/gen_20260602_174612_90c1b1/generated_results.csv",
+ "download_url": "https://superplausibly-nonflowering-keiko.ngrok-free.dev/files/jobs/gen_20260602_174612_90c1b1/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "relative_url": "/files/jobs/gen_20260602_174612_90c1b1/generated_results.json",
+ "download_url": "https://superplausibly-nonflowering-keiko.ngrok-free.dev/files/jobs/gen_20260602_174612_90c1b1/generated_results.json"
+ }
+ },
+ "created_at": "2026-06-02T17:46:10.000000Z",
+ "updated_at": "2026-06-02T17:51:19.000000Z",
+ "ligands": [
+ {
+ "SMILES": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5ncncc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 8.24,
+ "valid": true,
+ "canonical_smiles": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5ncncc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 6,
+ "qed": 0.505688822717197,
+ "sa_score": 2.715858141741272,
+ "pred_pAff_mean": 10.734132766723633,
+ "docking_score": -8.91,
+ "docking_status": "completed",
+ "rank": 1
+ },
+ {
+ "SMILES": "Cn1cnc2ccc(Nc3ncnc4ccc(NC(=O)CCN5CCC5)cc34)cc21",
+ "SMILES_state": 1,
+ "NLL": 5.32,
+ "valid": true,
+ "canonical_smiles": "Cn1cnc2ccc(Nc3ncnc4ccc(NC(=O)CCN5CCC5)cc34)cc21",
+ "mw": 401.4740000000003,
+ "logp": 3.2944000000000013,
+ "tpsa": 87.96999999999998,
+ "hbd": 2,
+ "hba": 6,
+ "rot_bonds": 6,
+ "qed": 0.5154635202823702,
+ "sa_score": 2.404394987645352,
+ "pred_pAff_mean": 9.775605201721191,
+ "docking_score": -8.27,
+ "docking_status": "completed",
+ "rank": 2
+ },
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 5.95,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
+ "mw": 426.5240000000002,
+ "logp": 3.768600000000002,
+ "tpsa": 74.25,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.523710400747619,
+ "sa_score": 2.669701840595609,
+ "pred_pAff_mean": 9.587095260620115,
+ "docking_score": -10.25,
+ "docking_status": "completed",
+ "rank": 3
+ },
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnnnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 8.32,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnnnc5c4)c3c2)C1",
+ "mw": 428.5000000000002,
+ "logp": 2.5586,
+ "tpsa": 100.03,
+ "hbd": 1,
+ "hba": 8,
+ "rot_bonds": 5,
+ "qed": 0.5177099598248629,
+ "sa_score": 2.917413951888572,
+ "pred_pAff_mean": 8.804740905761719,
+ "docking_score": -10.15,
+ "docking_status": "completed",
+ "rank": 4
+ },
+ {
+ "SMILES": "O=C(CN1CCNCC1)Nc1ccc2nncc(-c3ccc4cnncc4c3)c2c1",
+ "SMILES_state": 1,
+ "NLL": 8.41,
+ "valid": true,
+ "canonical_smiles": "O=C(CN1CCNCC1)Nc1ccc2nncc(-c3ccc4cnncc4c3)c2c1",
+ "mw": 399.45800000000014,
+ "logp": 2.0836999999999994,
+ "tpsa": 95.93,
+ "hbd": 2,
+ "hba": 7,
+ "rot_bonds": 4,
+ "qed": 0.5423758682727866,
+ "sa_score": 2.665545251407164,
+ "pred_pAff_mean": 7.531160354614258,
+ "docking_score": -9.5,
+ "docking_status": "completed",
+ "rank": 5
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "user_id": 1,
+ "job_id": "gen_20260602_162928_00f1a6",
+ "status": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "all",
+ "dock_top_k": 5,
+ "summary": {
+ "num_requested": 5,
+ "num_generated": 5,
+ "num_valid": 5,
+ "num_returned": 5,
+ "num_docked": 5
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "relative_url": "/files/jobs/gen_20260602_162928_00f1a6/generated_results.csv",
+ "download_url": "https://abcd-1234.ngrok-free.app/files/jobs/gen_20260602_162928_00f1a6/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "relative_url": "/files/jobs/gen_20260602_162928_00f1a6/generated_results.json",
+ "download_url": "https://abcd-1234.ngrok-free.app/files/jobs/gen_20260602_162928_00f1a6/generated_results.json"
+ }
+ },
+ "created_at": "2026-06-02T16:29:26.000000Z",
+ "updated_at": "2026-06-02T16:45:06.000000Z",
+ "ligands": [
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 5.95,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5234216428527144,
+ "sa_score": 2.7645745083533857,
+ "pred_pAff_mean": 10.726750373840332,
+ "docking_score": -9.17,
+ "docking_status": "completed",
+ "rank": 1
+ },
+ {
+ "SMILES": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "SMILES_state": 1,
+ "NLL": 5.14,
+ "valid": true,
+ "canonical_smiles": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)CC1",
+ "mw": 427.5120000000002,
+ "logp": 2.8160000000000007,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5240018720180243,
+ "sa_score": 2.567665997715085,
+ "pred_pAff_mean": 10.2472505569458,
+ "docking_score": -9.04,
+ "docking_status": "completed",
+ "rank": 2
+ },
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 5.85,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
+ "mw": 426.5240000000002,
+ "logp": 3.768600000000002,
+ "tpsa": 74.25,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.523710400747619,
+ "sa_score": 2.669701840595609,
+ "pred_pAff_mean": 9.5870943069458,
+ "docking_score": -9.18,
+ "docking_status": "completed",
+ "rank": 3
+ },
+ {
+ "SMILES": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)CC1",
+ "SMILES_state": 1,
+ "NLL": 7.45,
+ "valid": true,
+ "canonical_smiles": "CN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)CC1",
+ "mw": 426.5240000000002,
+ "logp": 3.421000000000002,
+ "tpsa": 74.25,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.5271519273668187,
+ "sa_score": 2.4727933299573124,
+ "pred_pAff_mean": 9.139262199401855,
+ "docking_score": -9.59,
+ "docking_status": "completed",
+ "rank": 4
+ },
+ {
+ "SMILES": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4cccc(F)c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 7.31,
+ "valid": true,
+ "canonical_smiles": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4cccc(F)c4)c3c2)C1",
+ "mw": 393.4660000000002,
+ "logp": 3.359500000000001,
+ "tpsa": 61.36,
+ "hbd": 1,
+ "hba": 5,
+ "rot_bonds": 6,
+ "qed": 0.6958924258182049,
+ "sa_score": 2.483388547894851,
+ "pred_pAff_mean": 8.095303535461426,
+ "docking_score": -8.79,
+ "docking_status": "completed",
+ "rank": 5
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "user_id": 1,
+ "job_id": "gen_20260602_161105_95a4f3",
+ "status": "completed",
+ "preset": "egfr_generator",
+ "num_molecules": 5,
+ "return_top_k": 5,
+ "docking_mode": "off",
+ "dock_top_k": 0,
+ "summary": {
+ "num_requested": 5,
+ "num_generated": 5,
+ "num_valid": 5,
+ "num_returned": 5,
+ "num_docked": 0
+ },
+ "files": {
+ "csv": {
+ "filename": "generated_results.csv",
+ "relative_url": "/files/jobs/gen_20260602_161105_95a4f3/generated_results.csv",
+ "download_url": "https://abcd-1234.ngrok-free.app/files/jobs/gen_20260602_161105_95a4f3/generated_results.csv"
+ },
+ "json": {
+ "filename": "generated_results.json",
+ "relative_url": "/files/jobs/gen_20260602_161105_95a4f3/generated_results.json",
+ "download_url": "https://abcd-1234.ngrok-free.app/files/jobs/gen_20260602_161105_95a4f3/generated_results.json"
+ }
+ },
+ "created_at": "2026-06-02T16:11:02.000000Z",
+ "updated_at": "2026-06-02T16:45:52.000000Z",
+ "ligands": [
+ {
+ "SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "SMILES_state": 1,
+ "NLL": 5.97,
+ "valid": true,
+ "canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1",
+ "mw": 427.5120000000002,
+ "logp": 3.1636000000000006,
+ "tpsa": 87.14,
+ "hbd": 1,
+ "hba": 7,
+ "rot_bonds": 5,
+ "qed": 0.5234216428527144,
+ "sa_score": 2.7645745083533857,
+ "pred_pAff_mean": 10.726750373840332,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 1
+ },
+ {
+ "SMILES": "O=C(CCN1CCCCC1)Nc1ccc2nncc(-c3ccc4cncnc4c3)c2c1",
+ "SMILES_state": 1,
+ "NLL": 8.21,
+ "valid": true,
+ "canonical_smiles": "O=C(CCN1CCCCC1)Nc1ccc2nncc(-c3ccc4cncnc4c3)c2c1",
+ "mw": 412.4970000000002,
+ "logp": 4.054500000000003,
+ "tpsa": 83.9,
+ "hbd": 1,
+ "hba": 6,
+ "rot_bonds": 5,
+ "qed": 0.532283447216262,
+ "sa_score": 2.5088336367127653,
+ "pred_pAff_mean": 9.917120933532717,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 2
+ },
+ {
+ "SMILES": "CNCCCC(=O)Nc1ccc2nncc(-c3cccc4cncnc34)c2c1",
+ "SMILES_state": 1,
+ "NLL": 8.3,
+ "valid": true,
+ "canonical_smiles": "CNCCCC(=O)Nc1ccc2nncc(-c3cccc4cncnc34)c2c1",
+ "mw": 372.4320000000001,
+ "logp": 3.178100000000001,
+ "tpsa": 92.69,
+ "hbd": 2,
+ "hba": 6,
+ "rot_bonds": 6,
+ "qed": 0.5051251070006001,
+ "sa_score": 2.5602974133305807,
+ "pred_pAff_mean": 9.24955940246582,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 3
+ },
+ {
+ "SMILES": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCCN(C)C4)cc23)c1",
+ "SMILES_state": 1,
+ "NLL": 6.61,
+ "valid": true,
+ "canonical_smiles": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCN4CCCN(C)C4)cc23)c1",
+ "mw": 446.5550000000004,
+ "logp": 3.568900000000003,
+ "tpsa": 90.45999999999998,
+ "hbd": 2,
+ "hba": 6,
+ "rot_bonds": 7,
+ "qed": 0.5767061490837626,
+ "sa_score": 2.580768170904193,
+ "pred_pAff_mean": 8.717927932739258,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 4
+ },
+ {
+ "SMILES": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCCN)cc23)c1",
+ "SMILES_state": 1,
+ "NLL": 7.34,
+ "valid": true,
+ "canonical_smiles": "CCC(=O)Nc1cccc(-c2cnnc3ccc(NC(=O)CCCN)cc23)c1",
+ "mw": 377.4480000000002,
+ "logp": 3.322700000000001,
+ "tpsa": 110,
+ "hbd": 3,
+ "hba": 5,
+ "rot_bonds": 7,
+ "qed": 0.5849389128589325,
+ "sa_score": 2.2821504292780475,
+ "pred_pAff_mean": 7.681546688079834,
+ "docking_score": null,
+ "docking_status": "not_run",
+ "rank": 5
+ }
+ ]
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 1,
+ "totalResults": 5,
+ "perPage": 10,
+ "hasNextPage": false,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+## ligands export
+
+### POST `/api/ai/ligands/export`
+
+**Authentication:** Bearer token required
+
+**Input Options:**
+
+| Name | Type | Description |
+| -------- | ------ | -------------------------------------- |
+| `smiles` | string | SMILES strings |
+| `format` | string | Supported formats: pdbqt or pdb or sdf |
+
+**Example:**
+
+```bash
+curl -X POST http://localhost:8080/api/ai/ligands/export \
+ -H "Authorization: Bearer {token}" \
+ -H "Content-Type: application/json" \
+ -d '{"smiles": "CCO", "format": "pdbqt"}'
+```
+
+**Response:**
+
+```json
+{
+ "success": true,
+ "message": "Ligands exported successfully",
+ "data": {
+ "job_id": "lig_20260627_193222_e57b0b",
+ "status": "completed",
+ "format": "pdb",
+ "filename": "ligand_3d.pdb",
+ "smiles": "CCC",
+ "download_url": "https://shdwrow-ailixir-generation.hf.space/files/jobs/lig_20260627_193222_e57b0b/ligand_3d.pdb"
+ }
+}
+```
+
+---
+
+## ligands download files or Generation files
+
+### GET `/api/ai/files/{job_id}/{filename}`
+
+**Authentication:** Bearer token required
+
+**Example ligands download:**
+
+```bash
+curl -X GET http://localhost:8080/api/ai/files/lig_20260602_181751_0227c4/ligand_3d.pdbqt \
+ -H "Authorization: Bearer {token}"
+```
+
+**Response:**
+
+```pdbqt
+REMARK SMILES CCC
+REMARK SMILES IDX 1 1 2 2 3 3
+REMARK H PARENT
+ROOT
+ATOM 1 C UNL 1 1.223 -0.144 -0.355 1.00 0.00 0.003 C
+ATOM 2 C UNL 1 0.031 -0.177 0.596 1.00 0.00 -0.007 C
+ATOM 3 C UNL 1 -1.247 0.279 -0.101 1.00 0.00 0.003 C
+ENDROOT
+TORSDOF 0
+
+```
+
+**Example Generation files download:**
+
+```bash
+curl -X GET http://127.0.0.1:8000/api/ai/files/gen_20260602_185419_15fc7b/generated_results.csv \
+ -H "Authorization: Bearer {token}"
+```
+
+**Response:**
+
+```csv
+SMILES,SMILES_state,NLL,valid,canonical_smiles,mw,logp,tpsa,hbd,hba,rot_bonds,qed,sa_score,pred_pAff_mean,docking_score,docking_status
+CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1,1,4.14,True,CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1,427.5120000000002,3.1636000000000006,87.14,1,7,5,0.5234216428527144,2.7645745083533857,10.726750373840332,-9.16,completed
+O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1,1,6.06,True,O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1,395.89400000000006,4.451200000000003,70.15,2,5,6,0.6447750051881624,2.11252799494409,8.844084739685059,-8.23,completed
+CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1,1,22.74,True,CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1,468.5650000000001,3.4236000000000013,99.17,2,8,6,0.4441562846144389,3.462932738106919,8.693540573120117,-9.2,completed
+```
+
+---
+
+## MD Simulation API
+
+Molecular Dynamics simulation of protein-ligand complexes via an external OpenMM service. All endpoints require authentication.
+
+### POST `/api/md-simulation/process`
+
+Submit a new MD simulation job.
+
+**Authentication:** Bearer token required
+
+**Content-Type:** `multipart/form-data`
+
+**Required fields:**
+
+| Field | Type | Description |
+| --------- | ---- | ---------------- |
+| `protein` | file | Protein PDB file |
+| `ligand` | file | Ligand PDB file |
+
+**Optional fields:**
+
+| Field | Type | Default | Description |
+| --------------- | ------- | -------- | ------------------------------- |
+| `force_field` | string | `ff19SB` | `ff19SB` or `ff14SB` |
+| `net_charge` | integer | `0` | Ligand net formal charge |
+| `box_size` | float | `12.0` | Solvation box size (Å) |
+| `ion_type` | string | `NaCl` | `NaCl` or `KCl` |
+| `salt_conc` | float | `0.15` | Salt concentration (M) |
+| `remove_waters` | boolean | `True` | Strip crystal waters |
+| `add_hydrogens` | boolean | `True` | Add H to ligand |
+| `equil_time_ns` | float | `5.0` | Equilibration time (ns) |
+| `sim_time_ns` | float | `0.1` | Production time per stride (ns) |
+| `n_strides` | integer | `1` | Number of production strides |
+| `temperature_k` | float | `298.0` | Temperature (K) |
+| `pressure_bar` | float | `1.0` | Pressure (bar) |
+| `dt_fs` | integer | `2` | Integration timestep (fs) |
+
+**Note:**
+when you send boolean values(True or False) multipart/form-data cast it to string unless you use json, in this case you should use:
+1 as True
+0 as False
+
+**Example:**
+
+```bash
+curl -X POST /api/md-simulation/process \
+ -H "Authorization: Bearer {token}" \
+ -F "protein=@protein.pdb" \
+ -F "ligand=@ligand.pdb" \
+ -F "sim_time_ns=1.0" \
+ -F "temperature_k=310"
+```
+
+**Response (202 Accepted):**
+
+```json
+{
+ "success": true,
+ "message": "MD Simulation job submitted successfully",
+ "data": {
+ "remote_job_id": "a1b2c3d4",
+ "status": "processing",
+ "created_at": "2026-06-22 12:00:00"
+ }
+}
+```
+
+---
+
+### GET `/api/md-simulation/status/{remoteJobId}`
+
+Poll job status. Syncs the local status from the remote service on each call.
+
+**Authentication:** Bearer token required
+
+**Response (200 OK — processing):**
+
+```json
+{
+ "success": true,
+ "message": "Status retrieved",
+ "data": {
+ "remote_job_id": "a1b2c3d4",
+ "status": "processing",
+ "remote_status": "Step 2/7 — Building GAFF2 topology and solvated system",
+ "protein": "4w52.pdb",
+ "ligand": "ligand.pdb",
+ "result_meta": null,
+ "analysis_meta": null,
+ "error_message": null,
+ "created_at": "2026-06-22 12:00:00"
+ }
+}
+```
+
+**Response (200 OK — completed):**
+
+```json
+{
+ "success": true,
+ "message": "Status retrieved",
+ "data": {
+ "remote_job_id": "a1b2c3d4",
+ "status": "completed",
+ "remote_status": "Success: MD Pipeline Completed",
+ "protein": "4w52.pdb",
+ "ligand": "ligand.pdb",
+ "result_meta": {
+ "download_url": "/download/a1b2c3d4",
+ "download_analysis_url": "/download_analysis/a1b2c3d4"
+ },
+ "analysis_meta": null,
+ "error_message": null,
+ "created_at": "2026-06-22 12:00:00"
+ }
+}
+```
+
+**Response (200 OK — failed):**
+
+```json
+{
+ "success": true,
+ "message": "Status retrieved",
+ "data": {
+ "remote_job_id": "a1b2c3d4",
+ "status": "failed",
+ "remote_status": "Failed: antechamber failed...",
+ "error_message": "antechamber failed: unable to assign parameters",
+ ...
+ }
+}
+```
+
+---
+
+### GET `/api/md-simulation/download/{remoteJobId}`
+
+Download the simulation results ZIP (streamed from the remote service).
+
+**Authentication:** Bearer token required
+
+**Response (200 OK):** Binary ZIP attachment — `{job_id}_Results.zip`
+
+---
+
+### POST `/api/md-simulation/analyze/{remoteJobId}`
+
+Run post-simulation analysis (RMSD, RMSF, RoG, PCA, etc.) on a completed job.
+
+**Authentication:** Bearer token required
+
+**Content-Type:** `application/json`
+
+**Optional body fields:**
+
+| Field | Type | Default | Description |
+| ----------- | ------- | ------- | ----------------------------------------- |
+| `rmsd_mask` | string | `@CA` | Atom selection mask for RMSD |
+| `cc_mask` | string | `@CA` | Atom selection mask for cross-correlation |
+| `skip` | integer | `1` | Frame stride for analysis |
+| `dpi` | integer | `300` | DPI for output plots |
+| `threshold` | float | `0.3` | ProLIF interaction threshold |
+
+**Example:**
+
+```bash
+curl -X POST /api/md-simulation/analyze/a1b2c3d4 \
+ -H "Authorization: Bearer {token}" \
+ -H "Content-Type: application/json" \
+ -d '{"rmsd_mask": "@CA", "dpi": 150}'
+```
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "Analysis triggered successfully",
+ "data": {
+ "download_url": "/download_analysis/a1b2c3d4",
+ "outputs": [
+ "rmsd",
+ "rmsf",
+ "radgyr",
+ "2d_rmsd",
+ "pca",
+ "cross_corr",
+ "interaction_e",
+ "prolif"
+ ]
+ }
+}
+```
+
+---
+
+### GET `/api/md-simulation/download-analysis/{remoteJobId}`
+
+Download the analysis results ZIP (streamed from the remote service).
+
+**Authentication:** Bearer token required
+
+**Response (200 OK):** Binary ZIP attachment — `{job_id}_Analysis.zip`
+
+---
+
+### GET `/api/md-simulation/history`
+
+List all MD simulation jobs for the authenticated user.
+
+**Authentication:** Bearer token required
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+| ---------- | ------- | ------- | ---------------- |
+| `per_page` | integer | `15` | Results per page |
+
+**Response (200 OK):**
+
+```json
+{
+ "success": true,
+ "message": "MD Simulation history retrieved",
+ "data": {
+ "results": [
+ {
+ "remote_job_id": "x9y8z7w6",
+ "status": "completed",
+ "input_params": {
+ "sim_time_ns": "5.0",
+ "temperature_k": "310"
+ },
+ "protein_original_name": "1ake.pdb",
+ "ligand_original_name": "stl.pdb",
+ "result_meta": {
+ "download_url": "/download/x9y8z7w6",
+ "download_analysis_url": "/download_analysis/x9y8z7w6"
+ },
+ "analysis_meta": {
+ "download_url": "/download_analysis/x9y8z7w6",
+ "outputs": ["rmsd", "rmsf", "radgyr"]
+ },
+ "error_message": null,
+ "created_at": "2026-06-22 14:30:00",
+ "updated_at": "2026-06-22 18:45:00"
+ }
+ ],
+ "pagination": {
+ "currentPage": 1,
+ "totalPages": 1,
+ "totalResults": 3,
+ "perPage": 15,
+ "hasNextPage": false,
+ "hasPrevPage": false
+ }
+ }
+}
+```
+
+---
+
+'''
+
+## Error Handling
+
+### Error Response Format
+
+All errors follow this standard format:
+
+```json
+{
+ "success": false,
+ "message": "Human-readable error message",
+ "error_code": "SPECIFIC_ERROR_CODE",
+ "details": {
+ "field_errors": {...}
+ },
+ "timestamp": "2026-05-29T12:30:45Z"
+}
+```
+
+### HTTP Status Codes
+
+| Code | Meaning | Example |
+| ------- | ------------------- | ------------------------ |
+| **200** | OK | Request successful |
+| **201** | Created | Resource created |
+| **202** | Accepted | Async job queued |
+| **400** | Bad Request | Invalid parameters |
+| **401** | Unauthorized | Missing/invalid token |
+| **403** | Forbidden | Insufficient permissions |
+| **404** | Not Found | Resource not found |
+| **422** | Unprocessable | Validation failed |
+| **429** | Too Many Requests | Rate limit exceeded |
+| **500** | Server Error | Internal error |
+| **502** | Bad Gateway | Upstream service down |
+| **503** | Service Unavailable | System maintenance |
+
+### Common Error Codes
+
+| Code | Description |
+| ----------------------- | --------------------------------------- |
+| `INVALID_SMILES` | Given SMILES string is invalid |
+| `JOB_NOT_FOUND` | Job ID doesn't exist |
+| `SERVICE_UNAVAILABLE` | AI service not responding |
+| `AUTHENTICATION_FAILED` | Invalid credentials |
+| `INSUFFICIENT_QUOTA` | User quota exceeded |
+| `UNSUPPORTED_OPERATION` | Feature not available |
+| `QUOTA_EXCEEDED` | LLM API daily quota reached |
+| `THREAD_NOT_FOUND` | Thread ID doesn't exist or unauthorized |
+| `JOB_NOT_COMPLETE` | Results requested before job completion |
+| `CSV_TOO_LARGE` | Exceeds 100 row limit |
+| `MISSING_SMILES` | smiles field not provided |
+
+### Example Error Response
+
+```http
+POST /ai/run
+Authorization: Bearer invalid_token
+
+HTTP 401 Unauthorized
+Content-Type: application/json
+
+{
+ "success": false,
+ "message": "Unauthenticated",
+ "error_code": "AUTHENTICATION_FAILED",
+ "timestamp": "2026-05-29T12:30:45Z"
+}
+```
+
+---
+
+## Rate Limiting
+
+### Rate Limit Headers
+
+All responses include rate limit information:
+
+```http
+X-RateLimit-Limit: 1000
+X-RateLimit-Remaining: 987
+X-RateLimit-Reset: 1716954645
+```
+
+### Limits by Endpoint Category
+
+| Category | Limit | Window |
+| ----------- | ------------- | ---------- |
+| **Auth** | 10 requests | 15 minutes |
+| **AI Jobs** | 100 requests | 1 hour |
+| **Search** | 500 requests | 1 hour |
+| **General** | 1000 requests | 1 hour |
+
+### AI Agent Rate Limits
+
+| Endpoint | Limit | Window |
+| ------------------ | ------------- | ------------------------ |
+| `/chat` | 20 requests | Per day (upstream quota) |
+| `/analyze/smiles` | 20 requests | Per day (upstream quota) |
+| `/analyze/compare` | 20 requests | Per day (upstream quota) |
+| `/analyze/docking` | 20 requests | Per day (upstream quota) |
+| `/csv/upload` | 10 uploads | Per day |
+| All others | 1000 requests | 1 hour |
+
+**Note:** AI-powered endpoints are limited by upstream Google Gemini free tier (20 requests/day). Use `analysis_type: quick` to reduce consumption.
+
+### Handling Rate Limits
+
+When rate limited (HTTP 429):
+
+```json
+{
+ "success": false,
+ "message": "Rate limit exceeded",
+ "retry_after": 120,
+ "error_code": "RATE_LIMIT_EXCEEDED"
+}
+```
+
+**Retry-After Header:** Seconds to wait before retry
+
+---
+
+## Integration Examples
+
+### Example 1: Complete ADMET Prediction Workflow
+
+```bash
+#!/bin/bash
+
+BASE_URL="http://localhost:8080/api"
+TOKEN="your_access_token_here"
+SMILES="CC(=O)Oc1ccccc1C(=O)O"
+
+# Step 1: Submit job
+echo "1. Submitting ADMET job..."
+JOB_RESPONSE=$(curl -s -X POST "$BASE_URL/ai/run" \\
+ -H "Authorization: Bearer $TOKEN" \\
+ -H "Content-Type: application/json" \\
+ -d "{\\"job_type\\": \\"admet_prediction\\", \\"parameters\\": {\\"smiles\\": \\"$SMILES\\"}}")
+
+JOB_ID=$(echo $JOB_RESPONSE | jq -r '.job_id')
+echo "Job ID: $JOB_ID"
+
+# Step 2: Poll for completion
+echo "2. Waiting for results..."
+while true; do
+ STATUS_RESPONSE=$(curl -s -X GET "$BASE_URL/ai/status/$JOB_ID" \\
+ -H "Authorization: Bearer $TOKEN")
+
+ STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')
+
+ if [ "$STATUS" == "completed" ]; then
+ echo "Job completed!"
+ echo $STATUS_RESPONSE | jq '.results'
+ break
+ elif [ "$STATUS" == "failed" ]; then
+ echo "Job failed!"
+ echo $STATUS_RESPONSE | jq '.error'
+ exit 1
+ else
+ echo "Status: $STATUS, waiting..."
+ sleep 5
+ fi
+done
+
+# Step 3: Download results
+echo "3. Downloading full results..."
+curl -X GET "$BASE_URL/ai/download/full/$JOB_ID" \\
+ -H "Authorization: Bearer $TOKEN" \\
+ -o results.zip
+
+echo "✓ Results saved to results.zip"
+```
+
+### Example 2: Chemical Similarity Search
+
+```python
+import requests
+import json
+import time
+
+class AILIXIRClient:
+ def __init__(self, base_url, token):
+ self.base_url = base_url
+ self.token = token
+ self.headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json"
+ }
+
+ def search_similar_compounds(self, smiles, top_k=10, use_rag=True):
+ """Search for compounds similar to query SMILES"""
+
+ endpoint = "full-rag" if use_rag else "retrieval-only"
+ url = f"{self.base_url}/chemical-search/{endpoint}"
+
+ response = requests.post(url, headers=self.headers, json={
+ "smiles": smiles,
+ "top_k": top_k
+ })
+
+ if response.status_code != 202:
+ raise Exception(f"Failed to submit search: {response.text}")
+
+ job_id = response.json()["job_id"]
+ print(f"Search submitted: {job_id}")
+
+ # Poll for results
+ while True:
+ status_url = f"{self.base_url}/chemical-search/{job_id}/status"
+ status_response = requests.get(status_url, headers=self.headers)
+ status_data = status_response.json()
+
+ if status_data["status"] == "completed":
+ return status_data["compounds"]
+ elif status_data["status"] == "failed":
+ raise Exception(f"Search failed: {status_data.get('error')}")
+
+ print(f"Status: {status_data['status']}, waiting...")
+ time.sleep(2)
+
+# Usage
+client = AILIXIRClient("http://localhost:8080/api", "your_token_here")
+results = client.search_similar_compounds("CC(=O)Oc1ccccc1C(=O)O", top_k=5)
+
+for compound in results:
+ print(f"Rank {compound['rank']}: {compound['name']} (similarity: {compound['similarity']:.2f})")
+ if 'explanation' in compound:
+ print(f" Explanation: {compound['explanation'][:100]}...")
+```
+
+### Example 3: Health Monitoring
+
+```javascript
+// Node.js / JavaScript example
+const axios = require("axios");
+
+async function checkSystemHealth() {
+ const baseURL = "http://localhost:8080/api";
+
+ try {
+ // Check overall system
+ const healthResponse = await axios.get(`${baseURL}/health`);
+ console.log("System Health:", healthResponse.data);
+
+ // Check all AI services
+ const servicesResponse = await axios.get(
+ `${baseURL}/ai-services/health`,
+ );
+
+ const allHealthy = servicesResponse.data.success;
+ const services = servicesResponse.data.services;
+
+ console.log("\\nAI Services Status:");
+ Object.entries(services).forEach(([name, status]) => {
+ const indicator = status.status === "healthy" ? "✓" : "✗";
+ console.log(`${indicator} ${name}: ${status.status}`);
+ });
+
+ return {
+ healthy: allHealthy,
+ timestamp: new Date(),
+ };
+ } catch (error) {
+ console.error("Health check failed:", error.message);
+ }
+}
+
+// Run every 60 seconds
+setInterval(checkSystemHealth, 60000);
+```
+
+### Example 4: AI Agent Drug Discovery Workflow
+
+```bash
+#!/bin/bash
+
+BASE_URL="http://localhost:8080/api"
+TOKEN="your_access_token_here"
+
+# Step 1: Create thread
+echo "1. Creating thread..."
+THREAD_RESPONSE=$(curl -s -X POST "$BASE_URL/chemistry/thread" \\
+ -H "Authorization: Bearer $TOKEN")
+
+THREAD_ID=$(echo $THREAD_RESPONSE | jq -r '.data.thread_id')
+echo "Thread ID: $THREAD_ID"
+
+# Step 2: Analyze SMILES
+echo "2. Analyzing Aspirin..."
+curl -s -X POST "$BASE_URL/chemistry/analyze/smiles" \\
+ -H "Authorization: Bearer $TOKEN" \\
+ -H "Content-Type: application/json" \\
+ -d "{\\"smiles\\": \\"CC(=O)Oc1ccccc1C(=O)O\\", \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
+
+# Step 3: Compare with Ibuprofen
+echo "3. Comparing molecules..."
+curl -s -X POST "$BASE_URL/chemistry/analyze/compare" \\
+ -H "Authorization: Bearer $TOKEN" \\
+ -H "Content-Type: application/json" \\
+ -d "{\\"smiles\\": [\\"CC(=O)Oc1ccccc1C(=O)O\\", \\"CC(C)Cc1ccc(cc1)C(C)C(=O)O\\"], \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
+
+# Step 4: Chat follow-up
+echo "4. Follow-up question..."
+curl -s -X POST "$BASE_URL/chemistry/chat" \\
+ -H "Authorization: Bearer $TOKEN" \\
+ -H "Content-Type: application/json" \\
+ -d "{\\"message\\": \\"Which molecule has better CNS penetration?\\", \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
+
+echo "✓ Workflow complete!"
+```
+
+### Example 5: Flutter Integration
+
+```dart
+class ChemistryApiService {
+ final String baseUrl = 'http://your-domain.com/api/chemistry';
+ String? token;
+
+ Future