attempt direct install grc model on script load
Browse files
app.py
CHANGED
|
@@ -3,11 +3,11 @@ import spacy
|
|
| 3 |
from spacy import displacy
|
| 4 |
import base64
|
| 5 |
import traceback
|
|
|
|
| 6 |
import sys
|
| 7 |
import os
|
| 8 |
-
import
|
| 9 |
-
import
|
| 10 |
-
from typing import Dict, Optional, Tuple, List
|
| 11 |
|
| 12 |
# ============================================================================
|
| 13 |
# CONFIGURATION
|
|
@@ -77,44 +77,31 @@ UI_TEXT = {
|
|
| 77 |
}
|
| 78 |
}
|
| 79 |
|
|
|
|
|
|
|
| 80 |
# ============================================================================
|
| 81 |
-
# MODEL
|
| 82 |
# ============================================================================
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
stderr=subprocess.PIPE
|
| 94 |
-
)
|
| 95 |
-
print(f"✓ Successfully installed {model_name}")
|
| 96 |
return True
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
return False
|
| 100 |
-
|
| 101 |
-
def install_grecy_model(model_name: str) -> bool:
|
| 102 |
-
"""Install a grecy model for Ancient Greek."""
|
| 103 |
try:
|
| 104 |
-
#
|
| 105 |
-
print(f"Ensuring grecy package is installed...")
|
| 106 |
-
subprocess.check_call(
|
| 107 |
-
[sys.executable, "-m", "pip", "install", "-U", "grecy"],
|
| 108 |
-
stdout=subprocess.PIPE,
|
| 109 |
-
stderr=subprocess.PIPE
|
| 110 |
-
)
|
| 111 |
-
|
| 112 |
-
print(f"Installing grecy model: {model_name}")
|
| 113 |
result = subprocess.run(
|
| 114 |
[sys.executable, "-m", "grecy", "install", model_name],
|
| 115 |
capture_output=True,
|
| 116 |
text=True,
|
| 117 |
-
timeout=
|
| 118 |
)
|
| 119 |
|
| 120 |
print(result.stdout)
|
|
@@ -122,395 +109,195 @@ def install_grecy_model(model_name: str) -> bool:
|
|
| 122 |
print(f"stderr: {result.stderr}")
|
| 123 |
|
| 124 |
if result.returncode == 0:
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
return True
|
| 127 |
else:
|
| 128 |
-
print(f"✗
|
| 129 |
return False
|
| 130 |
|
| 131 |
except subprocess.TimeoutExpired:
|
| 132 |
-
print(f"✗ Installation
|
| 133 |
return False
|
| 134 |
except Exception as e:
|
| 135 |
-
print(f"✗
|
| 136 |
traceback.print_exc()
|
| 137 |
return False
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
| 144 |
try:
|
| 145 |
-
import site
|
| 146 |
-
import glob
|
| 147 |
-
|
| 148 |
-
# Find the model in site-packages
|
| 149 |
-
site_packages = site.getsitepackages()
|
| 150 |
-
|
| 151 |
-
for sp in site_packages:
|
| 152 |
-
# Look for the model directory
|
| 153 |
-
model_pattern = os.path.join(sp, model_name.replace('_', '-') + '*')
|
| 154 |
-
matches = glob.glob(model_pattern)
|
| 155 |
-
|
| 156 |
-
if matches:
|
| 157 |
-
model_path = matches[0]
|
| 158 |
-
print(f"Found grecy model at: {model_path}")
|
| 159 |
-
|
| 160 |
-
# Try to load from the path
|
| 161 |
-
nlp = spacy.load(model_path)
|
| 162 |
-
print(f"✓ Successfully loaded grecy model from {model_path}")
|
| 163 |
-
return nlp
|
| 164 |
-
|
| 165 |
-
# If not found in site-packages, try standard load
|
| 166 |
-
print(f"Model not found in site-packages, trying standard load...")
|
| 167 |
nlp = spacy.load(model_name)
|
| 168 |
return nlp
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
print(f"✗ Failed to load grecy model directly: {e}")
|
| 172 |
-
traceback.print_exc()
|
| 173 |
-
return None
|
| 174 |
-
|
| 175 |
-
def load_model_with_retry(lang_code: str, model_name: str, model_type: str, max_retries: int = 2) -> Optional[spacy.Language]:
|
| 176 |
-
"""
|
| 177 |
-
Load a spaCy model with installation retry logic.
|
| 178 |
-
|
| 179 |
-
Args:
|
| 180 |
-
lang_code: Language code (e.g., 'de', 'en', 'grc')
|
| 181 |
-
model_name: Name of the model to load
|
| 182 |
-
model_type: Type of model ('spacy' or 'grecy')
|
| 183 |
-
max_retries: Maximum number of installation attempts
|
| 184 |
-
|
| 185 |
-
Returns:
|
| 186 |
-
Loaded spaCy model or None if loading fails
|
| 187 |
-
"""
|
| 188 |
-
for attempt in range(max_retries):
|
| 189 |
try:
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
# For grecy models, use special loading method
|
| 193 |
-
if model_type == "grecy":
|
| 194 |
-
nlp = load_grecy_model_directly(model_name)
|
| 195 |
-
if nlp is not None:
|
| 196 |
-
print(f"✓ Successfully loaded {model_name}")
|
| 197 |
-
return nlp
|
| 198 |
-
raise OSError(f"Could not load grecy model {model_name}")
|
| 199 |
-
else:
|
| 200 |
-
# Standard spaCy model loading
|
| 201 |
-
nlp = spacy.load(model_name)
|
| 202 |
-
print(f"✓ Successfully loaded {model_name}")
|
| 203 |
-
return nlp
|
| 204 |
-
|
| 205 |
-
except OSError as e:
|
| 206 |
-
print(f"✗ Model '{model_name}' not found: {e}")
|
| 207 |
-
|
| 208 |
-
if attempt < max_retries - 1:
|
| 209 |
-
# Try to install the model
|
| 210 |
-
print(f"Attempting to install {model_name}...")
|
| 211 |
-
|
| 212 |
-
if model_type == "spacy":
|
| 213 |
-
success = install_spacy_model(model_name)
|
| 214 |
-
elif model_type == "grecy":
|
| 215 |
-
success = install_grecy_model(model_name)
|
| 216 |
-
else:
|
| 217 |
-
print(f"Unknown model type: {model_type}")
|
| 218 |
-
return None
|
| 219 |
-
|
| 220 |
-
if not success:
|
| 221 |
-
print(f"Installation failed for {model_name}")
|
| 222 |
-
if attempt == max_retries - 2:
|
| 223 |
-
return None
|
| 224 |
-
else:
|
| 225 |
-
# Give the system a moment to register the new model
|
| 226 |
-
import time
|
| 227 |
-
time.sleep(2)
|
| 228 |
-
# Refresh Python's module cache
|
| 229 |
-
importlib.invalidate_caches()
|
| 230 |
-
else:
|
| 231 |
-
print(f"✗ All attempts to load {model_name} failed")
|
| 232 |
-
return None
|
| 233 |
-
|
| 234 |
except Exception as e:
|
| 235 |
-
print(f"✗
|
| 236 |
-
traceback.print_exc()
|
| 237 |
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
def initialize_models():
|
| 242 |
-
"""
|
| 243 |
print("\n" + "="*70)
|
| 244 |
print("INITIALIZING MODELS")
|
| 245 |
-
print("="*70)
|
| 246 |
|
| 247 |
for lang_code, (lang_name, model_name, model_type) in MODEL_INFO.items():
|
| 248 |
-
print(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
-
|
| 251 |
|
| 252 |
-
if nlp
|
| 253 |
-
|
| 254 |
-
print(f"✓ {lang_name} model ready")
|
| 255 |
else:
|
| 256 |
-
|
| 257 |
-
print(f"✗ {lang_name} model FAILED - analysis will be unavailable")
|
| 258 |
|
| 259 |
-
|
| 260 |
-
print("
|
| 261 |
-
print("="*70)
|
| 262 |
-
|
| 263 |
-
# Print summary
|
| 264 |
-
loaded = sum(1 for model in MODELS.values() if model is not None)
|
| 265 |
-
total = len(MODELS)
|
| 266 |
-
print(f"\nLoaded {loaded}/{total} models successfully")
|
| 267 |
-
|
| 268 |
-
if loaded < total:
|
| 269 |
-
print("\n⚠ WARNING: Some models failed to load")
|
| 270 |
-
for lang_code, model in MODELS.items():
|
| 271 |
-
if model is None:
|
| 272 |
-
lang_name = MODEL_INFO[lang_code][0]
|
| 273 |
-
print(f" - {lang_name} ({lang_code}): UNAVAILABLE")
|
| 274 |
-
|
| 275 |
-
print("\n")
|
| 276 |
|
| 277 |
# ============================================================================
|
| 278 |
-
#
|
| 279 |
# ============================================================================
|
| 280 |
|
| 281 |
def get_analysis(ui_lang: str, model_lang: str, text: str):
|
| 282 |
-
"""
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
Returns tuple of outputs for Gradio.
|
| 286 |
-
"""
|
| 287 |
-
current_ui_lang_code = ui_lang.lower()
|
| 288 |
-
current_ui_config = UI_TEXT.get(current_ui_lang_code, UI_TEXT["en"])
|
| 289 |
-
error_prefix = current_ui_config["error_message"]
|
| 290 |
|
| 291 |
try:
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
[],
|
| 296 |
-
[],
|
| 297 |
-
"<p style='color: orange;'>No text provided for analysis.</p>",
|
| 298 |
-
gr.Button(value=current_ui_config["button_text"], interactive=True)
|
| 299 |
-
)
|
| 300 |
-
|
| 301 |
-
# Extract language code from selection like 'GRC (grc_proiel_trf)'
|
| 302 |
-
lang_code = model_lang.split(" ")[0].lower()
|
| 303 |
|
| 304 |
-
|
| 305 |
-
if lang_code not in MODELS:
|
| 306 |
-
raise ValueError(f"Unknown language code: '{lang_code}'")
|
| 307 |
|
| 308 |
-
if MODELS[lang_code] is None:
|
| 309 |
-
|
| 310 |
-
model_name = MODEL_INFO[lang_code][1]
|
| 311 |
-
raise ValueError(
|
| 312 |
-
f"Model for {lang_name} ('{model_name}') failed to load during startup. "
|
| 313 |
-
f"Please check the logs or try restarting the Space."
|
| 314 |
-
)
|
| 315 |
|
| 316 |
-
# Get the model and process text
|
| 317 |
nlp = MODELS[lang_code]
|
| 318 |
doc = nlp(text)
|
| 319 |
|
| 320 |
-
# Prepare outputs
|
| 321 |
dataframe_output = []
|
| 322 |
json_output = []
|
| 323 |
|
| 324 |
-
# Extract linguistic information for each token
|
| 325 |
for token in doc:
|
| 326 |
-
# Safely extract attributes
|
| 327 |
morph_str = str(token.morph) if hasattr(token, 'morph') else ''
|
| 328 |
dep_str = token.dep_ if doc.has_annotation("DEP") else ''
|
| 329 |
tag_str = token.tag_ if hasattr(token, 'tag_') else ''
|
| 330 |
pos_str = token.pos_ if hasattr(token, 'pos_') else ''
|
| 331 |
lemma_str = token.lemma_ if token.lemma != 0 else token.text
|
| 332 |
|
| 333 |
-
# Add to JSON output
|
| 334 |
json_output.append({
|
| 335 |
-
"word": token.text,
|
| 336 |
-
"
|
| 337 |
-
"pos": pos_str,
|
| 338 |
-
"tag": tag_str,
|
| 339 |
-
"morphology": morph_str,
|
| 340 |
-
"dependency": dep_str,
|
| 341 |
"is_stopword": token.is_stop
|
| 342 |
})
|
| 343 |
|
| 344 |
-
|
| 345 |
-
dataframe_output.append([
|
| 346 |
-
token.text,
|
| 347 |
-
lemma_str,
|
| 348 |
-
pos_str,
|
| 349 |
-
tag_str,
|
| 350 |
-
morph_str,
|
| 351 |
-
dep_str
|
| 352 |
-
])
|
| 353 |
|
| 354 |
-
# Generate
|
| 355 |
-
|
| 356 |
if doc.has_annotation("DEP"):
|
| 357 |
try:
|
| 358 |
-
options = {
|
| 359 |
-
"compact": True,
|
| 360 |
-
"bg": "#ffffff",
|
| 361 |
-
"color": "#000000",
|
| 362 |
-
"font": "Source Sans Pro"
|
| 363 |
-
}
|
| 364 |
html_svg = displacy.render(doc, style="dep", jupyter=False, options=options)
|
| 365 |
svg_b64 = base64.b64encode(html_svg.encode("utf-8")).decode("utf-8")
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
<img src="data:image/svg+xml;base64,{svg_b64}" alt="Dependency Parse" />
|
| 370 |
-
</div>
|
| 371 |
-
"""
|
| 372 |
-
except Exception as viz_error:
|
| 373 |
-
print(f"Warning: Could not generate visualization: {viz_error}")
|
| 374 |
-
html_out_content = f"<p style='color: orange;'>Dependency visualization could not be generated: {viz_error}</p>"
|
| 375 |
else:
|
| 376 |
-
|
| 377 |
-
html_out_content = f"""
|
| 378 |
-
<p style='color: orange;'>
|
| 379 |
-
Dependency parse visualization is not available for the selected model ('{model_name}').
|
| 380 |
-
The model may not include a parser component.
|
| 381 |
-
</p>
|
| 382 |
-
"""
|
| 383 |
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
dataframe_output,
|
| 387 |
-
json_output,
|
| 388 |
-
html_out_content,
|
| 389 |
-
gr.Button(value=current_ui_config["button_text"], interactive=True)
|
| 390 |
-
)
|
| 391 |
|
| 392 |
except Exception as e:
|
| 393 |
-
print(f"--- ERROR during get_analysis ---")
|
| 394 |
traceback.print_exc()
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
<div style='color: red; border: 1px solid red; padding: 10px; border-radius: 5px;
|
| 399 |
-
background-color: #fff5f5;'>
|
| 400 |
-
<strong>{error_prefix}</strong> {error_details}
|
| 401 |
-
</div>
|
| 402 |
-
"""
|
| 403 |
-
|
| 404 |
-
return (
|
| 405 |
-
[[f"{error_prefix}{error_details}"]],
|
| 406 |
-
{"error": error_details},
|
| 407 |
-
error_html,
|
| 408 |
-
gr.Button(value=current_ui_config["button_text"], interactive=True)
|
| 409 |
-
)
|
| 410 |
|
| 411 |
# ============================================================================
|
| 412 |
-
# UI
|
| 413 |
# ============================================================================
|
| 414 |
|
| 415 |
def update_ui(ui_lang: str):
|
| 416 |
-
"""Update
|
| 417 |
-
|
| 418 |
-
ui_config = UI_TEXT.get(lang_code, UI_TEXT["en"])
|
| 419 |
-
|
| 420 |
return [
|
| 421 |
gr.Markdown(value=ui_config["title"]),
|
| 422 |
gr.Markdown(value=ui_config["subtitle"]),
|
| 423 |
gr.Radio(label=ui_config["ui_lang_label"]),
|
| 424 |
gr.Radio(label=ui_config["model_lang_label"]),
|
| 425 |
-
gr.Textbox(
|
| 426 |
-
label=ui_config["input_label"],
|
| 427 |
-
placeholder=ui_config["input_placeholder"]
|
| 428 |
-
),
|
| 429 |
gr.Button(value=ui_config["button_text"]),
|
| 430 |
gr.Tab(label=ui_config["tab_graphic"]),
|
| 431 |
gr.Tab(label=ui_config["tab_table"]),
|
| 432 |
gr.Tab(label=ui_config["tab_json"]),
|
| 433 |
gr.HTML(label=ui_config["html_label"]),
|
| 434 |
-
gr.DataFrame(
|
| 435 |
-
label=ui_config["table_label"],
|
| 436 |
-
headers=ui_config["table_headers"],
|
| 437 |
-
interactive=False
|
| 438 |
-
),
|
| 439 |
gr.JSON(label=ui_config["json_label"])
|
| 440 |
]
|
| 441 |
|
| 442 |
-
# ============================================================================
|
| 443 |
-
# GRADIO INTERFACE
|
| 444 |
-
# ============================================================================
|
| 445 |
-
|
| 446 |
def create_interface():
|
| 447 |
-
"""Create
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
# Generate model choices dynamically
|
| 451 |
-
model_lang_choices = [
|
| 452 |
-
f"{k.upper()} ({v[1]})"
|
| 453 |
-
for k, v in MODEL_INFO.items()
|
| 454 |
-
]
|
| 455 |
|
| 456 |
with gr.Blocks(title="Multilingual Morpho-Syntactic Analyzer") as demo:
|
| 457 |
-
# Header
|
| 458 |
with gr.Row():
|
| 459 |
-
ui_lang_radio = gr.Radio(
|
| 460 |
-
|
| 461 |
-
label=default_config["ui_lang_label"],
|
| 462 |
-
value="EN"
|
| 463 |
-
)
|
| 464 |
-
model_lang_radio = gr.Radio(
|
| 465 |
-
model_lang_choices,
|
| 466 |
-
label=default_config["model_lang_label"],
|
| 467 |
-
value=model_lang_choices[0]
|
| 468 |
-
)
|
| 469 |
|
| 470 |
-
markdown_title = gr.Markdown(
|
| 471 |
-
markdown_subtitle = gr.Markdown(
|
|
|
|
|
|
|
| 472 |
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
with gr.Tab(default_config["tab_graphic"]) as tab_graphic:
|
| 484 |
-
html_out = gr.HTML(label=default_config["html_label"])
|
| 485 |
-
|
| 486 |
-
with gr.Tab(default_config["tab_table"]) as tab_table:
|
| 487 |
-
df_out = gr.DataFrame(
|
| 488 |
-
label=default_config["table_label"],
|
| 489 |
-
headers=default_config["table_headers"],
|
| 490 |
-
interactive=False
|
| 491 |
-
)
|
| 492 |
-
|
| 493 |
-
with gr.Tab(default_config["tab_json"]) as tab_json:
|
| 494 |
-
json_out = gr.JSON(label=default_config["json_label"])
|
| 495 |
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
outputs=[df_out, json_out, html_out, analyze_button],
|
| 501 |
-
api_name="get_morphology"
|
| 502 |
-
)
|
| 503 |
-
|
| 504 |
-
ui_lang_radio.change(
|
| 505 |
-
fn=update_ui,
|
| 506 |
-
inputs=ui_lang_radio,
|
| 507 |
-
outputs=[
|
| 508 |
-
markdown_title, markdown_subtitle, ui_lang_radio, model_lang_radio,
|
| 509 |
-
text_input, analyze_button, tab_graphic, tab_table, tab_json,
|
| 510 |
-
html_out, df_out, json_out
|
| 511 |
-
]
|
| 512 |
-
)
|
| 513 |
-
|
| 514 |
return demo
|
| 515 |
|
| 516 |
# ============================================================================
|
|
@@ -520,16 +307,8 @@ def create_interface():
|
|
| 520 |
if __name__ == "__main__":
|
| 521 |
print("\n" + "="*70)
|
| 522 |
print("MULTILINGUAL MORPHO-SYNTACTIC ANALYZER")
|
| 523 |
-
print("Starting application...")
|
| 524 |
print("="*70 + "\n")
|
| 525 |
|
| 526 |
-
# Initialize all models
|
| 527 |
initialize_models()
|
| 528 |
-
|
| 529 |
-
# Create and launch the interface
|
| 530 |
demo = create_interface()
|
| 531 |
-
demo.launch(
|
| 532 |
-
server_name="0.0.0.0",
|
| 533 |
-
server_port=7860,
|
| 534 |
-
show_error=True
|
| 535 |
-
)
|
|
|
|
| 3 |
from spacy import displacy
|
| 4 |
import base64
|
| 5 |
import traceback
|
| 6 |
+
import subprocess
|
| 7 |
import sys
|
| 8 |
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Dict, Optional
|
|
|
|
| 11 |
|
| 12 |
# ============================================================================
|
| 13 |
# CONFIGURATION
|
|
|
|
| 77 |
}
|
| 78 |
}
|
| 79 |
|
| 80 |
+
MODELS: Dict[str, Optional[spacy.Language]] = {}
|
| 81 |
+
|
| 82 |
# ============================================================================
|
| 83 |
+
# GRECY MODEL INSTALLATION
|
| 84 |
# ============================================================================
|
| 85 |
|
| 86 |
+
def install_grecy_once(model_name: str) -> bool:
|
| 87 |
+
"""
|
| 88 |
+
Install grecy model only if not already installed.
|
| 89 |
+
Uses a marker file to track installation.
|
| 90 |
+
"""
|
| 91 |
+
marker_file = Path(f".{model_name}_installed")
|
| 92 |
+
|
| 93 |
+
if marker_file.exists():
|
| 94 |
+
print(f"✓ {model_name} already installed (marker found)")
|
|
|
|
|
|
|
|
|
|
| 95 |
return True
|
| 96 |
+
|
| 97 |
+
print(f"Installing grecy model: {model_name} (this will take several minutes)...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
try:
|
| 99 |
+
# Run the grecy install command
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
result = subprocess.run(
|
| 101 |
[sys.executable, "-m", "grecy", "install", model_name],
|
| 102 |
capture_output=True,
|
| 103 |
text=True,
|
| 104 |
+
timeout=900 # 15 minute timeout
|
| 105 |
)
|
| 106 |
|
| 107 |
print(result.stdout)
|
|
|
|
| 109 |
print(f"stderr: {result.stderr}")
|
| 110 |
|
| 111 |
if result.returncode == 0:
|
| 112 |
+
# Create marker file
|
| 113 |
+
marker_file.touch()
|
| 114 |
+
print(f"✓ Successfully installed {model_name}")
|
| 115 |
return True
|
| 116 |
else:
|
| 117 |
+
print(f"✗ Installation failed with return code {result.returncode}")
|
| 118 |
return False
|
| 119 |
|
| 120 |
except subprocess.TimeoutExpired:
|
| 121 |
+
print(f"✗ Installation timed out")
|
| 122 |
return False
|
| 123 |
except Exception as e:
|
| 124 |
+
print(f"✗ Installation error: {e}")
|
| 125 |
traceback.print_exc()
|
| 126 |
return False
|
| 127 |
|
| 128 |
+
# ============================================================================
|
| 129 |
+
# MODEL LOADING
|
| 130 |
+
# ============================================================================
|
| 131 |
+
|
| 132 |
+
def load_spacy_model(model_name: str) -> Optional[spacy.Language]:
|
| 133 |
+
"""Load or install a standard spaCy model."""
|
| 134 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
nlp = spacy.load(model_name)
|
| 136 |
return nlp
|
| 137 |
+
except OSError:
|
| 138 |
+
print(f"Installing {model_name}...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
try:
|
| 140 |
+
subprocess.check_call([sys.executable, "-m", "spacy", "download", model_name])
|
| 141 |
+
return spacy.load(model_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
except Exception as e:
|
| 143 |
+
print(f"✗ Failed to install {model_name}: {e}")
|
|
|
|
| 144 |
return None
|
| 145 |
+
|
| 146 |
+
def load_grecy_model(model_name: str) -> Optional[spacy.Language]:
|
| 147 |
+
"""Load a grecy model, installing if necessary."""
|
| 148 |
+
# First, ensure it's installed
|
| 149 |
+
install_grecy_once(model_name)
|
| 150 |
|
| 151 |
+
# Try to load it
|
| 152 |
+
try:
|
| 153 |
+
nlp = spacy.load(model_name)
|
| 154 |
+
print(f"✓ Loaded {model_name}")
|
| 155 |
+
return nlp
|
| 156 |
+
except Exception as e:
|
| 157 |
+
print(f"✗ Failed to load {model_name}: {e}")
|
| 158 |
+
print(f" Try: python -m grecy install {model_name}")
|
| 159 |
+
traceback.print_exc()
|
| 160 |
+
return None
|
| 161 |
|
| 162 |
def initialize_models():
|
| 163 |
+
"""Load all models at startup."""
|
| 164 |
print("\n" + "="*70)
|
| 165 |
print("INITIALIZING MODELS")
|
| 166 |
+
print("="*70 + "\n")
|
| 167 |
|
| 168 |
for lang_code, (lang_name, model_name, model_type) in MODEL_INFO.items():
|
| 169 |
+
print(f"Loading {lang_name} ({model_name})...")
|
| 170 |
+
|
| 171 |
+
if model_type == "grecy":
|
| 172 |
+
nlp = load_grecy_model(model_name)
|
| 173 |
+
else:
|
| 174 |
+
nlp = load_spacy_model(model_name)
|
| 175 |
|
| 176 |
+
MODELS[lang_code] = nlp
|
| 177 |
|
| 178 |
+
if nlp:
|
| 179 |
+
print(f"✓ {lang_name} ready\n")
|
|
|
|
| 180 |
else:
|
| 181 |
+
print(f"✗ {lang_name} FAILED\n")
|
|
|
|
| 182 |
|
| 183 |
+
loaded = sum(1 for m in MODELS.values() if m is not None)
|
| 184 |
+
print(f"Loaded {loaded}/{len(MODELS)} models successfully")
|
| 185 |
+
print("="*70 + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
# ============================================================================
|
| 188 |
+
# ANALYSIS
|
| 189 |
# ============================================================================
|
| 190 |
|
| 191 |
def get_analysis(ui_lang: str, model_lang: str, text: str):
|
| 192 |
+
"""Analyze text and return results."""
|
| 193 |
+
ui_config = UI_TEXT.get(ui_lang.lower(), UI_TEXT["en"])
|
| 194 |
+
error_prefix = ui_config["error_message"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
try:
|
| 197 |
+
if not text.strip():
|
| 198 |
+
return ([], [], "<p style='color: orange;'>No text provided.</p>",
|
| 199 |
+
gr.Button(value=ui_config["button_text"], interactive=True))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
+
lang_code = model_lang.split()[0].lower()
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
if lang_code not in MODELS or MODELS[lang_code] is None:
|
| 204 |
+
raise ValueError(f"Model for {lang_code} is not available. Please check logs.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
|
|
|
| 206 |
nlp = MODELS[lang_code]
|
| 207 |
doc = nlp(text)
|
| 208 |
|
|
|
|
| 209 |
dataframe_output = []
|
| 210 |
json_output = []
|
| 211 |
|
|
|
|
| 212 |
for token in doc:
|
|
|
|
| 213 |
morph_str = str(token.morph) if hasattr(token, 'morph') else ''
|
| 214 |
dep_str = token.dep_ if doc.has_annotation("DEP") else ''
|
| 215 |
tag_str = token.tag_ if hasattr(token, 'tag_') else ''
|
| 216 |
pos_str = token.pos_ if hasattr(token, 'pos_') else ''
|
| 217 |
lemma_str = token.lemma_ if token.lemma != 0 else token.text
|
| 218 |
|
|
|
|
| 219 |
json_output.append({
|
| 220 |
+
"word": token.text, "lemma": lemma_str, "pos": pos_str,
|
| 221 |
+
"tag": tag_str, "morphology": morph_str, "dependency": dep_str,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
"is_stopword": token.is_stop
|
| 223 |
})
|
| 224 |
|
| 225 |
+
dataframe_output.append([token.text, lemma_str, pos_str, tag_str, morph_str, dep_str])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
+
# Generate visualization
|
| 228 |
+
html_out = ""
|
| 229 |
if doc.has_annotation("DEP"):
|
| 230 |
try:
|
| 231 |
+
options = {"compact": True, "bg": "#ffffff", "color": "#000000", "font": "Source Sans Pro"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
html_svg = displacy.render(doc, style="dep", jupyter=False, options=options)
|
| 233 |
svg_b64 = base64.b64encode(html_svg.encode("utf-8")).decode("utf-8")
|
| 234 |
+
html_out = f'<div style="background-color: #ffffff; overflow-x: auto; border: 1px solid #e6e9ef; border-radius: 0.25rem; padding: 1rem; line-height: 2.5;"><img src="data:image/svg+xml;base64,{svg_b64}" /></div>'
|
| 235 |
+
except Exception as e:
|
| 236 |
+
html_out = f"<p style='color: orange;'>Visualization error: {e}</p>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
else:
|
| 238 |
+
html_out = "<p style='color: orange;'>Dependency parsing not available for this model.</p>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
+
return (dataframe_output, json_output, html_out,
|
| 241 |
+
gr.Button(value=ui_config["button_text"], interactive=True))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
except Exception as e:
|
|
|
|
| 244 |
traceback.print_exc()
|
| 245 |
+
error_html = f"<div style='color: red; border: 1px solid red; padding: 10px; border-radius: 5px; background-color: #fff5f5;'><strong>{error_prefix}</strong> {str(e)}</div>"
|
| 246 |
+
return ([[f"{error_prefix}{str(e)}"]], {"error": str(e)}, error_html,
|
| 247 |
+
gr.Button(value=ui_config["button_text"], interactive=True))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
# ============================================================================
|
| 250 |
+
# UI
|
| 251 |
# ============================================================================
|
| 252 |
|
| 253 |
def update_ui(ui_lang: str):
|
| 254 |
+
"""Update UI language."""
|
| 255 |
+
ui_config = UI_TEXT.get(ui_lang.lower(), UI_TEXT["en"])
|
|
|
|
|
|
|
| 256 |
return [
|
| 257 |
gr.Markdown(value=ui_config["title"]),
|
| 258 |
gr.Markdown(value=ui_config["subtitle"]),
|
| 259 |
gr.Radio(label=ui_config["ui_lang_label"]),
|
| 260 |
gr.Radio(label=ui_config["model_lang_label"]),
|
| 261 |
+
gr.Textbox(label=ui_config["input_label"], placeholder=ui_config["input_placeholder"]),
|
|
|
|
|
|
|
|
|
|
| 262 |
gr.Button(value=ui_config["button_text"]),
|
| 263 |
gr.Tab(label=ui_config["tab_graphic"]),
|
| 264 |
gr.Tab(label=ui_config["tab_table"]),
|
| 265 |
gr.Tab(label=ui_config["tab_json"]),
|
| 266 |
gr.HTML(label=ui_config["html_label"]),
|
| 267 |
+
gr.DataFrame(label=ui_config["table_label"], headers=ui_config["table_headers"], interactive=False),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
gr.JSON(label=ui_config["json_label"])
|
| 269 |
]
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
def create_interface():
|
| 272 |
+
"""Create Gradio interface."""
|
| 273 |
+
config = UI_TEXT["en"]
|
| 274 |
+
model_choices = [f"{k.upper()} ({v[1]})" for k, v in MODEL_INFO.items()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
with gr.Blocks(title="Multilingual Morpho-Syntactic Analyzer") as demo:
|
|
|
|
| 277 |
with gr.Row():
|
| 278 |
+
ui_lang_radio = gr.Radio(["DE", "EN", "ES"], label=config["ui_lang_label"], value="EN")
|
| 279 |
+
model_lang_radio = gr.Radio(model_choices, label=config["model_lang_label"], value=model_choices[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
+
markdown_title = gr.Markdown(config["title"])
|
| 282 |
+
markdown_subtitle = gr.Markdown(config["subtitle"])
|
| 283 |
+
text_input = gr.Textbox(label=config["input_label"], placeholder=config["input_placeholder"], lines=5)
|
| 284 |
+
analyze_button = gr.Button(config["button_text"], variant="primary")
|
| 285 |
|
| 286 |
+
with gr.Tabs():
|
| 287 |
+
with gr.Tab(config["tab_graphic"]) as tab_graphic:
|
| 288 |
+
html_out = gr.HTML(label=config["html_label"])
|
| 289 |
+
with gr.Tab(config["tab_table"]) as tab_table:
|
| 290 |
+
df_out = gr.DataFrame(label=config["table_label"], headers=config["table_headers"], interactive=False)
|
| 291 |
+
with gr.Tab(config["tab_json"]) as tab_json:
|
| 292 |
+
json_out = gr.JSON(label=config["json_label"])
|
| 293 |
|
| 294 |
+
analyze_button.click(fn=get_analysis, inputs=[ui_lang_radio, model_lang_radio, text_input],
|
| 295 |
+
outputs=[df_out, json_out, html_out, analyze_button], api_name="get_morphology")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
+
ui_lang_radio.change(fn=update_ui, inputs=ui_lang_radio,
|
| 298 |
+
outputs=[markdown_title, markdown_subtitle, ui_lang_radio, model_lang_radio,
|
| 299 |
+
text_input, analyze_button, tab_graphic, tab_table, tab_json,
|
| 300 |
+
html_out, df_out, json_out])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
return demo
|
| 302 |
|
| 303 |
# ============================================================================
|
|
|
|
| 307 |
if __name__ == "__main__":
|
| 308 |
print("\n" + "="*70)
|
| 309 |
print("MULTILINGUAL MORPHO-SYNTACTIC ANALYZER")
|
|
|
|
| 310 |
print("="*70 + "\n")
|
| 311 |
|
|
|
|
| 312 |
initialize_models()
|
|
|
|
|
|
|
| 313 |
demo = create_interface()
|
| 314 |
+
demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
|
|
|
|
|
|
|
|
|
|
|
|