PraisonAI 1.0.5__tar.gz → 1.0.8__tar.gz

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 (55) hide show
  1. {praisonai-1.0.5 → praisonai-1.0.8}/PKG-INFO +16 -3
  2. {praisonai-1.0.5 → praisonai-1.0.8}/README.md +15 -2
  3. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/deploy.py +1 -1
  4. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/chat.py +60 -257
  5. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/code.py +30 -130
  6. praisonai-1.0.8/praisonai/ui/db.py +242 -0
  7. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/sql_alchemy.py +5 -3
  8. {praisonai-1.0.5 → praisonai-1.0.8}/pyproject.toml +11 -1
  9. {praisonai-1.0.5 → praisonai-1.0.8}/LICENSE +0 -0
  10. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/__init__.py +0 -0
  11. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/__main__.py +0 -0
  12. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/agents_generator.py +0 -0
  13. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/api/call.py +0 -0
  14. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/auto.py +0 -0
  15. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/chainlit_ui.py +0 -0
  16. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/cli.py +0 -0
  17. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/inbuilt_tools/__init__.py +0 -0
  18. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  19. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/inc/__init__.py +0 -0
  20. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/inc/config.py +0 -0
  21. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/inc/models.py +0 -0
  22. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/android-chrome-192x192.png +0 -0
  23. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/android-chrome-512x512.png +0 -0
  24. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/apple-touch-icon.png +0 -0
  25. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/fantasy.svg +0 -0
  26. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/favicon-16x16.png +0 -0
  27. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/favicon-32x32.png +0 -0
  28. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/favicon.ico +0 -0
  29. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/game.svg +0 -0
  30. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/logo_dark.png +0 -0
  31. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/logo_light.png +0 -0
  32. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/movie.svg +0 -0
  33. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/public/thriller.svg +0 -0
  34. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/__init__.py +0 -0
  35. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/build.py +0 -0
  36. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/config.yaml +0 -0
  37. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/post_install.py +0 -0
  38. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/setup_conda_env.py +0 -0
  39. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup/setup_conda_env.sh +0 -0
  40. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/setup.py +0 -0
  41. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/test.py +0 -0
  42. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/train.py +0 -0
  43. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/README.md +0 -0
  44. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/context.py +0 -0
  45. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/fantasy.svg +0 -0
  46. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/game.svg +0 -0
  47. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/logo_dark.png +0 -0
  48. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/logo_light.png +0 -0
  49. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/movie.svg +0 -0
  50. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/public/thriller.svg +0 -0
  51. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/realtime.py +0 -0
  52. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/realtimeclient/__init__.py +0 -0
  53. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/realtimeclient/realtimedocs.txt +0 -0
  54. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/ui/realtimeclient/tools.py +0 -0
  55. {praisonai-1.0.5 → praisonai-1.0.8}/praisonai/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PraisonAI
3
- Version: 1.0.5
3
+ Version: 1.0.8
4
4
  Summary: PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration.
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10,<3.13
@@ -116,11 +116,24 @@ Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, rep
116
116
 
117
117
  ## Installation Options
118
118
 
119
- ### Basic Installation
119
+ ### Using pip
120
120
  ```bash
121
121
  pip install praisonai
122
122
  ```
123
123
 
124
+ ### Using uv (Fast Python Package Installer)
125
+ ```bash
126
+ # Install uv if you haven't already
127
+ pip install uv
128
+
129
+ # Install from requirements
130
+ uv pip install -r pyproject.toml
131
+
132
+ # Install with extras
133
+ uv pip install -r pyproject.toml --extra code
134
+ uv pip install -r pyproject.toml --extra "crewai,autogen"
135
+ ```
136
+
124
137
  ### Framework-specific Installation
125
138
  ```bash
126
139
  # Install with CrewAI support
@@ -435,6 +448,7 @@ if __name__ == "__main__":
435
448
  ```
436
449
 
437
450
  4. **Install only dev dependencies:**
451
+
438
452
  ```sh
439
453
  poetry install --with dev
440
454
  ```
