navidfalah commited on
Commit
b7684e9
·
verified ·
1 Parent(s): ec22a3b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +319 -268
app.py CHANGED
@@ -1,307 +1,358 @@
1
  import gradio as gr
2
  import torch
3
- from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
4
- from peft import PeftModel
5
  import os
6
- from typing import Tuple, Optional
 
7
 
8
- # Configuration
9
- class Config:
10
- MODEL_PATH = "navidfalah/3ai" # Your HF model repo
11
- BASE_MODEL = "mistralai/Mistral-7B-Instruct-v0.1" # Mistral base model
12
- ADAPTER_PATH = "./model" # Local adapter path if needed
13
- MAX_NEW_TOKENS = 2000
14
- TEMPERATURE = 0.7
15
- TOP_P = 0.9
16
-
17
- # Global variables for model and tokenizer
18
- model = None
19
- tokenizer = None
20
 
21
- def load_model() -> Tuple[Optional[object], Optional[object]]:
22
- """Load the fine-tuned satisfaction analysis model."""
23
- global model, tokenizer
24
-
25
- if model is not None and tokenizer is not None:
26
- return model, tokenizer
27
-
 
 
 
 
 
 
 
 
28
  try:
29
- print("🔄 Loading Mistral model and tokenizer...")
30
-
31
- # Load tokenizer from base model (Mistral)
32
- tokenizer = AutoTokenizer.from_pretrained(Config.BASE_MODEL)
33
- if tokenizer.pad_token is None:
34
- tokenizer.pad_token = tokenizer.eos_token
35
- tokenizer.padding_side = "right"
36
-
37
- # Quantization config for efficient inference
38
- bnb_config = BitsAndBytesConfig(
39
- load_in_4bit=True,
40
- bnb_4bit_use_double_quant=True,
41
- bnb_4bit_quant_type="nf4",
42
- bnb_4bit_compute_dtype=torch.float16
43
- )
44
-
45
- # Load base Mistral model
46
- base_model = AutoModelForCausalLM.from_pretrained(
47
- Config.BASE_MODEL,
48
- quantization_config=bnb_config,
49
- device_map="auto",
50
- trust_remote_code=True,
51
- torch_dtype=torch.float16
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  )
53
-
54
- # Try loading adapter from HF repo first
 
55
  try:
56
- model = PeftModel.from_pretrained(
57
- base_model,
58
- Config.MODEL_PATH,
59
- is_trainable=False
 
60
  )
61
- print(" Loaded model from Hugging Face repo")
62
- except:
63
- # Fallback to local adapter if available
64
- if os.path.exists(Config.ADAPTER_PATH):
65
- model = PeftModel.from_pretrained(
66
- base_model,
67
- Config.ADAPTER_PATH,
68
- is_trainable=False
 
69
  )
70
- print(" Loaded model from local adapter")
71
- else:
72
- raise Exception("Could not load adapter from HF or local path")
73
-
74
- model.eval()
75
- print(" Mistral-7B model loaded successfully!")
76
- return model, tokenizer
77
-
78
- except Exception as e:
79
- print(f" Error loading model: {e}")
80
- return None, None
81
-
82
- def create_prompt(work: int, health: int, financial: int, relationship: int, context: str) -> str:
83
- """Create the analysis prompt with user inputs."""
84
- prompt = f"""As a holistic life satisfaction analyst, please provide a comprehensive analysis of this person's overall life satisfaction across all major life domains.
85
-
86
- **Complete Life Satisfaction Assessment:**
87
 
88
- **WORK SATISFACTION** (1-10 scale) {work}/10:
 
 
89
 
90
- **HEALTH & WELLNESS** (1-10 scale) {health}/10:
91
-
92
- **FINANCIAL SATISFACTION** (1-10 scale) {financial}/10:
93
-
94
- **RELATIONSHIP SATISFACTION** (1-10 scale) {relationship}/10:
95
-
96
- **Please provide a comprehensive analysis that includes:**
97
-
98
- 1. **Overall Life Satisfaction Score** (1-10) with detailed rationale
99
- 2. **Domain Rankings:** Rank all four life areas from strongest to weakest
100
- 3. **Interconnection Analysis:** How different life domains influence each other
101
- 4. **Priority Assessment:** Which area needs the most immediate attention and why
102
- 5. **Holistic Improvement Strategy:**
103
- - Critical first steps (next 30 days)
104
- - Balanced development plan (3-6 months)
105
- - Long-term life optimization (6-12 months)
106
- 6. **Life Balance Recommendations:** How to create synergy between all life areas
107
- 7. **Resilience Building:** Strategies to strengthen overall life satisfaction foundation
108
- 8. **Success Metrics:** How to track progress across all domains
109
-
110
- **Context:** {context}"""
111
 
