adikwok commited on
Commit
88d91ee
Β·
verified Β·
1 Parent(s): c34e3ae

Update app.py

Browse files

chat. journal AI

Files changed (1) hide show
  1. app.py +447 -178
app.py CHANGED
@@ -2,221 +2,477 @@ import gradio as gr
2
  import requests
3
  import os
4
  import json
5
- from typing import List, Dict
6
- from datetime import datetime
 
 
 
7
 
8
  # Groq API Configuration
9
  API_URL = "https://api.groq.com/openai/v1/chat/completions"
10
  API_KEY = os.getenv("GROQ_API_KEY")
11
 
12
- # In-memory chat history storage
13
- chat_history: List[Dict[str, str]] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  def count_tokens_rough(text: str) -> int:
16
- """Rough token estimation (1 token β‰ˆ 4 chars)"""
17
  return len(text) // 4
18
 
19
  def groq_with_memory(message: str, topic: str = "general") -> tuple:
20
- """Groq API call with smart context management"""
21
-
22
  if not API_KEY:
23
- return "❌ No API Key found", ""
24
-
25
  if not message.strip():
26
  return "❌ Empty message", ""
27
-
28
  try:
29
  headers = {
30
  "Authorization": f"Bearer {API_KEY}",
31
  "Content-Type": "application/json"
32
  }
33
-
34
- # Add current message to history
35
- chat_history.append({
36
- "role": "user",
37
- "content": message.strip(),
38
- "topic": topic,
39
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M")
40
- })
41
-
42
- # Smart context management - keep within ~6000 tokens (24k chars)
43
  messages = []
44
  total_chars = 0
45
- max_chars = 24000 # Safe limit for gemma2-9b-it
46
-
47
- # Add messages from most recent, working backwards
48
- for msg in reversed(chat_history):
49
  msg_content = f"{msg['content']}"
50
  msg_chars = len(msg_content)
51
-
52
  if total_chars + msg_chars < max_chars:
53
  messages.insert(0, {"role": msg["role"], "content": msg["content"]})
54
  total_chars += msg_chars
55
  else:
56
  break
57
-
58
- # Ensure we have at least the current message
59
  if not messages:
60
  messages = [{"role": "user", "content": message.strip()}]
61
-
62
  payload = {
63
  "model": "gemma2-9b-it",
64
  "messages": messages,
65
- "max_tokens": 2000, # Conservative for safety
66
  "temperature": 0.7
67
  }
68
-
69
  response = requests.post(API_URL, headers=headers, json=payload, timeout=30)
70
-
71
  if response.status_code == 200:
72
  result = response.json()
73
  if "choices" in result and result["choices"]:
74
  response_content = result["choices"][0]["message"]["content"]
75
- chat_history.append({
76
- "role": "assistant",
77
- "content": response_content,
78
- "topic": topic,
79
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M")
80
- })
81
- return response_content, "" # Clear input after send
82
  return f"❌ No response: {result}", ""
83
  return f"❌ HTTP {response.status_code}: {response.text}", ""
84
-
85
  except Exception as e:
86
  return f"❌ Error: {str(e)}", ""
87
 
88
  def get_chat_summary(topic_filter: str = None) -> str:
89
- """Generate conversation summary"""
90
- if not chat_history:
91
  return "❌ No chat history to summarize"
92
-
93
- filtered = [msg for msg in chat_history if not topic_filter or msg.get("topic") == topic_filter]
94
-
95
  if not filtered:
96
  return f"❌ No messages found for topic: {topic_filter}"
97
-
98
- # Group by topic
99
  topics = {}
100
  for msg in filtered:
101
  topic = msg.get("topic", "general")
102
  if topic not in topics:
103
  topics[topic] = []
104
  topics[topic].append(msg)
105
-
106
  summary = "πŸ“‹ **Chat Summary**\n\n"
107
  for topic, messages in topics.items():
108
  user_msgs = [m for m in messages if m["role"] == "user"]
109
  ai_msgs = [m for m in messages if m["role"] == "assistant"]
110
-
111
  summary += f"**🏷️ Topic: {topic}**\n"
112
  summary += f"- Messages: {len(user_msgs)} user, {len(ai_msgs)} AI\n"
113
- summary += f"- Time span: {messages[0]['timestamp']} - {messages[-1]['timestamp']}\n"
114
-
115
  if user_msgs:
116
  summary += f"- Key topics discussed: {', '.join(msg['content'][:50] + '...' for msg in user_msgs[:3])}\n"
117
  summary += "\n"
118
-
119
  return summary
120
 
121
  def get_full_history(topic_filter: str = None) -> str:
122
- """Get full chat history with optional topic filter"""
123
- if not chat_history:
124
  return "❌ No chat history available"
125
-
126
- filtered = [msg for msg in chat_history if not topic_filter or msg.get("topic") == topic_filter]
127
-
128
  if not filtered:
129
  return f"❌ No messages found for topic: {topic_filter}"
130
-
131
  history_text = f"πŸ“š **Chat History** ({len(filtered)} messages)\n\n"
132
-
133
  current_topic = None
134
  for msg in filtered:
135
- # Add topic header when topic changes
136
  if msg.get("topic") != current_topic:
137
  current_topic = msg.get("topic")
138
  history_text += f"\n**🏷️ Topic: {current_topic}**\n"
139
  history_text += "---\n"
140
-
141
  role_icon = "πŸ‘€" if msg["role"] == "user" else "πŸ€–"