@@ -480,4 +494,3 @@ Praison AI is an open-sourced software licensed under the **[MIT license](https:
480
494
 
481
495
  Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
482
496
 
483
-
@@ -53,11 +53,24 @@ Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, rep
53
53
 
54
54
  ## Installation Options
55
55
 
56
- ### Basic Installation
56
+ ### Using pip
57
57
  ```bash
58
58
  pip install praisonai
59
59
  ```
60
60
 
61
+ ### Using uv (Fast Python Package Installer)
62
+ ```bash
63
+ # Install uv if you haven't already
64
+ pip install uv
65
+
66
+ # Install from requirements
67
+ uv pip install -r pyproject.toml
68
+
69
+ # Install with extras
70
+ uv pip install -r pyproject.toml --extra code
71
+ uv pip install -r pyproject.toml --extra "crewai,autogen"
72
+ ```
73
+
61
74
  ### Framework-specific Installation
62
75
  ```bash
63
76
  # Install with CrewAI support
@@ -372,6 +385,7 @@ if __name__ == "__main__":
372
385
  ```
373
386
 
374
387
  4. **Install only dev dependencies:**
388
+
375
389
  ```sh
376
390
  poetry install --with dev
377
391
  ```
@@ -416,4 +430,3 @@ Praison AI is an open-sourced software licensed under the **[MIT license](https:
416
430
  ## License
417
431
 
418
432
  Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
419
-
@@ -56,7 +56,7 @@ class CloudDeployer:
56
56
  file.write("FROM python:3.11-slim\n")
57
57
  file.write("WORKDIR /app\n")
58
58
  file.write("COPY . .\n")
59
- file.write("RUN pip install flask praisonai==1.0.5 gunicorn markdown\n")
59
+ file.write("RUN pip install flask praisonai==1.0.8 gunicorn markdown\n")
60
60
  file.write("EXPOSE 8080\n")
61
61
  file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
62
62
 
@@ -1,29 +1,32 @@
1
- import chainlit as cl
2
- from chainlit.input_widget import TextInput
3
- from chainlit.types import ThreadDict
4
- from litellm import acompletion
1
+ # Standard library imports
5
2
  import os
6
- import sqlite3
7
3
  from datetime import datetime
8
- from typing import Dict, List, Optional
9
- from dotenv import load_dotenv
10
- load_dotenv()
11
- import chainlit.data as cl_data
12
- from chainlit.step import StepDict
13
- from literalai.helper import utc_now
4
+ from typing import Dict, Optional
14
5
  import logging
15
6
  import json
16
- from sql_alchemy import SQLAlchemyDataLayer
17
- from tavily import TavilyClient
18
- from crawl4ai import AsyncWebCrawler
19
7
  import asyncio
20
- from PIL import Image
21
8
  import io
22
9
  import base64
23
10
 
24
- from sqlalchemy.ext.asyncio import create_async_engine
25
- from sqlalchemy import text
11
+ # Third-party imports
12
+ from dotenv import load_dotenv
13
+ from PIL import Image
14
+ from tavily import TavilyClient
15
+ from crawl4ai import AsyncWebCrawler
26
16
 
17
+ # Local application/library imports
18
+ import chainlit as cl
19
+ from chainlit.input_widget import TextInput
20
+ from chainlit.types import ThreadDict
21
+ import chainlit.data as cl_data
22
+ from litellm import acompletion
23
+ from literalai.helper import utc_now
24
+ from db import DatabaseManager
25
+
26
+ # Load environment variables
27
+ load_dotenv()
28
+
29
+ # Logging configuration
27
30
  logger = logging.getLogger(__name__)
28
31
  log_level = os.getenv("LOGLEVEL", "INFO").upper()
29
32
  logger.handlers = []
@@ -42,233 +45,19 @@ if not CHAINLIT_AUTH_SECRET:
42
45
  now = utc_now()
43
46
  create_step_counter = 0
44
47
 
45
- DATABASE_URL = os.getenv("DATABASE_URL")
46
-
47
- async def create_schema_async():
48
- if not DATABASE_URL:
49
- return
50
- engine = create_async_engine(DATABASE_URL, echo=False)
51
- async with engine.begin() as conn:
52
- # Use double quotes for column names and renamed start/end to startTime/endTime
53
- await conn.execute(text('''
54
- CREATE TABLE IF NOT EXISTS users (
55
- "id" TEXT PRIMARY KEY,
56
- "identifier" TEXT NOT NULL UNIQUE,
57
- "meta" TEXT NOT NULL DEFAULT '{}',
58
- "createdAt" TEXT
59
- );
60
- '''))
61
- await conn.execute(text('''
62
- CREATE TABLE IF NOT EXISTS threads (
63
- "id" TEXT PRIMARY KEY,
64
- "createdAt" TEXT,
65
- "name" TEXT,
66
- "userId" TEXT,
67
- "userIdentifier" TEXT,
68
- "tags" TEXT DEFAULT '[]',
69
- "meta" TEXT NOT NULL DEFAULT '{}',
70
- FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE
71
- );
72
- '''))
73
- await conn.execute(text('''
74
- CREATE TABLE IF NOT EXISTS steps (
75
- "id" TEXT PRIMARY KEY,
76
- "name" TEXT NOT NULL,
77
- "type" TEXT NOT NULL,
78
- "threadId" TEXT NOT NULL,
79
- "parentId" TEXT,
80
- "disableFeedback" BOOLEAN NOT NULL DEFAULT FALSE,
81
- "streaming" BOOLEAN NOT NULL DEFAULT FALSE,
82
- "waitForAnswer" BOOLEAN DEFAULT FALSE,
83
- "isError" BOOLEAN NOT NULL DEFAULT FALSE,
84
- "meta" TEXT DEFAULT '{}',
85
- "tags" TEXT DEFAULT '[]',
86
- "input" TEXT,
87
- "output" TEXT,
88
- "createdAt" TEXT,
89
- "startTime" TEXT,
90
- "endTime" TEXT,
91
- "generation" TEXT,
92
- "showInput" TEXT,
93
- "language" TEXT,
94
- "indent" INT,
95
- FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
96
- );
97
- '''))
98
- await conn.execute(text('''
99
- CREATE TABLE IF NOT EXISTS elements (
100
- "id" TEXT PRIMARY KEY,
101
- "threadId" TEXT,
102
- "type" TEXT,
103
- "url" TEXT,
104
- "chainlitKey" TEXT,
105
- "name" TEXT NOT NULL,
106
- "display" TEXT,
107
- "objectKey" TEXT,
108
- "size" TEXT,
109
- "page" INT,
110
- "language" TEXT,
111
- "forId" TEXT,
112
- "mime" TEXT,
113
- FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
114
- );
115
- '''))
116
- await conn.execute(text('''
117
- CREATE TABLE IF NOT EXISTS feedbacks (
118
- "id" TEXT PRIMARY KEY,
119
- "forId" TEXT NOT NULL,
120
- "value" INT NOT NULL,
121
- "threadId" TEXT,
122
- "comment" TEXT
123
- );
124
- '''))
125
- await conn.execute(text('''
126
- CREATE TABLE IF NOT EXISTS settings (
127
- "id" SERIAL PRIMARY KEY,
128
- "key" TEXT UNIQUE,
129
- "value" TEXT
130
- );
131
- '''))
132
- await engine.dispose()
133
-
134
- def create_schema_sqlite(db_path: str):
135
- os.makedirs(os.path.dirname(db_path), exist_ok=True)
136
- conn = sqlite3.connect(db_path)
137
- cursor = conn.cursor()
138
- # Renamed start to startTime, end to endTime
139
- cursor.execute('''
140
- CREATE TABLE IF NOT EXISTS users (
141
- "id" TEXT PRIMARY KEY,
142
- "identifier" TEXT NOT NULL UNIQUE,
143
- "meta" TEXT NOT NULL DEFAULT '{}',
144
- "createdAt" TEXT
145
- )
146
- ''')
147
- cursor.execute('''
148
- CREATE TABLE IF NOT EXISTS threads (
149
- "id" TEXT PRIMARY KEY,
150
- "createdAt" TEXT,
151
- "name" TEXT,
152
- "userId" TEXT,
153
- "userIdentifier" TEXT,
154
- "tags" TEXT DEFAULT '[]',
155
- "meta" TEXT NOT NULL DEFAULT '{}',
156
- FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE
157
- )
158
- ''')
159
- cursor.execute('''
160
- CREATE TABLE IF NOT EXISTS steps (
161
- "id" TEXT PRIMARY KEY,
162
- "name" TEXT NOT NULL,
163
- "type" TEXT NOT NULL,
164
- "threadId" TEXT NOT NULL,
165
- "parentId" TEXT,
166
- "disableFeedback" BOOLEAN NOT NULL DEFAULT 0,
167
- "streaming" BOOLEAN NOT NULL DEFAULT 0,
168
- "waitForAnswer" BOOLEAN DEFAULT 0,
169
- "isError" BOOLEAN NOT NULL DEFAULT 0,
170
- "meta" TEXT DEFAULT '{}',
171
- "tags" TEXT DEFAULT '[]',
172
- "input" TEXT,
173
- "output" TEXT,
174
- "createdAt" TEXT,
175
- "startTime" TEXT,
176
- "endTime" TEXT,
177
- "generation" TEXT,
178
- "showInput" TEXT,
179
- "language" TEXT,
180
- "indent" INT,
181
- FOREIGN KEY ("threadId") REFERENCES threads ("id") ON DELETE CASCADE
182
- )
183
- ''')
184
- cursor.execute('''
185
- CREATE TABLE IF NOT EXISTS elements (
186
- "id" TEXT PRIMARY KEY,
187
- "threadId" TEXT,
188
- "type" TEXT,
189
- "url" TEXT,
190
- "chainlitKey" TEXT,
191
- "name" TEXT NOT NULL,
192
- "display" TEXT,
193
- "objectKey" TEXT,
194
- "size" TEXT,
195
- "page" INT,
196
- "language" TEXT,
197
- "forId" TEXT,
198
- "mime" TEXT,
199
- FOREIGN KEY ("threadId") REFERENCES threads ("id") ON DELETE CASCADE
200
- )
201
- ''')
202
- cursor.execute('''
203
- CREATE TABLE IF NOT EXISTS feedbacks (
204
- "id" TEXT PRIMARY KEY,
205
- "forId" TEXT NOT NULL,
206
- "value" INT NOT NULL,
207
- "threadId" TEXT,
208
- "comment" TEXT
209
- )
210
- ''')
211
- cursor.execute('''
212
- CREATE TABLE IF NOT EXISTS settings (
213
- "id" INTEGER PRIMARY KEY AUTOINCREMENT,
214
- "key" TEXT UNIQUE,
215
- "value" TEXT
216
- )
217
- ''')
218
- conn.commit()
219
- conn.close()
220
-
221
- if DATABASE_URL:
222
- asyncio.run(create_schema_async())
223
- conninfo = DATABASE_URL
224
- else:
225
- DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
226
- create_schema_sqlite(DB_PATH)
227
- conninfo = f"sqlite+aiosqlite:///{DB_PATH}"
48
+ # Initialize database
49
+ db_manager = DatabaseManager()
50
+ db_manager.initialize()
228
51
 
229
52
  def save_setting(key: str, value: str):
230
- if DATABASE_URL:
231
- async def save_setting_async():
232
- engine = create_async_engine(DATABASE_URL, echo=False)
233
- async with engine.begin() as conn:
234
- await conn.execute(text("""
235
- INSERT INTO settings ("key", "value") VALUES (:key, :value)
236
- ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value"
237
- """), {"key": key, "value": value})
238
- await engine.dispose()
239
- asyncio.run(save_setting_async())
240
- else:
241
- conn = sqlite3.connect(DB_PATH)
242
- cursor = conn.cursor()
243
- cursor.execute(
244
- """
245
- INSERT OR REPLACE INTO settings (id, "key", "value")
246
- VALUES ((SELECT id FROM settings WHERE "key" = ?), ?, ?)
247
- """,
248
- (key, key, value),
249
- )
250
- conn.commit()
251
- conn.close()
53
+ """Save a setting to the database"""
54
+ asyncio.run(db_manager.save_setting(key, value))
252
55
 
253
56
  def load_setting(key: str) -> str:
254
- if DATABASE_URL:
255
- async def load_setting_async():
256
- engine = create_async_engine(DATABASE_URL, echo=False)
257
- async with engine.connect() as conn:
258
- result = await conn.execute(text('SELECT "value" FROM settings WHERE "key" = :key'), {"key": key})
259
- row = result.fetchone()
260
- await engine.dispose()
261
- return row[0] if row else None
262
- return asyncio.run(load_setting_async())
263
- else:
264
- conn = sqlite3.connect(DB_PATH)
265
- cursor = conn.cursor()
266
- cursor.execute('SELECT "value" FROM settings WHERE "key" = ?', (key,))
267
- result = cursor.fetchone()
268
- conn.close()
269
- return result[0] if result else None
57
+ """Load a setting from the database"""
58
+ return asyncio.run(db_manager.load_setting(key))
270
59
 
271
- cl_data._data_layer = SQLAlchemyDataLayer(conninfo=conninfo)
60
+ cl_data._data_layer = db_manager
272
61
 
273
62
  tavily_api_key = os.getenv("TAVILY_API_KEY")
274
63
  tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
@@ -323,6 +112,37 @@ tools = [{
323
112
  }
324
113
  }] if tavily_api_key else []
325
114
 
115
+ # Authentication configuration
116
+ AUTH_PASSWORD_ENABLED = os.getenv("AUTH_PASSWORD_ENABLED", "true").lower() == "true" # Password authentication enabled by default
117
+ AUTH_OAUTH_ENABLED = os.getenv("AUTH_OAUTH_ENABLED", "false").lower() == "true" # OAuth authentication disabled by default
118
+
119
+ username = os.getenv("CHAINLIT_USERNAME", "admin")
120
+ password = os.getenv("CHAINLIT_PASSWORD", "admin")
121
+
122
+ def auth_callback(u: str, p: str):
123
+ if (u, p) == (username, password):
124
+ return cl.User(identifier=username, metadata={"role": "ADMIN", "provider": "credentials"})
125
+ return None
126
+
127
+ def oauth_callback(
128
+ provider_id: str,
129
+ token: str,
130
+ raw_user_data: Dict[str, str],
131
+ default_user: cl.User,
132
+ ) -> Optional[cl.User]:
133
+ return default_user
134
+
135
+ if AUTH_PASSWORD_ENABLED:
136
+ auth_callback = cl.password_auth_callback(auth_callback)
137
+
138
+ if AUTH_OAUTH_ENABLED:
139
+ oauth_callback = cl.oauth_callback(oauth_callback)
140
+
141
+ async def send_count():
142
+ await cl.Message(
143
+ f"Create step counter: {create_step_counter}", disable_feedback=True
144
+ ).send()
145
+
326
146
  @cl.on_chat_start
327
147
  async def start():
328
148
  model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
@@ -358,7 +178,7 @@ async def setup_agent(settings):
358
178
  if isinstance(metadata, str):
359
179
  try:
360
180
  metadata = json.loads(metadata)
361
- except:
181
+ except json.JSONDecodeError:
362
182
  metadata = {}
363
183
  metadata["model_name"] = model_name
364
184
  await cl_data.update_thread(thread_id, metadata=metadata)
@@ -516,23 +336,6 @@ User Question: {message.content}
516
336
  msg.content = full_response
517
337
  await msg.update()
518
338
 
519
- username = os.getenv("CHAINLIT_USERNAME", "admin")
520
- password = os.getenv("CHAINLIT_PASSWORD", "admin")
521
-
522
- @cl.password_auth_callback
523
- def auth_callback(u: str, p: str):
524
- if (u, p) == (username, password):
525
- return cl.User(
526
- identifier=username, metadata={"role": "ADMIN", "provider": "credentials"}
527
- )
528
- else:
529
- return None
530
-
531
- async def send_count():
532
- await cl.Message(
533
- f"Create step counter: {create_step_counter}", disable_feedback=True
534
- ).send()
535
-
536
339
  @cl.on_chat_resume
537
340
  async def on_chat_resume(thread: ThreadDict):
538
341
  logger.info(f"Resuming chat: {thread['id']}")
@@ -1,23 +1,29 @@
1
- import chainlit as cl
2
- from chainlit.input_widget import TextInput
3
- from chainlit.types import ThreadDict, StepDict
1
+ # Standard library imports
4
2
  import os
5
- import sqlite3
6
3
  from datetime import datetime
7
- from typing import Dict, List, Optional
8
- from dotenv import load_dotenv
9
- load_dotenv()
10
- import chainlit.data as cl_data
11
4
  import logging
12
5
  import json
13
- from sql_alchemy import SQLAlchemyDataLayer
6
+ import io
7
+ import base64
8
+ import asyncio
9
+
10
+ # Third-party imports
11
+ from dotenv import load_dotenv
12
+ from PIL import Image
14
13
  from context import ContextGatherer
15
14
  from tavily import TavilyClient
16
- from datetime import datetime
17
15
  from crawl4ai import AsyncWebCrawler
18
- from PIL import Image
19
- import io
20
- import base64
16
+
17
+ # Local application/library imports
18
+ import chainlit as cl
19
+ from chainlit.input_widget import TextInput
20
+ from chainlit.types import ThreadDict
21
+ import chainlit.data as cl_data
22
+ from litellm import acompletion
23
+ from db import DatabaseManager
24
+
25
+ # Load environment variables
26
+ load_dotenv()
21
27
 
22
28
  # Set up logging
23
29
  logger = logging.getLogger(__name__)
@@ -41,143 +47,38 @@ if not CHAINLIT_AUTH_SECRET:
41
47
  CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
42
48
 
43
49
  now = datetime.now()
44
-
45
50
  create_step_counter = 0
46
51
 
47
- DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
48
-
49
- def initialize_db():
50
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
51
- conn = sqlite3.connect(DB_PATH)
52
- cursor = conn.cursor()
53
- cursor.execute('''
54
- CREATE TABLE IF NOT EXISTS users (
55
- id UUID PRIMARY KEY,
56
- identifier TEXT NOT NULL UNIQUE,
57
- metadata JSONB NOT NULL,
58
- createdAt TEXT
59
- )
60
- ''')
61
- cursor.execute('''
62
- CREATE TABLE IF NOT EXISTS threads (
63
- id UUID PRIMARY KEY,
64
- createdAt TEXT,
65
- name TEXT,
66
- userId UUID,
67
- userIdentifier TEXT,
68
- tags TEXT[],
69
- metadata JSONB NOT NULL DEFAULT '{}',
70
- FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
71
- )
72
- ''')
73
- cursor.execute('''
74
- CREATE TABLE IF NOT EXISTS steps (
75
- id UUID PRIMARY KEY,
76
- name TEXT NOT NULL,
77
- type TEXT NOT NULL,
78
- threadId UUID NOT NULL,
79
- parentId UUID,
80
- disableFeedback BOOLEAN NOT NULL DEFAULT 0,
81
- streaming BOOLEAN NOT NULL DEFAULT 0,
82
- waitForAnswer BOOLEAN DEFAULT 0,
83
- isError BOOLEAN NOT NULL DEFAULT 0,
84
- metadata JSONB DEFAULT '{}',
85
- tags TEXT[],
86
- input TEXT,
87
- output TEXT,
88
- createdAt TEXT,
89
- start TEXT,
90
- end TEXT,
91
- generation JSONB,
92
- showInput TEXT,
93
- language TEXT,
94
- indent INT,
95
- FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
96
- )
97
- ''')
98
- cursor.execute('''
99
- CREATE TABLE IF NOT EXISTS elements (
100
- id UUID PRIMARY KEY,
101
- threadId UUID,
102
- type TEXT,
103
- url TEXT,
104
- chainlitKey TEXT,
105
- name TEXT NOT NULL,
106
- display TEXT,
107
- objectKey TEXT,
108
- size TEXT,
109
- page INT,
110
- language TEXT,
111
- forId UUID,
112
- mime TEXT,
113
- FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
114
- )
115
- ''')
116
- cursor.execute('''
117
- CREATE TABLE IF NOT EXISTS feedbacks (
118
- id UUID PRIMARY KEY,
119
- forId UUID NOT NULL,
120
- value INT NOT NULL,
121
- threadId UUID,
122
- comment TEXT
123
- )
124
- ''')
125
- cursor.execute('''
126
- CREATE TABLE IF NOT EXISTS settings (
127
- id INTEGER PRIMARY KEY AUTOINCREMENT,
128
- key TEXT UNIQUE,
129
- value TEXT
130
- )
131
- ''')
132
- conn.commit()
133
- conn.close()
52
+ # Initialize database
53
+ db_manager = DatabaseManager()
54
+ db_manager.initialize()
55
+
56
+ deleted_thread_ids = [] # type: List[str]
134
57
 
135
58
  def save_setting(key: str, value: str):
136
59
  """Saves a setting to the database.
137
-
60
+
138
61
  Args:
139
62
  key: The setting key.
140
63
  value: The setting value.
141
64
  """
142
- conn = sqlite3.connect(DB_PATH)
143
- cursor = conn.cursor()
144
- cursor.execute(
145
- """
146
- INSERT OR REPLACE INTO settings (id, key, value)
147
- VALUES ((SELECT id FROM settings WHERE key = ?), ?, ?)
148
- """,
149
- (key, key, value),
150
- )
151
- conn.commit()
152
- conn.close()
65
+ asyncio.run(db_manager.save_setting(key, value))
153
66
 
154
67
  def load_setting(key: str) -> str:
155
68
  """Loads a setting from the database.
156
-
69
+
157
70
  Args:
158
71
  key: The setting key.
159
-
72
+
160
73
  Returns:
161
74
  The setting value, or None if the key is not found.
162
75
  """
163
- conn = sqlite3.connect(DB_PATH)
164
- cursor = conn.cursor()
165
- cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
166
- result = cursor.fetchone()
167
- conn.close()
168
- return result[0] if result else None
169
-
170
-
171
- # Initialize the database
172
- initialize_db()
173
-
174
- deleted_thread_ids = [] # type: List[str]
76
+ return asyncio.run(db_manager.load_setting(key))
175
77
 
176
- cl_data._data_layer = SQLAlchemyDataLayer(conninfo=f"sqlite+aiosqlite:///{DB_PATH}")
78
+ cl_data._data_layer = db_manager
177
79
 
178
80
  @cl.on_chat_start
179
81
  async def start():
180
- initialize_db()
181
82
  model_name = load_setting("model_name")
182
83
 
183
84
  if (model_name):
@@ -498,7 +399,6 @@ async def on_chat_resume(thread: ThreadDict):
498
399
  ]
