euler314 commited on
Commit
585f5d0
·
verified ·
1 Parent(s): d0f80bb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -179
app.py CHANGED
@@ -8,10 +8,6 @@ import shutil
8
  import io
9
  from PIL import Image
10
  import fitz # PyMuPDF
11
- import re
12
- import json
13
- import time
14
- import components.html as html
15
 
16
  # Set page configuration
17
  st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
@@ -198,11 +194,26 @@ latex_commands = {
198
  }
199
  }
200
 
201
- # Flatten all commands for autocomplete
202
- all_commands = []
203
  for category, commands in latex_commands.items():
204
- for cmd in commands:
205
- all_commands.append(cmd)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  # Default LaTeX template
208
  default_template = r"""\documentclass{article}
@@ -276,141 +287,7 @@ Your conclusion here.
276
  \end{document}
277
  """
278
 
279
- # Create the Monaco Editor component with autocomplete
280
- def create_monaco_editor():
281
- # Convert the LaTeX commands to a format usable by Monaco
282
- monaco_suggestions = []
283
- for category, commands in latex_commands.items():
284
- for cmd, desc in commands.items():
285
- # Format command for Monaco
286
- cmd_text = cmd.replace("{...}", "").replace("...", "")
287
- monaco_suggestions.append({
288
- "label": cmd_text,
289
- "kind": 14, # Snippet
290
- "insertText": cmd_text,
291
- "detail": desc,
292
- "documentation": f"Category: {category}"
293
- })
294
-
295
- # JSON stringify the suggestions for passing to JavaScript
296
- suggestions_json = json.dumps(monaco_suggestions)
297
-
298
- # Create the Monaco editor component with LaTeX syntax highlighting and autocomplete
299
- monaco_editor = f"""
300
- <div id="container" style="width:100%;height:500px;border:1px solid #ccc;"></div>
301
- <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.36.1/min/vs/loader.min.js"></script>
302
- <script>
303
- require.config({{ paths: {{ 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.36.1/min/vs' }}});
304
-
305
- // Before loading vs/editor/editor.main, define a global MonacoEnvironment that overwrites
306
- // the default worker url location (used when creating WebWorkers). The problem here is that
307
- // HTML5 does not allow cross-domain web workers, so we need to proxy the instantiation of
308
- // a web worker through a same-domain script
309
- window.MonacoEnvironment = {{
310
- getWorkerUrl: function(workerId, label) {{
311
- return `data:text/javascript;charset=utf-8,
312
- self.MonacoEnvironment = {{
313
- baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.36.1/min/'
314
- }};
315
- importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.36.1/min/vs/base/worker/workerMain.js');`;
316
- }}
317
- }};
318
-
319
- require(['vs/editor/editor.main'], function() {{
320
- // Define LaTeX language
321
- monaco.languages.register({{ id: 'latex' }});
322
-
323
- // Define LaTeX syntax highlighting
324
- monaco.languages.setMonarchTokensProvider('latex', {{
325
- tokenizer: {{
326
- root: [
327
- [/\\\\[a-zA-Z]+/, 'keyword'],
328
- [/\\\\begin\\{{[^}}]*\\}}/, 'keyword'],
329
- [/\\\\end\\{{[^}}]*\\}}/, 'keyword'],
330
- [/\\$\\$.*?\\$\\$/, 'string'],
331
- [/\\$.*?\\$/, 'string'],
332
- [/%.*$/, 'comment'],
333
- [/\\{{/, 'delimiter.curly'],
334
- [/\\}}/, 'delimiter.curly'],
335
- [/\\[/, 'delimiter.square'],
336
- [/\\]/, 'delimiter.square']
337
- ]
338
- }}
339
- }});
340
-
341
- // Setup autocomplete provider for LaTeX
342
- monaco.languages.registerCompletionItemProvider('latex', {{
343
- provideCompletionItems: function(model, position) {{
344
- const textUntilPosition = model.getValueInRange({{
345
- startLineNumber: position.lineNumber,
346
- startColumn: 1,
347
- endLineNumber: position.lineNumber,
348
- endColumn: position.column
349
- }});
350
-
351
- // Check if we're typing a LaTeX command
352
- const match = textUntilPosition.match(/\\\\([a-zA-Z]*)$/);
353
- if (!match) return {{ suggestions: [] }};
354
-
355
- const word = match[1];
356
- const suggestions = {suggestions_json};
357
-
358
- // Filter suggestions based on what has been typed
359
- return {{
360
- suggestions: suggestions.filter(function(s) {{
361
- return s.label.substring(1).toLowerCase().indexOf(word.toLowerCase()) === 0;
362
- }})
363
- }};
364
- }},
365
- triggerCharacters: ['\\\\']
366
- }});
367
-
368
- // Get the initial value from localStorage or use default
369
- let initialValue = localStorage.getItem('latexEditorContent') ||
370
- {json.dumps(default_template)};
371
-
372
- // Create the editor
373
- const editor = monaco.editor.create(document.getElementById('container'), {{
374
- value: initialValue,
375
- language: 'latex',
376
- theme: 'vs',
377
- automaticLayout: true,
378
- minimap: {{ enabled: false }},
379
- fontSize: 14,
380
- fontFamily: "'Courier New', monospace",
381
- lineNumbers: "on",
382
- scrollBeyondLastLine: false,
383
- tabSize: 2,
384
- wordWrap: "on"
385
- }});
386
-
387
- // Set up change handler to save content to localStorage
388
- editor.onDidChangeModelContent(function() {{
389
- const value = editor.getValue();
390
- localStorage.setItem('latexEditorContent', value);
391
-
392
- // Send value back to Streamlit
393
- if (window.Streamlit) {{
394
- window.Streamlit.setComponentValue(value);
395
- }}
396
- }});
397
-
398
- // Handle initialization from Streamlit
399
- window.addEventListener('message', function(event) {{
400
- if (event.data.type === 'streamlit:render') {{
401
- const args = event.data.args;
402
- if (args.value) {{
403
- editor.setValue(args.value);
404
- }}
405
- }}
406
- }});
407
- }});
408
- </script>
409
- """
410
-
411
- return monaco_editor
412
-
413
- # Add custom CSS
414
  st.markdown("""
415
  <style>
416
  /* Editor styling */
@@ -420,6 +297,11 @@ st.markdown("""
420
  padding: 10px;
421
  background-color: #f8f9fa;
422
  }
 
 
 
 
 