142
- history_text += f"{role_icon} **{msg['timestamp']}**\n"
 
143
  history_text += f"{msg['content']}\n\n"
144
-
145
  return history_text
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def clear_all_history():
148
- """Clear all chat history"""
149
- global chat_history
150
- chat_history.clear()
151
- return "βœ… All chat history cleared", "", ""
152
 
153
  def get_topics_list() -> List[str]:
154
- """Get list of unique topics"""
155
- topics = list(set(msg.get("topic", "general") for msg in chat_history))
156
  return ["All Topics"] + sorted(topics)
157
 
158
- # Custom CSS for better UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  custom_css = """
160
  .gradio-container {
161
  max-width: 100% !important;
162
  padding: 0 !important;
163
  margin: 0 !important;
164
  }
165
-
166
  .main {
167
  max-width: 100% !important;
168
  padding: 10px !important;
169
  }
170
-
171
- /* Better font for readability */
172
  * {
173
  font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif !important;
174
  }
175
-
176
- /* AI Response text spacing */
177
  .response-area textarea {
178
  line-height: 1.7 !important;
179
  padding: 20px !important;
180
  font-size: 15px !important;
181
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
182
  }
183
-
184
- /* History text spacing */
185
  .history-display textarea {
186
  line-height: 1.7 !important;
187
  padding: 20px !important;
188
  font-size: 15px !important;
189
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
190
  }
191
-
192
- /* Input text styling */
193
  .input-area textarea {
194
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
195
  font-size: 15px !important;
196
  line-height: 1.6 !important;
197
  }
198
-
199
- /* Better text readability everywhere */
200
- textarea, input {
201
- line-height: 1.6 !important;
202
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
203
  }