112
- return prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- def analyze_satisfaction(
115
- work: int,
116
- health: int,
117
- financial: int,
118
- relationship: int,
119
- context: str
120
- ) -> str:
121
- """Generate satisfaction analysis based on user inputs."""
122
-
123
- # Load model if not already loaded
124
- model, tokenizer = load_model()
125
 
126
- if model is None or tokenizer is None:
127
- return "❌ Error: Could not load the model. Please try again later."
 
128
 
129
  try:
130
- # Create prompt
131
- prompt = create_prompt(work, health, financial, relationship, context)
 
 
 
 
 
132
 
133
  # Tokenize input
134
- inputs = tokenizer(
135
- prompt,
136
- return_tensors="pt",
137
- truncation=True,
138
- max_length=512
139
- )
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- # Move to GPU if available
142
- if torch.cuda.is_available():
143
- inputs = {k: v.to(model.device) for k, v in inputs.items()}
 
 
 
 
 
 
 
144
 
145
  # Generate response
146
- with torch.no_grad():
147
- outputs = model.generate(
148
- **inputs,
149
- max_new_tokens=Config.MAX_NEW_TOKENS,
150
- temperature=Config.TEMPERATURE,
151
- top_p=Config.TOP_P,
152
- do_sample=True,
153
- pad_token_id=tokenizer.eos_token_id,
154
- eos_token_id=tokenizer.eos_token_id
155
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  # Decode response
158
- full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- # Extract generated text (remove prompt)
161
- generated_text = full_response[len(prompt):].strip()
162
 
163
- # Calculate average score
164
- avg_score = (work + health + financial + relationship) / 4
 
 
 
 
 
 
 
165
 
166
- # Add summary at the beginning
167
- summary = f"📊 **Quick Summary**\n"
168
- summary += f"- Average Satisfaction Score: {avg_score:.1f}/10\n"
169
- summary += f"- Highest Domain: {get_highest_domain(work, health, financial, relationship)}\n"
170
- summary += f"- Lowest Domain: {get_lowest_domain(work, health, financial, relationship)}\n\n"
171
- summary += "---\n\n"
172
 
173
- return summary + generated_text
 
 
 
 
 
 
 
 
174
 
175
  except Exception as e:
176
- return f" Error during analysis: {str(e)}"
 
177
 
178
- def get_highest_domain(work: int, health: int, financial: int, relationship: int) -> str:
179
- """Get the domain with highest satisfaction."""
180
- scores = {
181
- "Work": work,
182
- "Health & Wellness": health,
183
- "Financial": financial,
184
- "Relationships": relationship
185
- }
186
- return max(scores, key=scores.get) + f" ({scores[max(scores, key=scores.get)]}/10)"
187
 
188
- def get_lowest_domain(work: int, health: int, financial: int, relationship: int) -> str:
189
- """Get the domain with lowest satisfaction."""
190
- scores = {
191
- "Work": work,
192
- "Health & Wellness": health,
193
- "Financial": financial,
194
- "Relationships": relationship
195
- }
196
- return min(scores, key=scores.get) + f" ({scores[min(scores, key=scores.get)]}/10)"
197
 
198
- # Gradio Interface
199
- def create_interface():
200
- """Create the Gradio interface."""
 
 
 
201
 
202
- with gr.Blocks(title="Life Satisfaction Analysis", theme=gr.themes.Soft()) as demo:
203
- gr.Markdown(
204
- """
205
- # 🌟 Life Satisfaction Analysis Tool
206
-
207
- This AI-powered tool provides comprehensive analysis of your life satisfaction across four key domains:
208
- Work, Health & Wellness, Financial, and Relationships.
209
-
210
- **How to use:**
211
- 1. Rate your satisfaction in each life domain (1-10 scale)
212
- 2. Provide brief context about your situation
213
- 3. Click "Analyze" to receive personalized insights and recommendations
214
- """
215
- )
216
-
217
- with gr.Row():
218
- with gr.Column(scale=1):
219
- gr.Markdown("### 📊 Rate Your Satisfaction")
220
-
221
- work_score = gr.Slider(
222
- minimum=1,
223
- maximum=10,
224
- value=5,
225
- step=1,
226
- label="💼 Work Satisfaction",
227
- info="How satisfied are you with your career/work life?"
228
- )
229
-
230
- health_score = gr.Slider(
231
- minimum=1,
232
- maximum=10,
233
- value=5,
234
- step=1,
235
- label="🏃 Health & Wellness",
236
- info="How satisfied are you with your physical and mental health?"
237
- )
238
-
239
- financial_score = gr.Slider(
240
- minimum=1,
241
- maximum=10,
242
- value=5,
243
- step=1,
244
- label="💰 Financial Satisfaction",
245
- info="How satisfied are you with your financial situation?"
246
- )
247
-
248
- relationship_score = gr.Slider(
249
- minimum=1,
250
- maximum=10,
251
- value=5,
252
- step=1,
253
- label="❤️ Relationships",
254
- info="How satisfied are you with your personal relationships?"
255
- )
256
-
257
- context_input = gr.Textbox(
258
- label="📝 Context (Optional)",
259
- placeholder="Share any relevant context about your situation (age, goals, challenges, etc.)",
260
- lines=3
261
- )
262
-
263
- analyze_btn = gr.Button("🔍 Analyze My Life Satisfaction", variant="primary")
264
-
265
- with gr.Column(scale=2):
266
- gr.Markdown("### 📋 Your Personalized Analysis")
267
- output = gr.Markdown()
268
-
269
- # Example section
270
- with gr.Row():
271
- gr.Examples(
272
- examples=[
273
- [3, 5, 7, 8, "29-year-old professional seeking work-life balance"],
274
- [7, 4, 6, 5, "45-year-old focusing on health improvement"],
275
- [5, 8, 4, 9, "Recent graduate starting career journey"],
276
- ],
277
- inputs=[work_score, health_score, financial_score, relationship_score, context_input],
278
- label="Example Scenarios"
279
  )
280
-
281
- # Connect the analyze button
282
- analyze_btn.click(
283
- fn=analyze_satisfaction,
284
- inputs=[work_score, health_score, financial_score, relationship_score, context_input],
285
- outputs=output
286
- )
287
-
288
- # Footer
289
- gr.Markdown(
290
- """
291
- ---
292
- 💡 **Note:** This tool provides AI-generated insights based on your inputs.
293
- For professional advice, please consult qualified experts in relevant fields.
294
- """
295
- )
 
 
 
 
 
 
 
296
 
297
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
- # Launch the app
300
  if __name__ == "__main__":
301
- # Load model on startup
302
- print("🚀 Starting Life Satisfaction Analysis Tool...")
303
- load_model()
304
-
305
- # Create and launch interface
306
- demo = create_interface()
307
  demo.launch()
 
1
  import gradio as gr
2
  import torch
3
+ from transformers import AutoTokenizer, AutoModelForCausalLM
4
+ from huggingface_hub import login
5
  import os
6
+ import subprocess
7
+ import sys
8
 
9
+ print("Starting 3AI application...")
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Install required dependencies
12
+ print("Installing required dependencies...")
13
+ try:
14
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "sentencepiece", "protobuf", "peft", "--quiet"])
15
+ print("Dependencies installed successfully!")
16
+ except Exception as e:
17
+ print(f"Warning: Could not install dependencies: {e}")
18
+
19
+ # Import PEFT after installation
20
+ try:
21
+ from peft import PeftModel, PeftConfig
22
+ print("PEFT imported successfully!")
23
+ except ImportError as e:
24
+ print(f"Could not import PEFT: {e}")
25
+ print("Trying to install PEFT again...")
26
  try:
