superbrain-server 1.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/superbrain.js +196 -0
- package/package.json +23 -0
- package/payload/.dockerignore +45 -0
- package/payload/.env.example +58 -0
- package/payload/Dockerfile +73 -0
- package/payload/analyzers/__init__.py +0 -0
- package/payload/analyzers/audio_transcribe.py +225 -0
- package/payload/analyzers/caption.py +244 -0
- package/payload/analyzers/music_identifier.py +346 -0
- package/payload/analyzers/text_analyzer.py +117 -0
- package/payload/analyzers/visual_analyze.py +218 -0
- package/payload/analyzers/webpage_analyzer.py +789 -0
- package/payload/analyzers/youtube_analyzer.py +320 -0
- package/payload/api.py +1676 -0
- package/payload/config/.api_keys.example +22 -0
- package/payload/config/model_rankings.json +492 -0
- package/payload/config/openrouter_free_models.json +1364 -0
- package/payload/config/whisper_model.txt +1 -0
- package/payload/config_settings.py +185 -0
- package/payload/core/__init__.py +0 -0
- package/payload/core/category_manager.py +219 -0
- package/payload/core/database.py +811 -0
- package/payload/core/link_checker.py +300 -0
- package/payload/core/model_router.py +1253 -0
- package/payload/docker-compose.yml +120 -0
- package/payload/instagram/__init__.py +0 -0
- package/payload/instagram/instagram_downloader.py +253 -0
- package/payload/instagram/instagram_login.py +190 -0
- package/payload/main.py +912 -0
- package/payload/requirements.txt +39 -0
- package/payload/reset.py +311 -0
- package/payload/start-docker-prod.sh +125 -0
- package/payload/start-docker.sh +56 -0
- package/payload/start.py +1302 -0
- package/payload/static/favicon.ico +0 -0
- package/payload/stop-docker.sh +16 -0
- package/payload/utils/__init__.py +0 -0
- package/payload/utils/db_stats.py +108 -0
- package/payload/utils/manage_token.py +91 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
base
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperBrain Production Configuration
|
|
3
|
+
Production-grade settings for deployment
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
|
|
10
|
+
class Settings:
|
|
11
|
+
"""Application settings with sensible production defaults."""
|
|
12
|
+
|
|
13
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
14
|
+
# Core Settings
|
|
15
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
# API Configuration
|
|
18
|
+
API_TITLE: str = "SuperBrain API"
|
|
19
|
+
API_VERSION: str = "1.0.0"
|
|
20
|
+
API_DESCRIPTION: str = "AI-powered content analysis and archival system"
|
|
21
|
+
ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development")
|
|
22
|
+
DEBUG: bool = ENVIRONMENT == "development"
|
|
23
|
+
|
|
24
|
+
# Server
|
|
25
|
+
HOST: str = os.getenv("HOST", "0.0.0.0")
|
|
26
|
+
PORT: int = int(os.getenv("PORT", 5000))
|
|
27
|
+
WORKERS: int = int(os.getenv("WORKERS", 4))
|
|
28
|
+
|
|
29
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
30
|
+
# Database Configuration
|
|
31
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./superbrain.db")
|
|
34
|
+
DATABASE_PATH: str = os.getenv("DATABASE_PATH", "superbrain.db")
|
|
35
|
+
DATABASE_TIMEOUT: int = int(os.getenv("DATABASE_TIMEOUT", 30))
|
|
36
|
+
|
|
37
|
+
# SQLite WAL mode for better concurrency
|
|
38
|
+
ENABLE_WAL: bool = True
|
|
39
|
+
|
|
40
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
41
|
+
# Security & Authentication
|
|
42
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
# API key file path
|
|
45
|
+
TOKEN_FILE: str = os.getenv("TOKEN_FILE", "token.txt")
|
|
46
|
+
API_KEY_HEADER: str = "X-API-Key"
|
|
47
|
+
|
|
48
|
+
# CORS Configuration
|
|
49
|
+
CORS_ORIGINS: list = [
|
|
50
|
+
"http://localhost",
|
|
51
|
+
"http://localhost:19000",
|
|
52
|
+
"http://localhost:19001",
|
|
53
|
+
"http://localhost:8081",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Allow credentials in CORS
|
|
57
|
+
CORS_CREDENTIALS: bool = True
|
|
58
|
+
CORS_METHODS: list = ["*"]
|
|
59
|
+
CORS_HEADERS: list = ["*"]
|
|
60
|
+
|
|
61
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
62
|
+
# AI Provider Configuration
|
|
63
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
# Groq API
|
|
66
|
+
GROQ_API_KEY: Optional[str] = os.getenv("GROQ_API_KEY")
|
|
67
|
+
|
|
68
|
+
# Google Gemini API
|
|
69
|
+
GEMINI_API_KEY: Optional[str] = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
|
70
|
+
GOOGLE_API_KEY: Optional[str] = os.getenv("GOOGLE_API_KEY")
|
|
71
|
+
|
|
72
|
+
# OpenRouter API (for model fallback)
|
|
73
|
+
OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY")
|
|
74
|
+
|
|
75
|
+
# Whisper Model Configuration
|
|
76
|
+
WHISPER_MODEL: str = os.getenv("WHISPER_MODEL", "base")
|
|
77
|
+
WHISPER_USE_CLOUD: bool = os.getenv("WHISPER_USE_CLOUD", "true").lower() == "true"
|
|
78
|
+
|
|
79
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
80
|
+
# Instagram Configuration
|
|
81
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
INSTAGRAM_USERNAME: Optional[str] = os.getenv("INSTAGRAM_USERNAME")
|
|
84
|
+
INSTAGRAM_PASSWORD: Optional[str] = os.getenv("INSTAGRAM_PASSWORD")
|
|
85
|
+
|
|
86
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
87
|
+
# File & Storage Configuration
|
|
88
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
# Upload configuration
|
|
91
|
+
UPLOAD_DIR: str = os.getenv("UPLOAD_DIR", "static/uploads")
|
|
92
|
+
TEMP_DIR: str = os.getenv("TEMP_DIR", "temp")
|
|
93
|
+
MAX_UPLOAD_SIZE: int = int(os.getenv("MAX_UPLOAD_SIZE", 50 * 1024 * 1024)) # 50MB
|
|
94
|
+
|
|
95
|
+
# Allowed file types
|
|
96
|
+
ALLOWED_EXTENSIONS: set = {
|
|
97
|
+
"jpg", "jpeg", "png", "gif", "webp", # Images
|
|
98
|
+
"mp4", "mov", "avi", "mkv", # Videos
|
|
99
|
+
"mp3", "wav", "m4a", "aac", # Audio
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
103
|
+
# Logging Configuration
|
|
104
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO" if ENVIRONMENT == "production" else "DEBUG")
|
|
107
|
+
LOG_FILE: Optional[str] = os.getenv("LOG_FILE")
|
|
108
|
+
LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
109
|
+
|
|
110
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
111
|
+
# Performance & Rate Limiting
|
|
112
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
# Rate limiting
|
|
115
|
+
RATE_LIMIT_ENABLED: bool = True
|
|
116
|
+
RATE_LIMIT_PER_MINUTE: int = int(os.getenv("RATE_LIMIT_PER_MINUTE", 60))
|
|
117
|
+
|
|
118
|
+
# Request timeout (seconds)
|
|
119
|
+
REQUEST_TIMEOUT: int = int(os.getenv("REQUEST_TIMEOUT", 60))
|
|
120
|
+
|
|
121
|
+
# Connection pool size
|
|
122
|
+
CONNECTION_POOL_SIZE: int = int(os.getenv("CONNECTION_POOL_SIZE", 10))
|
|
123
|
+
|
|
124
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
125
|
+
# Feature Flags
|
|
126
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
# Feature flags for analytics/monitoring
|
|
129
|
+
ENABLE_METRICS: bool = True
|
|
130
|
+
ENABLE_HEALTH_CHECK: bool = True
|
|
131
|
+
ENABLE_API_DOCS: bool = not (ENVIRONMENT == "production")
|
|
132
|
+
|
|
133
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
134
|
+
# Timeouts & Retries
|
|
135
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
# Analysis timeout (seconds)
|
|
138
|
+
ANALYSIS_TIMEOUT: int = int(os.getenv("ANALYSIS_TIMEOUT", 120))
|
|
139
|
+
|
|
140
|
+
# Retry configuration
|
|
141
|
+
MAX_RETRIES: int = int(os.getenv("MAX_RETRIES", 3))
|
|
142
|
+
RETRY_DELAY: int = int(os.getenv("RETRY_DELAY", 5))
|
|
143
|
+
|
|
144
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
145
|
+
# Directories (ensure they exist)
|
|
146
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def ensure_dirs(cls):
|
|
150
|
+
"""Ensure all required directories exist."""
|
|
151
|
+
for dir_path in [cls.UPLOAD_DIR, cls.TEMP_DIR]:
|
|
152
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DevelopmentSettings(Settings):
|
|
156
|
+
"""Development environment settings."""
|
|
157
|
+
DEBUG: bool = True
|
|
158
|
+
CORS_ORIGINS: list = [
|
|
159
|
+
"*", # Allow all in development
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ProductionSettings(Settings):
|
|
164
|
+
"""Production environment settings."""
|
|
165
|
+
DEBUG: bool = False
|
|
166
|
+
ENABLE_API_DOCS: bool = False
|
|
167
|
+
# Restrict CORS in production
|
|
168
|
+
CORS_ORIGINS: list = [
|
|
169
|
+
os.getenv("APP_URL", "http://localhost"),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@lru_cache()
|
|
174
|
+
def get_settings() -> Settings:
|
|
175
|
+
"""Get settings based on environment."""
|
|
176
|
+
environment = os.getenv("ENVIRONMENT", "development")
|
|
177
|
+
|
|
178
|
+
if environment == "production":
|
|
179
|
+
return ProductionSettings()
|
|
180
|
+
else:
|
|
181
|
+
return DevelopmentSettings()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Expose active settings
|
|
185
|
+
settings = get_settings()
|
|
File without changes
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Category Manager for SuperBrain
|
|
4
|
+
Interactive tool to list, edit, and delete categories
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Ensure backend root is in sys.path (needed when run directly)
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
12
|
+
|
|
13
|
+
from core.database import get_db
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.prompt import Prompt, Confirm
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
def print_header(text):
|
|
23
|
+
"""Print fancy header"""
|
|
24
|
+
console.print(Panel(text, style="bold magenta", expand=False))
|
|
25
|
+
|
|
26
|
+
def list_all_categories():
|
|
27
|
+
"""List all categories with post counts"""
|
|
28
|
+
db = get_db()
|
|
29
|
+
|
|
30
|
+
if not db.is_connected():
|
|
31
|
+
console.print("[red]❌ Database not connected[/red]")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# Get category counts using aggregation
|
|
36
|
+
pipeline = [
|
|
37
|
+
{"$group": {"_id": "$category", "count": {"$sum": 1}}},
|
|
38
|
+
{"$sort": {"count": -1}}
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
results = list(db.collection.aggregate(pipeline))
|
|
42
|
+
|
|
43
|
+
if not results:
|
|
44
|
+
console.print("[yellow]No categories found in database[/yellow]")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Create table
|
|
48
|
+
table = Table(title="📁 All Categories", show_header=True, header_style="bold cyan")
|
|
49
|
+
table.add_column("Category", style="cyan", width=30)
|
|
50
|
+
table.add_column("Posts", justify="right", style="green")
|
|
51
|
+
|
|
52
|
+
total_posts = 0
|
|
53
|
+
for item in results:
|
|
54
|
+
category = item['_id'] or "Uncategorized"
|
|
55
|
+
count = item['count']
|
|
56
|
+
table.add_row(category, str(count))
|
|
57
|
+
total_posts += count
|
|
58
|
+
|
|
59
|
+
console.print(table)
|
|
60
|
+
console.print(f"\n[bold]Total: {len(results)} categories, {total_posts} posts[/bold]\n")
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
64
|
+
|
|
65
|
+
def list_posts_by_category(category=None):
|
|
66
|
+
"""List all posts in a specific category"""
|
|
67
|
+
db = get_db()
|
|
68
|
+
|
|
69
|
+
if not db.is_connected():
|
|
70
|
+
console.print("[red]❌ Database not connected[/red]")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not category:
|
|
74
|
+
category = Prompt.ask("Enter category name")
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# Find posts in this category
|
|
78
|
+
posts = list(db.collection.find({"category": category}).sort("analyzed_at", -1))
|
|
79
|
+
|
|
80
|
+
if not posts:
|
|
81
|
+
console.print(f"[yellow]No posts found in category '{category}'[/yellow]")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Create table
|
|
85
|
+
table = Table(title=f"📋 Posts in '{category}'", show_header=True, header_style="bold cyan")
|
|
86
|
+
table.add_column("#", style="dim", width=4)
|
|
87
|
+
table.add_column("Title", style="cyan", width=50)
|
|
88
|
+
table.add_column("Username", style="green", width=15)
|
|
89
|
+
table.add_column("Shortcode", style="yellow", width=15)
|
|
90
|
+
|
|
91
|
+
for idx, post in enumerate(posts, 1):
|
|
92
|
+
title = post.get('title', 'N/A')[:47] + "..." if len(post.get('title', '')) > 50 else post.get('title', 'N/A')
|
|
93
|
+
username = post.get('username', 'N/A')
|
|
94
|
+
shortcode = post.get('shortcode', 'N/A')
|
|
95
|
+
table.add_row(str(idx), title, username, shortcode)
|
|
96
|
+
|
|
97
|
+
console.print(table)
|
|
98
|
+
console.print(f"\n[bold]Total: {len(posts)} posts[/bold]\n")
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
102
|
+
|
|
103
|
+
def edit_category():
|
|
104
|
+
"""Edit/rename a category"""
|
|
105
|
+
db = get_db()
|
|
106
|
+
|
|
107
|
+
if not db.is_connected():
|
|
108
|
+
console.print("[red]❌ Database not connected[/red]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# Show current categories
|
|
112
|
+
console.print("\n[bold cyan]Current Categories:[/bold cyan]")
|
|
113
|
+
list_all_categories()
|
|
114
|
+
|
|
115
|
+
# Get old category name
|
|
116
|
+
old_category = Prompt.ask("\nEnter category name to rename")
|
|
117
|
+
|
|
118
|
+
# Check if exists
|
|
119
|
+
count = db.collection.count_documents({"category": old_category})
|
|
120
|
+
if count == 0:
|
|
121
|
+
console.print(f"[red]❌ Category '{old_category}' not found[/red]")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
console.print(f"[yellow]Found {count} posts in '{old_category}'[/yellow]")
|
|
125
|
+
|
|
126
|
+
# Get new category name
|
|
127
|
+
new_category = Prompt.ask("Enter new category name")
|
|
128
|
+
|
|
129
|
+
# Confirm
|
|
130
|
+
if not Confirm.ask(f"Rename '{old_category}' to '{new_category}' for {count} posts?"):
|
|
131
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Update all posts
|
|
136
|
+
result = db.collection.update_many(
|
|
137
|
+
{"category": old_category},
|
|
138
|
+
{"$set": {"category": new_category}}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
console.print(f"[green]✅ Updated {result.modified_count} posts[/green]")
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
145
|
+
|
|
146
|
+
def delete_category():
|
|
147
|
+
"""Delete a category (moves posts to 'Uncategorized')"""
|
|
148
|
+
db = get_db()
|
|
149
|
+
|
|
150
|
+
if not db.is_connected():
|
|
151
|
+
console.print("[red]❌ Database not connected[/red]")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Show current categories
|
|
155
|
+
console.print("\n[bold cyan]Current Categories:[/bold cyan]")
|
|
156
|
+
list_all_categories()
|
|
157
|
+
|
|
158
|
+
# Get category name
|
|
159
|
+
category = Prompt.ask("\nEnter category name to delete")
|
|
160
|
+
|
|
161
|
+
# Check if exists
|
|
162
|
+
count = db.collection.count_documents({"category": category})
|
|
163
|
+
if count == 0:
|
|
164
|
+
console.print(f"[red]❌ Category '{category}' not found[/red]")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
console.print(f"[yellow]Found {count} posts in '{category}'[/yellow]")
|
|
168
|
+
console.print("[yellow]These posts will be moved to 'Uncategorized'[/yellow]")
|
|
169
|
+
|
|
170
|
+
# Confirm
|
|
171
|
+
if not Confirm.ask(f"Delete category '{category}'?", default=False):
|
|
172
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
# Move posts to Uncategorized
|
|
177
|
+
result = db.collection.update_many(
|
|
178
|
+
{"category": category},
|
|
179
|
+
{"$set": {"category": "Uncategorized"}}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
console.print(f"[green]✅ Moved {result.modified_count} posts to 'Uncategorized'[/green]")
|
|
183
|
+
console.print(f"[green]✅ Category '{category}' deleted[/green]")
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
187
|
+
|
|
188
|
+
def main_menu():
|
|
189
|
+
"""Main interactive menu"""
|
|
190
|
+
print_header("📁 CATEGORY MANAGER")
|
|
191
|
+
|
|
192
|
+
while True:
|
|
193
|
+
console.print("\n[bold cyan]Options:[/bold cyan]")
|
|
194
|
+
console.print("1. List all categories")
|
|
195
|
+
console.print("2. List posts by category")
|
|
196
|
+
console.print("3. Rename category")
|
|
197
|
+
console.print("4. Delete category")
|
|
198
|
+
console.print("5. Exit")
|
|
199
|
+
|
|
200
|
+
choice = Prompt.ask("\nChoose option", choices=["1", "2", "3", "4", "5"])
|
|
201
|
+
|
|
202
|
+
if choice == "1":
|
|
203
|
+
list_all_categories()
|
|
204
|
+
elif choice == "2":
|
|
205
|
+
list_posts_by_category()
|
|
206
|
+
elif choice == "3":
|
|
207
|
+
edit_category()
|
|
208
|
+
elif choice == "4":
|
|
209
|
+
delete_category()
|
|
210
|
+
elif choice == "5":
|
|
211
|
+
console.print("\n[green]👋 Goodbye![/green]")
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
try:
|
|
216
|
+
main_menu()
|
|
217
|
+
except KeyboardInterrupt:
|
|
218
|
+
console.print("\n\n[yellow]Interrupted by user[/yellow]")
|
|
219
|
+
sys.exit(0)
|