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.
Files changed (39) hide show
  1. package/bin/superbrain.js +196 -0
  2. package/package.json +23 -0
  3. package/payload/.dockerignore +45 -0
  4. package/payload/.env.example +58 -0
  5. package/payload/Dockerfile +73 -0
  6. package/payload/analyzers/__init__.py +0 -0
  7. package/payload/analyzers/audio_transcribe.py +225 -0
  8. package/payload/analyzers/caption.py +244 -0
  9. package/payload/analyzers/music_identifier.py +346 -0
  10. package/payload/analyzers/text_analyzer.py +117 -0
  11. package/payload/analyzers/visual_analyze.py +218 -0
  12. package/payload/analyzers/webpage_analyzer.py +789 -0
  13. package/payload/analyzers/youtube_analyzer.py +320 -0
  14. package/payload/api.py +1676 -0
  15. package/payload/config/.api_keys.example +22 -0
  16. package/payload/config/model_rankings.json +492 -0
  17. package/payload/config/openrouter_free_models.json +1364 -0
  18. package/payload/config/whisper_model.txt +1 -0
  19. package/payload/config_settings.py +185 -0
  20. package/payload/core/__init__.py +0 -0
  21. package/payload/core/category_manager.py +219 -0
  22. package/payload/core/database.py +811 -0
  23. package/payload/core/link_checker.py +300 -0
  24. package/payload/core/model_router.py +1253 -0
  25. package/payload/docker-compose.yml +120 -0
  26. package/payload/instagram/__init__.py +0 -0
  27. package/payload/instagram/instagram_downloader.py +253 -0
  28. package/payload/instagram/instagram_login.py +190 -0
  29. package/payload/main.py +912 -0
  30. package/payload/requirements.txt +39 -0
  31. package/payload/reset.py +311 -0
  32. package/payload/start-docker-prod.sh +125 -0
  33. package/payload/start-docker.sh +56 -0
  34. package/payload/start.py +1302 -0
  35. package/payload/static/favicon.ico +0 -0
  36. package/payload/stop-docker.sh +16 -0
  37. package/payload/utils/__init__.py +0 -0
  38. package/payload/utils/db_stats.py +108 -0
  39. 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)