27
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "peft", "--force-reinstall"])
28
+ from peft import PeftModel, PeftConfig
29
+ print("PEFT installed and imported successfully!")
30
+ except Exception as e2:
31
+ print(f"Failed to install PEFT: {e2}")
32
+ print("Continuing without PEFT - will try alternative approach")
33
+ PeftModel = None
34
+ PeftConfig = None
35
+
36
+ # Login using the secret token
37
+ token = os.getenv("HF_TOKEN")
38
+ if token:
39
+ login(token=token)
40
+ print("Successfully logged in to Hugging Face!")
41
+
42
+ # Use your own Hugging Face model
43
+ original_mistral_model = "navidfalah/3ai" # Your model on Hugging Face
44
+ adapter_path = "./model" # Your local LoRA adapter directory (if available)
45
+
46
+ print(f"Loading original Mistral tokenizer from {original_mistral_model}...")
47
+ try:
48
+ # First try: Load with slow tokenizer from your model
49
+ tokenizer = AutoTokenizer.from_pretrained(
50
+ original_mistral_model,
51
+ use_fast=False, # Use slow tokenizer to avoid issues
52
+ force_download=True, # Force fresh download
53
+ resume_download=False
54
+ )
55
+ print("Your model tokenizer loaded successfully!")
56
+ except Exception as e:
57
+ print(f"Error loading tokenizer from your model: {e}")
58
+ try:
59
+ # Second try: Use original Mistral tokenizer
60
+ tokenizer = AutoTokenizer.from_pretrained(
61
+ "mistralai/Mistral-7B-Instruct-v0.1",
62
+ use_fast=False
63
  )
