PraisonAI 1.0.8__cp312-cp312-manylinux_2_39_x86_64.whl

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.

Potentially problematic release.


This version of PraisonAI might be problematic. Click here for more details.

Files changed (56) hide show
  1. praisonai/__init__.py +6 -0
  2. praisonai/__main__.py +10 -0
  3. praisonai/agents_generator.py +474 -0
  4. praisonai/api/call.py +292 -0
  5. praisonai/auto.py +226 -0
  6. praisonai/chainlit_ui.py +304 -0
  7. praisonai/cli.py +506 -0
  8. praisonai/deploy.py +138 -0
  9. praisonai/inbuilt_tools/__init__.py +24 -0
  10. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  11. praisonai/inc/__init__.py +2 -0
  12. praisonai/inc/config.py +96 -0
  13. praisonai/inc/models.py +128 -0
  14. praisonai/public/android-chrome-192x192.png +0 -0
  15. praisonai/public/android-chrome-512x512.png +0 -0
  16. praisonai/public/apple-touch-icon.png +0 -0
  17. praisonai/public/fantasy.svg +3 -0
  18. praisonai/public/favicon-16x16.png +0 -0
  19. praisonai/public/favicon-32x32.png +0 -0
  20. praisonai/public/favicon.ico +0 -0
  21. praisonai/public/game.svg +3 -0
  22. praisonai/public/logo_dark.png +0 -0
  23. praisonai/public/logo_light.png +0 -0
  24. praisonai/public/movie.svg +3 -0
  25. praisonai/public/thriller.svg +3 -0
  26. praisonai/setup/__init__.py +1 -0
  27. praisonai/setup/build.py +21 -0
  28. praisonai/setup/config.yaml +60 -0
  29. praisonai/setup/post_install.py +23 -0
  30. praisonai/setup/setup_conda_env.py +25 -0
  31. praisonai/setup/setup_conda_env.sh +72 -0
  32. praisonai/setup.py +16 -0
  33. praisonai/test.py +105 -0
  34. praisonai/train.py +276 -0
  35. praisonai/ui/README.md +21 -0
  36. praisonai/ui/chat.py +387 -0
  37. praisonai/ui/code.py +440 -0
  38. praisonai/ui/context.py +283 -0
  39. praisonai/ui/db.py +242 -0
  40. praisonai/ui/public/fantasy.svg +3 -0
  41. praisonai/ui/public/game.svg +3 -0
  42. praisonai/ui/public/logo_dark.png +0 -0
  43. praisonai/ui/public/logo_light.png +0 -0
  44. praisonai/ui/public/movie.svg +3 -0
  45. praisonai/ui/public/thriller.svg +3 -0
  46. praisonai/ui/realtime.py +421 -0
  47. praisonai/ui/realtimeclient/__init__.py +653 -0
  48. praisonai/ui/realtimeclient/realtimedocs.txt +1484 -0
  49. praisonai/ui/realtimeclient/tools.py +236 -0
  50. praisonai/ui/sql_alchemy.py +706 -0
  51. praisonai/version.py +1 -0
  52. praisonai-1.0.8.dist-info/LICENSE +20 -0
  53. praisonai-1.0.8.dist-info/METADATA +496 -0
  54. praisonai-1.0.8.dist-info/RECORD +56 -0
  55. praisonai-1.0.8.dist-info/WHEEL +4 -0
  56. praisonai-1.0.8.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,283 @@