204
  """
205
 
206
  # Main Gradio Interface
207
  with gr.Blocks(
208
- title="πŸ€– AI Journal Chat",
209
  theme=gr.themes.Soft(),
210
  css=custom_css
211
  ) as demo:
212
 
213
  gr.Markdown("# πŸ“ AI Journal Chat Interface")
214
- gr.Markdown("*Write, chat, and keep track of your thoughts with AI assistance*")
215
 
216
  with gr.Tabs() as tabs:
217
- # MAIN CHAT TAB
218
  with gr.Tab("πŸ’¬ Chat"):
219
- # AI Response Area (Top)
220
  ai_response = gr.Textbox(
221
  label="πŸ€– AI Response",
222
  lines=12,
@@ -226,8 +482,6 @@ with gr.Blocks(
226
  show_copy_button=True,
227
  elem_classes="response-area"
228
  )
229
-
230
- # Input Area (Bottom)
231
  with gr.Group():
232
  with gr.Row():
233
  user_input = gr.Textbox(
@@ -235,7 +489,8 @@ with gr.Blocks(
235
  placeholder="Type your thoughts, questions, or journal entry here...",
236
  lines=4,
237
  max_lines=10,
238
- scale=3
 
239
  )
240
  with gr.Column(scale=1):
241
  topic_input = gr.Textbox(
@@ -244,28 +499,24 @@ with gr.Blocks(
244
  placeholder="e.g., work, personal, ideas"
245
  )
246
  send_btn = gr.Button("πŸ“€ Send", variant="primary", size="lg")
247
-
248
  with gr.Row():
249
  clear_response_btn = gr.Button("πŸ—‘οΈ Clear Response", variant="secondary")
250
  show_context_btn = gr.Button("πŸ“‹ Show Current Context", variant="secondary")
251
 
252
- # HISTORY TAB
253
  with gr.Tab("πŸ“š Chat History"):
254
  with gr.Group():
255
  with gr.Row():
256
  topic_filter = gr.Dropdown(
257
  label="πŸ” Filter by Topic",
258
- choices=["All Topics"],
259
  value="All Topics",
260
  interactive=True
261
  )
262
  refresh_topics_btn = gr.Button("πŸ”„ Refresh Topics", variant="secondary")
263
-
264
  with gr.Row():
265
  show_history_btn = gr.Button("πŸ“– Show Full History", variant="primary")
266
  show_summary_btn = gr.Button("πŸ“‹ Show Summary", variant="secondary")
267
  clear_history_btn = gr.Button("πŸ—‘οΈ Clear All History", variant="stop")
268
-
269
  history_display = gr.Textbox(
270
  label="πŸ“š History & Summary",
271
  lines=20,
@@ -275,109 +526,127 @@ with gr.Blocks(
275
  placeholder="Chat history and summaries will appear here...",
276
  elem_classes="history-display"
277
  )
278
-
279
- # Event Handlers
280
- def send_message(message, topic):
281
- response, cleared_input = groq_with_memory(message, topic)
282
- return response, cleared_input
283
-
284
- def show_current_context():
285
- """Show current conversation context that AI can see"""
286
- if not chat_history:
287
- return "❌ No conversation context yet"
288
 
289
- # Show actual context that will be sent to AI
290
- messages = []
291
- total_chars = 0
292
- max_chars = 24000
293
-
294
- for msg in reversed(chat_history):
295
- msg_chars = len(msg['content'])
296
- if total_chars + msg_chars < max_chars:
297
- messages.insert(0, msg)
298
- total_chars += msg_chars
299
- else:
300
- break
301
-
302
- context_text = f"🧠 **Current AI Context** ({len(messages)} messages, ~{total_chars:,} chars)\n\n"
303
-
304
- for msg in messages:
305
- role_icon = "πŸ‘€" if msg["role"] == "user" else "πŸ€–"
306
- context_text += f"{role_icon} **{msg['timestamp']}** [{msg.get('topic', 'general')}]\n"
307
- context_text += f"{msg['content'][:150]}{'...' if len(msg['content']) > 150 else ''}\n\n"
 
 
 
 
 
 
308
 
309
- context_text += f"\nπŸ’‘ *AI remembers {len(messages)} messages (~{total_chars:,} characters)*"
310
- context_text += f"\nπŸ”’ *Context limit: {max_chars:,} characters*"
311
- return context_text
312
-
313
- def refresh_topic_choices():
314
- return gr.Dropdown(choices=get_topics_list())
315
-
316
- def clear_only_response():
317
- return ""
318
-
319
- def filter_and_show_history(topic_filter):
320
- filter_topic = None if topic_filter == "All Topics" else topic_filter
321
- return get_full_history(filter_topic)
322
-
323
- def filter_and_show_summary(topic_filter):
324
- filter_topic = None if topic_filter == "All Topics" else topic_filter
325
- return get_chat_summary(filter_topic)
 
 
 
 
326
 
327
- # Button Events
328
  send_btn.click(
329
- send_message,
330
- inputs=[user_input, topic_input],
331
- outputs=[ai_response, user_input]
332
- )
333
-
334
- user_input.submit(
335
- send_message,
336
  inputs=[user_input, topic_input],
337
  outputs=[ai_response, user_input]
338
  )
339
 
340
  clear_response_btn.click(
341
- clear_only_response,
342
- outputs=[ai_response]
 
343
  )
344
 
345
  show_context_btn.click(
346
- show_current_context,
347
- outputs=[ai_response]
 
 
 
 
 
 
 
348
  )
349
 
350
  refresh_topics_btn.click(
351
- refresh_topic_choices,
352
- outputs=[topic_filter]
 
353
  )
354
 
355
  show_history_btn.click(
356
- filter_and_show_history,
357
- inputs=[topic_filter],
358
- outputs=[history_display]
359
  )
360
 
361
  show_summary_btn.click(
362
- filter_and_show_summary,
363
- inputs=[topic_filter],
364
- outputs=[history_display]
365
  )
366
 
367
  clear_history_btn.click(
368
- clear_all_history,
369
- outputs=[ai_response, user_input, history_display]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  )
371
 
372
- # Launch configuration
373
- print("πŸš€ Starting AI Journal Chat Interface...")
374
- print(f"🌐 Access at: http://localhost:7860")
375
- print(f"πŸ”‘ API Key: {'βœ… Found' if API_KEY else '❌ Missing'}")
376
-
377
- if __name__ == "__main__":
378
- demo.launch(
379
- server_name="0.0.0.0",
380
- server_port=7860,
381
- show_error=True,
382
- share=False
383
- )
 
2
  import requests
3
  import os
4
  import json
5
+ from typing import List, Dict, Optional, Tuple
6
+ from datetime import datetime, timedelta
7
+ from collections import Counter, defaultdict
8
+ import re
9
+ from pathlib import Path
10
 
11
  # Groq API Configuration
12
  API_URL = "https://api.groq.com/openai/v1/chat/completions"
13
  API_KEY = os.getenv("GROQ_API_KEY")
14
 
15
+ # Storage Configuration
16
+ STORAGE_DIR = Path("chat_storage")
17
+ STORAGE_DIR.mkdir(exist_ok=True)
18
+ HISTORY_FILE = STORAGE_DIR / "chat_history.json"
19
+ ANALYTICS_FILE = STORAGE_DIR / "analytics.json"
20
+
21
+ class ChatStorage:
22
+ """Handles persistent JSON storage for chat history"""
23
+
24
+ def __init__(self):
25
+ self.history_file = HISTORY_FILE
26
+ self.analytics_file = ANALYTICS_FILE
27
+ self.chat_history = self.load_history()
28
+ self.analytics = self.load_analytics()
29
+
30
+ def load_history(self) -> List[Dict]:
31
+ try:
32
+ if self.history_file.exists():
33
+ with open(self.history_file, ' Retired: 'r', encoding='utf-8') as f:
34
+ data = json.load(f)
35
+ print(f"πŸ“š Loaded {len(data)} messages from storage")
36
+ return data
37
+ except Exception as e:
38
+ print(f"❌ Error loading history: {e}")
39
+ return []
40
+
41
+ def save_history(self):
42
+ try:
43
+ with open(self.history_file, 'w', encoding='utf-8') as f:
44
+ json.dump(self.chat_history, f, indent=2, ensure_ascii=False)
45
+ print(f"πŸ’Ύ Saved {len(self.chat_history)} messages to storage")
46
+ except Exception as e:
47
+ print(f"❌ Error saving history: {e}")
48
+
49
+ def load_analytics(self) -> Dict:
50
+ try:
51
+ if self.analytics_file.exists():
52
+ with open(self.analytics_file, 'r', encoding='utf-8') as f:
53
+ return json.load(f)
54
+ except Exception as e:
55
+ print(f"❌ Error loading analytics: {e}")
56
+ return {}
57
+
58
+ def save_analytics(self):
59
+ try:
60
+ with open(self.analytics_file, 'w', encoding='utf-8') as f:
61
+ json.dump(self.analytics, f, indent=2, ensure_ascii=False)
62
+ except Exception as e:
63
+ print(f"❌ Error saving analytics: {e}")
64
+
65
+ def add_message(self, role: str, content: str, topic: str = "general"):
66
+ message = {
67
+ "role": role,
68
+ "content": content,
69
+ "topic": topic,
70
+ "timestamp": datetime.now().isoformat(),
71
+ "word_count": len(content.split()),
72
+ "char_count": len(content)
73
+ }
74
+ self.chat_history.append(message)
75
+ self.save_history()
76
+ self.update_analytics(message)
77
+
78
+ def update_analytics(self, message: Dict):
79
+ today = datetime.now().strftime("%Y-%m-%d")
80
+ if "daily_stats" not in self.analytics:
81
+ self.analytics["daily_stats"] = {}
82
+ if today not in self.analytics["daily_stats"]:
83
+ self.analytics["daily_stats"][today] = {
84
+ "messages": 0,
85
+ "words": 0,
86
+ "chars": 0,
87
+ "topics": set()
88
+ }
89
+ stats = self.analytics["daily_stats"][today]
90
+ stats["messages"] += 1
91
+ stats["words"] += message["word_count"]
92
+ stats["chars"] += message["char_count"]
93
+ stats["topics"].add(message["topic"])
94
+ stats["topics"] = list(stats["topics"])
95
+ self.save_analytics()
96
+
97
+ def clear_history(self):
98
+ self.chat_history.clear()
99
+ self.analytics.clear()
100
+ self.save_history()
101
+ self.save_analytics()
102
+
103
+ def create_backup(self):
104
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
105
+ backup_dir = STORAGE_DIR / "backups"
106
+ backup_dir.mkdir(exist_ok=True)
107
+ for file in [HISTORY_FILE, ANALYTICS_FILE]:
108
+ backup_path = backup_dir / f"{file.name}.{timestamp}.bak"
109
+ with open(file, 'r') as src, open(backup_path, 'w') as dst:
110
+ dst.write(src.read())
111
+ return f"βœ… Backup created at {backup_dir}"
112
+
113
+ class SmartAnalyzer:
114
+ """Advanced analytics for chat history"""
115
+
116
+ def __init__(self, storage: ChatStorage):
117
+ self.storage = storage
118
+
119
+ def get_conversation_patterns(self) -> Dict:
120
+ if not self.storage.chat_history:
121
+ return {"error": "No data available"}
122
+ topics = [msg.get("topic", "general") for msg in self.storage.chat_history]
123
+ topic_counts = Counter(topics)
124
+ timestamps = [datetime.fromisoformat(msg["timestamp"]) for msg in self.storage.chat_history]
125
+ hour_counts = Counter([ts.hour for ts in timestamps])
126
+ day_counts = Counter([ts.strftime("%A") for ts in timestamps])
127
+ all_words = []
128
+ for msg in self.storage.chat_history:
129
+ if msg["role"] == "user":
130
+ words = re.findall(r'\b\w+\b', msg["content"].lower())
131
+ all_words.extend(words)
132
+ common_words = Counter(all_words).most_common(10)
133
+ user_messages = [msg for msg in self.storage.chat_history if msg["role"] == "user"]
134
+ avg_words = sum(msg["word_count"] for msg in user_messages) / len(user_messages) if user_messages else 0
135
+ return {
136
+ "total_messages": len(self.storage.chat_history),
137
+ "user_messages": len(user_messages),
138
+ "topics": dict(topic_counts),
139
+ "peak_hours": dict(sorted(hour_counts.items())),
140
+ "peak_days": dict(day_counts),
141
+ "common_words": common_words,
142
+ "avg_words_per_message": round(avg_words, 1),
143
+ "date_range": {
144
+ "start": min(timestamps).strftime("%Y-%m-%d") if timestamps else None,
145
+ "end": max(timestamps).strftime("%Y-%m-%d") if timestamps else None
146
+ }
147
+ }
148
+
149
+ def get_mood_trends(self) -> Dict:
150
+ if not self.storage.chat_history:
151
+ return {"error": "No data available"}
152
+ user_messages = [msg for msg in self.storage.chat_history if msg["role"] == "user"]
153
+ positive_words = {'good', 'great', 'happy', 'excited', 'amazing', 'wonderful', 'excellent', 'love', 'like', 'enjoy'}
154
+ negative_words = {'bad', 'sad', 'angry', 'frustrated', 'terrible', 'awful', 'hate', 'dislike', 'worried', 'stress'}
155
+ question_words = {'what', 'how', 'why', 'when', 'where', 'who', 'which'}
156
+ sentiments = []
157
+ for msg in user_messages:
158
+ words = set(re.findall(r'\b\w+\b', msg["content"].lower()))
159
+ pos_score = len(words & positive_words)
160
+ neg_score = len(words & negative_words)
161
+ question_score = len(words & question_words)
162
+ if pos_score > neg_score:
163
+ sentiment = "positive"
164
+ elif neg_score > pos_score:
165
+ sentiment = "negative"
166
+ elif question_score > 0:
167
+ sentiment = "curious"
168
+ else:
169
+ sentiment = "neutral"
170
+ sentiments.append({
171
+ "date": datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d"),
172
+ "sentiment": sentiment,
173
+ "topic": msg.get("topic", "general")
174
+ })
175
+ daily_moods = defaultdict(list)
176
+ for item in sentiments:
177
+ daily_moods[item["date"]].append(item["sentiment"])
178
+ mood_summary = {}
179
+ for date, moods in daily_moods.items():
180
+ mood_counter = Counter(moods)
181
+ dominant_mood = mood_counter.most_common(1)[0][0]
182
+ mood_summary[date] = {
183
+ "dominant": dominant_mood,
184
+ "distribution": dict(mood_counter)
185
+ }
186
+ return {
187
+ "daily_moods": mood_summary,
188
+ "overall_sentiment": Counter([s["sentiment"] for s in sentiments]),
189
+ "sentiment_by_topic": self._group_sentiment_by_topic(sentiments)
190
+ }
191
+
192
+ def _group_sentiment_by_topic(self, sentiments: List[Dict]) -> Dict:
193
+ topic_sentiments = defaultdict(list)
194
+ for item in sentiments:
195
+ topic_sentiments[item["topic"]].append(item["sentiment"])
196
+ result = {}
197
+ for topic, sent_list in topic_sentiments.items():
198
+ result[topic] = dict(Counter(sent_list))
199
+ return result
200
+
201
+ def get_productivity_insights(self) -> Dict:
202
+ if not self.storage.chat_history:
203
+ return {"error": "No data available"}
204
+ daily_activity = defaultdict(int)
205
+ for msg in self.storage.chat_history:
206
+ if msg["role"] == "user":
207
+ date = datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d")
208
+ daily_activity[date] += 1
209
+ weekly_activity = defaultdict(int)
210
+ for msg in self.storage.chat_history:
211
+ if msg["role"] == "user":
212
+ day = datetime.fromisoformat(msg["timestamp"]).strftime("%A")
213
+ weekly_activity[day] += 1
214
+ topic_timeline = []
215
+ for msg in self.storage.chat_history:
216
+ if msg["role"] == "user":
217
+ topic_timeline.append({
218
+ "date": datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d"),
219
+ "topic": msg.get("topic", "general")
220
+ })
221
+ recent_cutoff = datetime.now() - timedelta(days=7)
222
+ recent_messages = [
223
+ msg for msg in self.storage.chat_history
224
+ if datetime.fromisoformat(msg["timestamp"]) > recent_cutoff and msg["role"] == "user"
225
+ ]
226
+ return {
227
+ "daily_activity": dict(sorted(daily_activity.items())),
228
+ "weekly_patterns": dict(weekly_activity),
229
+ "most_active_day": max(daily_activity.items(), key=lambda x: x[1]) if daily_activity else None,
230
+ "recent_activity": len(recent_messages),
231
+ "topic_evolution": topic_timeline[-10:],
232
+ "consistency_score": self._calculate_consistency(daily_activity)
233
+ }
234
+
235
+ def _calculate_consistency(self, daily_activity: Dict) -> float:
236
+ if len(daily_activity) < 2:
237
+ return 0.0
238
+ values = list(daily_activity.values())
239
+ avg = sum(values) / len(values)
240
+ variance = sum((x - avg) ** 2 for x in values) / len(values)
241
+ consistency = max(0, 100 - (variance / avg * 10)) if avg > 0 else 0
242
+ return round(consistency, 1)
243
+
244
+ # Initialize storage and analyzer
245
+ storage = ChatStorage()
246
+ analyzer = SmartAnalyzer(storage)
247
 
248
  def count_tokens_rough(text: str) -> int:
 
249
  return len(text) // 4
250
 
251
  def groq_with_memory(message: str, topic: str = "general") -> tuple:
 
 
252
  if not API_KEY:
253
+ return "❌ No API Key found. Please set GROQ_API_KEY environment variable.", ""
 
254
  if not message.strip():
255
  return "❌ Empty message", ""
 
256
  try:
257
  headers = {
258
  "Authorization": f"Bearer {API_KEY}",
259
  "Content-Type": "application/json"
260
  }
261
+ storage.add_message("user", message.strip(), topic)
 
 
 
 
 
 
 
 
 
262
  messages = []
263
  total_chars = 0
264
+ max_chars = 24000
265
+ for msg in reversed(storage.chat_history):
 
 
266
  msg_content = f"{msg['content']}"
267
  msg_chars = len(msg_content)
 
268
  if total_chars + msg_chars < max_chars:
269
  messages.insert(0, {"role": msg["role"], "content": msg["content"]})
270
  total_chars += msg_chars
271
  else:
272
  break
 
 
273
  if not messages:
274
  messages = [{"role": "user", "content": message.strip()}]
 
275
  payload = {
276
  "model": "gemma2-9b-it",
277
  "messages": messages,
278
+ "max_tokens": 2000,
279
  "temperature": 0.7
280
  }
 
281
  response = requests.post(API_URL, headers=headers, json=payload, timeout=30)
 
282
  if response.status_code == 200:
283
  result = response.json()
284
  if "choices" in result and result["choices"]:
285
  response_content = result["choices"][0]["message"]["content"]
286
+ storage.add_message("assistant", response_content, topic)
287
+ return response_content, ""
 
 
 
 
 
288
  return f"❌ No response: {result}", ""
289
  return f"❌ HTTP {response.status_code}: {response.text}", ""
 
290
  except Exception as e:
291
  return f"❌ Error: {str(e)}", ""
292
 
293
  def get_chat_summary(topic_filter: str = None) -> str:
294
+ if not storage.chat_history:
 
295
  return "❌ No chat history to summarize"
296
+ filtered = [msg for msg in storage.chat_history if not topic_filter or msg.get("topic") == topic_filter]
 
 
297
  if not filtered:
298
  return f"❌ No messages found for topic: {topic_filter}"
 
 
299
  topics = {}
300
  for msg in filtered:
301
  topic = msg.get("topic", "general")
302
  if topic not in topics:
303
  topics[topic] = []
304
  topics[topic].append(msg)
 
305
  summary = "πŸ“‹ **Chat Summary**\n\n"
306
  for topic, messages in topics.items():
307
  user_msgs = [m for m in messages if m["role"] == "user"]
308
  ai_msgs = [m for m in messages if m["role"] == "assistant"]
 
309
  summary += f"**🏷️ Topic: {topic}**\n"
310
  summary += f"- Messages: {len(user_msgs)} user, {len(ai_msgs)} AI\n"
311
+ summary += f"- Time span: {messages[0]['timestamp'][:10]} - {messages[-1]['timestamp'][:10]}\n"
312
+ summary += f"- Total words: {sum(m['word_count'] for m in user_msgs):,}\n"
313
  if user_msgs:
314
  summary += f"- Key topics discussed: {', '.join(msg['content'][:50] + '...' for msg in user_msgs[:3])}\n"
315
  summary += "\n"
 
316
  return summary
317
 
318
  def get_full_history(topic_filter: str = None) -> str:
319
+ if not storage.chat_history:
 
320
  return "❌ No chat history available"
321
+ filtered = [msg for msg in storage.chat_history if not topic_filter or msg.get("topic") == topic_filter]
 
 
322
  if not filtered:
323
  return f"❌ No messages found for topic: {topic_filter}"
 
324
  history_text = f"πŸ“š **Chat History** ({len(filtered)} messages)\n\n"
 
325
  current_topic = None
326
  for msg in filtered:
 
327
  if msg.get("topic") != current_topic:
328
  current_topic = msg.get("topic")
329
  history_text += f"\n**🏷️ Topic: {current_topic}**\n"
330
  history_text += "---\n"
 
331
  role_icon = "πŸ‘€" if msg["role"] == "user" else "πŸ€–"
332
+ timestamp = msg['timestamp'][:19].replace('T', ' ')
333
+ history_text += f"{role_icon} **{timestamp}** ({msg['word_count']} words)\n"
334
  history_text += f"{msg['content']}\n\n"
 
335
  return history_text
336
 
337
+ def get_analytics_report() -> str:
338
+ patterns = analyzer.get_conversation_patterns()
339
+ moods = analyzer.get_mood_trends()
340
+ productivity = analyzer.get_productivity_insights()
341
+ if "error" in patterns:
342
+ return "❌ No data available for analytics"
343
+ report = "πŸ“Š **Smart Analytics Report**\n\n"
344
+ report += "## πŸ“ˆ Overview\n"
345
+ report += f"- **Total Messages**: {patterns['total_messages']:,}\n"
346
+ report += f"- **Your Messages**: {patterns['user_messages']:,}\n"
347
+ report += f"- **Average Words per Message**: {patterns['avg_words_per_message']}\n"
348
+ report += f"- **Date Range**: {patterns['date_range']['start']} to {patterns['date_range']['end']}\n\n"
349
+ report += "## 🏷️ Topic Distribution\n"
350
+ for topic, count in sorted(patterns['topics'].items(), key=lambda x: x[1], reverse=True):
351
+ percentage = (count / patterns['total_messages']) * 100
352
+ report += f"- **{topic}**: {count} messages ({percentage:.1f}%)\n"
353
+ report += "\n"
354
+ report += "## ⏰ Activity Patterns\n"
355
+ if productivity['most_active_day']:
356
+ report += f"- **Most Active Day**: {productivity['most_active_day'][0]} ({productivity['most_active_day'][1]} messages)\n"
357
+ peak_hours = sorted(patterns['peak_hours'].items(), key=lambda x: x[1], reverse=True)[:3]
358
+ report += f"- **Peak Hours**: {', '.join(f'{h}:00 ({c} msgs)' for h, c in peak_hours)}\n"
359
+ report += "- **Weekly Activity**:\n"
360
+ for day, count in patterns['peak_days'].items():
361
+ report += f" - {day}: {count} messages\n"
362
+ report += f"- **Consistency Score**: {productivity['consistency_score']}/100\n"
363
+ report += f"- **Recent Activity (7 days)**: {productivity['recent_activity']} messages\n\n"
364
+ if "error" not in moods:
365
+ report += "## 😊 Mood & Sentiment Analysis\n"
366
+ overall_sentiment = moods['overall_sentiment']
367
+ total_analyzed = sum(overall_sentiment.values())
368
+ for sentiment, count in overall_sentiment.most_common():
369
+ percentage = (count / total_analyzed) * 100
370
+ emoji = {"positive": "😊", "negative": "πŸ˜”", "curious": "πŸ€”", "neutral": "😐"}.get(sentiment, "❓")
371
+ report += f"- **{sentiment.title()}** {emoji}: {count} messages ({percentage:.1f}%)\n"
372
+ report += "\n**Sentiment by Topic**:\n"
373
+ for topic, sentiments in moods['sentiment_by_topic'].items():
374
+ report += f"- **{topic}**: "
375
+ sentiment_strs = [f"{sent}({count})" for sent, count in sentiments.items()]
376
+ report += ", ".join(sentiment_strs) + "\n"
377
+ report += "\n"
378
+ report += "## πŸ”€ Most Common Words\n"
379
+ for word, count in patterns['common_words']:
380
+ report += f"- **{word}**: {count} times\n"
381
+ report += "\n"
382
+ if productivity['topic_evolution']:
383
+ report += "## πŸ“ˆ Recent Topic Evolution\n"
384
+ for entry in productivity['topic_evolution'][-5:]:
385
+ report += f"- {entry['date']}: {entry['topic']}\n"
386
+ return report
387
+
388
+ def export_data() -> str:
389
+ export_data = {
390
+ "chat_history": storage.chat_history,
391
+ "analytics": storage.analytics,
392
+ "export_timestamp": datetime.now().isoformat(),
393
+ "total_messages": len(storage.chat_history)
394
+ }
395
+ return json.dumps(export_data, indent=2, ensure_ascii=False)
396
+
397
  def clear_all_history():
398
+ storage.clear_history()
399
+ return "βœ… All chat history and analytics cleared", "", ""
 
 
400
 
401
  def get_topics_list() -> List[str]:
402
+ topics = list(set(msg.get("topic", "general") for msg in storage.chat_history))
 
403
  return ["All Topics"] + sorted(topics)
404
 
405
+ def show_current_context():
406
+ if not storage.chat_history:
407
+ return "❌ No conversation context yet"
408
+ messages = []
409
+ total_chars = 0
410
+ max_chars = 24000
411
+ for msg in reversed(storage.chat_history):
412
+ msg_chars = len(msg['content'])
413
+ if total_chars + msg_chars < max_chars:
414
+ messages.insert(0, msg)
415
+ total_chars += msg_chars
416
+ else:
417
+ break
418
+ context_text = f"🧠 **Current AI Context** ({len(messages)} messages, ~{total_chars:,} chars)\n\n"
419
+ for msg in messages:
420
+ role_icon = "πŸ‘€" if msg["role"] == "user" else "πŸ€–"
421
+ timestamp = msg['timestamp'][:19].replace('T', ' ')
422
+ context_text += f"{role_icon} **{timestamp}** [{msg.get('topic', 'general')}] ({msg['word_count']} words)\n"
423
+ context_text += f"{msg['content'][:150]}{'...' if len(msg['content']) > 150 else ''}\n\n"
424
+ context_text += f"\nπŸ’‘ *AI remembers {len(messages)} messages (~{total_chars:,} characters)*"
425
+ context_text += f"\nπŸ”’ *Context limit: {max_chars:,} characters*"
426
+ context_text += f"\nπŸ’Ύ *Total stored: {len(storage.chat_history)} messages*"
427
+ return context_text
428
+
429
+ # Custom CSS for better UI
430
  custom_css = """