64
+ print("Original Mistral tokenizer loaded successfully!")
65
+ except Exception as e2:
66
+ print(f"Error with original Mistral: {e2}")
67
  try:
68
+ # Third try: Use different Mistral model version
69
+ print("Trying Mistral-7B-Instruct-v0.2...")
70
+ tokenizer = AutoTokenizer.from_pretrained(
71
+ "mistralai/Mistral-7B-Instruct-v0.2",
72
+ use_fast=False
73
  )
74
+ print("Mistral v0.2 tokenizer loaded successfully!")
75
+ except Exception as e3:
76
+ print(f"Error with Mistral v0.2: {e3}")
77
+ try:
78
+ # Fourth try: Use compatible tokenizer
79
+ print("Trying compatible tokenizer...")
80
+ tokenizer = AutoTokenizer.from_pretrained(
81
+ "microsoft/DialoGPT-medium",
82
+ use_fast=False
83
  )
84
+ print("Compatible tokenizer loaded successfully!")
85
+ except Exception as e4:
86
+ print(f"Error with compatible tokenizer: {e4}")
87
+ try:
88
+ # Fifth try: Use GPT-2 as fallback
89
+ print("Using GPT-2 as fallback...")
90
+ tokenizer = AutoTokenizer.from_pretrained("gpt2")
91
+ print("GPT-2 tokenizer loaded successfully!")
92
+ except Exception as e5:
93
+ print(f"Cannot load any tokenizer: {e5}")
94
+ print("Exiting - cannot proceed without tokenizer")
95
+ exit(1)
 
 
 
 
 
96
 
97
+ # Ensure tokenizer has proper tokens
98
+ if tokenizer.pad_token is None:
99
+ tokenizer.pad_token = tokenizer.eos_token
100
 
101
+ print(f"Loading your fine-tuned Mistral model from {model_path}...")
102
+ try:
103
+ # Load your fine-tuned model weights
104
+ model = AutoModelForCausalLM.from_pretrained(
105
+ model_path,
106
+ torch_dtype=torch.float16,
107
+ device_map="auto",
108
+ trust_remote_code=True,
109
+ low_cpu_mem_usage=True,
110
+ local_files_only=True
111
+ )
112
+ print("Fine-tuned Mistral model loaded successfully!")
 
 
 
 
 
 
 
 
 
113
 
114
+ except Exception as e:
115
+ print(f"Error loading fine-tuned model from {model_path}: {e}")
116
+ print("Trying without local_files_only...")
117
+ try:
118
+ model = AutoModelForCausalLM.from_pretrained(
119
+ model_path,
120
+ torch_dtype=torch.float16,
121
+ device_map="auto",
122
+ trust_remote_code=True,
123
+ low_cpu_mem_usage=True
124
+ )
125
+ print("Fine-tuned Mistral model loaded successfully!")
126
+ except Exception as e2:
127
+ print(f"Cannot load fine-tuned model: {e2}")
128
+ print("Exiting - cannot proceed without your fine-tuned model")
129
+ exit(1)
130
 
