VladBoyko commited on
Commit
fa4b185
Β·
verified Β·
1 Parent(s): f4da6d0

Update app.py

Browse files

Harmonized how the output is parsed and styled. added and expansion for repetitive text

Files changed (1) hide show
  1. app.py +143 -105
app.py CHANGED
@@ -38,12 +38,11 @@ class VibeThinkerModel:
38
  def generate_response(self, prompt, temperature=0.6, max_new_tokens=8192, max_thinking_tokens=4096):
39
  """Generate response with thinking length control"""
40
  if not self.model or not self.tokenizer:
41
- return "Model not loaded!", 0, 0, 0
42
 
43
  try:
44
  start_time = time.time()
45
 
46
- # Format prompt for competitive coding
47
  formatted_prompt = f"""<|im_start|>system
48
  You are a competitive programming expert. Provide clear, concise solutions to coding problems.
49
 
@@ -59,11 +58,9 @@ Keep reasoning under {max_thinking_tokens} tokens. Be direct and avoid repetitio
59
  <|im_start|>assistant
60
  """
61
 
62
- # Tokenize input
63
  inputs = self.tokenizer(formatted_prompt, return_tensors="pt").to(self.device)
64
  prompt_length = inputs.input_ids.shape[1]
65
 
66
- # Generate
67
  with torch.no_grad():
68
  outputs = self.model.generate(
69
  **inputs,
@@ -76,30 +73,34 @@ Keep reasoning under {max_thinking_tokens} tokens. Be direct and avoid repetitio
76
  pad_token_id=self.tokenizer.eos_token_id,
77
  )
78
 
79
- # Decode output
80
  full_output = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
81
 
82
- # Extract only assistant's response
83
  if "<|im_start|>assistant" in full_output:
84
  generated_text = full_output.split("<|im_start|>assistant")[-1].strip()
85
  else:
86
  generated_text = full_output[len(formatted_prompt):].strip()
87
 
88
- # Check for loops
 
 
 
 
89
  if self._detect_loop(generated_text):
90
  generated_text = self._truncate_loop(generated_text)
 
 
 
91
  generated_text += "\n\n⚠️ *[Repetitive content detected and truncated]*"
92
 
93
  generation_time = time.time() - start_time
94
  completion_length = outputs.shape[1] - prompt_length
95
 
96
- return generated_text, prompt_length, completion_length, generation_time
97
 
98
  except Exception as e:
99
- return f"Error during generation: {str(e)}", 0, 0, 0
100
 
101
  def _detect_loop(self, text):
102
- """Detect repetitive loops"""
103
  words = text.split()
104
  if len(words) < 20:
105
  return False
@@ -115,7 +116,6 @@ Keep reasoning under {max_thinking_tokens} tokens. Be direct and avoid repetitio
115
  return False
116
 
117
  def _truncate_loop(self, text):
118
- """Truncate at loop start"""
119
  words = text.split()
120
  for length in [10, 15, 20]:
121
  if len(words) < length * 2:
@@ -128,30 +128,47 @@ Keep reasoning under {max_thinking_tokens} tokens. Be direct and avoid repetitio
128
  return ' '.join(words[:rest_start])
129
  return text
130
 
131
- def parse_model_output(text):
132
  """
133
- Parse model output into three clean sections:
134
- 1. Thinking (reasoning/analysis) - collapsed by default
135
- 2. Explanation (solution approach without code) - main display
136
- 3. Code blocks (implementation) - separate section with copy/download
137
  """
138
  loop_warning = ""
 
 
139
  if "[Repetitive content detected and truncated]" in text:
140
- loop_warning = "\n\n⚠️ **Note**: Repetitive content was detected and removed"
141
  text = text.replace("⚠️ *[Repetitive content detected and truncated]*", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # Step 1: Extract ALL code blocks first (to separate them from text)
144
  code_pattern = r'```(\w+)?\n(.*?)```'
145
  code_blocks = re.findall(code_pattern, text, re.DOTALL)
146
 
147
- # Remove code blocks from text to get pure text content
148
- text_without_code = re.sub(code_pattern, '###CODE_BLOCK_PLACEHOLDER###', text, flags=re.DOTALL)
149
 
150
- # Step 2: Split into thinking and explanation
151
  thinking_content = ""
152
  explanation_content = text_without_code
153
 
154
- # Try to find explicit thinking tags
155
  thinking_patterns = [
156
  r'<think>(.*?)</think>',
157
  r'<thinking>(.*?)</thinking>',
@@ -164,12 +181,11 @@ def parse_model_output(text):
164
  explanation_content = re.sub(pattern, '', text_without_code, flags=re.DOTALL | re.IGNORECASE).strip()
165
  break
166
 
167
- # If no explicit tags, try to detect thinking section by content
168
  if not thinking_content:
169
- # Look for sections before "Solution:", "Implementation:", "Code:", "Here's"
170
  split_patterns = [
171
  r'^(.*?)(?=\n\n(?:Solution|Implementation|Code|Here\'s|Let me|Let\'s code|Final code))',
172
- r'^(.*?)(?=###CODE_BLOCK_PLACEHOLDER###)',
173
  ]
174
 
175
  for pattern in split_patterns:
@@ -177,7 +193,6 @@ def parse_model_output(text):
177
  if match:
178
  potential_thinking = match.group(1).strip()
179
 
180
- # Only classify as "thinking" if it's substantial and contains reasoning keywords
181
  if len(potential_thinking) > 150:
182
  thinking_lower = potential_thinking.lower()
183
  reasoning_keywords = [
@@ -189,11 +204,9 @@ def parse_model_output(text):
189
  explanation_content = text_without_code[len(potential_thinking):].strip()
190
  break
191
 
192
- # Step 3: Clean up explanation (remove code placeholders and extra whitespace)
193
- explanation_content = explanation_content.replace('###CODE_BLOCK_PLACEHOLDER###', '').strip()
194
- explanation_content = re.sub(r'\n{3,}', '\n\n', explanation_content) # Remove excessive blank lines
195
-
196
- # Remove common prefixes that indicate code section headers
197
  explanation_content = re.sub(
198
  r'(?:Implementation|Code|Solution|Here\'s the code|Final code):\s*$',
199
  '',
@@ -201,7 +214,7 @@ def parse_model_output(text):
201
  flags=re.IGNORECASE
202
  ).strip()
203
 
204
- # Step 4: Handle boxed answers
205
  answer_match = re.search(r'\\boxed\{([^}]+)\}', explanation_content)
206
  if answer_match:
207
  explanation_content = f"**Final Answer:** {answer_match.group(1)}\n\n{explanation_content}"
@@ -212,36 +225,62 @@ def parse_model_output(text):
212
 
213
  def format_output_html(thinking, explanation, code_blocks, prompt_tokens, completion_tokens, generation_time):
214
  """
215
- Format output with clear separation:
216
- - Stats at top
217
- - Thinking (collapsed)
218
- - Explanation (main content, no code)
219
- - Code blocks (separate section)
220
  """
221
  total_tokens = prompt_tokens + completion_tokens
222
  thinking_tokens_est = len(thinking.split()) * 1.3 if thinking else 0
223
  tokens_per_sec = completion_tokens / generation_time if generation_time > 0 else 0
224
 
225
- # Thinking section (collapsed by default)
 
 
 
226
  thinking_html = ""
227
- if thinking:
228
  thinking_escaped = thinking.replace('<', '&lt;').replace('>', '&gt;')
229
  thinking_html = f"""
230
- <details style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
231
- <summary style="cursor: pointer; font-weight: 600; font-size: 16px; color: #495057; user-select: none; display: flex; align-items: center; gap: 8px;">
232
  <span style="font-size: 20px;">🧠</span>
233
- <span>Reasoning Process (~{int(thinking_tokens_est):,} tokens)</span>
234
- <span style="margin-left: auto; font-size: 12px; color: #6c757d; font-weight: normal;">β–Ά Click to expand</span>
235
  </summary>
236
- <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #dee2e6; color: #212529; line-height: 1.7; white-space: pre-wrap; font-size: 14px; font-family: 'SF Mono', Monaco, Consolas, monospace; background: #ffffff; padding: 16px; border-radius: 8px;">
237
  {thinking_escaped}
238
  </div>
239
  </details>
240
  """
241
 
242
- # Code blocks section
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  code_html = ""
244
- if code_blocks:
245
  code_blocks_html = ""
246
  for idx, (lang, code) in enumerate(code_blocks):
247
  lang_display = lang if lang else "code"
@@ -249,32 +288,36 @@ def format_output_html(thinking, explanation, code_blocks, prompt_tokens, comple
249
  code_clean = code.strip()
250
 
251
  code_blocks_html += f"""
252
- <div style="margin-bottom: 16px; background: #1e1e1e; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
253
- <div style="background: #2d2d2d; padding: 12px 20px; color: #ffffff; font-weight: 600; font-size: 13px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #3d3d3d;">
254
- <span>πŸ’» {lang_display}</span>
 
 
 
255
  <div style="display: flex; gap: 8px;">
256
- <button onclick="navigator.clipboard.writeText(document.getElementById('{code_id}').textContent)"
257
- style="background: #4CAF50; color: white; border: none; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.2s;"
258
- onmouseover="this.style.background='#45a049'"
259
- onmouseout="this.style.background='#4CAF50'">
260
  πŸ“‹ Copy
261
  </button>
262
  <button onclick="downloadCode(document.getElementById('{code_id}').textContent, '{lang_display}')"
263
- style="background: #2196F3; color: white; border: none; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.2s;"
264
- onmouseover="this.style.background='#0b7dda'"
265
- onmouseout="this.style.background='#2196F3'">
266
  πŸ’Ύ Download
267
  </button>
268
  </div>
269
  </div>
270
- <pre style="margin: 0; padding: 20px; color: #d4d4d4; overflow-x: auto; font-family: 'SF Mono', Monaco, Consolas, monospace; font-size: 14px; line-height: 1.6; background: #1e1e1e;"><code id="{code_id}">{code_clean}</code></pre>
271
  </div>
272
  """
273
 
274
  code_html = f"""
275
- <div style="margin-top: 24px;">
276
- <h3 style="color: #1a1a1a; font-size: 18px; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
277
- <span style="font-size: 22px;">πŸ“</span> Implementation
 
278
  </h3>
279
  {code_blocks_html}
280
  </div>
@@ -302,57 +345,53 @@ def format_output_html(thinking, explanation, code_blocks, prompt_tokens, comple
302
  </script>
303
  """
304
 
305
- # Escape HTML in explanation to prevent rendering issues
306
- explanation_escaped = explanation.replace('<', '&lt;').replace('>', '&gt;')
 
 
 
 
 
 
 
307
 
308
  html = f"""
309
- <div style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 100%; margin: 0 auto; background: #ffffff; color: #1a1a1a;">
310
 
311
- <!-- Stats -->
312
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; margin-bottom: 24px; color: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
313
- <h3 style="margin: 0 0 12px 0; font-size: 18px; font-weight: 600;">πŸ“Š Generation Stats</h3>
314
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; font-size: 14px;">
315
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
316
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Time</div>
317
- <div style="font-size: 20px; font-weight: bold;">{generation_time:.1f}s</div>
318
  </div>
319
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
320
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Speed</div>
321
- <div style="font-size: 20px; font-weight: bold;">{tokens_per_sec:.1f} t/s</div>
322
  </div>
323
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
324
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Prompt</div>
325
- <div style="font-size: 20px; font-weight: bold;">{prompt_tokens:,}</div>
326
  </div>
327
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
328
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Output</div>
329
- <div style="font-size: 20px; font-weight: bold;">{completion_tokens:,}</div>
330
  </div>
331
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
332
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Thinking</div>
333
- <div style="font-size: 20px; font-weight: bold;">~{int(thinking_tokens_est):,}</div>
334
  </div>
335
- <div style="background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;">
336
- <div style="opacity: 0.9; font-size: 12px; margin-bottom: 4px;">Total</div>
337
- <div style="font-size: 20px; font-weight: bold;">{total_tokens:,}</div>
338
  </div>
339
  </div>
340
  </div>
341
 
342
- <!-- Thinking (Collapsed) -->
343
  {thinking_html}
344
-
345
- <!-- Explanation (Main Content - No Code) -->
346
- <div style="background: #ffffff; border: 2px solid #28a745; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 4px rgba(40,167,69,0.1);">
347
- <h3 style="margin: 0 0 16px 0; color: #28a745; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 8px;">
348
- <span style="font-size: 22px;">βœ…</span> Solution Explanation
349
- </h3>
350
- <div style="color: #212529; line-height: 1.8; font-size: 15px; white-space: pre-wrap; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
351
- {explanation_escaped}
352
- </div>
353
- </div>
354
-
355
- <!-- Code (Separate Section) -->
356
  {code_html}
357
 
358
  </div>
@@ -371,7 +410,7 @@ def generate_solution(prompt, temperature=0.6, max_tokens=8192, max_thinking_tok
371
  progress(0, desc="πŸ”„ Initializing...")
372
  progress(0.2, desc="🧠 Generating solution...")
373
 
374
- response, prompt_tokens, completion_tokens, gen_time = vibe_model.generate_response(
375
  prompt,
376
  temperature=temperature,
377
  max_new_tokens=max_tokens,
@@ -380,8 +419,7 @@ def generate_solution(prompt, temperature=0.6, max_tokens=8192, max_thinking_tok
380
 
381
  progress(0.8, desc="πŸ“ Formatting output...")
382
 
383
- # Parse into three sections: thinking, explanation, code
384
- thinking, explanation, code_blocks = parse_model_output(response)
385
  html_output = format_output_html(thinking, explanation, code_blocks, prompt_tokens, completion_tokens, gen_time)
386
 
387
  progress(1.0, desc="βœ… Complete!")
@@ -429,12 +467,12 @@ with gr.Blocks(
429
  - Lower thinking tokens (1024-2048) for faster, direct solutions
430
  - Higher thinking tokens (4096-8192) for complex reasoning
431
  - Temperature 0.6 balances creativity and accuracy
432
- - Automatic loop detection and truncation
433
 
434
  **Output Structure:**
435
- - 🧠 **Reasoning Process** (collapsed) - The model's thinking steps
436
- - βœ… **Solution Explanation** - Approach and logic without code
437
- - πŸ“ **Implementation** - Clean code with copy/download buttons
 
438
  """)
439
 
440
  generate_btn = gr.Button("πŸš€ Generate Solution", variant="primary", size="lg")
 
38
  def generate_response(self, prompt, temperature=0.6, max_new_tokens=8192, max_thinking_tokens=4096):
39
  """Generate response with thinking length control"""
40
  if not self.model or not self.tokenizer:
41
+ return "Model not loaded!", 0, 0, 0, None
42
 
43
  try:
44
  start_time = time.time()
45
 
 
46
  formatted_prompt = f"""<|im_start|>system
47
  You are a competitive programming expert. Provide clear, concise solutions to coding problems.
48
 
 
58
  <|im_start|>assistant
59
  """
60
 
 
61
  inputs = self.tokenizer(formatted_prompt, return_tensors="pt").to(self.device)
62
  prompt_length = inputs.input_ids.shape[1]
63
 
 
64
  with torch.no_grad():
65
  outputs = self.model.generate(
66
  **inputs,
 
73
  pad_token_id=self.tokenizer.eos_token_id,
74
  )
75
 
 
76
  full_output = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
77
 
 
78
  if "<|im_start|>assistant" in full_output:
79
  generated_text = full_output.split("<|im_start|>assistant")[-1].strip()
80
  else:
81
  generated_text = full_output[len(formatted_prompt):].strip()
82
 
83
+ # Store original before truncation
84
+ original_text = generated_text
85
+ truncated_content = None
86
+
87
+ # Check for loops and truncate if needed
88
  if self._detect_loop(generated_text):
89
  generated_text = self._truncate_loop(generated_text)
90
+ # Calculate what was removed
91
+ if len(generated_text) < len(original_text):
92
+ truncated_content = original_text[len(generated_text):].strip()
93
  generated_text += "\n\n⚠️ *[Repetitive content detected and truncated]*"
94
 
95
  generation_time = time.time() - start_time
96
  completion_length = outputs.shape[1] - prompt_length
97
 
98
+ return generated_text, prompt_length, completion_length, generation_time, truncated_content
99
 
100
  except Exception as e:
101
+ return f"Error during generation: {str(e)}", 0, 0, 0, None
102
 
103
  def _detect_loop(self, text):
 
104
  words = text.split()
105
  if len(words) < 20:
106
  return False
 
116
  return False
117
 
118
  def _truncate_loop(self, text):
 
119
  words = text.split()
120
  for length in [10, 15, 20]:
121
  if len(words) < length * 2:
 
128
  return ' '.join(words[:rest_start])
129
  return text
130
 
131
+ def parse_model_output(text, truncated_content=None):
132
  """
133
+ Parse model output into sections with proper edge case handling
 
 
 
134
  """
135
  loop_warning = ""
136
+ loop_details_html = ""
137
+
138
  if "[Repetitive content detected and truncated]" in text:
 
139
  text = text.replace("⚠️ *[Repetitive content detected and truncated]*", "")
140
+
141
+ # Create expandable section for truncated content
142
+ if truncated_content:
143
+ truncated_escaped = truncated_content.replace('<', '&lt;').replace('>', '&gt;')
144
+ truncated_word_count = len(truncated_content.split())
145
+ loop_details_html = f"""
146
+ <details style="background: #fff3cd; border: 2px solid #ffc107; border-radius: 8px; padding: 16px; margin-top: 12px;">
147
+ <summary style="cursor: pointer; font-weight: 600; font-size: 14px; color: #856404; user-select: none; display: flex; align-items: center; gap: 8px;">
148
+ <span style="font-size: 18px;">⚠️</span>
149
+ <span>Truncated Repetitive Content ({truncated_word_count} words removed)</span>
150
+ <span style="margin-left: auto; font-size: 12px; font-weight: normal;">β–Ά Click to view what was removed</span>
151
+ </summary>
152
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 2px solid #ffc107; color: #856404; line-height: 1.6; white-space: pre-wrap; font-size: 13px; font-family: 'SF Mono', Monaco, 'Courier New', monospace; max-height: 400px; overflow-y: auto;">
153
+ {truncated_escaped}
154
+ </div>
155
+ </details>
156
+ """
157
+
158
+ loop_warning = loop_details_html
159
 
160
+ # Extract all code blocks
161
  code_pattern = r'```(\w+)?\n(.*?)```'
162
  code_blocks = re.findall(code_pattern, text, re.DOTALL)
163
 
164
+ # Remove code blocks from text
165
+ text_without_code = re.sub(code_pattern, '###CODE_PLACEHOLDER###', text, flags=re.DOTALL)
166
 
167
+ # Try to find thinking section
168
  thinking_content = ""
169
  explanation_content = text_without_code
170
 
171
+ # Check for explicit thinking tags
172
  thinking_patterns = [
173
  r'<think>(.*?)</think>',
174
  r'<thinking>(.*?)</thinking>',
 
181
  explanation_content = re.sub(pattern, '', text_without_code, flags=re.DOTALL | re.IGNORECASE).strip()
182
  break
183
 
184
+ # If no explicit tags, try to detect thinking by content
185
  if not thinking_content:
 
186
  split_patterns = [
187
  r'^(.*?)(?=\n\n(?:Solution|Implementation|Code|Here\'s|Let me|Let\'s code|Final code))',
188
+ r'^(.*?)(?=###CODE_PLACEHOLDER###)',
189
  ]
190
 
191
  for pattern in split_patterns:
 
193
  if match:
194
  potential_thinking = match.group(1).strip()
195
 
 
196
  if len(potential_thinking) > 150:
197
  thinking_lower = potential_thinking.lower()
198
  reasoning_keywords = [
 
204
  explanation_content = text_without_code[len(potential_thinking):].strip()
205
  break
206
 
207
+ # Clean up explanation
208
+ explanation_content = explanation_content.replace('###CODE_PLACEHOLDER###', '').strip()
209
+ explanation_content = re.sub(r'\n{3,}', '\n\n', explanation_content)
 
 
210
  explanation_content = re.sub(
211
  r'(?:Implementation|Code|Solution|Here\'s the code|Final code):\s*$',
212
  '',
 
214
  flags=re.IGNORECASE
215
  ).strip()
216
 
217
+ # Handle boxed answers
218
  answer_match = re.search(r'\\boxed\{([^}]+)\}', explanation_content)
219
  if answer_match:
220
  explanation_content = f"**Final Answer:** {answer_match.group(1)}\n\n{explanation_content}"
 
225
 
226
  def format_output_html(thinking, explanation, code_blocks, prompt_tokens, completion_tokens, generation_time):
227
  """
228
+ Format output with harmonized design and edge case handling
 
 
 
 
229
  """
230
  total_tokens = prompt_tokens + completion_tokens
231
  thinking_tokens_est = len(thinking.split()) * 1.3 if thinking else 0
232
  tokens_per_sec = completion_tokens / generation_time if generation_time > 0 else 0
233
 
234
+ # Card style for consistent sections
235
+ card_base_style = "background: #ffffff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"
236
+
237
+ # Thinking section (collapsed, only show if exists)
238
  thinking_html = ""
239
+ if thinking and len(thinking.strip()) > 0:
240
  thinking_escaped = thinking.replace('<', '&lt;').replace('>', '&gt;')
241
  thinking_html = f"""
242
+ <details style="{card_base_style} border-left: 4px solid #6c757d;">
243
+ <summary style="cursor: pointer; font-weight: 600; font-size: 16px; color: #495057; user-select: none; display: flex; align-items: center; gap: 10px; padding: 4px 0;">
244
  <span style="font-size: 20px;">🧠</span>
245
+ <span>Reasoning Process</span>
246
+ <span style="margin-left: auto; font-size: 13px; color: #6c757d; font-weight: normal;">~{int(thinking_tokens_est):,} tokens β€’ Click to expand β–Ό</span>
247
  </summary>
248
+ <div style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #e9ecef; color: #495057; line-height: 1.8; white-space: pre-wrap; font-size: 14px; font-family: 'SF Mono', Monaco, 'Courier New', monospace;">
249
  {thinking_escaped}
250
  </div>
251
  </details>
252
  """
253
 
254
+ # Explanation section (only show if has meaningful content)
255
+ # Note: explanation may contain the loop warning HTML which should NOT be escaped
256
+ explanation_html = ""
257
+ if explanation and len(explanation.strip()) > 10:
258
+ # Split into text and HTML parts
259
+ # If it contains our loop warning HTML, don't escape that part
260
+ if '<details style="background: #fff3cd' in explanation:
261
+ # Split at the warning
262
+ parts = explanation.split('<details style="background: #fff3cd', 1)
263
+ text_part = parts[0].replace('<', '&lt;').replace('>', '&gt;')
264
+ html_part = '<details style="background: #fff3cd' + parts[1] if len(parts) > 1 else ''
265
+ explanation_display = text_part + html_part
266
+ else:
267
+ explanation_display = explanation.replace('<', '&lt;').replace('>', '&gt;')
268
+
269
+ explanation_html = f"""
270
+ <div style="{card_base_style} border-left: 4px solid #28a745;">
271
+ <h3 style="margin: 0 0 16px 0; color: #28a745; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 10px;">
272
+ <span style="font-size: 22px;">βœ…</span>
273
+ <span>Solution Explanation</span>
274
+ </h3>
275
+ <div style="color: #495057; line-height: 1.8; font-size: 15px; white-space: pre-wrap;">
276
+ {explanation_display}
277
+ </div>
278
+ </div>
279
+ """
280
+
281
+ # Code section (only show if code exists)
282
  code_html = ""
283
+ if code_blocks and len(code_blocks) > 0:
284
  code_blocks_html = ""
285
  for idx, (lang, code) in enumerate(code_blocks):
286
  lang_display = lang if lang else "code"
 
288
  code_clean = code.strip()
289
 
290
  code_blocks_html += f"""
291
+ <div style="margin-bottom: 16px; background: #1e1e1e; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.15);">
292
+ <div style="background: #2d2d2d; padding: 12px 20px; color: #e0e0e0; font-weight: 600; font-size: 13px; display: flex; justify-content: space-between; align-items: center;">
293
+ <span style="display: flex; align-items: center; gap: 8px;">
294
+ <span style="font-size: 16px;">πŸ’»</span>
295
+ <span>{lang_display}</span>
296
+ </span>
297
  <div style="display: flex; gap: 8px;">
298
+ <button onclick="navigator.clipboard.writeText(document.getElementById('{code_id}').textContent); this.textContent='βœ“ Copied'; setTimeout(() => this.textContent='πŸ“‹ Copy', 2000)"
299
+ style="background: #28a745; color: white; border: none; padding: 7px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.2s;"
300
+ onmouseover="if(this.textContent==='πŸ“‹ Copy') this.style.background='#218838'"
301
+ onmouseout="if(this.textContent==='πŸ“‹ Copy') this.style.background='#28a745'">
302
  πŸ“‹ Copy
303
  </button>
304
  <button onclick="downloadCode(document.getElementById('{code_id}').textContent, '{lang_display}')"
305
+ style="background: #007bff; color: white; border: none; padding: 7px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.2s;"
306
+ onmouseover="this.style.background='#0056b3'"
307
+ onmouseout="this.style.background='#007bff'">
308
  πŸ’Ύ Download
309
  </button>
310
  </div>
311
  </div>
312
+ <pre style="margin: 0; padding: 20px; color: #d4d4d4; overflow-x: auto; font-family: 'SF Mono', Monaco, 'Courier New', monospace; font-size: 14px; line-height: 1.6; background: #1e1e1e;"><code id="{code_id}">{code_clean}</code></pre>
313
  </div>
314
  """
315
 
316
  code_html = f"""
317
+ <div style="{card_base_style} border-left: 4px solid #007bff;">
318
+ <h3 style="margin: 0 0 20px 0; color: #007bff; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 10px;">
319
+ <span style="font-size: 22px;">πŸ“</span>
320
+ <span>Implementation</span>
321
  </h3>
322
  {code_blocks_html}
323
  </div>
 
345
  </script>
346
  """
347
 
348
+ # If no explanation but has code, add a minimal message
349
+ if not explanation_html and code_html:
350
+ explanation_html = f"""
351
+ <div style="{card_base_style} border-left: 4px solid #6c757d;">
352
+ <p style="color: #6c757d; font-size: 14px; margin: 0; font-style: italic;">
353
+ No explanation provided - see implementation below.
354
+ </p>
355
+ </div>
356
+ """
357
 
358
  html = f"""
359
+ <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 100%; margin: 0 auto; background: #f8f9fa; padding: 20px; border-radius: 12px;">
360
 
361
+ <!-- Stats Card -->
362
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; border-radius: 12px; margin-bottom: 20px; color: white; box-shadow: 0 4px 12px rgba(102,126,234,0.3);">
363
+ <h3 style="margin: 0 0 16px 0; font-size: 17px; font-weight: 600; opacity: 0.95;">πŸ“Š Generation Stats</h3>
364
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; font-size: 13px;">
365
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
366
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Time</div>
367
+ <div style="font-size: 22px; font-weight: 700;">{generation_time:.1f}s</div>
368
  </div>
369
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
370
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Speed</div>
371
+ <div style="font-size: 22px; font-weight: 700;">{tokens_per_sec:.1f} t/s</div>
372
  </div>
373
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
374
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Prompt</div>
375
+ <div style="font-size: 22px; font-weight: 700;">{prompt_tokens:,}</div>
376
  </div>
377
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
378
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Output</div>
379
+ <div style="font-size: 22px; font-weight: 700;">{completion_tokens:,}</div>
380
  </div>
381
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
382
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Thinking</div>
383
+ <div style="font-size: 22px; font-weight: 700;">~{int(thinking_tokens_est):,}</div>
384
  </div>
385
+ <div style="background: rgba(255,255,255,0.15); padding: 14px; border-radius: 8px; backdrop-filter: blur(10px);">
386
+ <div style="opacity: 0.85; font-size: 11px; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;">Total</div>
387
+ <div style="font-size: 22px; font-weight: 700;">{total_tokens:,}</div>
388
  </div>
389
  </div>
390
  </div>
391
 
392
+ <!-- Content Sections -->
393
  {thinking_html}
394
+ {explanation_html}
 
 
 
 
 
 
 
 
 
 
 
395
  {code_html}
396
 
397
  </div>
 
410
  progress(0, desc="πŸ”„ Initializing...")
411
  progress(0.2, desc="🧠 Generating solution...")
412
 
413
+ response, prompt_tokens, completion_tokens, gen_time, truncated = vibe_model.generate_response(
414
  prompt,
415
  temperature=temperature,
416
  max_new_tokens=max_tokens,
 
419
 
420
  progress(0.8, desc="πŸ“ Formatting output...")
421
 
422
+ thinking, explanation, code_blocks = parse_model_output(response, truncated)
 
423
  html_output = format_output_html(thinking, explanation, code_blocks, prompt_tokens, completion_tokens, gen_time)
424
 
425
  progress(1.0, desc="βœ… Complete!")
 
467
  - Lower thinking tokens (1024-2048) for faster, direct solutions
468
  - Higher thinking tokens (4096-8192) for complex reasoning
469
  - Temperature 0.6 balances creativity and accuracy
 
470
 
471
  **Output Structure:**
472
+ - 🧠 **Reasoning** (collapsed) - Model's thinking process
473
+ - βœ… **Explanation** - Solution approach without code
474
+ - πŸ“ **Implementation** - Clean code with copy/download
475
+ - ⚠️ **Truncated Content** (if detected) - View removed repetitions
476
  """)
477
 
478
  generate_btn = gr.Button("πŸš€ Generate Solution", variant="primary", size="lg")