423
 
424
  /* Download button styling */
425
  .download-button {
@@ -509,52 +391,49 @@ st.markdown("""
509
  background-color: #0069d9;
510
  }
511
 
512
- /* PDF preview container */
513
- .pdf-preview-container {
514
- border: 1px solid #dee2e6;
515
- border-radius: 5px;
516
- padding: 15px;
517
  background-color: #f8f9fa;
 
 
 
 
518
  }
519
 
520
- /* Hide Streamlit footer */
521
- footer {display: none !important;}
522
- #MainMenu {visibility: hidden;}
 
 
 
 
 
 
 
 
 
 
 
 
523
  </style>
524
  """, unsafe_allow_html=True)
525
 
526
- # Create a component for the Monaco editor
527
- def monaco_editor_component(key, default_value=""):
528
- if key not in st.session_state:
529
- st.session_state[key] = default_value
530
-
531
- # Create a placeholder for the editor
532
- placeholder = st.empty()
533
-
534
- # Display the Monaco editor
535
- editor_html = create_monaco_editor()
536
- component_value = placeholder.html(editor_html, height=520)
537
-
538
- # Return the current value
539
- if component_value is not None:
540
- st.session_state[key] = component_value
541
-
542
- return st.session_state[key]
543
-
544
  # Main application
545
  def main():
546
  st.title("LaTeX Editor & PDF Compiler")
547
 
548
- # Display installation status
549
- if not is_pdflatex_installed():
550
- st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
551
- st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
552
-
553
  # Initialize session state
554
  if 'latex_code' not in st.session_state:
555
  st.session_state.latex_code = default_template
556
  if 'show_preview' not in st.session_state:
557
  st.session_state.show_preview = False
 
 
 
 
 
 
 
558
 
559
  # Create layout
560
  col1, col2 = st.columns([3, 2])
@@ -562,12 +441,60 @@ def main():
562
  with col1:
563
  st.subheader("LaTeX Editor")
564
 
565
- # Use the Monaco editor component
566
- latex_code = monaco_editor_component("monaco_editor", st.session_state.latex_code)
567
- st.session_state.latex_code = latex_code
568
 
569
- # Create a small spacer
570
- st.write("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
  # Control buttons
573
  col1_1, col1_2, col1_3 = st.columns(3)
@@ -585,6 +512,12 @@ def main():
585
  if st.button("Clear Editor", use_container_width=True):
586
  st.session_state.latex_code = ""
587
  st.rerun()
 
 
 
 
 
 
588
 
589
  with col2:
590
  st.subheader("PDF Output")
 
8
  import io
9
  from PIL import Image
10
  import fitz # PyMuPDF
 
 
 
 
11
 
12
  # Set page configuration
13
  st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
 
194
  }
195
  }