131
+ def chat_function(message):
132
+ if not message or not message.strip():
133
+ return "Please enter a message to get started!"
 
 
 
 
 
 
 
 
134
 
135
+ # Limit input length
136
+ if len(message) > 300:
137
+ return "Message too long! Please keep it under 300 characters."
138
 
139
  try:
140
+ # Use flexible prompt format based on tokenizer type
141
+ if hasattr(tokenizer, 'chat_template') or 'mistral' in tokenizer.name_or_path.lower():
142
+ # Use Mistral format if it's actually Mistral
143
+ prompt = f"<s>[INST] {message.strip()} [/INST]"
144
+ else:
145
+ # Use simple format for other tokenizers
146
+ prompt = f"Human: {message.strip()}\nAssistant:"
147
 
148
  # Tokenize input
149
+ try:
150
+ inputs = tokenizer(
151
+ prompt,
152
+ return_tensors='pt',
153
+ truncation=True,
154
+ max_length=512,
155
+ padding=True
156
+ )
157
+ input_ids = inputs['input_ids']
158
+ attention_mask = inputs.get('attention_mask', None)
159
+
160
+ except Exception as e:
161
+ print(f"Tokenization error: {e}")
162
+ return f"Error processing your message: {str(e)}"
163
+
164
+ # Validate input
165
+ if input_ids.shape[-1] == 0:
166
+ return "Error: Empty input after encoding"
167
 
168
+ print(f"Input shape: {input_ids.shape}")
169
+
170
+ # Move to model device
171
+ try:
172
+ device = next(model.parameters()).device
173
+ input_ids = input_ids.to(device)
174
+ if attention_mask is not None:
175
+ attention_mask = attention_mask.to(device)
176
+ except Exception as e:
177
+ print(f"Device move error: {e}")
178
 
179
  # Generate response
180
+ try:
181
+ with torch.no_grad():
182
+ # Clear cache to prevent memory issues
183
+ if torch.cuda.is_available():
184
+ torch.cuda.empty_cache()
185
+
186
+ # Conservative generation parameters
187
+ generation_kwargs = {
188
+ 'input_ids': input_ids,
189
+ 'max_new_tokens': 150,
190
+ 'temperature': 0.7,
191
+ 'do_sample': True,
192
+ 'pad_token_id': tokenizer.pad_token_id,
193
+ 'eos_token_id': tokenizer.eos_token_id,
194
+ 'num_return_sequences': 1,
195
+ 'repetition_penalty': 1.1,
196
+ 'top_p': 0.9,
197
+ 'use_cache': True,
198
+ 'num_beams': 1,
199
+ }
200
+
201
+ # Add attention mask if available
202
+ if attention_mask is not None:
203
+ generation_kwargs['attention_mask'] = attention_mask
204
+
205
+ print(f"Generating with input_ids shape: {input_ids.shape}")
206
+ outputs = model.generate(**generation_kwargs)
207
+ print(f"Generated output shape: {outputs.shape}")
208
+
209
+ except Exception as e:
210
+ print(f"Generation error: {e}")
211
+ # Try with minimal settings
212
+ try:
213
+ print("Trying with minimal settings...")
214
+ outputs = model.generate(
215
+ input_ids,
216
+ max_new_tokens=80,
217
+ do_sample=False, # Greedy decoding
218
+ pad_token_id=tokenizer.pad_token_id,
219
+ eos_token_id=tokenizer.eos_token_id,
220
+ )
221
+ print(f"Minimal generation output shape: {outputs.shape}")
222
+ except Exception as e2:
223
+ print(f"Minimal generation also failed: {e2}")
224
+ return f"Error generating response: {str(e)}"
225
 
226
  # Decode response
227
+ try:
228
+ # Extract only the new tokens (response part)
229
+ if outputs.shape[1] > input_ids.shape[1]:
230
+ response_ids = outputs[0][input_ids.shape[1]:]
231
+ response = tokenizer.decode(response_ids, skip_special_tokens=True)
232
+ else:
233
+ # Fallback: decode full output and remove prompt
234
+ full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
235
+ response = full_response.replace(prompt.replace("<s>", "").replace("</s>", ""), "").strip()
236
+
237
+ except Exception as e:
238
+ print(f"Decoding error: {e}")
239
+ try:
240
+ # Last resort: decode full output
241
+ full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
242
+ response = full_response
243
+ except:
244
+ return f"Error decoding response: {str(e)}"
245
 
