GundeRichardson commited on
Commit
eb548e5
β€’
1 Parent(s): 727c286

requirements.txt

Browse files

streamlit>=1.24.0
openai>=1.0.0
requests>=2.31.0
beautifulsoup4>=4.12.0
python-dotenv>=1.0.0
markdown>=3.4.3
tiktoken>=0.5.1
Pillow>=10.0.0
numpy>=1.24.0
Crawl4AI==0.3.72

Files changed (1) hide show
  1. app.py +858 -0
app.py ADDED
@@ -0,0 +1,858 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from openai import OpenAI
3
+ import streamlit as st
4
+ from datetime import datetime
5
+ import json
6
+ import time
7
+ import tiktoken
8
+ from crawl4ai import WebCrawler
9
+ import base64
10
+ import re
11
+ import os
12
+ import json
13
+ import requests
14
+ from PIL import Image
15
+ from io import BytesIO
16
+
17
+ class ChatbotConfig:
18
+ def __init__(self):
19
+ self.DEFAULT_MODEL = "nvidia/llama-3.1-nemotron-70b-instruct"
20
+ self.MAX_TOKENS = 128000 # Maximum context window
21
+ self.BATCH_SIZE = 4000 # Tokens per batch
22
+ self.TEMPERATURE_RANGES = {
23
+ 'Conservative': 0.2,
24
+ 'Balanced': 0.4,
25
+ 'Creative': 0.6
26
+ }
27
+ self.PERSONA_PROMPTS = {
28
+ 'General Assistant': (
29
+ "I am your friendly and versatile assistant, ready to provide clear and actionable support across a variety of topics. "
30
+ "I can help you with: \n"
31
+ "β€’ Answering general questions in an informative and concise manner\n"
32
+ "β€’ Offering practical tips and resources for day-to-day tasks\n"
33
+ "β€’ Guiding you through decisions with thoughtful suggestions\n"
34
+ "β€’ Explaining complex ideas in a simple, easy-to-understand way\n"
35
+ "Let me know how I can assist you today!"
36
+ ),
37
+ 'Technical Expert': (
38
+ "I am your expert technical companion, with deep expertise in software development, system architecture, and emerging technologies. "
39
+ "I can help you with: \n"
40
+ "β€’ Writing and debugging code across multiple programming languages\n"
41
+ "β€’ Explaining complex technical concepts with practical examples\n"
42
+ "β€’ Providing system design recommendations and best practices\n"
43
+ "β€’ Troubleshooting technical issues with detailed step-by-step guidance\n"
44
+ "β€’ Staying updated with cutting-edge technology trends\n"
45
+ "I emphasize clean code, scalable solutions, and industry best practices in all my responses. What technical challenge can I help you with?"
46
+ ),
47
+ 'Academic Tutor': (
48
+ "I am your patient and knowledgeable academic tutor, specializing in helping students grasp complex concepts, especially in STEM fields. "
49
+ "I can assist you by: \n"
50
+ "β€’ Breaking down difficult subjects into simple, easy-to-follow explanations\n"
51
+ "β€’ Offering step-by-step walkthroughs for solving problems\n"
52
+ "β€’ Using real-world examples and analogies to clarify abstract ideas\n"
53
+ "β€’ Providing practice problems and solutions for deeper understanding\n"
54
+ "How can I support your learning today?"
55
+ ),
56
+ 'Creative Writer': (
57
+ "I am a passionate creative writer skilled in crafting stories, poetry, and vivid descriptions. "
58
+ "I can assist you with: \n"
59
+ "β€’ Writing captivating narratives with emotional depth\n"
60
+ "β€’ Creating rich metaphors, analogies, and vivid imagery\n"
61
+ "β€’ Developing unique characters, worlds, and plotlines\n"
62
+ "β€’ Helping with poetry, song lyrics, or other forms of artistic expression\n"
63
+ "Let's collaborate on your next creative project!"
64
+ ),
65
+ 'Business Consultant': (
66
+ "I am an insightful business consultant with a focus on strategy, growth, and financial optimization. "
67
+ "I can assist with: \n"
68
+ "β€’ Crafting effective business strategies for scaling and growth\n"
69
+ "β€’ Providing financial analysis and budgeting advice\n"
70
+ "β€’ Offering market insights and recommendations for business expansion\n"
71
+ "β€’ Assisting with operational improvements for efficiency and profitability\n"
72
+ "How can I help you drive your business forward?"
73
+ ),
74
+ 'Health & Wellness Coach': (
75
+ "I am a holistic health and wellness coach, ready to guide you toward a balanced lifestyle. "
76
+ "I can help you with: \n"
77
+ "β€’ Personalized workout routines and fitness plans\n"
78
+ "β€’ Nutrition advice tailored to your specific goals\n"
79
+ "β€’ Tips for maintaining mental well-being and reducing stress\n"
80
+ "β€’ Guidance on establishing healthy habits and routines\n"
81
+ "What aspect of your health journey can I assist you with today?"
82
+ ),
83
+ 'Legal Advisor': (
84
+ "I am your trusted legal advisor, here to provide clear and practical legal guidance. "
85
+ "I can help with: \n"
86
+ "β€’ Explaining legal concepts in an easy-to-understand way\n"
87
+ "β€’ Offering advice on contract law, intellectual property, and corporate law\n"
88
+ "β€’ Guiding you through legal decisions and ensuring compliance\n"
89
+ "β€’ Assisting with risk assessment and protection strategies\n"
90
+ "Let me know how I can help with your legal questions!"
91
+ ),
92
+ 'Project Manager': (
93
+ "I am your organized and results-driven project manager, here to help you lead successful projects. "
94
+ "I can assist with: \n"
95
+ "β€’ Developing project plans, timelines, and milestones\n"
96
+ "β€’ Offering guidance on agile methodologies and project management tools\n"
97
+ "β€’ Coordinating team efforts to ensure on-time delivery\n"
98
+ "β€’ Managing risks and communicating effectively with stakeholders\n"
99
+ "What project can I help you plan and execute today?"
100
+ ),
101
+ 'Language Translator': (
102
+ "I am a skilled language translator, experienced in translating both technical and non-technical content. "
103
+ "I can assist you with: \n"
104
+ "β€’ Translating text while preserving context, tone, and cultural nuances\n"
105
+ "β€’ Helping with multilingual communication, from emails to documents\n"
106
+ "β€’ Offering insights into linguistic subtleties between different languages\n"
107
+ "What translation do you need help with today?"
108
+ ),
109
+ 'Financial Advisor': (
110
+ "I am a knowledgeable financial advisor, ready to assist with personal finance and investment strategies. "
111
+ "I can help you with: \n"
112
+ "β€’ Creating and managing a budget tailored to your goals\n"
113
+ "β€’ Offering advice on saving, investing, and growing your wealth\n"
114
+ "β€’ Guiding you through retirement planning and debt management\n"
115
+ "β€’ Providing insights on smart investment opportunities\n"
116
+ "How can I help you achieve financial success today?"
117
+ ),
118
+ 'Motivational Coach': (
119
+ "I am your personal motivational coach, here to inspire and empower you to reach your full potential. "
120
+ "I can assist with: \n"
121
+ "β€’ Offering strategies to overcome obstacles and stay focused\n"
122
+ "β€’ Providing motivational tips to keep you energized and committed\n"
123
+ "β€’ Helping you build confidence and set achievable goals\n"
124
+ "β€’ Offering encouragement to help you stay positive and determined\n"
125
+ "What goal are you working on today, and how can I support you?"
126
+ ),
127
+ 'Travel Guide': (
128
+ "I am your seasoned travel guide, with a wealth of knowledge on destinations, travel tips, and local experiences. "
129
+ "I can assist you with: \n"
130
+ "β€’ Curating personalized travel itineraries based on your interests\n"
131
+ "β€’ Recommending hidden gems and must-visit spots around the world\n"
132
+ "β€’ Offering travel tips, from packing advice to navigating airports\n"
133
+ "β€’ Sharing local customs, traditions, and insider knowledge\n"
134
+ "Where are you headed next, and how can I help you plan your trip?"
135
+ ),
136
+ 'Life Coach': (
137
+ "I am your thoughtful life coach, ready to help you navigate personal challenges and discover your true potential. "
138
+ "I can help you with: \n"
139
+ "β€’ Setting meaningful goals and creating a plan to achieve them\n"
140
+ "β€’ Offering strategies for overcoming obstacles and self-doubt\n"
141
+ "β€’ Helping you cultivate self-awareness and personal growth\n"
142
+ "β€’ Providing insights on improving work-life balance and overall fulfillment\n"
143
+ "How can I support your personal growth journey today?"
144
+ ),
145
+ 'Parenting Expert': (
146
+ "I am your compassionate parenting expert, with extensive knowledge in child development and family dynamics. "
147
+ "I can assist with: \n"
148
+ "β€’ Offering practical advice for managing child behavior and discipline\n"
149
+ "β€’ Guiding you through developmental milestones for all age groups\n"
150
+ "β€’ Providing strategies for creating a positive and nurturing environment\n"
151
+ "β€’ Offering tips on parenting challenges, from bedtime routines to school issues\n"
152
+ "What parenting challenge can I help you with today?"
153
+ ),
154
+ 'Career Counselor': (
155
+ "I am your experienced career counselor, here to help you navigate career transitions and opportunities. "
156
+ "I can assist with: \n"
157
+ "β€’ Offering personalized advice on career planning and development\n"
158
+ "β€’ Guiding you through resume building, cover letters, and interview preparation\n"
159
+ "β€’ Providing insights on industry trends and skill development\n"
160
+ "β€’ Helping you find and pursue new career opportunities\n"
161
+ "What career challenge or opportunity can I help you with today?"
162
+ ),
163
+ 'Fitness Trainer': (
164
+ "I am your dedicated fitness trainer, focused on helping you achieve your health and fitness goals. "
165
+ "I can assist you with: \n"
166
+ "β€’ Creating customized workout plans based on your fitness level\n"
167
+ "β€’ Offering guidance on proper exercise form and technique\n"
168
+ "β€’ Providing nutritional advice to complement your fitness journey\n"
169
+ "β€’ Offering tips on staying motivated and consistent with your routine\n"
170
+ "What are your fitness goals, and how can I support you today?"
171
+ ),
172
+ 'Environmental Specialist': (
173
+ "I am an expert in environmental science and sustainability, passionate about helping you make eco-friendly choices. "
174
+ "I can assist with: \n"
175
+ "β€’ Offering advice on sustainable living practices and green technology\n"
176
+ "β€’ Helping you understand the environmental impact of human activities\n"
177
+ "β€’ Providing tips on waste reduction, energy efficiency, and conservation\n"
178
+ "β€’ Sharing insights on renewable energy and environmental protection\n"
179
+ "How can I help you live more sustainably today?"
180
+ ),
181
+ 'Entrepreneur Mentor': (
182
+ "I am your experienced mentor, dedicated to helping aspiring entrepreneurs launch and grow successful businesses. "
183
+ "I can help with: \n"
184
+ "β€’ Developing business ideas and crafting a viable business plan\n"
185
+ "β€’ Offering advice on funding, scaling, and managing a startup\n"
186
+ "β€’ Providing insights on market trends, competition, and growth strategies\n"
187
+ "β€’ Helping you navigate the challenges of entrepreneurship with practical solutions\n"
188
+ "What part of your entrepreneurial journey can I assist you with today?"
189
+ )
190
+ }
191
+
192
+ def extract_urls(text):
193
+ url_pattern = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+')
194
+ return url_pattern.findall(text)
195
+
196
+ def download_markdown(content, filename="extracted_content.md"):
197
+ b64 = base64.b64encode(content.encode()).decode()
198
+ href = f'<a href="data:file/markdown;base64,{b64}" download="{filename}">Download Markdown File</a>'
199
+ return href
200
+
201
+ # Constants for image processing
202
+ INVOKE_URL = "https://ai.api.nvidia.com/v1/gr/meta/llama-3.2-90b-vision-instruct/chat/completions"
203
+ STREAM = True
204
+
205
+ def compress_image(image_file, max_size_kb=175):
206
+ """Compress the uploaded image to meet size requirements"""
207
+ max_size_bytes = max_size_kb * 1024
208
+ quality = 95
209
+
210
+ img = Image.open(image_file)
211
+ img.thumbnail((800, 800))
212
+
213
+ while True:
214
+ img_byte_arr = BytesIO()
215
+ img.save(img_byte_arr, format='JPEG', quality=quality)
216
+ if img_byte_arr.tell() <= max_size_bytes or quality <= 10:
217
+ return img_byte_arr.getvalue()
218
+ quality = max(quality - 10, 10)
219
+
220
+ def process_image(image_file, api_key, question):
221
+ """Process the image and get response from the vision model"""
222
+ try:
223
+ compressed_image = compress_image(image_file)
224
+ image_b64 = base64.b64encode(compressed_image).decode()
225
+
226
+ if len(image_b64) >= 180_000:
227
+ return "Error: Image is still too large after compression. Please try a smaller image."
228
+
229
+ if not api_key:
230
+ api_key = os.getenv("YOUR_API_KEY")
231
+
232
+ prompt = f"{question}"
233
+
234
+ headers = {
235
+ "Authorization": f"Bearer {api_key}",
236
+ "Accept": "text/event-stream" if STREAM else "application/json"
237
+ }
238
+
239
+ payload = {
240
+ "model": 'meta/llama-3.2-90b-vision-instruct',
241
+ "messages": [
242
+ {
243
+ "role": "user",
244
+ "content": f'{prompt} <img src="data:image/jpeg;base64,{image_b64}" />'
245
+ }
246
+ ],
247
+ "max_tokens": 512,
248
+ "temperature": 1.00,
249
+ "top_p": 1.00,
250
+ "stream": STREAM
251
+ }
252
+
253
+ with st.spinner('Analyzing image...'):
254
+ response = requests.post(INVOKE_URL, headers=headers, json=payload, stream=True)
255
+
256
+ if response.status_code == 200:
257
+ full_response = ""
258
+ response_placeholder = st.empty()
259
+
260
+ for line in response.iter_lines():
261
+ if line:
262
+ line = line.decode('utf-8')
263
+ if line.startswith('data: '):
264
+ json_str = line[6:]
265
+ if json_str.strip() == '[DONE]':
266
+ break
267
+ try:
268
+ json_obj = json.loads(json_str)
269
+ content = json_obj['choices'][0]['delta'].get('content', '')
270
+ full_response += content
271
+ response_placeholder.write(full_response)
272
+ except json.JSONDecodeError:
273
+ st.error(f"Failed to parse JSON: {json_str}")
274
+
275
+ return full_response
276
+
277
+ elif response.status_code == 402:
278
+ return "Error: API account credits have expired. Please check your account status on the NVIDIA website."
279
+ else:
280
+ error_message = f"Error {response.status_code}: {response.text}"
281
+ st.error(error_message)
282
+ return f"An error occurred. Please try again later or contact support. Error code: {response.status_code}"
283
+
284
+ except Exception as e:
285
+ st.error(f"An error occurred: {str(e)}")
286
+ return f"Error processing request: {str(e)}"
287
+
288
+ class ResponseManager:
289
+ def __init__(self, client: OpenAI, model: str):
290
+ self.client = client
291
+ self.model = model
292
+ self.config = ChatbotConfig()
293
+
294
+ def count_tokens(self, text: str) -> int:
295
+ """Approximate token count using the appropriate tokenizer for the NVIDIA model."""
296
+ try:
297
+ # Assuming you have a function or library that provides the correct tokenization for the NVIDIA model
298
+ encoding = tiktoken.encoding_for_model("nvidia/llama-3.1-nemotron-70b-instruct") # Use the correct model
299
+ return len(encoding.encode(text))
300
+ except Exception:
301
+ # Fallback to word-based approximation
302
+ return len(text.split()) * 1.3 # Adjust this as necessary for a better approximation
303
+
304
+ def generate_response(self, messages, temperature, placeholder):
305
+ """Generate response with continuation handling in batches."""
306
+ full_response = ""
307
+ continuation_prompt = "\nPlease continue from where you left off..."
308
+ current_messages = messages.copy()
309
+
310
+ try:
311
+ while True:
312
+ # Calculate remaining tokens
313
+ remaining_tokens = self.config.MAX_TOKENS - self.count_tokens(full_response)
314
+ tokens_to_generate = min(self.config.BATCH_SIZE, remaining_tokens)
315
+
316
+ # Generate response in batches
317
+ stream = self.client.chat.completions.create(
318
+ model=self.model,
319
+ messages=current_messages,
320
+ temperature=temperature,
321
+ max_tokens=tokens_to_generate,
322
+ stream=True
323
+ )
324
+
325
+ batch_response = ""
326
+ for chunk in stream:
327
+ if chunk.choices[0].delta.content is not None:
328
+ chunk_content = chunk.choices[0].delta.content
329
+ batch_response += chunk_content
330
+ full_response += chunk_content
331
+ placeholder.markdown(full_response + "β–Œ")
332
+ time.sleep(0.01)
333
+
334
+ # Check if response seems complete
335
+ if batch_response.strip().endswith((".", "!", "?", "\n")) or \
336
+ len(batch_response.strip()) < tokens_to_generate * 0.9:
337
+ break
338
+
339
+ # Prepare for continuation
340
+ current_messages.append({"role": "assistant", "content": full_response})
341
+ current_messages.append({"role": "user", "content": continuation_prompt})
342
+
343
+ return full_response
344
+
345
+ except Exception as e:
346
+ st.error(f"An error occurred: {str(e)}")
347
+ return f"Error generating response: {str(e)}"
348
+
349
+ def initialize_session_state():
350
+ """Initialize all session state variables"""
351
+ if "messages" not in st.session_state:
352
+ # Set the initial system message based on the default persona
353
+ initial_persona = ChatbotConfig().PERSONA_PROMPTS['General Assistant']
354
+ st.session_state.messages = [{"role": "system", "content": initial_persona}]
355
+ if "conversation_history" not in st.session_state:
356
+ st.session_state.conversation_history = []
357
+ if "nvidia_model" not in st.session_state:
358
+ st.session_state.nvidia_model = ChatbotConfig().DEFAULT_MODEL
359
+ if "image_mode" not in st.session_state:
360
+ st.session_state.image_mode = False
361
+
362
+ def load_conversations():
363
+ """Load conversation history from JSON files."""
364
+ conversation_files = [f for f in os.listdir() if f.startswith('chat_history_') and f.endswith('.json')]
365
+ return conversation_files
366
+
367
+ def load_conversation(file_name):
368
+ """Load a specific conversation from a JSON file."""
369
+ with open(file_name, 'r') as f:
370
+ return json.load(f)
371
+
372
+ def save_conversation(filename="chat_history.json"):
373
+ """Save the current conversation to a file"""
374
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
375
+ filename = f"chat_history_{timestamp}.json"
376
+
377
+ conversation_data = {
378
+ "timestamp": timestamp,
379
+ "messages": st.session_state.messages[1:], # Exclude system message
380
+ }
381
+
382
+ with open(filename, 'w') as f:
383
+ json.dump(conversation_data, f, indent=2)
384
+
385
+ return filename
386
+
387
+ def create_sidebar():
388
+ """Create and handle sidebar elements"""
389
+ config = ChatbotConfig()
390
+
391
+ st.sidebar.title("NVIDIA NIM Chatbot βš™οΈ")
392
+
393
+ # Web Scraping Toggle
394
+ st.sidebar.header("Web Scraping")
395
+ enable_web_scraping = st.sidebar.toggle("Enable Automatic Web Scraping", value=False)
396
+
397
+ if enable_web_scraping:
398
+ st.sidebar.info("URLs detected in your input will be automatically scraped for additional context by using Crawl4AI.")
399
+
400
+ # Model Settings
401
+ st.sidebar.header("Model Configuration")
402
+
403
+ # Persona Selection
404
+ personas = list(config.PERSONA_PROMPTS.keys())
405
+
406
+ # Add custom persona input fields within an expander
407
+ with st.sidebar.expander("✨ Create Custom Persona"):
408
+ st.markdown("### Create Your Own Persona")
409
+ custom_persona_name = st.text_input("Persona Name",
410
+ placeholder="e.g., Data Science Expert, Marketing Specialist")
411
+ custom_persona_description = st.text_area("Persona Description",
412
+ placeholder="Describe the persona's expertise, tone, and capabilities...",
413
+ height=150)
414
+
415
+ if st.button("Add Custom Persona", type="primary"):
416
+ if custom_persona_name and custom_persona_description:
417
+ # Add the custom persona to the list of personas
418
+ config.PERSONA_PROMPTS[custom_persona_name] = custom_persona_description
419
+ st.success(f"βœ… Custom persona '{custom_persona_name}' added successfully!")
420
+ time.sleep(1) # Show success message briefly
421
+ st.rerun() # Refresh to update the persona list
422
+ else:
423
+ st.error("Please provide both a name and a description for the custom persona.")
424
+
425
+ # Update persona selection with custom personas
426
+ selected_persona = st.sidebar.selectbox(
427
+ "Choose Assistant Persona",
428
+ list(config.PERSONA_PROMPTS.keys()),
429
+ help="Select from pre-defined personas or create your own custom persona"
430
+ )
431
+
432
+ # Display current persona description
433
+ with st.sidebar.expander("Current Persona Description", expanded=False):
434
+ st.markdown(f"### {selected_persona}")
435
+ st.markdown(config.PERSONA_PROMPTS[selected_persona])
436
+
437
+ # Response Style
438
+ temperature_style = st.sidebar.selectbox(
439
+ "Response Style",
440
+ list(config.TEMPERATURE_RANGES.keys())
441
+ )
442
+
443
+ # Advanced Settings Expander
444
+ with st.sidebar.expander("βš™οΈ Advanced Settings"):
445
+ show_token_count = st.checkbox("Show Token Count", value=False)
446
+ enable_code_highlighting = st.checkbox("Enable Code Highlighting", value=True)
447
+ enable_markdown = st.checkbox("Enable Markdown Support", value=True)
448
+ batch_size = st.slider("Response Batch Size (tokens)",
449
+ min_value=100,
450
+ max_value=4000,
451
+ value=1000,
452
+ step=100)
453
+
454
+ # Image Chat Mode Toggle
455
+ st.sidebar.header("πŸ€– Llama 3.2 90B Vision Analysis")
456
+ image_mode = st.sidebar.toggle("Enable Image Chat", value=st.session_state.image_mode)
457
+ st.session_state.image_mode = image_mode
458
+
459
+ if image_mode:
460
+ st.sidebar.info("Image chat mode is enabled. You can now upload images and ask questions about them.")
461
+
462
+ # Load previous conversations
463
+ st.sidebar.header("Load Previous Conversations")
464
+ conversation_files = load_conversations()
465
+ if conversation_files:
466
+ selected_file = st.sidebar.selectbox("Choose a conversation to load", conversation_files)
467
+ if st.sidebar.button("Load Conversation"):
468
+ conversation_data = load_conversation(selected_file)
469
+ st.session_state.messages = conversation_data['messages']
470
+ st.success("Conversation loaded successfully!")
471
+ st.experimental_rerun() # Refresh to display loaded conversation
472
+ else:
473
+ st.sidebar.info("No previous conversations found.")
474
+
475
+ # Conversation Management
476
+ st.sidebar.header("Conversation Management")
477
+ col1, col2 = st.sidebar.columns(2)
478
+ with col1:
479
+ if st.button("πŸ—‘οΈ Clear Chat", use_container_width=True):
480
+ st.session_state.messages = [{"role": "system", "content": config.PERSONA_PROMPTS[selected_persona]}]
481
+ st.rerun()
482
+
483
+ with col2:
484
+ if st.button("πŸ’Ύ Save Chat", use_container_width=True):
485
+ conversation_json = save_conversation()
486
+ st.download_button(
487
+ label="πŸ“₯ Download",
488
+ data=conversation_json,
489
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
490
+ mime="application/json",
491
+ use_container_width=True
492
+ )
493
+
494
+ return {
495
+ 'temperature': config.TEMPERATURE_RANGES[temperature_style],
496
+ 'batch_size': batch_size,
497
+ 'show_token_count': show_token_count,
498
+ 'enable_code_highlighting': enable_code_highlighting,
499
+ 'enable_markdown': enable_markdown,
500
+ 'persona': config.PERSONA_PROMPTS[selected_persona],
501
+ 'enable_web_scraping': enable_web_scraping
502
+ }
503
+
504
+ def format_message(message, enable_code_highlighting=True, enable_markdown=True):
505
+ """Format message with optional code highlighting and markdown support"""
506
+ if message["role"] == "system" and message["content"].startswith("Additional context from web scraping:"):
507
+ # Don't display system messages with scraped content in the chat window
508
+ return
509
+
510
+ content = message["content"]
511
+
512
+ if enable_code_highlighting and "```" in content:
513
+ # Enhanced code block detection and formatting
514
+ parts = content.split("```")
515
+ formatted_parts = []
516
+ for i, part in enumerate(parts):
517
+ if i % 2 == 1: # Code block
518
+ try:
519
+ lang, code = part.split("\n", 1)
520
+ formatted_parts.append(f'<div class="code-block {lang}">\n{code}\n</div>')
521
+ except ValueError:
522
+ formatted_parts.append(f'<div class="code-block">\n{part}\n</div>')
523
+ else: # Regular text
524
+ formatted_parts.append(part)
525
+ content = "".join(formatted_parts)
526
+
527
+ if enable_markdown:
528
+ st.markdown(content, unsafe_allow_html=True)
529
+ else:
530
+ st.write(content)
531
+
532
+ def main():
533
+ st.title("🧠 BrainWave AI IntelliChat πŸ€–")
534
+ st.markdown("<h3 style='text-align: center;'>Powered by Llama 3.1 Nemotron-70B</h3>", unsafe_allow_html=True)
535
+ st.markdown("---")
536
+
537
+ # Initialize session state
538
+ initialize_session_state()
539
+
540
+ # Setup sidebar and get settings
541
+ settings = create_sidebar()
542
+
543
+ # Initialize OpenAI client and ResponseManager
544
+ client = OpenAI(
545
+ base_url="https://integrate.api.nvidia.com/v1",
546
+ api_key="YOUR API KEY HERE OR PLACE IT IN .env"
547
+ )
548
+ response_manager = ResponseManager(client, st.session_state.nvidia_model)
549
+
550
+ # Display chat history
551
+ for message in st.session_state.messages[1:]: # Skip system message
552
+ if message["role"] != "system" or not message["content"].startswith("Additional context from web scraping:"):
553
+ with st.chat_message(message["role"]):
554
+ format_message(
555
+ message,
556
+ enable_code_highlighting=settings['enable_code_highlighting'],
557
+ enable_markdown=settings['enable_markdown']
558
+ )
559
+
560
+ # Initialize session state for expander visibility
561
+ if 'show_scraped_content' not in st.session_state:
562
+ st.session_state.show_scraped_content = False
563
+
564
+ if st.session_state.image_mode:
565
+ # Image chat mode
566
+ col1, col2 = st.columns([2, 1])
567
+
568
+ with col1:
569
+ uploaded_file = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png'])
570
+ if uploaded_file:
571
+ st.image(uploaded_file, caption="Uploaded Image", use_column_width=True)
572
+
573
+ with col2:
574
+ api_key = st.text_input("Enter your API Key", type="password",
575
+ placeholder="API authentication key")
576
+ question = st.text_input("Enter your question",
577
+ placeholder="Example: What is in this image?")
578
+
579
+ if st.button("Analyze Image", use_container_width=True):
580
+ if uploaded_file and question:
581
+ response = process_image(uploaded_file, api_key, question)
582
+ st.markdown("### Analysis Result:")
583
+ st.markdown(response)
584
+ else:
585
+ st.warning("Please upload an image and enter a question.")
586
+ else:
587
+ # Chat input
588
+ if prompt := st.chat_input("What would you like to know?"):
589
+ # Add user message
590
+ st.session_state.messages.append({"role": "user", "content": prompt})
591
+ with st.chat_message("user"):
592
+ st.markdown(prompt)
593
+
594
+ if settings['enable_web_scraping']:
595
+ urls = extract_urls(prompt)
596
+ if urls:
597
+ scraped_contents = {}
598
+ progress_bar = st.progress(0)
599
+ status_text = st.empty()
600
+
601
+ for i, url in enumerate(urls):
602
+ status_text.text(f"Scraping URL {i+1}/{len(urls)}: {url}")
603
+ try:
604
+ crawler = WebCrawler()
605
+ crawler.warmup()
606
+ result = crawler.run(url=url)
607
+ scraped_contents[url] = result.markdown
608
+ st.sidebar.success(f"Scraped content from: {url}")
609
+ st.sidebar.markdown(download_markdown(result.markdown, f"content_from_{url.replace('://', '_')}.md"), unsafe_allow_html=True)
610
+ except Exception as e:
611
+ st.sidebar.error(f"Error scraping {url}: {str(e)}")
612
+ progress_bar.progress((i + 1) / len(urls))
613
+
614
+ status_text.text("Scraping completed!")
615
+ progress_bar.empty()
616
+
617
+ if scraped_contents:
618
+ # Create checkboxes for each URL
619
+ st.write("Select URLs to include in the context:")
620
+ url_selections = {url: st.checkbox(f"Include {url}", value=True) for url in scraped_contents.keys()}
621
+
622
+ # Combine selected scraped contents
623
+ selected_contents = "\n\n".join([f"Content from {url}:\n{content}"
624
+ for url, content in scraped_contents.items()
625
+ if url_selections[url]])
626
+
627
+ # Add selected scraped content as a system message (hidden from chat window)
628
+ st.session_state.messages.append({"role": "system", "content": f"Additional context from web scraping:{selected_contents}"})
629
+
630
+ # Store scraped contents in session state
631
+ st.session_state.scraped_contents = scraped_contents
632
+
633
+ # Set flag to show scraped content
634
+ st.session_state.show_scraped_content = True
635
+
636
+ # Check if the prompt is a question about identity or help
637
+ if "who are you" in prompt.lower() or "how can you help" in prompt.lower():
638
+ # Respond based on the selected persona prompt
639
+ persona_response = settings['persona']
640
+ st.session_state.messages.append({"role": "assistant", "content": persona_response})
641
+ with st.chat_message("assistant"):
642
+ st.markdown(persona_response)
643
+ else:
644
+ # Append the selected persona prompt to the messages
645
+ selected_persona_prompt = settings['persona']
646
+ st.session_state.messages.append({"role": "system", "content": selected_persona_prompt})
647
+
648
+ # Generate and display assistant response
649
+ with st.chat_message("assistant"):
650
+ message_placeholder = st.empty()
651
+
652
+ # Generate response with continuation handling
653
+ full_response = response_manager.generate_response(
654
+ messages=st.session_state.messages,
655
+ temperature=settings['temperature'],
656
+ placeholder=message_placeholder
657
+ )
658
+
659
+ # Final update
660
+ message_placeholder.markdown(full_response)
661
+
662
+ # Add assistant response to history
663
+ st.session_state.messages.append({"role": "assistant", "content": full_response})
664
+
665
+ # Display token count if enabled
666
+ if settings['show_token_count']:
667
+ token_count = response_manager.count_tokens(full_response)
668
+ st.caption(f"Approximate tokens: {token_count}")
669
+
670
+ # Display scraped content expander (outside the if prompt block)
671
+ if st.session_state.get('show_scraped_content', False):
672
+ with st.expander("View Scraped Content", expanded=False):
673
+ if 'scraped_contents' in st.session_state and st.session_state.scraped_contents:
674
+ selected_url = st.selectbox("Choose URL to view content:", list(st.session_state.scraped_contents.keys()))
675
+ st.markdown(st.session_state.scraped_contents[selected_url])
676
+ else:
677
+ st.write("No scraped content available.")
678
+
679
+ if __name__ == "__main__":
680
+ main()
681
+
682
+ st.markdown("""
683
+ <style>
684
+ /* Main container styling */
685
+ .main {
686
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
687
+ padding: 2rem;
688
+ border-radius: 20px;
689
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
690
+ }
691
+
692
+ /* Header styling */
693
+ .title-container {
694
+ background: linear-gradient(45deg, #2193b0, #6dd5ed);
695
+ padding: 2rem;
696
+ border-radius: 15px;
697
+ text-align: center;
698
+ margin-bottom: 2rem;
699
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
700
+ }
701
+
702
+ .main-title {
703
+ color: white;
704
+ font-family: 'Poppins', sans-serif;
705
+ font-size: 2.5rem;
706
+ font-weight: 700;
707
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
708
+ margin-bottom: 1rem;
709
+ }
710
+
711
+ /* Chat container styling */
712
+ .chat-container {
713
+ background: white;
714
+ border-radius: 15px;
715
+ padding: 1.5rem;
716
+ margin: 1rem 0;
717
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
718
+ }
719
+
720
+ /* Message styling */
721
+ .stTextInput>div>div>input {
722
+ border-radius: 25px !important;
723
+ border: 2px solid #e0e0e0;
724
+ padding: 1rem 1.5rem;
725
+ font-size: 1rem;
726
+ transition: all 0.3s ease;
727
+ }
728
+
729
+ .stTextInput>div>div>input:focus {
730
+ border-color: #2193b0;
731
+ box-shadow: 0 0 0 2px rgba(33, 147, 176, 0.2);
732
+ }
733
+
734
+ /* Button styling */
735
+ .stButton>button {
736
+ background: linear-gradient(45deg, #2193b0, #6dd5ed);
737
+ color: white;
738
+ border: none;
739
+ border-radius: 25px;
740
+ padding: 0.75rem 2rem;
741
+ font-weight: 600;
742
+ transition: all 0.3s ease;
743
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
744
+ }
745
+
746
+ .stButton>button:hover {
747
+ transform: translateY(-2px);
748
+ box-shadow: 0 6px 20px rgba(0,0,0,0.15);
749
+ }
750
+
751
+ /* Sidebar styling */
752
+ .css-1d391kg {
753
+ background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);
754
+ padding: 2rem 1rem;
755
+ }
756
+
757
+ /* Feature cards */
758
+ .feature-card {
759
+ background: white;
760
+ border-radius: 15px;
761
+ padding: 1.5rem;
762
+ margin: 1rem 0;
763
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
764
+ transition: all 0.3s ease;
765
+ }
766
+
767
+ .feature-card:hover {
768
+ transform: translateY(-5px);
769
+ box-shadow: 0 8px 15px rgba(0,0,0,0.1);
770
+ }
771
+
772
+ /* Creator section styling */
773
+ .creator-section {
774
+ background: linear-gradient(45deg, #141e30, #243b55);
775
+ color: white;
776
+ padding: 2rem;
777
+ border-radius: 15px;
778
+ margin-top: 2rem;
779
+ text-align: center;
780
+ }
781
+
782
+ .social-links a {
783
+ color: #6dd5ed;
784
+ text-decoration: none;
785
+ margin: 0 1rem;
786
+ transition: all 0.3s ease;
787
+ }
788
+
789
+ .social-links a:hover {
790
+ color: white;
791
+ text-decoration: none;
792
+ }
793
+
794
+ /* Animations */
795
+ @keyframes fadeIn {
796
+ from { opacity: 0; transform: translateY(20px); }
797
+ to { opacity: 1; transform: translateY(0); }
798
+ }
799
+
800
+ .animate-fade-in {
801
+ animation: fadeIn 0.5s ease-out;
802
+ }
803
+ </style>
804
+ """, unsafe_allow_html=True)
805
+
806
+ # Modified welcome section
807
+ # st.markdown("""
808
+ # <div class="title-container animate-fade-in">
809
+ # <h1 class="main-title">✨ Welcome to NVIDIA AI Chat Magic! ✨</h1>
810
+ # <p style="color: white; font-size: 1.2rem;">Experience the future of AI conversation</p>
811
+ # </div>
812
+
813
+ # <div class="feature-card animate-fade-in">
814
+ # <h2 style="color: #2193b0; margin-bottom: 1rem;">🎭 Discover Our Amazing Features</h2>
815
+ # <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;">
816
+ # <div class="feature-item">
817
+ # <h3>πŸ€– AI Companions</h3>
818
+ # <p>Engage with personalized AI assistants</p>
819
+ # </div>
820
+ # <div class="feature-item">
821
+ # <h3>🌐 Web Integration</h3>
822
+ # <p>Access real-time web content</p>
823
+ # </div>
824
+ # <div class="feature-item">
825
+ # <h3>πŸ–ΌοΈ Image Analysis</h3>
826
+ # <p>Intelligent image processing</p>
827
+ # </div>
828
+ # <div class="feature-item">
829
+ # <h3>🎨 Creative Control</h3>
830
+ # <p>Customize response styles</p>
831
+ # </div>
832
+ # </div>
833
+ # </div>
834
+ # """, unsafe_allow_html=True)
835
+
836
+ st.sidebar.markdown("---")
837
+ st.sidebar.title("✨ About the Creator")
838
+ st.sidebar.markdown("""
839
+ <div style="font-family: 'Brush Script MT', cursive; font-size: 20px; color: #4A90E2;">
840
+ Crafted with ❀️ by Richardson Gunde
841
+ </div>
842
+
843
+ <div style="font-family: 'Dancing Script', cursive; font-size: 16px; padding: 10px 0;">
844
+ Featuring:
845
+ <br>β€’ ✨ Custom AI Personas
846
+ <br>β€’ 🌐 Web Content Integration
847
+ <br>β€’ πŸ–ΌοΈ Image Analysis
848
+ <br>β€’ 🎨 Creative Response Control
849
+ <br>β€’ πŸ“Š Token Tracking
850
+ <br>β€’ πŸ’­ Smart Conversations
851
+ </div>
852
+
853
+ <div style="font-family: 'Dancing Script', cursive; font-size: 16px; padding-top: 10px;">
854
+ πŸ”— <a href="https://www.linkedin.com/in/richardson-gunde" style="color: #0077B5;">LinkedIn</a>
855
+ <br>πŸ“§ <a href="mailto:[email protected]" style="color: #D44638;">Email</a>
856
+ </div>
857
+
858
+ """, unsafe_allow_html=True)