196
 
197
+ # Flatten commands and shortcuts dictionary for autocomplete
198
+ flat_commands = {}
199
  for category, commands in latex_commands.items():
200
+ for cmd, desc in commands.items():
201
+ cmd_parts = cmd.split("{")[0].strip() # Get just the command part
202
+ flat_commands[cmd_parts] = {"full": cmd, "desc": desc, "category": category}
203
+
204
+ # Add common math shortcuts
205
+ math_shortcuts = {
206
+ "\\fr": "\\frac{}{}",
207
+ "\\eq": "\\begin{equation}\n\n\\end{equation}",
208
+ "\\al": "\\begin{align}\n\n\\end{align}",
209
+ "\\it": "\\item ",
210
+ "\\bf": "\\textbf{}",
211
+ "\\sq": "\\sqrt{}",
212
+ "\\vec": "\\vec{}",
213
+ "\\int": "\\int_{}^{}",
214
+ "\\sum": "\\sum_{}^{}",
215
+ "\\lim": "\\lim_{\\to }"
216
+ }
217
 
218
  # Default LaTeX template
219
  default_template = r"""\documentclass{article}
 
287
  \end{document}
288
  """
289
 
290
+ # Add custom CSS with improved sidebar styling
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  st.markdown("""
292
  <style>
293
  /* Editor styling */
 
297
  padding: 10px;
298
  background-color: #f8f9fa;
299
  }
300
+ .stTextArea textarea {
301
+ font-family: 'Courier New', Courier, monospace !important;
302
+ font-size: 14px !important;
303
+ line-height: 1.5 !important;
304
+ }
305
 
306
  /* Download button styling */
307
  .download-button {
 
391
  background-color: #0069d9;
392
  }
393
 
394
+ /* Hint box styling */
395
+ .hint-box {
 
 
 
396
  background-color: #f8f9fa;
397
+ border: 1px solid #dee2e6;
398
+ border-radius: 4px;
399
+ padding: 10px;
400
+ margin-top: 5px;
401
  }
402
 
403
+ /* Shortcut span */
404
+ .shortcut-span {
405
+ background-color: #e9ecef;
406
+ padding: 2px 6px;
407
+ border-radius: 3px;
408
+ font-family: monospace;
409
+ margin-right: 10px;
410
+ }
411
+
412
+ /* Shortcut tip */
413
+ .shortcut-tip {
414
+ font-style: italic;
415
+ color: #6c757d;
416
+ font-size: 0.85em;
417
+ }
418
  </style>
419
  """, unsafe_allow_html=True)
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  # Main application
422
  def main():