499
400
  )
500
401
  await settings.send()
501
- thread_id = thread["id"]
502
402
  cl.user_session.set("thread_id", thread["id"])
503
403
 
504
404
  # Ensure metadata is a dictionary
@@ -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
@@ -34,6 +34,10 @@ if TYPE_CHECKING:
34
34
  from chainlit.step import StepDict
35
35
 
36
36
  DATABASE_URL = os.getenv("DATABASE_URL")
37
+ SUPABASE_DATABASE_URL = os.getenv("SUPABASE_DATABASE_URL")
38
+ if SUPABASE_DATABASE_URL:
39
+ # If a Supabase database URL is provided, use it.
40
+ DATABASE_URL = SUPABASE_DATABASE_URL
37
41
 
38
42
  class SQLAlchemyDataLayer(BaseDataLayer):
39
43
  def __init__(
@@ -262,7 +266,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
262
266
  )
263
267
  if not filters.userId:
264
268
  raise ValueError("userId is required")
265
- all_user_threads: List[ThreadDict] = (
269
+ all_user_threads: Optional[List[ThreadDict]] = (
266
270
  await self.get_all_user_threads(user_id=filters.userId) or []
267
271
  )
268
272
 
@@ -324,8 +328,6 @@ class SQLAlchemyDataLayer(BaseDataLayer):
324
328
  else None
325
329
  )
326
330
 
327
- # Use updated column names: startTime, endTime
328
- # Make sure tags is always a JSON array
329
331
  tags = step_dict.get("tags")
330
332
  if not tags:
331
333
  tags = []
@@ -1,6 +1,16 @@
1
+ [project]
2
+ name = "PraisonAI"
3
+ version = "1.0.8"
4
+ description = "PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration."
5
+ readme = "README.md"
6
+ license = ""
7
+ authors = [
8
+ { name = "Mervin Praison" }
9
+ ]
10
+
1
11
  [tool.poetry]
2
12
  name = "PraisonAI"
3
- version = "1.0.5"
13
+ version = "1.0.8"
4
14
  description = "PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration."
5
15
  authors = ["Mervin Praison"]
6
16
  license = ""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes