1+ """Bridge praisonaiagents SessionStore → aiui BaseDataStore.
2+
3+ Allows any SessionStoreProtocol implementation (file/Redis/Mongo) to back
4+ the aiui dashboard's Sessions page. No Chainlit required.
5+
6+ This adapter enables PraisonAI's native session persistence to power
7+ the aiui dashboard, maintaining consistency with Core SDK storage.
8+ """
9+ from __future__ import annotations
10+
11+ import uuid
12+ from typing import Any , Optional , Dict , List
13+
14+ try :
15+ from praisonaiui .datastore import BaseDataStore
16+ except ImportError :
17+ # Graceful fallback for when aiui is not available
18+ class BaseDataStore :
19+ """Fallback BaseDataStore for when aiui is not installed."""
20+ pass
21+
22+ try :
23+ from praisonaiagents .session import (
24+ SessionStoreProtocol ,
25+ get_hierarchical_session_store ,
26+ )
27+ except ImportError :
28+ # Graceful fallback for when praisonaiagents is not available
29+ SessionStoreProtocol = None
30+ get_hierarchical_session_store = None
31+
32+
33+ class PraisonAISessionDataStore (BaseDataStore ):
34+ """Adapter that bridges SessionStoreProtocol → aiui BaseDataStore.
35+
36+ This allows any PraisonAI SessionStore implementation (file-based,
37+ Redis, MongoDB, etc.) to serve as the persistence layer for aiui
38+ dashboard sessions.
39+
40+ Args:
41+ store: SessionStoreProtocol implementation. If None, uses
42+ get_hierarchical_session_store() as default.
43+
44+ Example:
45+ from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
46+ import praisonaiui as aiui
47+
48+ # Use default hierarchical store
49+ aiui.set_datastore(PraisonAISessionDataStore())
50+
51+ # Or provide custom store
52+ from my_project import MyRedisStore
53+ aiui.set_datastore(PraisonAISessionDataStore(MyRedisStore()))
54+ """
55+
56+ def __init__ (self , store : Optional [SessionStoreProtocol ] = None ):
57+ if SessionStoreProtocol is None :
58+ raise ImportError (
59+ "praisonaiagents is required to use PraisonAISessionDataStore. "
60+ "Install with: pip install praisonaiagents"
61+ )
62+
63+ if store is None and get_hierarchical_session_store is None :
64+ raise ImportError (
65+ "praisonaiagents.session.get_hierarchical_session_store is required. "
66+ "Install with: pip install praisonaiagents"
67+ )
68+
69+ self ._store = store or get_hierarchical_session_store ()
70+
71+ def _new_id (self ) -> str :
72+ """Generate a new unique session ID."""
73+ return str (uuid .uuid4 ())
74+
75+ async def list_sessions (self ) -> List [Dict [str , Any ]]:
76+ """List all available sessions.
77+
78+ Note: SessionStoreProtocol doesn't expose a list_sessions method,
79+ so this is a limitation. For file-based stores, we could potentially
80+ scan the filesystem, but that would be implementation-specific.
81+
82+ For now, return empty list. Users will need to know their session IDs
83+ or create new sessions.
84+ """
85+ # TODO: If needed, could add optional list_sessions to SessionStoreProtocol
86+ # For now, return empty - sessions are created on-demand
87+ return []
88+
89+ async def get_session (self , session_id : str ) -> Optional [Dict [str , Any ]]:
90+ """Get a session by ID.
91+
92+ Args:
93+ session_id: Session identifier.
94+
95+ Returns:
96+ Session dict with 'id' and 'messages' fields, or None if not found.
97+ """
98+ if not self ._store .session_exists (session_id ):
99+ return None
100+
101+ messages = self ._store .get_chat_history (session_id )
102+ return {
103+ "id" : session_id ,
104+ "messages" : messages ,
105+ "metadata" : {
106+ "created_via" : "praisonai" ,
107+ "message_count" : len (messages ),
108+ },
109+ }
110+
111+ async def create_session (self , session_id : Optional [str ] = None ) -> Dict [str , Any ]:
112+ """Create a new session.
113+
114+ Args:
115+ session_id: Optional session ID. If None, generates a new UUID.
116+
117+ Returns:
118+ Session dict with 'id' and empty 'messages' list.
119+ """
120+ sid = session_id or self ._new_id ()
121+
122+ # SessionStoreProtocol creates sessions lazily on first add_message,
123+ # so we don't need to explicitly create here
124+ return {
125+ "id" : sid ,
126+ "messages" : [],
127+ "metadata" : {
128+ "created_via" : "praisonai" ,
129+ "message_count" : 0 ,
130+ },
131+ }
132+
133+ async def delete_session (self , session_id : str ) -> bool :
134+ """Delete a session completely.
135+
136+ Args:
137+ session_id: Session identifier.
138+
139+ Returns:
140+ True if deleted successfully.
141+ """
142+ return self ._store .delete_session (session_id )
143+
144+ async def add_message (
145+ self ,
146+ session_id : str ,
147+ message : Dict [str , Any ]
148+ ) -> bool :
149+ """Add a message to a session.
150+
151+ Args:
152+ session_id: Session identifier.
153+ message: Message dict with 'role', 'content', and optional 'metadata'.
154+
155+ Returns:
156+ True if added successfully.
157+ """
158+ role = message .get ("role" , "user" )
159+ content = message .get ("content" , "" )
160+ metadata = message .get ("metadata" )
161+
162+ return self ._store .add_message (
163+ session_id = session_id ,
164+ role = role ,
165+ content = content ,
166+ metadata = metadata ,
167+ )
168+
169+ async def get_messages (self , session_id : str ) -> List [Dict [str , Any ]]:
170+ """Get all messages for a session.
171+
172+ Args:
173+ session_id: Session identifier.
174+
175+ Returns:
176+ List of message dicts in LLM-compatible format.
177+ """
178+ return self ._store .get_chat_history (session_id )
179+
180+ async def clear_session (self , session_id : str ) -> bool :
181+ """Clear all messages from a session (keep metadata).
182+
183+ Args:
184+ session_id: Session identifier.
185+
186+ Returns:
187+ True if cleared successfully.
188+ """
189+ return self ._store .clear_session (session_id )
0 commit comments