246
+ # Clean up the response based on tokenizer type
247
+ response = response.strip()
248
 
249
+ # Remove prompt artifacts based on what we used
250
+ if "[/INST]" in response:
251
+ response = response.split("[/INST]")[-1].strip()
252
+ if "[INST]" in response:
253
+ response = response.split("[INST]")[0].strip()
254
+ if "Assistant:" in response:
255
+ response = response.split("Assistant:")[-1].strip()
256
+ if "Human:" in response:
257
+ response = response.split("Human:")[0].strip()
258
 
259
+ # Remove input message if it appears in response
260
+ if message.strip() in response:
261
+ response = response.replace(message.strip(), "").strip()
 
 
 
262
 
263
+ # Limit response length
264
+ if len(response) > 1000:
265
+ response = response[:1000] + "..."
266
+
267
+ # Ensure we have a meaningful response
268
+ if len(response.strip()) < 5:
269
+ response = "I understand your message. How can I help you with that?"
270
+
271
+ return response
272
 
273
  except Exception as e:
274
+ print(f"Unexpected error: {e}")
275
+ return f"Sorry, I encountered an unexpected error: {str(e)}"
276
 
277
+ def clear_chat():
278
+ return "", ""
 
 
 
 
 
 
 
279
 
280
+ # Simple custom CSS
281
+ css = """
282
+ .gradio-container {
283
+ max-width: 700px !important;
284
+ margin: auto !important;
285
+ }
286
+ """
 
 
287
 
288
+ # Create interface
289
+ with gr.Blocks(title="3AI Chat Bot - Fine-tuned Mistral", css=css, theme=gr.themes.Default()) as demo:
290
+ # Header
291
+ gr.Markdown("""
292
+ # 🤖 3AI Chat Bot
293
+ *Powered by your fine-tuned Mistral-7B-Instruct model*
294
 
295
+ **Using your navidfalah/3ai model**
296
+ """)
297
+
298
+ # Main chat area
299
+ with gr.Row():
300
+ with gr.Column():
301
+ message_input = gr.Textbox(
302
+ placeholder="Type your message here... (max 300 characters)",
303
+ label="Your Message",
304
+ lines=3,
305
+ max_lines=4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  )
307
+
308
+ with gr.Row():
309
+ submit_btn = gr.Button("Send", variant="primary", scale=3)
310
+ clear_btn = gr.Button("Clear", variant="secondary", scale=1)
311
+
312
+ # Response area
313
+ response_output = gr.Textbox(
314
+ label="AI Response",
315
+ lines=15,
316
+ max_lines=25,
317
+ interactive=False,
318
+ placeholder="Your fine-tuned model responses will appear here..."
319
+ )
320
+
321
+ # Character counter
322
+ char_count = gr.HTML("<div style='text-align: right; color: #666; font-size: 12px;'>0/300 characters</div>")
323
+
324
+ # Event handlers
325
+ submit_btn.click(
326
+ fn=chat_function,
327
+ inputs=message_input,
328
+ outputs=response_output
329
+ )
330
 
331
+ message_input.submit(
332
+ fn=chat_function,
333
+ inputs=message_input,
334
+ outputs=response_output
335
+ )
336
+
337
+ clear_btn.click(
338
+ fn=clear_chat,
339
+ outputs=[message_input, response_output]
340
+ )
341
+
342
+ # Update character counter
343
+ def update_char_count(text):
344
+ count = len(text) if text else 0
345
+ color = "#e74c3c" if count > 300 else "#666"
346
+ return f"<div style='text-align: right; color: {color}; font-size: 12px;'>{count}/300 characters</div>"
347
+
348
+ message_input.change(
349
+ fn=update_char_count,
350
+ inputs=message_input,
351
+ outputs=char_count
352
+ )
353
+
354
+ # Footer
355
+ gr.Markdown("---\n*Built with your navidfalah/3ai model • Gradio + Transformers*")
356
 
 
357
  if __name__ == "__main__":
 
 
 
 
 
 
358
  demo.launch()