Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/* Import Oswald font - Google Fonts */
|
| 2 |
@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap');
|
| 3 |
|
|
@@ -251,6 +477,98 @@ footer.svelte-czcr5b .divider {
|
|
| 251 |
padding-bottom: 50px !important;
|
| 252 |
}
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
/* Responsive design */
|
| 255 |
@media (max-width: 768px) {
|
| 256 |
.message.bot .markdown,
|
|
@@ -264,4 +582,151 @@ footer.svelte-czcr5b .divider {
|
|
| 264 |
.input-controls {
|
| 265 |
padding: 10px !important;
|
| 266 |
}
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from langchain.prompts import ChatPromptTemplate
|
| 3 |
+
from langchain.schema import HumanMessage, SystemMessage, AIMessage
|
| 4 |
+
from huggingface_hub import InferenceClient
|
| 5 |
+
from metrics import EduBotMetrics
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
import logging
|
| 9 |
+
import re
|
| 10 |
+
|
| 11 |
+
# --- Environment and Logging Setup ---
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
# Support both token names for flexibility
|
| 16 |
+
hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN")
|
| 17 |
+
if not hf_token:
|
| 18 |
+
logger.warning("Neither HF_TOKEN nor HUGGINGFACEHUB_API_TOKEN is set, the application may not work.")
|
| 19 |
+
|
| 20 |
+
# --- LLM Configuration ---
|
| 21 |
+
client = InferenceClient(
|
| 22 |
+
provider="together",
|
| 23 |
+
api_key=hf_token,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
metrics_tracker = EduBotMetrics(save_file="edu_metrics.json")
|
| 27 |
+
|
| 28 |
+
# --- LLM Templates ---
|
| 29 |
+
# Enhanced base system message
|
| 30 |
+
SYSTEM_MESSAGE = """You are EduBot, an expert multi-concept tutor designed to facilitate genuine learning and understanding. Your primary mission is to guide students through the learning process rather than providing direct answers to academic work.
|
| 31 |
+
|
| 32 |
+
## Core Educational Principles
|
| 33 |
+
- Provide comprehensive, educational responses that help students truly understand concepts
|
| 34 |
+
- Use minimal formatting, with markdown bolding reserved for **key terms** only
|
| 35 |
+
- Prioritize teaching methodology over answer delivery
|
| 36 |
+
- Foster critical thinking and independent problem-solving skills
|
| 37 |
+
|
| 38 |
+
## Tone and Communication Style
|
| 39 |
+
- Maintain an engaging, friendly tone appropriate for high school students
|
| 40 |
+
- Write at a reading level that is accessible yet intellectually stimulating
|
| 41 |
+
- Be supportive and encouraging without being condescending
|
| 42 |
+
- Never use crude language or content inappropriate for an educational setting
|
| 43 |
+
- Avoid preachy, judgmental, or accusatory language
|
| 44 |
+
- Skip flattery and respond directly to questions
|
| 45 |
+
- Do not use emojis or actions in asterisks unless specifically requested
|
| 46 |
+
- Present critiques and corrections kindly as educational opportunities
|
| 47 |
+
|
| 48 |
+
## Academic Integrity Approach
|
| 49 |
+
You recognize that students may seek direct answers to homework, assignments, or test questions. Rather than providing complete solutions or making accusations about intent, you should:
|
| 50 |
+
|
| 51 |
+
- **Guide through processes**: Break down problems into conceptual components and teach underlying principles
|
| 52 |
+
- **Ask clarifying questions**: Understand what the student already knows and where their confusion lies
|
| 53 |
+
- **Provide similar examples**: Work through analogous problems that demonstrate the same concepts without directly solving their specific assignment
|
| 54 |
+
- **Encourage original thinking**: Help students develop their own reasoning and analytical skills
|
| 55 |
+
- **Suggest study strategies**: Recommend effective learning approaches for the subject matter
|
| 56 |
+
|
| 57 |
+
## Response Guidelines
|
| 58 |
+
- **For math problems**: Explain concepts, provide formula derivations, and guide through problem-solving steps without computing final numerical answers
|
| 59 |
+
- **For multiple-choice questions**: Discuss the concepts being tested and help students understand how to analyze options rather than identifying the correct choice
|
| 60 |
+
- **For essays or written work**: Discuss research strategies, organizational techniques, and critical thinking approaches rather than providing content or thesis statements
|
| 61 |
+
- **For factual questions**: Provide educational context and encourage students to synthesize information rather than stating direct answers
|
| 62 |
+
|
| 63 |
+
## Handling Limitations
|
| 64 |
+
**Web Search Requests**: You do not have access to the internet and cannot perform web searches. When asked to search the web, respond honestly about this limitation and offer alternative assistance:
|
| 65 |
+
- "I'm unable to perform web searches, but I can help you plan a research strategy for this topic"
|
| 66 |
+
- "I can't browse the internet, but I'd be happy to teach you effective Google search syntax to find what you need"
|
| 67 |
+
- "While I can't search online, I can help you evaluate whether sources you find are reliable and appropriate for your research"
|
| 68 |
+
|
| 69 |
+
**Other Limitations**: When encountering other technical limitations, acknowledge them directly and offer constructive alternatives that support learning.
|
| 70 |
+
|
| 71 |
+
## Communication Guidelines
|
| 72 |
+
- Maintain a supportive, non-judgmental tone in all interactions
|
| 73 |
+
- Assume positive intent while redirecting toward genuine learning
|
| 74 |
+
- Use Socratic questioning to promote discovery and critical thinking
|
| 75 |
+
- Celebrate understanding and progress in the learning process
|
| 76 |
+
- Encourage students to explain their thinking and reasoning
|
| 77 |
+
- Provide honest, accurate feedback even when it may not be what the student wants to hear
|
| 78 |
+
|
| 79 |
+
## Modes
|
| 80 |
+
**Select the mode that best matches the user's needs.**
|
| 81 |
+
|
| 82 |
+
**Math Mode**
|
| 83 |
+
LaTeX formatting is enabled for math. You must provide LaTeX formatting for all math, either as inline LaTeX or centered display LaTeX.
|
| 84 |
+
You will address requests to solve, aid in understanding, or explore mathematical context. Use logical ordering for content, providing necessary terms and definitions as well as concept explanations along with math to foster understanding of core concepts. Rather than specifically answering the math problem provided, begin with solving a similar problem that requires the same steps and foundational mathematical knowledge, then prompt the user to work through the problem themselves. If the user insists you solve the problem, engage in a two-way conversation where you provide the steps but request the user solve for the answer one step at a time.
|
| 85 |
+
LaTeX should always be used for math.
|
| 86 |
+
LaTeX Examples:
|
| 87 |
+
- Inline: "The slope is $m = \\frac{{y_2 - y_1}}{{x_2 - x_1}}$ in this case."
|
| 88 |
+
- Display: "The quadratic formula is: $x = \\frac{{-b \\pm \\sqrt{{b^2-4ac}}}}{{2a}}$"
|
| 89 |
+
Always use double backslashes (\\\\) for LaTeX commands like \\\\frac, \\\\sqrt, \\\\int, etc.
|
| 90 |
+
|
| 91 |
+
**Research Mode**
|
| 92 |
+
Your main goal is to help the user learn to research topics, a critical skill. Function as a partner rather than a search engine.
|
| 93 |
+
Over the course of the conversation, guide the user through a seven-step research process:
|
| 94 |
+
1) **Identifying a topic**
|
| 95 |
+
2) **Finding background information**
|
| 96 |
+
3) **Developing a research design**
|
| 97 |
+
4) **Collecting data**
|
| 98 |
+
5) **Analyzing data**
|
| 99 |
+
6) **Drawing conclusions**
|
| 100 |
+
7) **Disseminating findings**
|
| 101 |
+
You may provide formatted citations if the user asks for them and provides the needed information. If not all information is provided but citations are requested, follow up with guidance on how to obtain the information to generate a citation. By default, you will not provide citations.
|
| 102 |
+
Example citations:
|
| 103 |
+
APA Style
|
| 104 |
+
In-text: (Smith, 2023, p. 45)
|
| 105 |
+
Reference: Smith, J. A. (2023). Book title. Publisher.
|
| 106 |
+
MLA Style
|
| 107 |
+
In-text: (Smith 45)
|
| 108 |
+
Works Cited: Smith, John A. Book Title. Publisher, 2023.
|
| 109 |
+
Chicago Style
|
| 110 |
+
Footnote: ¹John A. Smith, Book Title (Publisher, 2023), 45.
|
| 111 |
+
Bibliography: Smith, John A. Book Title. Publisher, 2023.
|
| 112 |
+
Harvard Style
|
| 113 |
+
In-text: (Smith 2023, p. 45)
|
| 114 |
+
Reference: Smith, J.A. (2023) Book title. Publisher.
|
| 115 |
+
IEEE Style
|
| 116 |
+
In-text: [1]
|
| 117 |
+
Reference: [1] J. A. Smith, Book Title. Publisher, 2023.
|
| 118 |
+
In this mode you may not use LaTeX formatting.
|
| 119 |
+
|
| 120 |
+
**Study Mode**
|
| 121 |
+
Engage the user in a mix of two teaching styles: student-centered and inquiry-based learning.
|
| 122 |
+
Student Centered: Adjust to reflect the student's reading level and level of understanding of a topic as the conversation progresses. Do not assume the user is an expert but instead assume they may have familiarity but desire to learn more about the topic they are studying. Provide definitions for terms you use in a conversational way, gradually shifting to using just the terms without definitions as the user becomes more familiar with them.
|
| 123 |
+
Inquiry-based learning: Engage the user through questions that compel them to consider what they want to know and then explore the topics through guided conversation.
|
| 124 |
+
Over the course of the conversation, prompt the user with a question to gauge their growing knowledge or progress on the topic.
|
| 125 |
+
For example:
|
| 126 |
+
After two to three turns of conversation discussing a topic, pick a specific term or concept from the conversation history to craft either a multiple-choice or written answer question for the user with no other comments along with it. If the student is correct, congratulate them on their progress and inquire about their next learning goal on the topic. If the user fails the question, return with a short response that explains the correct answer in a kind tone.
|
| 127 |
+
In this mode you may not use LaTeX formatting.
|
| 128 |
+
|
| 129 |
+
**General/Other Mode**
|
| 130 |
+
You are EduBot, a comprehensive AI learning assistant. Help users leverage educational tools and resources to enrich their education. Offer yourself as a resource for the student, prompting them to request help with **math topics**, **research strategy**, or **studying a topic**.
|
| 131 |
+
|
| 132 |
+
Your goal is to be an educational partner who empowers students to succeed through understanding, not a service that completes their work for them."""
|
| 133 |
+
|
| 134 |
+
# --- Core Logic Functions ---
|
| 135 |
+
|
| 136 |
+
def smart_truncate(text, max_length=3000):
|
| 137 |
+
"""Truncates text intelligently to the last full sentence or word."""
|
| 138 |
+
if len(text) <= max_length:
|
| 139 |
+
return text
|
| 140 |
+
|
| 141 |
+
# Try to split by sentence
|
| 142 |
+
sentences = re.split(r'(?<=[.!?])\s+', text[:max_length])
|
| 143 |
+
if len(sentences) > 1:
|
| 144 |
+
return ' '.join(sentences[:-1]) + "... [Response truncated - ask for continuation]"
|
| 145 |
+
# Otherwise, split by word
|
| 146 |
+
else:
|
| 147 |
+
words = text[:max_length].split()
|
| 148 |
+
return ' '.join(words[:-1]) + "... [Response truncated - ask for continuation]"
|
| 149 |
+
|
| 150 |
+
def respond_with_enhanced_streaming(message, history):
|
| 151 |
+
"""Streams the bot's response, handling errors with metrics tracking."""
|
| 152 |
+
|
| 153 |
+
# Start metrics timing
|
| 154 |
+
timing_context = metrics_tracker.start_timing()
|
| 155 |
+
error_occurred = False
|
| 156 |
+
error_message = None
|
| 157 |
+
response = ""
|
| 158 |
+
|
| 159 |
+
try:
|
| 160 |
+
# Build conversation history (last 5 exchanges)
|
| 161 |
+
api_messages = [{"role": "system", "content": SYSTEM_MESSAGE}]
|
| 162 |
+
if history:
|
| 163 |
+
for exchange in history[-5:]:
|
| 164 |
+
if exchange.get("role") == "user":
|
| 165 |
+
api_messages.append({"role": "user", "content": exchange["content"]})
|
| 166 |
+
elif exchange.get("role") == "assistant":
|
| 167 |
+
api_messages.append({"role": "assistant", "content": exchange["content"]})
|
| 168 |
+
|
| 169 |
+
# Add current user message
|
| 170 |
+
api_messages.append({"role": "user", "content": message})
|
| 171 |
+
|
| 172 |
+
# Mark provider API start
|
| 173 |
+
metrics_tracker.mark_provider_start(timing_context)
|
| 174 |
+
|
| 175 |
+
completion = client.chat.completions.create(
|
| 176 |
+
model="Qwen/Qwen2.5-7B-Instruct",
|
| 177 |
+
messages=api_messages,
|
| 178 |
+
max_tokens=4096,
|
| 179 |
+
temperature=0.7,
|
| 180 |
+
top_p=0.9,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Mark provider API end
|
| 184 |
+
metrics_tracker.mark_provider_end(timing_context)
|
| 185 |
+
|
| 186 |
+
response = smart_truncate(completion.choices[0].message.content, max_length=3000)
|
| 187 |
+
|
| 188 |
+
# Stream fake-chunks word by word
|
| 189 |
+
words = response.split()
|
| 190 |
+
partial_response = ""
|
| 191 |
+
|
| 192 |
+
for i, word in enumerate(words):
|
| 193 |
+
partial_response += word + " "
|
| 194 |
+
if i % 4 == 0:
|
| 195 |
+
metrics_tracker.record_chunk(timing_context)
|
| 196 |
+
yield partial_response
|
| 197 |
+
time.sleep(0.03)
|
| 198 |
+
|
| 199 |
+
logger.info(f"Response completed. Length: {len(response)} characters")
|
| 200 |
+
|
| 201 |
+
metrics_tracker.record_chunk(timing_context)
|
| 202 |
+
yield response
|
| 203 |
+
|
| 204 |
+
except Exception as e:
|
| 205 |
+
error_occurred = True
|
| 206 |
+
error_message = str(e)
|
| 207 |
+
logger.exception("Error in response generation")
|
| 208 |
+
yield "Sorry, I encountered an error while generating the response."
|
| 209 |
+
|
| 210 |
+
finally:
|
| 211 |
+
metrics_tracker.log_interaction(
|
| 212 |
+
query=message,
|
| 213 |
+
response=response,
|
| 214 |
+
timing_context=timing_context,
|
| 215 |
+
error_occurred=error_occurred,
|
| 216 |
+
error_message=error_message,
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
# ===============================================================================
|
| 222 |
+
# UI CONFIGURATION SECTION - ALL UI RELATED CODE CENTRALIZED HERE
|
| 223 |
+
# ===============================================================================
|
| 224 |
+
|
| 225 |
+
# --- UI: Custom CSS Styles ---
|
| 226 |
+
custom_css = """
|
| 227 |
/* Import Oswald font - Google Fonts */
|
| 228 |
@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap');
|
| 229 |
|
|
|
|
| 477 |
padding-bottom: 50px !important;
|
| 478 |
}
|
| 479 |
|
| 480 |
+
/* Additional universal selectors for better coverage */
|
| 481 |
+
.svelte-container,
|
| 482 |
+
[class*="svelte"],
|
| 483 |
+
.block,
|
| 484 |
+
[class*="block"] {
|
| 485 |
+
background-color: rgb(240, 236, 230) !important;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
/* Target textboxes more broadly */
|
| 489 |
+
textarea,
|
| 490 |
+
input[type="text"],
|
| 491 |
+
.textbox,
|
| 492 |
+
[class*="textbox"] {
|
| 493 |
+
background-color: #f0ece6 !important;
|
| 494 |
+
border: 1pt solid #59524f !important;
|
| 495 |
+
border-radius: 6px !important;
|
| 496 |
+
color: #120f0e !important;
|
| 497 |
+
font-family: "Oswald", sans-serif !important;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
/* Target buttons more broadly */
|
| 501 |
+
button,
|
| 502 |
+
.btn,
|
| 503 |
+
[class*="button"] {
|
| 504 |
+
font-family: "Oswald", sans-serif !important;
|
| 505 |
+
color: #120f0e !important;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
/* Ensure chatbot container gets the right background */
|
| 509 |
+
[data-testid="chatbot"],
|
| 510 |
+
.chatbot-container,
|
| 511 |
+
[class*="chatbot"] {
|
| 512 |
+
background-color: #d9d1ce !important;
|
| 513 |
+
border: 1pt solid #59524f !important;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
/* Message bubbles - even more comprehensive */
|
| 517 |
+
.message,
|
| 518 |
+
[class*="message"],
|
| 519 |
+
.chat-message,
|
| 520 |
+
[class*="chat"] [class*="message"] {
|
| 521 |
+
font-family: "Oswald", sans-serif !important;
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
/* Bot/Assistant messages */
|
| 525 |
+
.message.bot,
|
| 526 |
+
.message.assistant,
|
| 527 |
+
[class*="message"][class*="bot"],
|
| 528 |
+
[class*="message"][class*="assistant"],
|
| 529 |
+
.bot-message,
|
| 530 |
+
.assistant-message {
|
| 531 |
+
background-color: #f09c7d !important;
|
| 532 |
+
color: #120f0e !important;
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
/* User messages */
|
| 536 |
+
.message.user,
|
| 537 |
+
[class*="message"][class*="user"],
|
| 538 |
+
.user-message {
|
| 539 |
+
background-color: #a69189 !important;
|
| 540 |
+
color: #120f0e !important;
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
/* Force override any Gradio theme variables */
|
| 544 |
+
:root {
|
| 545 |
+
--background-fill-primary: rgb(240, 236, 230) !important;
|
| 546 |
+
--background-fill-secondary: #d9d1ce !important;
|
| 547 |
+
--color-accent: #f09c7d !important;
|
| 548 |
+
--color-accent-soft: #a69189 !important;
|
| 549 |
+
--body-text-color: #120f0e !important;
|
| 550 |
+
--block-background-fill: rgb(240, 236, 230) !important;
|
| 551 |
+
--panel-background-fill: rgb(240, 236, 230) !important;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
/* Override any default Gradio grays */
|
| 555 |
+
.bg-white,
|
| 556 |
+
.bg-gray-50,
|
| 557 |
+
.bg-gray-100,
|
| 558 |
+
.bg-gray-200,
|
| 559 |
+
[class*="bg-white"],
|
| 560 |
+
[class*="bg-gray"] {
|
| 561 |
+
background-color: rgb(240, 236, 230) !important;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
/* Text color overrides */
|
| 565 |
+
.text-gray-700,
|
| 566 |
+
.text-gray-800,
|
| 567 |
+
.text-gray-900,
|
| 568 |
+
[class*="text-gray"] {
|
| 569 |
+
color: #120f0e !important;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
/* Responsive design */
|
| 573 |
@media (max-width: 768px) {
|
| 574 |
.message.bot .markdown,
|
|
|
|
| 582 |
.input-controls {
|
| 583 |
padding: 10px !important;
|
| 584 |
}
|
| 585 |
+
|
| 586 |
+
.title-header h1 {
|
| 587 |
+
font-size: 1.2rem;
|
| 588 |
+
}
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
/* Debug helper - uncomment to see what elements are being styled */
|
| 592 |
+
/*
|
| 593 |
+
* {
|
| 594 |
+
border: 1px solid red !important;
|
| 595 |
+
}
|
| 596 |
+
*/
|
| 597 |
+
"""
|
| 598 |
+
|
| 599 |
+
# --- UI: HTML Head Content ---
|
| 600 |
+
html_head_content = '''
|
| 601 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 602 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 603 |
+
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap" rel="stylesheet">
|
| 604 |
+
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
| 605 |
+
<script>
|
| 606 |
+
// Force light theme in Gradio
|
| 607 |
+
window.addEventListener('DOMContentLoaded', function () {
|
| 608 |
+
const gradioURL = window.location.href;
|
| 609 |
+
const url = new URL(gradioURL);
|
| 610 |
+
const currentTheme = url.searchParams.get('__theme');
|
| 611 |
+
|
| 612 |
+
if (currentTheme !== 'light') {
|
| 613 |
+
url.searchParams.set('__theme', 'light');
|
| 614 |
+
window.location.replace(url.toString());
|
| 615 |
+
}
|
| 616 |
+
});
|
| 617 |
+
</script>
|
| 618 |
+
'''
|
| 619 |
+
|
| 620 |
+
# --- UI: MathJax Configuration ---
|
| 621 |
+
mathjax_config = '''
|
| 622 |
+
<script>
|
| 623 |
+
window.MathJax = {
|
| 624 |
+
tex: {
|
| 625 |
+
inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
|
| 626 |
+
displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
|
| 627 |
+
packages: {'[+]': ['ams']}
|
| 628 |
+
},
|
| 629 |
+
svg: {fontCache: 'global'},
|
| 630 |
+
startup: {
|
| 631 |
+
ready: () => {
|
| 632 |
+
MathJax.startup.defaultReady();
|
| 633 |
+
// Re-render math when new content is added
|
| 634 |
+
const observer = new MutationObserver(function(mutations) {
|
| 635 |
+
MathJax.typesetPromise();
|
| 636 |
+
});
|
| 637 |
+
observer.observe(document.body, {childList: true, subtree: true});
|
| 638 |
+
}
|
| 639 |
+
}
|
| 640 |
+
};
|
| 641 |
+
</script>
|
| 642 |
+
'''
|
| 643 |
+
|
| 644 |
+
# --- UI: Event Handlers ---
|
| 645 |
+
def respond_and_update(message, history):
|
| 646 |
+
"""Main function to handle user submission."""
|
| 647 |
+
if not message.strip():
|
| 648 |
+
return history, ""
|
| 649 |
+
|
| 650 |
+
# Add user message to history
|
| 651 |
+
history.append({"role": "user", "content": message})
|
| 652 |
+
# Yield history to show the user message immediately, and clear the textbox
|
| 653 |
+
yield history, ""
|
| 654 |
+
|
| 655 |
+
# Stream the bot's response
|
| 656 |
+
full_response = ""
|
| 657 |
+
for response_chunk in respond_with_enhanced_streaming(message, history):
|
| 658 |
+
full_response = response_chunk
|
| 659 |
+
# Update the last message (bot's response)
|
| 660 |
+
if len(history) > 0 and history[-1]["role"] == "user":
|
| 661 |
+
history.append({"role": "assistant", "content": full_response})
|
| 662 |
+
else:
|
| 663 |
+
history[-1] = {"role": "assistant", "content": full_response}
|
| 664 |
+
yield history, ""
|
| 665 |
+
|
| 666 |
+
def clear_chat():
|
| 667 |
+
"""Clear the chat history."""
|
| 668 |
+
return [], ""
|
| 669 |
+
|
| 670 |
+
# --- UI: Interface Creation ---
|
| 671 |
+
def create_interface():
|
| 672 |
+
"""Creates and configures the complete Gradio interface."""
|
| 673 |
+
|
| 674 |
+
with gr.Blocks(
|
| 675 |
+
title="EduBot",
|
| 676 |
+
fill_width=True,
|
| 677 |
+
fill_height=True,
|
| 678 |
+
theme=gr.themes.Base()
|
| 679 |
+
) as demo:
|
| 680 |
+
# Add head content and MathJax
|
| 681 |
+
gr.HTML(html_head_content)
|
| 682 |
+
gr.HTML('<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>')
|
| 683 |
+
gr.HTML(mathjax_config)
|
| 684 |
+
|
| 685 |
+
with gr.Column(elem_classes=["main-container"]):
|
| 686 |
+
# Title Section
|
| 687 |
+
gr.HTML('<div class="title-header"><h1>🎓 EduBot</h1></div>')
|
| 688 |
+
|
| 689 |
+
# Chat Section
|
| 690 |
+
with gr.Row():
|
| 691 |
+
chatbot = gr.Chatbot(
|
| 692 |
+
type="messages",
|
| 693 |
+
show_copy_button=True,
|
| 694 |
+
show_share_button=False,
|
| 695 |
+
avatar_images=None,
|
| 696 |
+
elem_id="main-chatbot",
|
| 697 |
+
container=False, # Remove wrapper
|
| 698 |
+
scale=1,
|
| 699 |
+
height="70vh" # Explicit height instead of min_height
|
| 700 |
+
)
|
| 701 |
+
|
| 702 |
+
# Input Section - fixed height
|
| 703 |
+
with gr.Row(elem_classes=["input-controls"]):
|
| 704 |
+
msg = gr.Textbox(
|
| 705 |
+
placeholder="Ask me about math, research, study strategies, or any educational topic...",
|
| 706 |
+
show_label=False,
|
| 707 |
+
lines=4,
|
| 708 |
+
max_lines=6,
|
| 709 |
+
elem_classes=["input-textbox"],
|
| 710 |
+
container=False,
|
| 711 |
+
scale=4
|
| 712 |
+
)
|
| 713 |
+
with gr.Column(elem_classes=["button-column"], scale=1):
|
| 714 |
+
send = gr.Button("Send", elem_classes=["send-button"], size="sm")
|
| 715 |
+
clear = gr.Button("Clear", elem_classes=["clear-button"], size="sm")
|
| 716 |
+
|
| 717 |
+
# Set up event handlers
|
| 718 |
+
msg.submit(respond_and_update, [msg, chatbot], [chatbot, msg])
|
| 719 |
+
send.click(respond_and_update, [msg, chatbot], [chatbot, msg])
|
| 720 |
+
clear.click(clear_chat, outputs=[chatbot, msg])
|
| 721 |
+
|
| 722 |
+
return demo
|
| 723 |
+
|
| 724 |
+
# ===============================================================================
|
| 725 |
+
# END UI CONFIGURATION SECTION
|
| 726 |
+
# ===============================================================================
|
| 727 |
+
|
| 728 |
+
if __name__ == "__main__":
|
| 729 |
+
logger.info("Starting EduBot...")
|
| 730 |
+
demo = create_interface()
|
| 731 |
+
demo.launch(debug=True, share=True)
|
| 732 |
+
|