Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -32,7 +32,6 @@ DATABASE_FILE = STORAGE_DIR / "chat_database.db"
|
|
| 32 |
|
| 33 |
@dataclass
|
| 34 |
class ChatMessage:
|
| 35 |
-
"""Data class untuk pesan chat"""
|
| 36 |
role: str
|
| 37 |
content: str
|
| 38 |
topic: str
|
|
@@ -50,15 +49,12 @@ class ChatMessage:
|
|
| 50 |
self.message_id = str(uuid.uuid4())
|
| 51 |
|
| 52 |
class DatabaseManager:
|
| 53 |
-
"""Mengelola database SQLite untuk pencarian yang lebih efisien"""
|
| 54 |
-
|
| 55 |
def __init__(self):
|
| 56 |
self.db_path = DATABASE_FILE
|
| 57 |
self.lock = threading.Lock()
|
| 58 |
self.init_database()
|
| 59 |
|
| 60 |
def init_database(self):
|
| 61 |
-
"""Inisialisasi tabel database"""
|
| 62 |
try:
|
| 63 |
with sqlite3.connect(self.db_path) as conn:
|
| 64 |
conn.execute('''
|
|
@@ -76,7 +72,6 @@ class DatabaseManager:
|
|
| 76 |
content_hash TEXT
|
| 77 |
)
|
| 78 |
''')
|
| 79 |
-
|
| 80 |
conn.execute('''
|
| 81 |
CREATE TABLE IF NOT EXISTS sessions (
|
| 82 |
session_id TEXT PRIMARY KEY,
|
|
@@ -86,19 +81,16 @@ class DatabaseManager:
|
|
| 86 |
message_count INTEGER DEFAULT 0
|
| 87 |
)
|
| 88 |
''')
|
| 89 |
-
|
| 90 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_content ON messages(content)')
|
| 91 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_topic ON messages(topic)')
|
| 92 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_timestamp ON messages(timestamp)')
|
| 93 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_session ON messages(session_id)')
|
| 94 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_role ON messages(role)')
|
| 95 |
-
|
| 96 |
conn.commit()
|
| 97 |
except sqlite3.Error as e:
|
| 98 |
raise Exception(f"Failed to initialize database: {str(e)}")
|
| 99 |
|
| 100 |
def add_message(self, message: ChatMessage):
|
| 101 |
-
"""Menambah pesan ke database"""
|
| 102 |
try:
|
| 103 |
with self.lock:
|
| 104 |
content_hash = hashlib.md5(message.content.encode('utf-8')).hexdigest()
|
|
@@ -112,7 +104,6 @@ class DatabaseManager:
|
|
| 112 |
message.timestamp, message.word_count, message.char_count,
|
| 113 |
message.session_id, json.dumps(message.tags), content_hash
|
| 114 |
))
|
| 115 |
-
|
| 116 |
conn.execute('''
|
| 117 |
INSERT OR REPLACE INTO sessions (session_id, title, created_at, last_activity, message_count)
|
| 118 |
VALUES (?, ?, ?, ?,
|
|
@@ -127,17 +118,14 @@ class DatabaseManager:
|
|
| 127 |
raise Exception(f"Failed to add message to database: {str(e)}")
|
| 128 |
|
| 129 |
def search_messages(self, query: str, filters: Dict = None) -> List[Dict]:
|
| 130 |
-
"""Pencarian pesan dengan berbagai filter"""
|
| 131 |
try:
|
| 132 |
with sqlite3.connect(self.db_path) as conn:
|
| 133 |
conn.row_factory = sqlite3.Row
|
| 134 |
-
|
| 135 |
base_query = '''
|
| 136 |
SELECT * FROM messages
|
| 137 |
WHERE content LIKE ? COLLATE NOCASE
|
| 138 |
'''
|
| 139 |
params = [f'%{query}%']
|
| 140 |
-
|
| 141 |
if filters:
|
| 142 |
if filters.get('topic'):
|
| 143 |
base_query += ' AND topic = ?'
|
|
@@ -154,26 +142,21 @@ class DatabaseManager:
|
|
| 154 |
if filters.get('date_to'):
|
| 155 |
base_query += ' AND timestamp <= ?'
|
| 156 |
params.append(filters['date_to'])
|
| 157 |
-
|
| 158 |
base_query += ' ORDER BY timestamp DESC LIMIT 100'
|
| 159 |
-
|
| 160 |
cursor = conn.execute(base_query, params)
|
| 161 |
results = []
|
| 162 |
for row in cursor.fetchall():
|
| 163 |
result = dict(row)
|
| 164 |
result['tags'] = json.loads(result['tags']) if result['tags'] else []
|
| 165 |
results.append(result)
|
| 166 |
-
|
| 167 |
return results
|
| 168 |
except sqlite3.Error as e:
|
| 169 |
raise Exception(f"Failed to search messages: {str(e)}")
|
| 170 |
|
| 171 |
def get_all_messages(self, session_id: str = None) -> List[Dict]:
|
| 172 |
-
"""Mengambil semua pesan"""
|
| 173 |
try:
|
| 174 |
with sqlite3.connect(self.db_path) as conn:
|
| 175 |
conn.row_factory = sqlite3.Row
|
| 176 |
-
|
| 177 |
if session_id:
|
| 178 |
cursor = conn.execute(
|
| 179 |
'SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp',
|
|
@@ -181,19 +164,16 @@ class DatabaseManager:
|
|
| 181 |
)
|
| 182 |
else:
|
| 183 |
cursor = conn.execute('SELECT * FROM messages ORDER BY timestamp')
|
| 184 |
-
|
| 185 |
results = []
|
| 186 |
for row in cursor.fetchall():
|
| 187 |
result = dict(row)
|
| 188 |
result['tags'] = json.loads(result['tags']) if result['tags'] else []
|
| 189 |
results.append(result)
|
| 190 |
-
|
| 191 |
return results
|
| 192 |
except sqlite3.Error as e:
|
| 193 |
raise Exception(f"Failed to get messages: {str(e)}")
|
| 194 |
|
| 195 |
def get_sessions(self) -> List[Dict]:
|
| 196 |
-
"""Mengambil semua sesi chat"""
|
| 197 |
try:
|
| 198 |
with sqlite3.connect(self.db_path) as conn:
|
| 199 |
conn.row_factory = sqlite3.Row
|
|
@@ -206,7 +186,6 @@ class DatabaseManager:
|
|
| 206 |
raise Exception(f"Failed to get sessions: {str(e)}")
|
| 207 |
|
| 208 |
def delete_session(self, session_id: str):
|
| 209 |
-
"""Menghapus sesi dan semua pesannya"""
|
| 210 |
try:
|
| 211 |
with self.lock:
|
| 212 |
with sqlite3.connect(self.db_path) as conn:
|
|
@@ -217,20 +196,15 @@ class DatabaseManager:
|
|
| 217 |
raise Exception(f"Failed to delete session: {str(e)}")
|
| 218 |
|
| 219 |
def get_statistics(self) -> Dict:
|
| 220 |
-
"""Statistik database"""
|
| 221 |
try:
|
| 222 |
with sqlite3.connect(self.db_path) as conn:
|
| 223 |
stats = {}
|
| 224 |
-
|
| 225 |
cursor = conn.execute('SELECT COUNT(*) FROM messages')
|
| 226 |
stats['total_messages'] = cursor.fetchone()[0]
|
| 227 |
-
|
| 228 |
cursor = conn.execute('SELECT role, COUNT(*) FROM messages GROUP BY role')
|
| 229 |
stats['by_role'] = dict(cursor.fetchall())
|
| 230 |
-
|
| 231 |
cursor = conn.execute('SELECT topic, COUNT(*) FROM messages GROUP BY topic ORDER BY COUNT(*) DESC LIMIT 10')
|
| 232 |
stats['by_topic'] = dict(cursor.fetchall())
|
| 233 |
-
|
| 234 |
cursor = conn.execute('''
|
| 235 |
SELECT DATE(timestamp) as date, COUNT(*)
|
| 236 |
FROM messages
|
|
@@ -238,17 +212,13 @@ class DatabaseManager:
|
|
| 238 |
ORDER BY date DESC LIMIT 30
|
| 239 |
''')
|
| 240 |
stats['by_date'] = dict(cursor.fetchall())
|
| 241 |
-
|
| 242 |
cursor = conn.execute('SELECT COUNT(*) FROM sessions')
|
| 243 |
stats['total_sessions'] = cursor.fetchone()[0]
|
| 244 |
-
|
| 245 |
return stats
|
| 246 |
except sqlite3.Error as e:
|
| 247 |
raise Exception(f"Failed to get statistics: {str(e)}")
|
| 248 |
|
| 249 |
class ChatStorage:
|
| 250 |
-
"""Handles persistent JSON storage dan database untuk chat history"""
|
| 251 |
-
|
| 252 |
def __init__(self):
|
| 253 |
self.history_file = HISTORY_FILE
|
| 254 |
self.analytics_file = ANALYTICS_FILE
|
|
@@ -259,7 +229,6 @@ class ChatStorage:
|
|
| 259 |
self.sync_to_database()
|
| 260 |
|
| 261 |
def sync_to_database(self):
|
| 262 |
-
"""Sinkronisasi data dari JSON ke database"""
|
| 263 |
try:
|
| 264 |
if self.chat_history and not self.db.get_all_messages():
|
| 265 |
print("π Syncing JSON data to database...")
|
|
@@ -318,7 +287,6 @@ class ChatStorage:
|
|
| 318 |
print(f"β Error saving analytics: {e}")
|
| 319 |
|
| 320 |
def add_message(self, role: str, content: str, topic: str = "general", tags: List[str] = None):
|
| 321 |
-
"""Menambah pesan ke storage dan database"""
|
| 322 |
if not content.strip():
|
| 323 |
raise ValueError("Content cannot be empty")
|
| 324 |
if len(topic) > 100:
|
|
@@ -416,7 +384,6 @@ class ChatStorage:
|
|
| 416 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 417 |
backup_dir = STORAGE_DIR / "backups"
|
| 418 |
backup_dir.mkdir(exist_ok=True)
|
| 419 |
-
|
| 420 |
for file in [HISTORY_FILE, ANALYTICS_FILE]:
|
| 421 |
if file.exists():
|
| 422 |
backup_path = backup_dir / f"{file.name}.{timestamp}.bak"
|
|
@@ -424,14 +391,12 @@ class ChatStorage:
|
|
| 424 |
dst.write(src.read())
|
| 425 |
if not backup_path.exists():
|
| 426 |
raise Exception(f"Failed to create backup for {file.name}")
|
| 427 |
-
|
| 428 |
db_backup_path = backup_dir / f"chat_database.db.{timestamp}.bak"
|
| 429 |
with sqlite3.connect(self.db.db_path) as src_conn:
|
| 430 |
with sqlite3.connect(db_backup_path) as dst_conn:
|
| 431 |
src_conn.backup(dst_conn)
|
| 432 |
if not db_backup_path.exists():
|
| 433 |
raise Exception("Failed to create database backup")
|
| 434 |
-
|
| 435 |
return f"β
Backup created at {backup_dir}"
|
| 436 |
except Exception as e:
|
| 437 |
return f"β Backup failed: {str(e)}"
|
|
@@ -468,8 +433,6 @@ class ChatStorage:
|
|
| 468 |
print(f"β Error updating analytics from import: {e}")
|
| 469 |
|
| 470 |
class AdvancedSearchManager:
|
| 471 |
-
"""Manager untuk pencarian lanjutan"""
|
| 472 |
-
|
| 473 |
def __init__(self, storage: ChatStorage):
|
| 474 |
self.storage = storage
|
| 475 |
|
|
@@ -569,8 +532,6 @@ class AdvancedSearchManager:
|
|
| 569 |
raise Exception(f"Failed to get conversation thread: {str(e)}")
|
| 570 |
|
| 571 |
class SmartAnalyzer:
|
| 572 |
-
"""Advanced analytics untuk chat history dengan database"""
|
| 573 |
-
|
| 574 |
def __init__(self, storage: ChatStorage):
|
| 575 |
self.storage = storage
|
| 576 |
|
|
@@ -739,7 +700,7 @@ class SmartAnalyzer:
|
|
| 739 |
"avg_messages_per_session": len(all_messages) / len(set(msg.get("session_id", "default") for msg in all_messages)) if all_messages else 0
|
| 740 |
}
|
| 741 |
except Exception as e:
|
| 742 |
-
return {"error": f"Failed to get productivity insights: {str(e)}"
|
| 743 |
|
| 744 |
def _calculate_consistency(self, daily_activity: Dict) -> float:
|
| 745 |
try:
|
|
@@ -753,13 +714,12 @@ class SmartAnalyzer:
|
|
| 753 |
except Exception as e:
|
| 754 |
raise Exception(f"Failed to calculate consistency: {str(e)}")
|
| 755 |
|
| 756 |
-
# Initialize storage
|
| 757 |
storage = ChatStorage()
|
| 758 |
analyzer = SmartAnalyzer(storage)
|
| 759 |
search_manager = AdvancedSearchManager(storage)
|
| 760 |
|
| 761 |
def count_tokens_rough(text: str) -> int:
|
| 762 |
-
"""Estimasi token count"""
|
| 763 |
try:
|
| 764 |
return len(text) // 4
|
| 765 |
except Exception as e:
|
|
@@ -767,7 +727,6 @@ def count_tokens_rough(text: str) -> int:
|
|
| 767 |
return 0
|
| 768 |
|
| 769 |
def groq_with_memory(message: str, topic: str = "general", retries: int = 3) -> tuple:
|
| 770 |
-
"""Main chat function dengan Groq API"""
|
| 771 |
if not API_KEY:
|
| 772 |
return "β No API Key found. Please set GROQ_API_KEY environment variable.", ""
|
| 773 |
|
|
@@ -824,7 +783,7 @@ def groq_with_memory(message: str, topic: str = "general", retries: int = 3) ->
|
|
| 824 |
return f"β No response: {result}", ""
|
| 825 |
except HTTPError as e:
|
| 826 |
if response.status_code == 429 and attempt < retries - 1:
|
| 827 |
-
sleep(2 ** attempt)
|
| 828 |
continue
|
| 829 |
return f"β HTTP {response.status_code}: {response.text}", ""
|
| 830 |
|
|
@@ -834,7 +793,6 @@ def groq_with_memory(message: str, topic: str = "general", retries: int = 3) ->
|
|
| 834 |
return f"β Error: {str(e)}", ""
|
| 835 |
|
| 836 |
def cleanup_old_messages(days: int = 30) -> str:
|
| 837 |
-
"""Hapus pesan yang lebih lama dari jumlah hari tertentu"""
|
| 838 |
try:
|
| 839 |
cutoff = (datetime.now() - timedelta(days=days)).isoformat()
|
| 840 |
with sqlite3.connect(storage.db.db_path) as conn:
|
|
@@ -846,8 +804,7 @@ def cleanup_old_messages(days: int = 30) -> str:
|
|
| 846 |
except Exception as e:
|
| 847 |
return f"β Failed to clean old messages: {str(e)}"
|
| 848 |
|
| 849 |
-
#
|
| 850 |
-
|
| 851 |
def get_topics_list() -> List[str]:
|
| 852 |
"""Retrieve a list of unique topics from the database, ensuring 'journal' is included."""
|
| 853 |
try:
|
|
@@ -863,20 +820,22 @@ def get_topics_list() -> List[str]:
|
|
| 863 |
print(f"β Error retrieving topics: {str(e)}")
|
| 864 |
return ["All Topics", "journal"]
|
| 865 |
|
| 866 |
-
def send_message(user_input: str, topic_input: str) -> Tuple[str, str]:
|
| 867 |
-
"""Handle sending a user message
|
| 868 |
try:
|
| 869 |
if not user_input.strip():
|
| 870 |
-
return "β Please enter a message", user_input
|
| 871 |
if not topic_input.strip():
|
| 872 |
-
return "β Please enter a topic", user_input
|
| 873 |
|
| 874 |
response, error = groq_with_memory(user_input, topic_input)
|
| 875 |
if error:
|
| 876 |
-
return f"β {error}", user_input
|
| 877 |
-
|
|
|
|
|
|
|
| 878 |
except Exception as e:
|
| 879 |
-
return f"β Error processing message: {str(e)}", user_input
|
| 880 |
|
| 881 |
def show_current_context() -> str:
|
| 882 |
"""Show the current session's conversation context."""
|
|
@@ -1002,7 +961,7 @@ def export_data() -> str:
|
|
| 1002 |
except Exception as e:
|
| 1003 |
return f"β Error exporting data: {str(e)}"
|
| 1004 |
|
| 1005 |
-
# Define custom CSS
|
| 1006 |
custom_css = """
|
| 1007 |
/* Full-screen layout: remove padding and margins, use full width */
|
| 1008 |
.gradio-container {
|
|
@@ -1010,8 +969,8 @@ custom_css = """
|
|
| 1010 |
max-width: 100% !important;
|
| 1011 |
padding: 0 !important;
|
| 1012 |
margin: 0 !important;
|
| 1013 |
-
background-color: #000000 !important;
|
| 1014 |
-
color: #ffffff !important;
|
| 1015 |
}
|
| 1016 |
|
| 1017 |
/* Remove padding and ensure full-width components */
|
|
@@ -1020,14 +979,14 @@ custom_css = """
|
|
| 1020 |
margin: 0 !important;
|
| 1021 |
width: 100% !important;
|
| 1022 |
box-sizing: border-box !important;
|
| 1023 |
-
border: 1px solid #333333 !important;
|
| 1024 |
}
|
| 1025 |
|
| 1026 |
/* Textbox and input styling */
|
| 1027 |
.response-area, .history-display, .analytics-display {
|
| 1028 |
-
background-color: #1a1a1a !important;
|
| 1029 |
color: #ffffff !important;
|
| 1030 |
-
border-radius: 0 !important;
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
/* Input area */
|
|
@@ -1040,10 +999,10 @@ custom_css = """
|
|
| 1040 |
/* Font styling */
|
| 1041 |
* {
|
| 1042 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
| 1043 |
-
font-size: 16px !important;
|
| 1044 |
}
|
| 1045 |
|
| 1046 |
-
/* Button styling
|
| 1047 |
button {
|
| 1048 |
background-color: #333333 !important;
|
| 1049 |
color: #ffffff !important;
|
|
@@ -1066,11 +1025,6 @@ select {
|
|
| 1066 |
}
|
| 1067 |
"""
|
| 1068 |
|
| 1069 |
-
# Initialize storage and components
|
| 1070 |
-
storage = ChatStorage()
|
| 1071 |
-
analyzer = SmartAnalyzer(storage)
|
| 1072 |
-
search_manager = AdvancedSearchManager(storage)
|
| 1073 |
-
|
| 1074 |
# Main Gradio Interface
|
| 1075 |
with gr.Blocks(
|
| 1076 |
title="π€ AI Journal Chat with Analytics",
|
|
@@ -1189,7 +1143,7 @@ with gr.Blocks(
|
|
| 1189 |
send_btn.click(
|
| 1190 |
fn=send_message,
|
| 1191 |
inputs=[user_input, topic_input],
|
| 1192 |
-
outputs=[ai_response, user_input]
|
| 1193 |
)
|
| 1194 |
|
| 1195 |
clear_response_btn.click(
|
|
|
|
| 32 |
|
| 33 |
@dataclass
|
| 34 |
class ChatMessage:
|
|
|
|
| 35 |
role: str
|
| 36 |
content: str
|
| 37 |
topic: str
|
|
|
|
| 49 |
self.message_id = str(uuid.uuid4())
|
| 50 |
|
| 51 |
class DatabaseManager:
|
|
|
|
|
|
|
| 52 |
def __init__(self):
|
| 53 |
self.db_path = DATABASE_FILE
|
| 54 |
self.lock = threading.Lock()
|
| 55 |
self.init_database()
|
| 56 |
|
| 57 |
def init_database(self):
|
|
|
|
| 58 |
try:
|
| 59 |
with sqlite3.connect(self.db_path) as conn:
|
| 60 |
conn.execute('''
|
|
|
|
| 72 |
content_hash TEXT
|
| 73 |
)
|
| 74 |
''')
|
|
|
|
| 75 |
conn.execute('''
|
| 76 |
CREATE TABLE IF NOT EXISTS sessions (
|
| 77 |
session_id TEXT PRIMARY KEY,
|
|
|
|
| 81 |
message_count INTEGER DEFAULT 0
|
| 82 |
)
|
| 83 |
''')
|
|
|
|
| 84 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_content ON messages(content)')
|
| 85 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_topic ON messages(topic)')
|
| 86 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_timestamp ON messages(timestamp)')
|
| 87 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_session ON messages(session_id)')
|
| 88 |
conn.execute('CREATE INDEX IF NOT EXISTS idx_role ON messages(role)')
|
|
|
|
| 89 |
conn.commit()
|
| 90 |
except sqlite3.Error as e:
|
| 91 |
raise Exception(f"Failed to initialize database: {str(e)}")
|
| 92 |
|
| 93 |
def add_message(self, message: ChatMessage):
|
|
|
|
| 94 |
try:
|
| 95 |
with self.lock:
|
| 96 |
content_hash = hashlib.md5(message.content.encode('utf-8')).hexdigest()
|
|
|
|
| 104 |
message.timestamp, message.word_count, message.char_count,
|
| 105 |
message.session_id, json.dumps(message.tags), content_hash
|
| 106 |
))
|
|
|
|
| 107 |
conn.execute('''
|
| 108 |
INSERT OR REPLACE INTO sessions (session_id, title, created_at, last_activity, message_count)
|
| 109 |
VALUES (?, ?, ?, ?,
|
|
|
|
| 118 |
raise Exception(f"Failed to add message to database: {str(e)}")
|
| 119 |
|
| 120 |
def search_messages(self, query: str, filters: Dict = None) -> List[Dict]:
|
|
|
|
| 121 |
try:
|
| 122 |
with sqlite3.connect(self.db_path) as conn:
|
| 123 |
conn.row_factory = sqlite3.Row
|
|
|
|
| 124 |
base_query = '''
|
| 125 |
SELECT * FROM messages
|
| 126 |
WHERE content LIKE ? COLLATE NOCASE
|
| 127 |
'''
|
| 128 |
params = [f'%{query}%']
|
|
|
|
| 129 |
if filters:
|
| 130 |
if filters.get('topic'):
|
| 131 |
base_query += ' AND topic = ?'
|
|
|
|
| 142 |
if filters.get('date_to'):
|
| 143 |
base_query += ' AND timestamp <= ?'
|
| 144 |
params.append(filters['date_to'])
|
|
|
|
| 145 |
base_query += ' ORDER BY timestamp DESC LIMIT 100'
|
|
|
|
| 146 |
cursor = conn.execute(base_query, params)
|
| 147 |
results = []
|
| 148 |
for row in cursor.fetchall():
|
| 149 |
result = dict(row)
|
| 150 |
result['tags'] = json.loads(result['tags']) if result['tags'] else []
|
| 151 |
results.append(result)
|
|
|
|
| 152 |
return results
|
| 153 |
except sqlite3.Error as e:
|
| 154 |
raise Exception(f"Failed to search messages: {str(e)}")
|
| 155 |
|
| 156 |
def get_all_messages(self, session_id: str = None) -> List[Dict]:
|
|
|
|
| 157 |
try:
|
| 158 |
with sqlite3.connect(self.db_path) as conn:
|
| 159 |
conn.row_factory = sqlite3.Row
|
|
|
|
| 160 |
if session_id:
|
| 161 |
cursor = conn.execute(
|
| 162 |
'SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp',
|
|
|
|
| 164 |
)
|
| 165 |
else:
|
| 166 |
cursor = conn.execute('SELECT * FROM messages ORDER BY timestamp')
|
|
|
|
| 167 |
results = []
|
| 168 |
for row in cursor.fetchall():
|
| 169 |
result = dict(row)
|
| 170 |
result['tags'] = json.loads(result['tags']) if result['tags'] else []
|
| 171 |
results.append(result)
|
|
|
|
| 172 |
return results
|
| 173 |
except sqlite3.Error as e:
|
| 174 |
raise Exception(f"Failed to get messages: {str(e)}")
|
| 175 |
|
| 176 |
def get_sessions(self) -> List[Dict]:
|
|
|
|
| 177 |
try:
|
| 178 |
with sqlite3.connect(self.db_path) as conn:
|
| 179 |
conn.row_factory = sqlite3.Row
|
|
|
|
| 186 |
raise Exception(f"Failed to get sessions: {str(e)}")
|
| 187 |
|
| 188 |
def delete_session(self, session_id: str):
|
|
|
|
| 189 |
try:
|
| 190 |
with self.lock:
|
| 191 |
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
| 196 |
raise Exception(f"Failed to delete session: {str(e)}")
|
| 197 |
|
| 198 |
def get_statistics(self) -> Dict:
|
|
|
|
| 199 |
try:
|
| 200 |
with sqlite3.connect(self.db_path) as conn:
|
| 201 |
stats = {}
|
|
|
|
| 202 |
cursor = conn.execute('SELECT COUNT(*) FROM messages')
|
| 203 |
stats['total_messages'] = cursor.fetchone()[0]
|
|
|
|
| 204 |
cursor = conn.execute('SELECT role, COUNT(*) FROM messages GROUP BY role')
|
| 205 |
stats['by_role'] = dict(cursor.fetchall())
|
|
|
|
| 206 |
cursor = conn.execute('SELECT topic, COUNT(*) FROM messages GROUP BY topic ORDER BY COUNT(*) DESC LIMIT 10')
|
| 207 |
stats['by_topic'] = dict(cursor.fetchall())
|
|
|
|
| 208 |
cursor = conn.execute('''
|
| 209 |
SELECT DATE(timestamp) as date, COUNT(*)
|
| 210 |
FROM messages
|
|
|
|
| 212 |
ORDER BY date DESC LIMIT 30
|
| 213 |
''')
|
| 214 |
stats['by_date'] = dict(cursor.fetchall())
|
|
|
|
| 215 |
cursor = conn.execute('SELECT COUNT(*) FROM sessions')
|
| 216 |
stats['total_sessions'] = cursor.fetchone()[0]
|
|
|
|
| 217 |
return stats
|
| 218 |
except sqlite3.Error as e:
|
| 219 |
raise Exception(f"Failed to get statistics: {str(e)}")
|
| 220 |
|
| 221 |
class ChatStorage:
|
|
|
|
|
|
|
| 222 |
def __init__(self):
|
| 223 |
self.history_file = HISTORY_FILE
|
| 224 |
self.analytics_file = ANALYTICS_FILE
|
|
|
|
| 229 |
self.sync_to_database()
|
| 230 |
|
| 231 |
def sync_to_database(self):
|
|
|
|
| 232 |
try:
|
| 233 |
if self.chat_history and not self.db.get_all_messages():
|
| 234 |
print("π Syncing JSON data to database...")
|
|
|
|
| 287 |
print(f"β Error saving analytics: {e}")
|
| 288 |
|
| 289 |
def add_message(self, role: str, content: str, topic: str = "general", tags: List[str] = None):
|
|
|
|
| 290 |
if not content.strip():
|
| 291 |
raise ValueError("Content cannot be empty")
|
| 292 |
if len(topic) > 100:
|
|
|
|
| 384 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 385 |
backup_dir = STORAGE_DIR / "backups"
|
| 386 |
backup_dir.mkdir(exist_ok=True)
|
|
|
|
| 387 |
for file in [HISTORY_FILE, ANALYTICS_FILE]:
|
| 388 |
if file.exists():
|
| 389 |
backup_path = backup_dir / f"{file.name}.{timestamp}.bak"
|
|
|
|
| 391 |
dst.write(src.read())
|
| 392 |
if not backup_path.exists():
|
| 393 |
raise Exception(f"Failed to create backup for {file.name}")
|
|
|
|
| 394 |
db_backup_path = backup_dir / f"chat_database.db.{timestamp}.bak"
|
| 395 |
with sqlite3.connect(self.db.db_path) as src_conn:
|
| 396 |
with sqlite3.connect(db_backup_path) as dst_conn:
|
| 397 |
src_conn.backup(dst_conn)
|
| 398 |
if not db_backup_path.exists():
|
| 399 |
raise Exception("Failed to create database backup")
|
|
|
|
| 400 |
return f"β
Backup created at {backup_dir}"
|
| 401 |
except Exception as e:
|
| 402 |
return f"β Backup failed: {str(e)}"
|
|
|
|
| 433 |
print(f"β Error updating analytics from import: {e}")
|
| 434 |
|
| 435 |
class AdvancedSearchManager:
|
|
|
|
|
|
|
| 436 |
def __init__(self, storage: ChatStorage):
|
| 437 |
self.storage = storage
|
| 438 |
|
|
|
|
| 532 |
raise Exception(f"Failed to get conversation thread: {str(e)}")
|
| 533 |
|
| 534 |
class SmartAnalyzer:
|
|
|
|
|
|
|
| 535 |
def __init__(self, storage: ChatStorage):
|
| 536 |
self.storage = storage
|
| 537 |
|
|
|
|
| 700 |
"avg_messages_per_session": len(all_messages) / len(set(msg.get("session_id", "default") for msg in all_messages)) if all_messages else 0
|
| 701 |
}
|
| 702 |
except Exception as e:
|
| 703 |
+
return {"error": f"Failed to get productivity insights: {str(e)}")
|
| 704 |
|
| 705 |
def _calculate_consistency(self, daily_activity: Dict) -> float:
|
| 706 |
try:
|
|
|
|
| 714 |
except Exception as e:
|
| 715 |
raise Exception(f"Failed to calculate consistency: {str(e)}")
|
| 716 |
|
| 717 |
+
# Initialize storage and components
|
| 718 |
storage = ChatStorage()
|
| 719 |
analyzer = SmartAnalyzer(storage)
|
| 720 |
search_manager = AdvancedSearchManager(storage)
|
| 721 |
|
| 722 |
def count_tokens_rough(text: str) -> int:
|
|
|
|
| 723 |
try:
|
| 724 |
return len(text) // 4
|
| 725 |
except Exception as e:
|
|
|
|
| 727 |
return 0
|
| 728 |
|
| 729 |
def groq_with_memory(message: str, topic: str = "general", retries: int = 3) -> tuple:
|
|
|
|
| 730 |
if not API_KEY:
|
| 731 |
return "β No API Key found. Please set GROQ_API_KEY environment variable.", ""
|
| 732 |
|
|
|
|
| 783 |
return f"β No response: {result}", ""
|
| 784 |
except HTTPError as e:
|
| 785 |
if response.status_code == 429 and attempt < retries - 1:
|
| 786 |
+
sleep(2 ** attempt)
|
| 787 |
continue
|
| 788 |
return f"β HTTP {response.status_code}: {response.text}", ""
|
| 789 |
|
|
|
|
| 793 |
return f"β Error: {str(e)}", ""
|
| 794 |
|
| 795 |
def cleanup_old_messages(days: int = 30) -> str:
|
|
|
|
| 796 |
try:
|
| 797 |
cutoff = (datetime.now() - timedelta(days=days)).isoformat()
|
| 798 |
with sqlite3.connect(storage.db.db_path) as conn:
|
|
|
|
| 804 |
except Exception as e:
|
| 805 |
return f"β Failed to clean old messages: {str(e)}"
|
| 806 |
|
| 807 |
+
# Helper Functions
|
|
|
|
| 808 |
def get_topics_list() -> List[str]:
|
| 809 |
"""Retrieve a list of unique topics from the database, ensuring 'journal' is included."""
|
| 810 |
try:
|
|
|
|
| 820 |
print(f"β Error retrieving topics: {str(e)}")
|
| 821 |
return ["All Topics", "journal"]
|
| 822 |
|
| 823 |
+
def send_message(user_input: str, topic_input: str) -> Tuple[str, str, List[str]]:
|
| 824 |
+
"""Handle sending a user message, getting AI response, and updating topic list."""
|
| 825 |
try:
|
| 826 |
if not user_input.strip():
|
| 827 |
+
return "β Please enter a message", user_input, get_topics_list()
|
| 828 |
if not topic_input.strip():
|
| 829 |
+
return "β Please enter a topic", user_input, get_topics_list()
|
| 830 |
|
| 831 |
response, error = groq_with_memory(user_input, topic_input)
|
| 832 |
if error:
|
| 833 |
+
return f"β {error}", user_input, get_topics_list()
|
| 834 |
+
|
| 835 |
+
# Return updated topic list to refresh dropdown
|
| 836 |
+
return response, "", get_topics_list()
|
| 837 |
except Exception as e:
|
| 838 |
+
return f"β Error processing message: {str(e)}", user_input, get_topics_list()
|
| 839 |
|
| 840 |
def show_current_context() -> str:
|
| 841 |
"""Show the current session's conversation context."""
|
|
|
|
| 961 |
except Exception as e:
|
| 962 |
return f"β Error exporting data: {str(e)}"
|
| 963 |
|
| 964 |
+
# Define custom CSS for full-screen, readable font, and black background
|
| 965 |
custom_css = """
|
| 966 |
/* Full-screen layout: remove padding and margins, use full width */
|
| 967 |
.gradio-container {
|
|
|
|
| 969 |
max-width: 100% !important;
|
| 970 |
padding: 0 !important;
|
| 971 |
margin: 0 !important;
|
| 972 |
+
background-color: #000000 !important;
|
| 973 |
+
color: #ffffff !important;
|
| 974 |
}
|
| 975 |
|
| 976 |
/* Remove padding and ensure full-width components */
|
|
|
|
| 979 |
margin: 0 !important;
|
| 980 |
width: 100% !important;
|
| 981 |
box-sizing: border-box !important;
|
| 982 |
+
border: 1px solid #333333 !important;
|
| 983 |
}
|
| 984 |
|
| 985 |
/* Textbox and input styling */
|
| 986 |
.response-area, .history-display, .analytics-display {
|
| 987 |
+
background-color: #1a1a1a !important;
|
| 988 |
color: #ffffff !important;
|
| 989 |
+
border-radius: 0 !important;
|
| 990 |
}
|
| 991 |
|
| 992 |
/* Input area */
|
|
|
|
| 999 |
/* Font styling */
|
| 1000 |
* {
|
| 1001 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
| 1002 |
+
font-size: 16px !important;
|
| 1003 |
}
|
| 1004 |
|
| 1005 |
+
/* Button styling */
|
| 1006 |
button {
|
| 1007 |
background-color: #333333 !important;
|
| 1008 |
color: #ffffff !important;
|
|
|
|
| 1025 |
}
|
| 1026 |
"""
|
| 1027 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1028 |
# Main Gradio Interface
|
| 1029 |
with gr.Blocks(
|
| 1030 |
title="π€ AI Journal Chat with Analytics",
|
|
|
|
| 1143 |
send_btn.click(
|
| 1144 |
fn=send_message,
|
| 1145 |
inputs=[user_input, topic_input],
|
| 1146 |
+
outputs=[ai_response, user_input, topic_filter]
|
| 1147 |
)
|
| 1148 |
|
| 1149 |
clear_response_btn.click(
|