1
+ import os
2
+ import fnmatch
3
+ import yaml
4
+ from pathlib import Path
5
+ import logging
6
+
7
+ # Set up logging
8
+ logger = logging.getLogger(__name__)
9
+ log_level = os.getenv("LOGLEVEL", "INFO").upper()
10
+ logger.handlers = []
11
+
12
+ # Set up logging to console
13
+ console_handler = logging.StreamHandler()
14
+ console_handler.setLevel(log_level)
15
+ console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
16
+ console_handler.setFormatter(console_formatter)
17
+ logger.addHandler(console_handler)
18
+
19
+ # Set the logging level for the logger
20
+ logger.setLevel(log_level)
21
+
22
+ class ContextGatherer:
23
+ def __init__(self, directory='.', output_file='context.txt',
24
+ relevant_extensions=None, max_file_size=1_000_000, max_tokens=900000):
25
+ self.directory = directory
26
+ self.output_file = output_file
27
+ self.relevant_extensions = relevant_extensions or [
28
+ '.py', '.js', '.ts', '.java', '.rb', '.php', '.pl', '.pm', '.c', '.h',
29
+ '.cpp', '.hpp', '.cs', '.vb', '.swift', '.kt', '.m', '.mm', '.go', '.rs',
30
+ '.hs', '.r', '.lua', '.sh', '.bat', '.clj', '.scala', '.erl', '.ex',
31
+ '.ml', '.fs', '.groovy', '.jsm', '.jsx', '.tsx', '.yaml'
32
+ ]
33
+ self.max_file_size = max_file_size
34
+ self.max_tokens = int(os.getenv("PRAISONAI_MAX_TOKENS", max_tokens))
35
+ self.ignore_patterns = self.get_ignore_patterns()
36
+ self.include_paths = self.get_include_paths()
37
+ self.included_files = []
38
+
39
+ def get_ignore_patterns(self):
40
+ """
41
+ Loads ignore patterns from various sources, prioritizing them in
42
+ the following order:
43
+ 1. .praisonignore
44
+ 2. settings.yaml (under code.ignore_files)
45
+ 3. PRAISONAI_IGNORE_FILES environment variable
46
+ 4. .gitignore
47
+ 5. Default patterns
48
+ """
49
+ ignore_patterns = []
50
+
51
+ def load_from_file(filepath):
52
+ if os.path.exists(filepath):
53
+ with open(filepath, 'r') as f:
54
+ ignore_patterns.extend(
55
+ line.strip() for line in f
56
+ if line.strip() and not line.startswith('#')
57
+ )
58
+
59
+ # 1. Load from .praisonignore
60
+ load_from_file(os.path.join(self.directory, '.praisonignore'))
61
+
62
+ # 2. Load from settings.yaml
63
+ settings_path = os.path.join(self.directory, 'settings.yaml')
64
+ if os.path.exists(settings_path):
65
+ with open(settings_path, 'r') as f:
66
+ settings = yaml.safe_load(f)
67
+ if 'code' in settings and 'ignore_files' in settings['code']:
68
+ ignore_patterns.extend(settings['code']['ignore_files'])
69
+
70
+ # 3. Load from environment variable
71
+ ignore_files_env = os.getenv("PRAISONAI_IGNORE_FILES")
72
+ if ignore_files_env:
73
+ ignore_patterns.extend(ignore_files_env.split(","))
74
+
75
+ # 4. Load from .gitignore
76
+ load_from_file(os.path.join(self.directory, '.gitignore'))
77
+
78
+ # 5. Default patterns (only if no patterns loaded from above sources)
79
+ if not ignore_patterns:
80
+ ignore_patterns = [
81
+ ".*", "*.pyc", "__pycache__", ".git", ".gitignore", ".vscode",
82
+ ".idea", ".DS_Store", "*.lock", "*.pyc", ".env", "docs", "tests",
83
+ "test", "tmp", "temp", "*.txt", "*.md", "*.json", "*.csv", "*.tsv",
84
+ "public", "*.sql", "*.sqlite", "*.db", "*.db3", "*.sqlite3",
85
+ "*.log", "*.zip", "*.gz", "*.tar", "*.rar", "*.7z", "*.pdf",
86
+ "*.jpg", "*.jpeg", "*.png", "*.gif", "*.svg", "cookbooks",
87
+ "assets", "__pycache__", "dist", "build", "node_modules", "venv"
88
+ ]
89
+ logger.debug(f"Using default ignore patterns: {ignore_patterns}")
90
+
91
+ # Modify patterns to match directories and add leading '*' if necessary
92
+ modified_ignore_patterns = [
93
+ '*' + pattern if not pattern.startswith('.') and not pattern.startswith('*') else pattern
94
+ for pattern in ignore_patterns
95
+ ]
96
+ logger.debug(f"Final ignore patterns: {modified_ignore_patterns}")
97
+ return modified_ignore_patterns
98
+
99
+ def get_include_paths(self):
100
+ """
101
+ Loads include paths from:
102
+ 1. .praisoninclude (includes ONLY files/directories listed)
103
+ 2. .praisoncontext (if .praisoninclude doesn't exist, this is used
104
+ to include all other relevant files, excluding ignore patterns)
105
+ """
106
+ include_paths = []
107
+ include_all = False # Flag to indicate if we need to include all files
108
+
109
+ include_file = os.path.join(self.directory, '.praisoncontext')
110
+ if os.path.exists(include_file):
111
+ with open(include_file, 'r') as f:
112
+ include_paths.extend(
113
+ line.strip() for line in f
114
+ if line.strip() and not line.startswith('#')
115
+ )
116
+
117
+ # If .praisoncontext doesn't exist, fall back to .praisoninclude
118
+ # for including all relevant files
119
+ if not include_paths:
120
+ include_file = os.path.join(self.directory, '.praisoninclude')
121
+ if os.path.exists(include_file):
122
+ with open(include_file, 'r') as f:
123
+ include_paths.extend(
124
+ line.strip() for line in f
125
+ if line.strip() and not line.startswith('#')
126
+ )
127
+ include_all = True # Include all files along with specified paths
128
+
129
+ return include_paths, include_all
130
+
131
+ def should_ignore(self, file_path):
132
+ """
133
+ Check if a file or directory should be ignored based on patterns.
134
+ Handles both file names and directory names for more comprehensive filtering.
135
+ """
136
+ relative_path = os.path.relpath(file_path, self.directory)
137
+ if relative_path.startswith('.'):
138
+ return True
139
+ for pattern in self.ignore_patterns:
140
+ if fnmatch.fnmatch(relative_path, pattern) or \
141
+ fnmatch.fnmatch(os.path.basename(file_path), pattern):
142
+ return True
143
+ return False
144
+
145
+ def is_relevant_file(self, file_path):
146
+ """Determine if a file is relevant for the context."""
147
+ return os.path.isfile(file_path) and \
148
+ os.path.getsize(file_path) <= self.max_file_size and \
149
+ any(file_path.endswith(ext) for ext in self.relevant_extensions)
150
+
151
+ def gather_context(self):
152
+ """
153
+ Gather context from relevant files, respecting ignore patterns
154
+ and include options from .praisoninclude and .praisoncontext.
155
+ """
156
+ context = []
157
+ total_files = 0
158
+ processed_files = 0
159
+ self.include_paths, include_all = self.get_include_paths()
160
+
161
+ def add_file_content(file_path):
162
+ """Helper function to add file content to context."""
163
+ try:
164
+ with open(file_path, 'r', encoding='utf-8') as f:
165
+ content = f.read()
166
+ context.append(
167
+ f"File: {file_path}\n\n{content}\n\n{'=' * 50}\n"
168
+ )
169
+ self.included_files.append(
170
+ Path(file_path).relative_to(self.directory)
171
+ )
172
+ except Exception as e:
173
+ logger.error(f"Error reading {file_path}: {e}")
174
+
175
+ def process_path(path):
176
+ """Helper function to process a single path (file or directory)."""
177
+ nonlocal total_files, processed_files
178
+ if os.path.isdir(path):
179
+ for root, dirs, files in os.walk(path):
180
+ total_files += len(files)
181
+ dirs[:] = [
182
+ d
183
+ for d in dirs
184
+ if not self.should_ignore(os.path.join(root, d))
185
+ ]
186
+ for file in files:
187
+ file_path = os.path.join(root, file)
188
+ if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
189
+ add_file_content(file_path)
190
+ processed_files += 1
191
+ print(
192
+ f"\rProcessed {processed_files}/{total_files} files",
193
+ end="",
194
+ flush=True,
195
+ )
196
+ elif os.path.isfile(path) and self.is_relevant_file(path):
197
+ add_file_content(path)
198
+ processed_files += 1
199
+ print(
200
+ f"\rProcessed {processed_files}/1 files",
201
+ end="",
202
+ flush=True,
203
+ )
204
+
205
+ if include_all:
206
+ # Include ALL relevant files from the entire directory
207
+ process_path(self.directory)
208
+
209
+ # Include files from .praisoninclude specifically
210
+ for include_path in self.include_paths:
211
+ full_path = os.path.join(self.directory, include_path)
212
+ process_path(full_path)
213
+ elif self.include_paths:
214
+ # Include only files specified in .praisoncontext
215
+ for include_path in self.include_paths:
216
+ full_path = os.path.join(self.directory, include_path)
217
+ process_path(full_path)
218
+ else:
219
+ # No include options, process the entire directory
220
+ process_path(self.directory)
221
+
222
+ print() # New line after progress indicator
223
+ return "\n".join(context)
224
+
225
+ def count_tokens(self, text):
226
+ """Count tokens using a simple whitespace-based tokenizer."""
227
+ return len(text.split())
228
+
229
+ def truncate_context(self, context):
230
+ """Truncate context to stay within the token limit."""
231
+ tokens = context.split()
232
+ if len(tokens) > self.max_tokens:
233
+ truncated_context = ' '.join(tokens[:self.max_tokens])
234
+ logger.warning("Context truncated due to token limit.")
235
+ return truncated_context
236
+ return context
237
+
238
+ def save_context(self, context):
239
+ """Save the gathered context to a file."""
240
+ with open(self.output_file, 'w', encoding='utf-8') as f:
241
+ f.write(context)
242
+
243
+ def get_context_tree(self):
244
+ """Generate a formatted tree structure of included files and folders."""
245
+ tree = []
246
+ start_dir = Path(self.directory)
247
+
248
+ def add_to_tree(path, prefix=''):
249
+ contents = sorted(path.iterdir())
250
+ pointers = [('└── ' if i == len(contents) - 1 else '├── ') for i in range(len(contents))]
251
+ for pointer, item in zip(pointers, contents):
252
+ rel_path = item.relative_to(start_dir)
253
+ if rel_path in self.included_files:
254
+ tree.append(f"{prefix}{pointer}{rel_path}")
255
+
256
+ if item.is_dir():
257
+ add_to_tree(item, prefix + (' ' if pointer == '└── ' else '│ '))
258
+
259
+ add_to_tree(start_dir)
260
+ return '\n'.join(tree)
261
+
262
+ def run(self):
263
+ """Execute the context gathering, truncation, and reporting."""
264
+ context = self.gather_context()
265
+ context = self.truncate_context(context)
266
+ token_count = self.count_tokens(context)
267
+ print(f"Context gathered successfully.")
268
+ print(f"Total number of tokens (estimated): {token_count}")
269
+ # self.save_context(context)
270
+ context_tree = self.get_context_tree()
271
+ logger.debug(f"Context tree:\n{context_tree}")
272
+ return context, token_count, context_tree
273
+
274
+ def main():
275
+ gatherer = ContextGatherer()
276
+ context, token_count, context_tree = gatherer.run()
277
+ print(context_tree)
278
+ print(f"\nThe context contains approximately {token_count} tokens.")
279
+ print("First 500 characters of context:")
280
+ print(context[:500] + "...")
281
+
282
+ if __name__ == "__main__":
283
+ main()
praisonai/ui/db.py ADDED
@@ -0,0 +1,242 @@
1
+ import os
2
+ import sqlite3
3
+ import asyncio
4
+ from sqlalchemy import text
5
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
6
+ from sqlalchemy.orm import sessionmaker
7
+ from sql_alchemy import SQLAlchemyDataLayer
8
+ import chainlit.data as cl_data
9
+ from chainlit.types import ThreadDict
10
+
11
+ class DatabaseManager(SQLAlchemyDataLayer):
12
+ def __init__(self):
13
+ self.database_url = os.getenv("DATABASE_URL")
14
+ supabase_url = os.getenv("SUPABASE_DATABASE_URL")
15
+ if supabase_url:
16
+ self.database_url = supabase_url
17
+
18
+ if self.database_url:
19
+ self.conninfo = self.database_url
20
+ else:
21
+ self.db_path = os.path.expanduser("~/.praison/database.sqlite")
22
+ os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
23
+ self.conninfo = f"sqlite+aiosqlite:///{self.db_path}"
24
+
25
+ # Initialize SQLAlchemyDataLayer with the connection info
26
+ super().__init__(conninfo=self.conninfo)
27
+
28
+ async def create_schema_async(self):
29
+ """Create the database schema for PostgreSQL"""
30
+ if not self.database_url:
31
+ return
32
+ engine = create_async_engine(self.database_url, echo=False)
33
+ async with engine.begin() as conn:
34
+ await conn.execute(text('''
35
+ CREATE TABLE IF NOT EXISTS users (
36
+ "id" TEXT PRIMARY KEY,
37
+ "identifier" TEXT NOT NULL UNIQUE,
38
+ "meta" TEXT NOT NULL DEFAULT '{}',
39
+ "createdAt" TEXT
40
+ );
41
+ '''))
42
+ await conn.execute(text('''
43
+ CREATE TABLE IF NOT EXISTS threads (
44
+ "id" TEXT PRIMARY KEY,
45
+ "createdAt" TEXT,
46
+ "name" TEXT,
47
+ "userId" TEXT,
48
+ "userIdentifier" TEXT,
49
+ "tags" TEXT DEFAULT '[]',
50
+ "meta" TEXT NOT NULL DEFAULT '{}',
51
+ FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE
52
+ );
53
+ '''))
54
+ await conn.execute(text('''
55
+ CREATE TABLE IF NOT EXISTS steps (
56
+ "id" TEXT PRIMARY KEY,
57
+ "name" TEXT NOT NULL,
58
+ "type" TEXT NOT NULL,
59
+ "threadId" TEXT NOT NULL,
60
+ "parentId" TEXT,
61
+ "disableFeedback" BOOLEAN NOT NULL DEFAULT FALSE,
62
+ "streaming" BOOLEAN NOT NULL DEFAULT FALSE,
63
+ "waitForAnswer" BOOLEAN DEFAULT FALSE,
64
+ "isError" BOOLEAN NOT NULL DEFAULT FALSE,
65
+ "meta" TEXT DEFAULT '{}',
66
+ "tags" TEXT DEFAULT '[]',
67
+ "input" TEXT,
68
+ "output" TEXT,
69
+ "createdAt" TEXT,
70
+ "startTime" TEXT,
71
+ "endTime" TEXT,
72
+ "generation" TEXT,
73
+ "showInput" TEXT,
74
+ "language" TEXT,
75
+ "indent" INT,
76
+ FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
77
+ );
78
+ '''))
79
+ await conn.execute(text('''
80
+ CREATE TABLE IF NOT EXISTS elements (
81
+ "id" TEXT PRIMARY KEY,
82
+ "threadId" TEXT,
83
+ "type" TEXT,
84
+ "url" TEXT,
85
+ "chainlitKey" TEXT,
86
+ "name" TEXT NOT NULL,
87
+ "display" TEXT,
88
+ "objectKey" TEXT,
89
+ "size" TEXT,
90
+ "page" INT,
91
+ "language" TEXT,
92
+ "forId" TEXT,
93
+ "mime" TEXT,
94
+ FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
95
+ );
96
+ '''))
97
+ await conn.execute(text('''
98
+ CREATE TABLE IF NOT EXISTS feedbacks (
99
+ "id" TEXT PRIMARY KEY,
100
+ "forId" TEXT NOT NULL,
101
+ "value" INT NOT NULL,
102
+ "threadId" TEXT,
103
+ "comment" TEXT
104
+ );
105
+ '''))
106
+ await conn.execute(text('''
107
+ CREATE TABLE IF NOT EXISTS settings (
108
+ "id" SERIAL PRIMARY KEY,
109
+ "key" TEXT UNIQUE,
110
+ "value" TEXT
111
+ );
112
+ '''))
113
+ await engine.dispose()
114
+
115
+ def create_schema_sqlite(self):
116
+ """Create the database schema for SQLite"""
117
+ conn = sqlite3.connect(self.db_path)
118
+ cursor = conn.cursor()
119
+ cursor.execute('''
120
+ CREATE TABLE IF NOT EXISTS users (
121
+ id TEXT PRIMARY KEY,
122
+ identifier TEXT NOT NULL UNIQUE,
123
+ meta TEXT NOT NULL DEFAULT '{}',
124
+ createdAt TEXT
125
+ );
126
+ ''')
127
+ cursor.execute('''
128
+ CREATE TABLE IF NOT EXISTS threads (
129
+ id TEXT PRIMARY KEY,
130
+ createdAt TEXT,
131
+ name TEXT,
132
+ userId TEXT,
133
+ userIdentifier TEXT,
134
+ tags TEXT DEFAULT '[]',
135
+ meta TEXT NOT NULL DEFAULT '{}',
136
+ FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
137
+ );
138
+ ''')
139
+ cursor.execute('''
140
+ CREATE TABLE IF NOT EXISTS steps (
141
+ id TEXT PRIMARY KEY,
142
+ name TEXT NOT NULL,
143
+ type TEXT NOT NULL,
144
+ threadId TEXT NOT NULL,
145
+ parentId TEXT,
146
+ disableFeedback BOOLEAN NOT NULL DEFAULT 0,
147
+ streaming BOOLEAN NOT NULL DEFAULT 0,
148
+ waitForAnswer BOOLEAN DEFAULT 0,
149
+ isError BOOLEAN NOT NULL DEFAULT 0,
150
+ meta TEXT DEFAULT '{}',
151
+ tags TEXT DEFAULT '[]',
152
+ input TEXT,
153
+ output TEXT,
154
+ createdAt TEXT,
155
+ startTime TEXT,
156
+ endTime TEXT,
157
+ generation TEXT,
158
+ showInput TEXT,
159
+ language TEXT,
160
+ indent INT,
161
+ FOREIGN KEY (threadId) REFERENCES threads(id) ON DELETE CASCADE
162
+ );
163
+ ''')
164
+ cursor.execute('''
165
+ CREATE TABLE IF NOT EXISTS elements (
166
+ id TEXT PRIMARY KEY,
167
+ threadId TEXT,
168
+ type TEXT,
169
+ url TEXT,
170
+ chainlitKey TEXT,
171
+ name TEXT NOT NULL,
172
+ display TEXT,
173
+ objectKey TEXT,
174
+ size TEXT,
175
+ page INT,
176
+ language TEXT,
177
+ forId TEXT,
178
+ mime TEXT,
179
+ FOREIGN KEY (threadId) REFERENCES threads(id) ON DELETE CASCADE
180
+ );
181
+ ''')
182
+ cursor.execute('''
183
+ CREATE TABLE IF NOT EXISTS feedbacks (
184
+ id TEXT PRIMARY KEY,
185
+ forId TEXT NOT NULL,
186
+ value INT NOT NULL,
187
+ threadId TEXT,
188
+ comment TEXT
189
+ );
190
+ ''')
191
+ cursor.execute('''
192
+ CREATE TABLE IF NOT EXISTS settings (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ key TEXT UNIQUE,
195
+ value TEXT
196
+ );
197
+ ''')
198
+ conn.commit()
199
+ conn.close()
200
+
201
+ def initialize(self):
202
+ """Initialize the database with schema based on the configuration"""
203
+ if self.database_url:
204
+ asyncio.run(self.create_schema_async())
205
+ else:
206
+ self.create_schema_sqlite()
207
+
208
+ async def save_setting(self, key: str, value: str):
209
+ """Save a setting to the database"""
210
+ if self.database_url:
211
+ async with self.engine.begin() as conn:
212
+ await conn.execute(text("""
213
+ INSERT INTO settings ("key", "value") VALUES (:key, :value)
214
+ ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value"
215
+ """), {"key": key, "value": value})
216
+ else:
217
+ conn = sqlite3.connect(self.db_path)
218
+ cursor = conn.cursor()
219
+ cursor.execute(
220
+ """
221
+ INSERT OR REPLACE INTO settings (id, key, value)
222
+ VALUES ((SELECT id FROM settings WHERE key = ?), ?, ?)
223
+ """,
224
+ (key, key, value),
225
+ )
226
+ conn.commit()
227
+ conn.close()
228
+
229
+ async def load_setting(self, key: str) -> str:
230
+ """Load a setting from the database"""
231
+ if self.database_url:
232
+ async with self.engine.connect() as conn:
233
+ result = await conn.execute(text('SELECT "value" FROM settings WHERE "key" = :key'), {"key": key})
234
+ row = result.fetchone()
235
+ return row[0] if row else None
236
+ else:
237
+ conn = sqlite3.connect(self.db_path)
238
+ cursor = conn.cursor()
239
+ cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
240
+ result = cursor.fetchone()
241
+ conn.close()
242
+ return result[0] if result else None
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M840.5 798.2L662.3 599.5l-151 173.7-173.7-173.7-167.7 201c-21 30.4 0.9 71.8 37.9 71.6l594.7-3.3c36.2-0.1 57.8-40.3 38-70.6z" fill="#FFB89A" /><path d="M741.6 647.3l-52.3-47.7c-12.2-11.2-31.2-10.3-42.4 1.9s-10.3 31.2 1.9 42.4l52.3 47.7c5.8 5.3 13 7.8 20.2 7.8 8.1 0 16.2-3.3 22.2-9.8 11.2-12.1 10.3-31.1-1.9-42.3zM631.2 546.5c-12.4-11-31.4-9.8-42.3 2.6l-98.8 111.7-171-165.7L87.9 724.7c-11.8 11.7-11.8 30.7-0.1 42.4 5.9 5.9 13.6 8.9 21.3 8.9 7.6 0 15.3-2.9 21.1-8.7l189.4-188.1 173.8 168.5L633.8 589c11-12.5 9.8-31.5-2.6-42.5z" fill="#33CC99" /><path d="M721.3 342.8m-35.1 0a35.1 35.1 0 1 0 70.2 0 35.1 35.1 0 1 0-70.2 0Z" fill="#33CC99" /><path d="M743.2 175.1H191.6c-70.6 0-128.3 57.7-128.3 128.3v499.2c0 70.6 57.7 128.3 128.3 128.3h551.5c70.6 0 128.3-57.7 128.3-128.3V303.5c0.1-70.6-57.7-128.4-128.2-128.4z m68.3 627.6c0 18.1-7.1 35.2-20.1 48.2-13 13-30.1 20.1-48.2 20.1H191.6c-18.1 0-35.2-7.1-48.2-20.1-13-13-20.1-30.1-20.1-48.2V303.5c0-18.1 7.1-35.2 20.1-48.2 13-13 30.1-20.1 48.2-20.1h551.5c18.1 0 35.2 7.1 48.2 20.1 13 13 20.1 30.1 20.1 48.2v499.2z" fill="#45484C" /><path d="M799.7 90.9H237.2c-16.6 0-30 13.4-30 30s13.4 30 30 30h562.4c26.1 0 50.8 10.3 69.4 28.9 18.6 18.6 28.9 43.3 28.9 69.4v482.4c0 16.6 13.4 30 30 30s30-13.4 30-30V249.2C958 161.9 887 90.9 799.7 90.9z" fill="#45484C" /></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M570.2 842c-50.6 0-278.7-180-278.7-401.9 0-58.8-2.9-133.1-1-183.9-50.8 3.2-91.4 45.7-91.4 97.3v272.1c37.4 194.7 137.5 334 255.2 334 69.5 0 132.9-48.6 180.9-128.5-20.8 7.1-42.6 10.9-65 10.9z" fill="#FFB89A" /><path d="M926.1 191.8C900.5 74.1 817.9 62.1 704.9 62.1c-29.1 0-60.3 0.8-93 0.8-36 0-70.5-1.1-102.5-1.1-109.7 0-189.8 12.5-201.3 123.7-20.4 198.3 30 617.1 306.1 617.1S939 414.3 926.1 191.8z m-76.9 268.5c-9.5 47.9-22.3 90.8-38.1 127.7-16.8 39.2-37 71.4-60 95.8-37.3 39.5-82.1 58.7-137 58.7-53.4 0-97.6-20.1-134.9-61.6-45.5-50.5-79.8-131.5-99-234.2-15.6-83.5-20.3-178.9-12.4-255.2 1.8-17.3 5.7-30.7 11.6-39.8 4.4-6.8 10.1-11.7 18.7-15.8 25.8-12.5 70.8-14.2 111.4-14.2 15 0 30.7 0.2 47.3 0.5 17.8 0.3 36.2 0.6 55.2 0.6 17.2 0 33.9-0.2 50-0.4 15.1-0.2 29.3-0.4 43.1-0.4 44.5 0 89.5 1.8 118 15.1 15.9 7.4 33.4 20.8 43.6 63 2.6 53.3 3.6 153.5-17.5 260.2z" fill="#4E5155" /><path d="M532 841.7c-32.5 22.3-70.6 33.7-113.2 33.7-29.7 0-57.3-6-82.1-17.7-23.2-11-44.7-27.4-63.9-48.7-46-50.9-80.3-131.3-99.2-232.4-15.1-80.6-19.6-172.9-12-246.8 3-29.5 12-50.2 27.5-63.2 14.2-12 35.1-19.2 65.8-22.9 16.5-2 28.2-16.9 26.3-33.3-2-16.5-16.9-28.2-33.3-26.3-42.9 5.1-73.8 16.7-97.4 36.5-27.9 23.5-43.8 57.2-48.5 103-8.2 79.3-3.4 178.1 12.7 264 9.7 51.9 23.4 99.4 40.6 141.2 19.8 48.1 44.4 88.6 73 120.4 51.6 57.2 115.7 86.2 190.6 86.2 55 0 104.5-14.9 147.2-44.2 13.7-9.4 17.1-28.1 7.7-41.7-9.4-13.7-28.1-17.2-41.8-7.8z" fill="#4E5155" /><path d="M519.7 248.5c-16.6 0-30 13.4-30 30v91.3c0 16.6 13.4 30 30 30s30-13.4 30-30v-91.3c0-16.6-13.5-30-30-30zM299.5 385.5c0-16.6-13.4-30-30-30s-30 13.4-30 30v91.3c0 16.6 13.4 30 30 30s30-13.4 30-30v-91.3zM754.6 248.5c-16.6 0-30 13.4-30 30v91.3c0 16.6 13.4 30 30 30s30-13.4 30-30v-91.3c0-16.6-13.4-30-30-30zM716.7 554.5c0-16.6-13.4-30-30-30H551v30c0 58.5 38.1 123.7 92.8 123.7 22.9 0 45-11.9 62.2-33.6 10.3-13 8.1-31.9-4.9-42.1-13-10.3-31.9-8.1-42.1 4.9-5.3 6.7-11.1 10.9-15.1 10.9-4.3 0-11.9-5.1-19.1-16.4-3.3-5.3-6.2-11.2-8.4-17.4h70.4c16.4 0 29.9-13.4 29.9-30zM401.6 704c-25.4 0-46.1-24.2-46.1-53.9 0-16.6-13.4-30-30-30s-30 13.4-30 30c0 62.8 47.6 113.9 106.1 113.9 16.6 0 30-13.4 30-30s-13.5-30-30-30z" fill="#33CC99" /></svg>
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M861.9 383.8H218.1c-36.4 0-66.1-29.8-66.1-66.1V288c0-36.4 29.8-66.1 66.1-66.1h643.8c36.4 0 66.1 29.8 66.1 66.1v29.7c0 36.3-29.8 66.1-66.1 66.1z" fill="#FFB89A" /><path d="M822.9 129.2H199.8c-77.2 0-140.4 63.2-140.4 140.4v487.2c0 77.2 63.2 140.4 140.4 140.4h623.1c77.2 0 140.4-63.2 140.4-140.4V269.6c0-77.2-63.2-140.4-140.4-140.4z m80.4 177H760.4L864.6 201c5.4 3.3 10.4 7.3 15 11.8 15.3 15.3 23.7 35.4 23.7 56.8v36.6z m-673.3 0l104-117h61.3l-109.1 117H230z m247.4-117h169.2L532 306.2H368.3l109.1-117z m248.8 0h65.6L676 306.2h-60l112.5-114.8-2.3-2.2zM143 212.9c15.3-15.3 35.4-23.7 56.8-23.7h53.9l-104 117h-30.4v-36.5c0.1-21.4 8.5-41.5 23.7-56.8z m736.6 600.7c-15.3 15.3-35.4 23.7-56.8 23.7h-623c-21.3 0-41.5-8.4-56.8-23.7-15.3-15.3-23.7-35.4-23.7-56.8V366.2h783.9v390.6c0.1 21.3-8.3 41.5-23.6 56.8z" fill="#45484C" /><path d="M400.5 770.6V430.9L534.1 508c14.3 8.3 19.3 26.6 11 41-8.3 14.3-26.6 19.3-41 11l-43.6-25.2v131.8l114.1-65.9-7.5-4.3c-14.3-8.3-19.3-26.6-11-41 8.3-14.3 26.6-19.3 41-11l97.5 56.3-294.1 169.9z" fill="#33CC99" /></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M188.3 766.5a94.4 135.8 0 1 0 188.8 0 94.4 135.8 0 1 0-188.8 0Z" fill="#FFB89A" /><path d="M931.5 397s0-0.1 0 0c-34.2-82.6-119.3-141-218.8-141-129.7 0-234.9 99.3-234.9 221.9 0 52.1 19.1 100.1 50.9 138 1 14.5 1.8 29.1 1.8 43.6 0 148.5 98.1 269 219.2 269 121 0 219.2-120.4 219.2-269 0-70.1-1.7-214.7-37.4-262.5z m-36.6 347.5c-8.7 25.3-21.1 47.9-36.8 67.1-29.8 36.5-68.3 56.7-108.5 56.7s-78.7-20.1-108.5-56.7c-15.7-19.2-28-41.8-36.8-67.1-9.3-26.9-13.9-55.5-13.9-85.1 0-16.8-1-33.5-2-47.7l-1.3-19.5-12.6-15c-24.1-28.6-36.8-63-36.8-99.3 0-89.3 78.5-161.9 174.9-161.9 36.4 0 71.4 10.3 101 29.7 28.4 18.7 65.5 81.7 65.5 81.7s17.9 27.5 24.7 98.2c4.5 46.5 5 95.9 5 133.8 0.1 29.6-4.6 58.2-13.9 85.1zM377.1 219.9c-51.8 0-93.8 42-93.8 93.8s42 93.8 93.8 93.8 93.8-42 93.8-93.8-42-93.8-93.8-93.8z m0 127.5c-18.6 0-33.8-15.2-33.8-33.8 0-18.6 15.2-33.8 33.8-33.8 18.6 0 33.8 15.2 33.8 33.8 0 18.7-15.1 33.8-33.8 33.8z" fill="#45484C" /><path d="M521.2 206.7m-50.3 0a50.3 50.3 0 1 0 100.6 0 50.3 50.3 0 1 0-100.6 0Z" fill="#45484C" /><path d="M653 156.4m-50.3 0a50.3 50.3 0 1 0 100.6 0 50.3 50.3 0 1 0-100.6 0Z" fill="#45484C" /><path d="M781.9 158.4m-50.3 0a50.3 50.3 0 1 0 100.6 0 50.3 50.3 0 1 0-100.6 0Z" fill="#45484C" /><path d="M909 206.7m-50.3 0a50.3 50.3 0 1 0 100.6 0 50.3 50.3 0 1 0-100.6 0Z" fill="#45484C" /><path d="M263.9 602.7c44.7 0 81 31.5 81 70.3 0 20.9-10.2 35.9-18.7 44.8l-15.9 19.7-0.5 27.2c0.7 7.2 0.6 16.9 0.6 24.7v4.8c0 33.7-27.4 61.2-61.2 61.2-14.9 0-33.3-9.6-48.1-25-15.2-15.9-24.6-35.9-24.6-52.3v-3.2c0-12.7 0-36.2 1-60.2 1.4-33 7.4-57.3 7.4-57.3 3.9-14.7 13.4-28.2 26.8-38 14.8-11 32.8-16.7 52.2-16.7m0-60c-66.4 0-122 42.4-137 99.4-10.9 23-10.4 112.6-10.4 135.9 0 66.9 65.8 137.3 132.7 137.3 66.9 0 121.2-54.3 121.2-121.2 0-9.2 0.3-23-0.8-34.9 22-23 35.4-53.2 35.4-86.3-0.1-71.9-63.2-130.2-141.1-130.2zM444.4 559.9c-26.4 0-47.8 21.4-47.8 47.8s21.4 47.8 47.8 47.8 47.8-21.4 47.8-47.8-21.4-47.8-47.8-47.8zM377.1 494.5c-15.2 0-27.5 12.3-27.5 27.5s12.3 27.5 27.5 27.5 27.5-12.3 27.5-27.5c0-15.3-12.3-27.5-27.5-27.5zM288.1 471.5c-15.2 0-27.5 12.3-27.5 27.5s12.3 27.5 27.5 27.5 27.5-12.3 27.5-27.5-12.4-27.5-27.5-27.5zM188.3 477.9c-15.2 0-27.5 12.3-27.5 27.5s12.3 27.5 27.5 27.5 27.5-12.3 27.5-27.5-12.3-27.5-27.5-27.5zM100.6 538.4c-15.2 0-27.5 12.3-27.5 27.5s12.3 27.5 27.5 27.5 27.5-12.3 27.5-27.5c-0.1-15.2-12.4-27.5-27.5-27.5z" fill="#45484C" /><path d="M670.1 584.6c-41.4 0-80.2-20.3-103.9-54.3-9.5-13.6-6.2-32.3 7.4-41.8 13.6-9.5 32.3-6.2 41.8 7.4 12.5 17.9 33 28.6 54.7 28.6 36.8 0 66.7-29.9 66.7-66.7 0-19.8-8.7-38.4-23.9-51.2-12.7-10.6-14.4-29.6-3.7-42.3s29.6-14.4 42.3-3.7c28.9 24.2 45.4 59.6 45.4 97.2-0.1 70-56.9 126.8-126.8 126.8z" fill="#33CC99" /><path d="M853 556.4c-26 0-49.6-14.5-60.1-36.9-7-15-0.6-32.9 14.4-39.9s32.9-0.6 39.9 14.4c0.3 0.6 2.2 2.4 5.8 2.4 1.2 0 2.3-0.2 3.3-0.6 15.5-5.9 32.8 1.8 38.7 17.3 5.9 15.5-1.8 32.8-17.3 38.7-7.9 3.1-16.2 4.6-24.7 4.6z" fill="#33CC99" /></svg>