431
  .gradio-container {
432
  max-width: 100% !important;
433
  padding: 0 !important;
434
  margin: 0 !important;
435
  }
 
436
  .main {
437
  max-width: 100% !important;
438
  padding: 10px !important;
439
  }
 
 
440
  * {
441
  font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif !important;
442
  }
 
 
443
  .response-area textarea {
444
  line-height: 1.7 !important;
445
  padding: 20px !important;
446
  font-size: 15px !important;
 
447
  }
 
 
448
  .history-display textarea {
449
  line-height: 1.7 !important;
450
  padding: 20px !important;
451
  font-size: 15px !important;
 
452
  }
 
 
453
  .input-area textarea {
 
454
  font-size: 15px !important;
455
  line-height: 1.6 !important;
456
  }
457
+ .analytics-display textarea {
458
+ line-height: 1.8 !important;
459
+ padding: 20px !important;
460
+ font-size: 14px !important;
 
461
  }
462
  """
463
 
464
  # Main Gradio Interface
465
  with gr.Blocks(
466
+ title="πŸ€– AI Journal Chat with Analytics",
467
  theme=gr.themes.Soft(),
468
  css=custom_css
469
  ) as demo:
470
 
471
  gr.Markdown("# πŸ“ AI Journal Chat Interface")
472
+ gr.Markdown("*Write, chat, and analyze your thoughts with AI assistance + persistent storage*")
473
 
474
  with gr.Tabs() as tabs:
 
475
  with gr.Tab("πŸ’¬ Chat"):
 
476
  ai_response = gr.Textbox(
477
  label="πŸ€– AI Response",
478
  lines=12,
 
482
  show_copy_button=True,
483
  elem_classes="response-area"
484
  )
 
 
485
  with gr.Group():
486
  with gr.Row():
487
  user_input = gr.Textbox(
 
489
  placeholder="Type your thoughts, questions, or journal entry here...",
490
  lines=4,
491
  max_lines=10,
492
+ scale=3,
493
+ elem_classes="input-area"
494
  )
495
  with gr.Column(scale=1):
496
  topic_input = gr.Textbox(
 
499
  placeholder="e.g., work, personal, ideas"
500
  )
501
  send_btn = gr.Button("πŸ“€ Send", variant="primary", size="lg")
 
502
  with gr.Row():
503
  clear_response_btn = gr.Button("πŸ—‘οΈ Clear Response", variant="secondary")
504
  show_context_btn = gr.Button("πŸ“‹ Show Current Context", variant="secondary")
505
 
 
506
  with gr.Tab("πŸ“š Chat History"):
507
  with gr.Group():
508
  with gr.Row():
509
  topic_filter = gr.Dropdown(
510
  label="πŸ” Filter by Topic",
511
+ choices=get_topics_list(),
512
  value="All Topics",
513
  interactive=True
514
  )
515
  refresh_topics_btn = gr.Button("πŸ”„ Refresh Topics", variant="secondary")
 
516
  with gr.Row():
517
  show_history_btn = gr.Button("πŸ“– Show Full History", variant="primary")
518
  show_summary_btn = gr.Button("πŸ“‹ Show Summary", variant="secondary")
519
  clear_history_btn = gr.Button("πŸ—‘οΈ Clear All History", variant="stop")
 
520
  history_display = gr.Textbox(
521
  label="πŸ“š History & Summary",
522
  lines=20,
 
526
  placeholder="Chat history and summaries will appear here...",
527
  elem_classes="history-display"
528
  )
 
 
 
 
 
 
 
 
 
 
529
 
530
+ with gr.Tab("πŸ“Š Smart Analytics"):
531
+ with gr.Group():
532
+ gr.Markdown("### πŸ” Advanced Analysis of Your Conversations")
533
+ with gr.Row():
534
+ analytics_btn = gr.Button("πŸ“Š Generate Analytics Report", variant="primary", size="lg")
535
+ export_btn = gr.Button("πŸ’Ύ Export All Data", variant="secondary")
536
+ analytics_display = gr.Textbox(
537
+ label="πŸ“Š Analytics Report",
538
+ lines=25,
539
+ max_lines=40,
540
+ interactive=False,
541
+ show_copy_button=True,
542
+ placeholder="Analytics report will appear here...",
543
+ elem_classes="analytics-display"
544
+ )
545
+ gr.Markdown("""
546
+ **Analytics Features:**
547
+ - πŸ“ˆ Conversation patterns and trends
548
+ - 🏷️ Topic distribution analysis
549
+ - ⏰ Activity patterns (daily/weekly/hourly)
550
+ - 😊 Mood and sentiment analysis
551
+ - πŸ”€ Most common words and phrases
552
+ - πŸ“… Historical trends and consistency
553
+ - 🎯 Productivity insights
554
+ """)
555
 
556
+ with gr.Tab("βš™οΈ Data Management"):
557
+ with gr.Group():
558
+ gr.Markdown("### πŸ’Ύ Persistent Storage Information")
559
+ storage_info = gr.Textbox(
560
+ label="πŸ“‚ Storage Status",
561
+ value=f"πŸ’Ύ Storage Location: {STORAGE_DIR.absolute()}\nπŸ“ History File: {HISTORY_FILE.name}\nπŸ“Š Analytics File: {ANALYTICS_FILE.name}\nπŸ“ˆ Messages Loaded: {len(storage.chat_history)}",
562
+ lines=4,
563
+ interactive=False
564
+ )
565
+ with gr.Row():
566
+ refresh_storage_btn = gr.Button("πŸ”„ Refresh Storage Info", variant="secondary")
567
+ backup_btn = gr.Button("πŸ“¦ Create Backup", variant="secondary")
568
+ export_display = gr.Textbox(
569
+ label="πŸ“€ Export Data (JSON)",
570
+ lines=15,
571
+ max_lines=25,
572
+ interactive=False,
573
+ show_copy_button=True,
574
+ placeholder="Exported data will appear here...",
575
+ elem_classes="analytics-display"
576
+ )
577
 
578
+ # Event Handlers
579
  send_btn.click(
580
+ fn=send_message,
 
 
 
 
 
 
581
  inputs=[user_input, topic_input],
582
  outputs=[ai_response, user_input]
583
  )
584
 
585
  clear_response_btn.click(
586
+ fn=lambda: "",
587
+ inputs=None,
588
+ outputs=ai_response
589
  )
590
 
591
  show_context_btn.click(
592
+ fn=show_current_context,
593
+ inputs=None,
594
+ outputs=ai_response
595
+ )
596
+
597
+ topic_filter.change(
598
+ fn=lambda x: x if x != "All Topics" else None,
599
+ inputs=topic_filter,
600
+ outputs=topic_filter
601
  )
602
 
603
  refresh_topics_btn.click(
604
+ fn=get_topics_list,
605
+ inputs=None,
606
+ outputs=topic_filter
607
  )
608
 
609
  show_history_btn.click(
610
+ fn=get_full_history,
611
+ inputs=topic_filter,
612
+ outputs=history_display
613
  )
614
 
615
  show_summary_btn.click(
616
+ fn=get_chat_summary,
617
+ inputs=topic_filter,
618
+ outputs=history_display
619
  )
620
 
621
  clear_history_btn.click(
622
+ fn=clear_all_history,
623
+ inputs=None,
624
+ outputs=[history_display, analytics_display, export_display]
625
+ )
626
+
627
+ analytics_btn.click(
628
+ fn=get_analytics_report,
629
+ inputs=None,
630
+ outputs=analytics_display
631
+ )
632
+
633
+ export_btn.click(
634
+ fn=export_data,
635
+ inputs=None,
636
+ outputs=export_display
637
+ )
638
+
639
+ refresh_storage_btn.click(
640
+ fn=lambda: f"πŸ’Ύ Storage Location: {STORAGE_DIR.absolute()}\nπŸ“ History File: {HISTORY_FILE.name}\nπŸ“Š Analytics File: {ANALYTICS_FILE.name}\nπŸ“ˆ Messages Loaded: {len(storage.chat_history)}",
641
+ inputs=None,
642
+ outputs=storage_info
643
+ )
644
+
645
+ backup_btn.click(
646
+ fn=storage.create_backup,
647
+ inputs=None,
648
+ outputs=storage_info
649
  )
650
 
651
+ # Launch the interface
652
+ demo.launch()