RoAr777 commited on
Commit
272c4a3
·
verified ·
1 Parent(s): df95e93

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +464 -0
app.py ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ['TZ'] = 'Asia/Kolkata'
3
+ import time
4
+ import functools
5
+ from langchain.schema import HumanMessage
6
+ from langchain_google_genai import ChatGoogleGenerativeAI
7
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
8
+ from langchain_core.prompts import ChatPromptTemplate
9
+ from langchain_core.runnables.history import RunnableWithMessageHistory
10
+ from langchain_core.tools import tool
11
+ import gradio as gr
12
+ from io import BytesIO
13
+ import json
14
+ from datetime import date, datetime
15
+ import random
16
+ import pandas as pd
17
+ from typing import List
18
+ from langchain_core.chat_history import BaseChatMessageHistory
19
+ from langchain_core.messages import BaseMessage
20
+ from langchain_core.pydantic_v1 import BaseModel, Field
21
+ from PIL import Image
22
+ import base64
23
+ from gradio import ChatMessage
24
+
25
+ os.environ['TZ'] = 'Asia/Kolkata'
26
+
27
+ print(time.strftime('%X %x %Z'))
28
+
29
+ # ---------------------- Sticky Pad Persistence Functions ---------------------- #
30
+ def load_sticky_pad(username: str) -> str:
31
+ """Load sticky pad content from the Sticky Notes directory."""
32
+ directory = "Sticky Notes"
33
+ if not os.path.exists(directory):
34
+ os.makedirs(directory)
35
+ filepath = os.path.join(directory, f"{username}-sticky.txt")
36
+ if os.path.exists(filepath):
37
+ with open(filepath, "r", encoding="utf-8") as f:
38
+ return f.read()
39
+ return ""
40
+
41
+ def save_sticky_pad(username: str, content: str) -> None:
42
+ """Save sticky pad content to the Sticky Notes directory."""
43
+ directory = "Sticky Notes"
44
+ if not os.path.exists(directory):
45
+ os.makedirs(directory)
46
+ filepath = os.path.join(directory, f"{username}-sticky.txt")
47
+ with open(filepath, "w", encoding="utf-8") as f:
48
+ f.write(content)
49
+ # ---------------------------------------------------------------------------- #
50
+
51
+ def load_schedules():
52
+ with open('schedules.json', 'r') as f:
53
+ return json.load(f)
54
+
55
+ def save_schedules(schedules):
56
+ with open('schedules.json', 'w') as f:
57
+ json.dump(schedules, f, indent=4)
58
+
59
+ def cache_with_timeout(timeout: int):
60
+ def decorator(func):
61
+ cache = {}
62
+ @functools.wraps(func)
63
+ def wrapper(*args):
64
+ if args in cache:
65
+ result, timestamp = cache[args]
66
+ if time.time() - timestamp < timeout:
67
+ return result
68
+ result = func(*args)
69
+ cache[args] = (result, time.time())
70
+ return result
71
+ return wrapper
72
+ return decorator
73
+
74
+ class InMemoryHistory(BaseChatMessageHistory, BaseModel):
75
+ messages: List[BaseMessage] = Field(default_factory=list)
76
+ def add_messages(self, messages: List[BaseMessage]) -> None:
77
+ self.messages.extend(messages)
78
+ def clear(self) -> None:
79
+ self.messages = []
80
+
81
+ def encode_image_to_base64(image_path):
82
+ image = Image.open(image_path)
83
+ buffered = BytesIO()
84
+ image.save(buffered, format=image.format)
85
+ img_bytes = buffered.getvalue()
86
+ return base64.b64encode(img_bytes).decode("utf-8")
87
+
88
+ def parse(history):
89
+ p = '\n'
90
+ if history == []:
91
+ return "No Chat till now"
92
+ for i in history:
93
+ try:
94
+ p += i['role'] + ': ' + i['content'] + '\n'
95
+ except:
96
+ pass
97
+ return p
98
+
99
+ def decode_image(encoded_image):
100
+ image_data = base64.b64decode(encoded_image)
101
+ image = Image.open(BytesIO(image_data))
102
+ global i
103
+ filename = f"temp{i}.jpeg"
104
+ i += 1
105
+ image.save(filename)
106
+ return filename
107
+
108
+ i = 0 # Global counter for image filenames
109
+
110
+ # Detailed persona descriptions
111
+ PERSONA_MAP = {
112
+ "Creative Muse": "A warm, imaginative persona that encourages creative exploration and inspiring writing.",
113
+ "Literary Critic": "A refined and insightful persona offering deep literary analysis and constructive feedback.",
114
+ "Storyteller": "A narrative-driven persona that weaves engaging, immersive stories with a touch of humor.",
115
+ "Academic Advisor": "A formal and knowledgeable persona that provides scholarly insights and structured guidance."
116
+ }
117
+
118
+ # A list of inspirational prompt examples (25 examples)
119
+ INSPIRATIONAL_PROMPTS = [
120
+ "Write about a hidden garden that only appears at dusk.",
121
+ "Imagine a world where dreams dictate reality.",
122
+ "Describe a city that floats in the sky.",
123
+ "Craft a story around a mysterious, timeless letter.",
124
+ "Write a poem about the sound of rain on a tin roof.",
125
+ "Imagine a dialogue between two ancient trees.",
126
+ "Describe a sunset as if seen through an artist’s eyes.",
127
+ "Craft a narrative about a forgotten melody.",
128
+ "Write about an unexpected friendship in an unlikely place.",
129
+ "Imagine a secret door in an ordinary room.",
130
+ "Describe the journey of a single, determined raindrop.",
131
+ "Write a story that begins with a chance encounter.",
132
+ "Imagine a future where art and science merge seamlessly.",
133
+ "Craft a tale inspired by the patterns of the stars.",
134
+ "Describe the magic hidden in everyday moments.",
135
+ "Write a narrative about a silent revolution of ideas.",
136
+ "Imagine a world where time flows backward.",
137
+ "Craft a poem celebrating the beauty of imperfections.",
138
+ "Describe a character who finds solace in solitude.",
139
+ "Write about a moment when everything suddenly made sense.",
140
+ "Imagine a landscape painted by the emotions of its inhabitants.",
141
+ "Craft a narrative that blurs the line between reality and fantasy.",
142
+ "Describe a long-forgotten legend in a modern setting.",
143
+ "Write a story inspired by the interplay of light and shadow.",
144
+ "Imagine a conversation with your future self."
145
+ ]
146
+
147
+ class User:
148
+ def __init__(self, username: str, title: str, persona: str):
149
+ self.username = username
150
+ self.session_title = title
151
+ self.persona = persona
152
+ self.persona_description = PERSONA_MAP.get(persona, "A creative writing assistant.")
153
+ # Store the last assistant response for the sticky pad.
154
+ self.last_response = ""
155
+
156
+ # Two Gemini 1.5 pro instances with tailored parameters:
157
+ self.llm_main = ChatGoogleGenerativeAI(
158
+ model="gemini-2.0-flash",
159
+ temperature=1,
160
+ max_tokens=1500,
161
+ timeout=None,
162
+ max_retries=5,
163
+ google_api_key=os.getenv('API_KEY')
164
+ )
165
+ self.llm_inquiry = ChatGoogleGenerativeAI(
166
+ model="gemini-2.0-flash",
167
+ temperature=0.8,
168
+ max_tokens=1024,
169
+ timeout=None,
170
+ max_retries=5,
171
+ google_api_key=os.getenv('API_KEY')
172
+ )
173
+
174
+ self.template = f"""
175
+ You are a Vision Enabled Creative Writing Assistant named 'CreativeMind' with the persona: {self.persona} - {self.persona_description}.
176
+ Your role is to provide human-like, engaging, and insightful creative writing advice, literary analysis, and writing prompts.
177
+ You utilize multiple instances of Carefully Prompt Engineered LLMs: one for general conversation and tool orchestration, and a dedicated one for creative inquiries.
178
+ Call the necessary TOOLS as required.
179
+ "ALWAYS call the `add_reminder` tool for EVERY reminder request."
180
+ You can:
181
+ 1. Schedule a review session (tool provided)
182
+ 2. Answer creative writing inquiries and provide literary analysis (tool provided)
183
+ 3. Provide inspiring writing prompts and ideas.
184
+ 4. Set reminders for creative tasks using the reminder tool.
185
+
186
+ Engage warmly, include emojis, and provide detailed explanations.
187
+ Current Date (For scheduling ONLY, if no date is mentioned assume Today): {date.today()}
188
+ Name of the User: {self.username}
189
+ """
190
+ self.prompt = ChatPromptTemplate.from_messages(
191
+ [
192
+ ("system", self.template),
193
+ ("placeholder", "{chat_history}"),
194
+ ("placeholder", "{input}"),
195
+ ("placeholder", "{agent_scratchpad}"),
196
+ ]
197
+ )
198
+ self.store = {}
199
+
200
+ @tool
201
+ def creative_inquiry(question: str) -> str:
202
+ """Answers creative writing queries and generates inspiring writing prompts using the dedicated Gemini 1.5 pro instance."""
203
+ p=self.llm_inquiry.invoke(self.PI_prompt.format(question), config=self.config)
204
+ return p.content
205
+
206
+ @tool
207
+ def add_reminder(time: str, name: str) -> str:
208
+ """
209
+ Adds a reminder for the given time and name.
210
+ Parameters:
211
+ - time: in format '%Y-%m-%d %H:%M'.
212
+ - name: Name of the creative task or event.
213
+ """
214
+ reminders = self.load_reminders()
215
+ reminder_entry = {"time": time, "name": name}
216
+ reminders.append(reminder_entry)
217
+ self.save_reminders(reminders)
218
+ return f"Reminder set for '{name}' at {time}."
219
+
220
+ @tool
221
+ def schedule_review(query: str) -> str:
222
+ """Schedules a creative review session.
223
+ Parameter: A single string in the format `%Y-%m-%d %H:%M` ONLY"""
224
+ schedules = load_schedules()
225
+ query = query.replace("`", '')
226
+ combined_time_str = datetime.strptime(query, "%Y-%m-%d %H:%M")
227
+ if schedules.get(str(combined_time_str), "") == "":
228
+ schedules[str(combined_time_str)] = self.username
229
+ save_schedules(schedules)
230
+ return f"Review session scheduled successfully for {self.username} at {combined_time_str}."
231
+ else:
232
+ return "The preferred time slot is unavailable. Please choose another time."
233
+
234
+ self.PI_prompt = '''Context:
235
+
236
+ You are a creative writing assistant. When given a literary query or a request for a writing prompt, provide thoughtful, inspiring, and creative responses.
237
+
238
+ Example Query:
239
+ "Can you suggest a writing prompt involving a mysterious lighthouse?"
240
+
241
+ AI-powered Response:
242
+ "Imagine a weather-beaten lighthouse standing alone on a rocky shore, its beacon a relic of forgotten times. Write about a stormy night when the light flickers mysteriously, revealing secrets hidden beneath the crashing waves."
243
+
244
+ User Query:
245
+ {}
246
+
247
+ Note: Focus on creativity, literary flair, and thoughtful insights.
248
+ '''
249
+ # Create the agent using the main instance.
250
+ self.agent = create_tool_calling_agent(self.llm_main, [schedule_review, creative_inquiry, add_reminder], self.prompt)
251
+ self.agent_executor = RunnableWithMessageHistory(
252
+ AgentExecutor(agent=self.agent, tools=[schedule_review, creative_inquiry, add_reminder], verbose=True),
253
+ self.get_by_session_id,
254
+ input_messages_key="input",
255
+ history_messages_key="chat_history",
256
+ )
257
+ self.config = {"configurable": {"session_id": self.username + "-" + self.session_title}}
258
+
259
+ def get_by_session_id(self, session_id: str) -> BaseChatMessageHistory:
260
+ if session_id not in self.store:
261
+ self.store[session_id] = InMemoryHistory()
262
+ return self.store[session_id]
263
+
264
+ def load_reminders(self):
265
+ try:
266
+ with open(f'reminders/{self.username}-reminders.json', 'r') as f:
267
+ return json.load(f)
268
+ except FileNotFoundError:
269
+ return []
270
+
271
+ def save_reminders(self, reminders):
272
+ with open(f'reminders/{self.username}-reminders.json', 'w') as f:
273
+ json.dump(reminders, f, indent=4)
274
+
275
+ def save_conversation_history(self, history_data):
276
+ if not os.path.exists(f'conv/{self.username}-conversation_history.json'):
277
+ with open(f'conv/{self.username}-conversation_history.json', 'w') as f:
278
+ json.dump({}, f)
279
+ with open(f'conv/{self.username}-conversation_history.json', 'w') as f:
280
+ json.dump(history_data, f, indent=4)
281
+
282
+ def save_conversation(self, title, user_input, ai_response, images=None):
283
+ history_data = self.load_conversation_history()
284
+ conversation_entry = [{"role": "user", "content": user_input}]
285
+ if images:
286
+ for img in images:
287
+ encoded_image = encode_image_to_base64(img)
288
+ conversation_entry.append({"role": "user", "content": encoded_image, "type": "image"})
289
+ conversation_entry.append({"role": "assistant", "content": ai_response, "persona": self.persona})
290
+ if title in history_data:
291
+ history_data[title].extend(conversation_entry)
292
+ else:
293
+ history_data[title] = conversation_entry
294
+ self.save_conversation_history(history_data)
295
+
296
+ def load_conversation_history(self):
297
+ if not os.path.exists(f'conv/{self.username}-conversation_history.json'):
298
+ with open(f'conv/{self.username}-conversation_history.json', 'w') as f:
299
+ json.dump({}, f)
300
+ with open(f'conv/{self.username}-conversation_history.json', 'r') as f:
301
+ return json.load(f)
302
+
303
+ def update_conversation_history(self, session_id, message_data):
304
+ conversation_history = self.load_conversation_history()
305
+ if session_id not in conversation_history:
306
+ conversation_history[session_id] = []
307
+ conversation_history[session_id].append(message_data)
308
+ with open(f'conv/{self.username}-conversation_history.json', 'w') as f:
309
+ json.dump(conversation_history, f, indent=4)
310
+
311
+ def system_message_reminder(self):
312
+ reminders = self.load_reminders()
313
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M')
314
+ for reminder in reminders:
315
+ if reminder['time'] == current_time:
316
+ print("TIME UP!!")
317
+ message = HumanMessage(content=[{"type": "text", "text": f"System: {reminder['time']} reached! Time for your creative task: {reminder['name']} 🎨"}])
318
+ result = self.agent_executor.invoke({"input": [message]}, config=self.config)
319
+ reminders.remove(reminder)
320
+ self.save_reminders(reminders)
321
+ response = result['output']
322
+ gr.Info(response, duration=30)
323
+
324
+ def load_selected_conversation(self, title):
325
+ history_data = self.load_conversation_history()
326
+ print(f"Title type: {type(title)}, Title: {title}, {history_data}")
327
+ return history_data.get(title, [])
328
+
329
+ def save_ai_response(self, response):
330
+ if not os.path.exists("responses"):
331
+ os.makedirs("responses")
332
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
333
+ filename = f"responses/{self.username}-{self.session_title}-{timestamp}.txt"
334
+ with open(filename, "w",encoding="utf-8") as f:
335
+ f.write(response)
336
+
337
+ def chatbot_response(self, history, query):
338
+ extra_text = ""
339
+ if query.get('files'):
340
+ image_data = []
341
+ for x in range(len(query["files"])):
342
+ image = encode_image_to_base64(query['files'][x])
343
+ image_data += [HumanMessage(
344
+ content=[{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image}"}}]
345
+ )]
346
+ image_data += [HumanMessage(
347
+ content=[{"type": "text", "text": "Invoke the necessary tools for the query: " + query['text'] + extra_text}]
348
+ )]
349
+ result = self.agent_executor.invoke({"input": image_data}, config=self.config)
350
+ self.save_conversation(self.session_title, query['text'], result['output'], images=query['files'])
351
+ else:
352
+ message = HumanMessage(
353
+ content=[{"type": "text", "text": "Invoke the necessary tools for the query: " + query['text'] + extra_text}]
354
+ )
355
+ result = self.agent_executor.invoke({"input": [message]}, config=self.config)
356
+ self.save_conversation(self.session_title, query['text'], result['output'])
357
+ response = result['output']
358
+ self.last_response = response # Store last response for the sticky pad.
359
+ self.save_ai_response(response)
360
+ return response
361
+
362
+ # ------------------ Modified Sticky Pad Function ------------------ #
363
+ def add_to_sticky(state, sticky_text):
364
+ if state and hasattr(state[0], "last_response"):
365
+ new_text = sticky_text + "\n" + state[0].last_response
366
+ else:
367
+ new_text = sticky_text
368
+ # Save the updated sticky pad content to file
369
+ save_sticky_pad(state[0].username, new_text)
370
+ return new_text
371
+ # ------------------------------------------------------------------ #
372
+
373
+ # Function to update inspirational prompt; updates every 10 seconds.
374
+ def update_inspiration():
375
+ prompt = random.choice(INSPIRATIONAL_PROMPTS)
376
+ return prompt
377
+
378
+ # Gradio Interface
379
+
380
+ with gr.Blocks(theme=gr.themes.Soft(secondary_hue="green"),
381
+ css="footer {visibility: hidden;} #login {display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; border: 1px solid #ccc; border-radius: 5px; background-color: #f0f0f0; width: 300px; margin: 15vh auto;}.message-row.svelte-1x5p6hu img{margin:0px !important;}.avatar-container.svelte-1x5p6hu:not(.thumbnail-item) img{padding: 0px !important;}") as app:
382
+ # Login block with persona selection.
383
+ with gr.Column(visible=True, min_width=400, elem_id="login") as input_block:
384
+ gr.Markdown("# Login Page")
385
+ with gr.Row():
386
+ name_input = gr.Textbox(label="Name")
387
+ with gr.Row():
388
+ session_title_input = gr.Textbox(label="Session Title")
389
+ with gr.Row():
390
+ persona_dropdown = gr.Dropdown(choices=list(PERSONA_MAP.keys()),
391
+ label="Select Persona",
392
+ value="Creative Muse",
393
+ interactive=True)
394
+ with gr.Row():
395
+ submit_button = gr.Button("Submit")
396
+
397
+ with gr.Column(visible=False) as output_container:
398
+ gr.Markdown("# 🌟 CreativeMind ✍️")
399
+ gr.Markdown("### Your *Personalized* Creative Writing Companion")
400
+ state = gr.State([])
401
+ rem = gr.Timer(15)
402
+ rem.tick(lambda state: state[0].system_message_reminder() if state else None, inputs=state, outputs=None, trigger_mode='once')
403
+ history_dropdown = gr.Dropdown()
404
+
405
+ # Arrange the main conversation area in two columns:
406
+ with gr.Row():
407
+ # Left Column: Chatbot and Query Input
408
+ with gr.Column(scale=3):
409
+ chatbot = gr.Chatbot(type="messages", avatar_images=("user.jpeg", "CreativeBuddy.jpg"), bubble_full_width=True)
410
+ query_input = gr.MultimodalTextbox(interactive=True,
411
+ placeholder="Enter message or upload file...", show_label=False)
412
+ query_input.submit(lambda state, chat, prompt: chatbot_interface(state, chat, prompt),
413
+ inputs=[state, chatbot, query_input],
414
+ outputs=[chatbot, query_input])
415
+ # Right Column: Sticky Pad (top) and Inspirational Prompt (bottom)
416
+ with gr.Column(scale=1):
417
+ sticky_pad = gr.Textbox(label="Sticky Pad (Your Saved Inspirations)", lines=10, interactive=True, value="")
418
+ add_sticky_btn = gr.Button("Add Last Response to Sticky Pad")
419
+ inspiration_label = gr.Label(value="Your inspirational prompt will appear here...", show_label=True)
420
+ insp_timer = gr.Timer(10)
421
+ insp_timer.tick(fn=update_inspiration, outputs=inspiration_label)
422
+ add_sticky_btn.click(fn=add_to_sticky, inputs=[state, sticky_pad], outputs=sticky_pad)
423
+
424
+ def update_chatbot_with_history(state, chatbot, selected_title):
425
+ print(selected_title)
426
+ conversation = state[0].load_selected_conversation(selected_title)
427
+ chatbot_list = []
428
+ for message in conversation:
429
+ if message.get("type") == "image":
430
+ f = decode_image(message['content'])
431
+ chatbot_list.append(ChatMessage(role=message['role'], content={"path": f, "mime_type": "image/png"}))
432
+ else:
433
+ tooltip_text = message.get("persona", "") if message.get("role") == "assistant" else ""
434
+ chatbot_list.append(ChatMessage(role=message['role'], content=message['content']))
435
+ return chatbot_list
436
+
437
+ history_dropdown.change(fn=update_chatbot_with_history,
438
+ inputs=[state, chatbot, history_dropdown],
439
+ outputs=chatbot)
440
+
441
+ def chatbot_interface(state, messages, prompt):
442
+ response = state[0].chatbot_response(messages, prompt)
443
+ for x in prompt["files"]:
444
+ messages.append(ChatMessage(role="user", content={"path": x, "mime_type": "image/png"}))
445
+ if prompt["text"] is not None:
446
+ messages.append(ChatMessage(role="user", content=prompt['text']))
447
+ messages.append(ChatMessage(role="assistant", content=response))
448
+ return messages, gr.MultimodalTextbox(value=None, interactive=True)
449
+
450
+ # ----------------- Modified Login Function ----------------- #
451
+ submit_button.click(
452
+ fn=lambda name, title, persona, chatbot, state: (
453
+ gr.Dropdown(choices=[title] + list(User(name, title, persona).load_conversation_history().keys()),
454
+ label="Select Conversation to Load", allow_custom_value=True, value=title, interactive=True),
455
+ state + [User(name, title, persona)],
456
+ gr.update(visible=False, elem_id=""),
457
+ gr.update(visible=True),
458
+ load_sticky_pad(name) # Load saved sticky pad content for the user
459
+ ),
460
+ inputs=[name_input, session_title_input, persona_dropdown, chatbot, state],
461
+ outputs=[history_dropdown, state, input_block, output_container, sticky_pad]
462
+ )
463
+
464
+ app.launch()