423
  st.title("LaTeX Editor & PDF Compiler")
424
 
 
 
 
 
 
425
  # Initialize session state
426
  if 'latex_code' not in st.session_state:
427
  st.session_state.latex_code = default_template
428
  if 'show_preview' not in st.session_state:
429
  st.session_state.show_preview = False
430
+ if 'last_word' not in st.session_state:
431
+ st.session_state.last_word = ""
432
+
433
+ # Display installation status
434
+ if not is_pdflatex_installed():
435
+ st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
436
+ st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
437
 
438
  # Create layout
439
  col1, col2 = st.columns([3, 2])
 
441
  with col1:
442
  st.subheader("LaTeX Editor")
443
 
444
+ # Setup autocompletion hints
445
+ hint_container = st.empty()
 
446
 
447
+ # Detect what's being typed for autocompletion
448
+ current_text = st.session_state.latex_code
449
+ if '\\' in current_text:
450
+ # Find the last command being typed
451
+ lines = current_text.split('\n')
452
+ for line in reversed(lines):
453
+ if '\\' in line:
454
+ last_slash_pos = line.rfind('\\')
455
+ command_start = line[last_slash_pos:]
456
+ # Extract the command without arguments
457
+ command_parts = command_start.split('{')[0].split(' ')[0].strip()
458
+ if command_parts and command_parts != st.session_state.last_word:
459
+ st.session_state.last_word = command_parts
460
+
461
+ # Show hints for matching commands
462
+ matching_commands = []
463
+
464
+ # Check for shortcut matches
465
+ shortcut_matches = []
466
+ for shortcut, expansion in math_shortcuts.items():
467
+ if shortcut.startswith(command_parts):
468
+ shortcut_matches.append((shortcut, expansion))
469
+
470
+ # Check for command matches
471
+ command_matches = []
472
+ for cmd, info in flat_commands.items():
473
+ if cmd.startswith(command_parts):
474
+ command_matches.append((cmd, info))
475
+
476
+ # Combine shortcuts and commands
477
+ matches = shortcut_matches + command_matches[:5] # Limit to top 5 commands
478
+
479
+ if matches:
480
+ hint_html = '<div class="hint-box"><p><strong>Suggestions:</strong></p>'
481
+ for match, info in matches:
482
+ if isinstance(info, dict):
483
+ hint_html += f'<div><span class="shortcut-span">{match}</span> {info["desc"]}</div>'
484
+ else:
485
+ hint_html += f'<div><span class="shortcut-span">{match}</span> expands to <code>{info}</code></div>'
486
+ hint_html += '<p class="shortcut-tip">Use the sidebar to insert full commands</p></div>'
487
+ hint_container.markdown(hint_html, unsafe_allow_html=True)
488
+ break
489
+
490
+ # LaTeX editor
491
+ latex_code = st.text_area(
492
+ "Edit your LaTeX document:",
493
+ value=st.session_state.latex_code,
494
+ height=500,
495
+ key="latex_editor"
496
+ )
497
+ st.session_state.latex_code = latex_code
498
 
499
  # Control buttons
500
  col1_1, col1_2, col1_3 = st.columns(3)
 
512
  if st.button("Clear Editor", use_container_width=True):
513
  st.session_state.latex_code = ""
514
  st.rerun()
515
+
516
+ # Display available shortcuts
517
+ with st.expander("LaTeX Shortcuts"):
518
+ st.markdown("### Quick Shortcuts")
519
+ for shortcut, expansion in math_shortcuts.items():
520
+ st.markdown(f"<span class='shortcut-span'>{shortcut}</span> → <code>{expansion}</code>", unsafe_allow_html=True)
521
 
522
  with col2:
523
  st.subheader("PDF Output")