Update app.py
Browse files
app.py
CHANGED
|
@@ -1,39 +1,12 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import torch
|
| 3 |
-
from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM, MT5Tokenizer
|
| 4 |
import re
|
| 5 |
import warnings
|
| 6 |
warnings.filterwarnings("ignore")
|
| 7 |
|
| 8 |
-
class
|
| 9 |
def __init__(self):
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
self.model = None
|
| 13 |
-
self.load_model()
|
| 14 |
-
|
| 15 |
-
def load_model(self):
|
| 16 |
-
"""Load the Persian-specific summarization model"""
|
| 17 |
-
try:
|
| 18 |
-
print("Loading Persian summarization model...")
|
| 19 |
-
# Using Persian-specific MT5 model
|
| 20 |
-
self.tokenizer = MT5Tokenizer.from_pretrained(self.model_name)
|
| 21 |
-
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_name)
|
| 22 |
-
print("Persian MT5 model loaded successfully!")
|
| 23 |
-
except Exception as e:
|
| 24 |
-
print(f"Error loading Persian model: {e}")
|
| 25 |
-
print("Falling back to multilingual model...")
|
| 26 |
-
try:
|
| 27 |
-
# Fallback to mT5 base model
|
| 28 |
-
self.model_name = "google/mt5-small"
|
| 29 |
-
self.tokenizer = MT5Tokenizer.from_pretrained(self.model_name)
|
| 30 |
-
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_name)
|
| 31 |
-
print("Fallback model loaded!")
|
| 32 |
-
except Exception as e2:
|
| 33 |
-
print(f"Fallback failed: {e2}")
|
| 34 |
-
# Last resort - simple extractive summarization
|
| 35 |
-
self.model = None
|
| 36 |
-
self.tokenizer = None
|
| 37 |
|
| 38 |
def preprocess_persian_text(self, text):
|
| 39 |
"""Clean and preprocess Persian text"""
|
|
@@ -45,8 +18,76 @@ class PersianSummarizer:
|
|
| 45 |
|
| 46 |
return text
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
def summarize_text(self, text, max_length=150, min_length=50):
|
| 49 |
-
"""
|
| 50 |
if not text or len(text.strip()) < 50:
|
| 51 |
return "❌ متن ورودی کوتاه است. لطفاً متن طولانیتری وارد کنید."
|
| 52 |
|
|
@@ -58,83 +99,21 @@ class PersianSummarizer:
|
|
| 58 |
if len(clean_text) < 100:
|
| 59 |
return "❌ متن پس از پردازش کوتاه است. متن بلندتری وارد کنید."
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
return self.extractive_summary(clean_text, max_length)
|
| 64 |
|
| 65 |
-
#
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
padding="max_length",
|
| 70 |
-
truncation=True,
|
| 71 |
-
return_attention_mask=True,
|
| 72 |
-
add_special_tokens=True,
|
| 73 |
-
return_tensors="pt"
|
| 74 |
-
)
|
| 75 |
|
| 76 |
-
|
| 77 |
-
with torch.no_grad():
|
| 78 |
-
generated_ids = self.model.generate(
|
| 79 |
-
input_ids=input_ids["input_ids"],
|
| 80 |
-
attention_mask=input_ids["attention_mask"],
|
| 81 |
-
num_beams=3,
|
| 82 |
-
max_length=max_length,
|
| 83 |
-
min_length=min_length,
|
| 84 |
-
repetition_penalty=1.2,
|
| 85 |
-
length_penalty=1.0,
|
| 86 |
-
early_stopping=True,
|
| 87 |
-
use_cache=True,
|
| 88 |
-
do_sample=False
|
| 89 |
-
)
|
| 90 |
-
|
| 91 |
-
# Decode the summary
|
| 92 |
-
summary = self.tokenizer.decode(
|
| 93 |
-
generated_ids[0],
|
| 94 |
-
skip_special_tokens=True,
|
| 95 |
-
clean_up_tokenization_spaces=True
|
| 96 |
-
)
|
| 97 |
-
|
| 98 |
-
# Clean the output
|
| 99 |
-
summary = self.clean_output(summary)
|
| 100 |
-
|
| 101 |
-
return summary if summary else "❌ نتوانستم خلاصه مناسبی تولید کنم. لطفاً متن دیگری امتحان کنید."
|
| 102 |
|
| 103 |
except Exception as e:
|
| 104 |
-
|
| 105 |
-
# Fallback to extractive method
|
| 106 |
-
return self.extractive_summary(clean_text, max_length)
|
| 107 |
-
|
| 108 |
-
def extractive_summary(self, text, max_length):
|
| 109 |
-
"""Simple extractive summarization fallback"""
|
| 110 |
-
sentences = re.split(r'[.!?؟]', text)
|
| 111 |
-
sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
|
| 112 |
-
|
| 113 |
-
if len(sentences) <= 2:
|
| 114 |
-
return text[:max_length] + "..."
|
| 115 |
-
|
| 116 |
-
# Take first, middle, and last sentences for a simple summary
|
| 117 |
-
summary_sentences = []
|
| 118 |
-
if len(sentences) >= 3:
|
| 119 |
-
summary_sentences = [sentences[0], sentences[len(sentences)//2], sentences[-1]]
|
| 120 |
-
else:
|
| 121 |
-
summary_sentences = sentences
|
| 122 |
-
|
| 123 |
-
summary = ". ".join(summary_sentences)
|
| 124 |
-
return summary[:max_length] + ("..." if len(summary) > max_length else "")
|
| 125 |
-
|
| 126 |
-
def clean_output(self, text):
|
| 127 |
-
"""Clean the generated summary"""
|
| 128 |
-
# Remove any potential English artifacts
|
| 129 |
-
text = re.sub(r'[a-zA-Z]+', '', text)
|
| 130 |
-
# Remove extra spaces
|
| 131 |
-
text = re.sub(r'\s+', ' ', text.strip())
|
| 132 |
-
# Ensure proper Persian punctuation
|
| 133 |
-
text = text.replace('.', '.')
|
| 134 |
-
return text
|
| 135 |
|
| 136 |
# Initialize the summarizer
|
| 137 |
-
persian_summarizer =
|
| 138 |
|
| 139 |
def summarize_persian_text(text, summary_length):
|
| 140 |
"""Main function to handle summarization requests"""
|
|
@@ -150,21 +129,21 @@ def summarize_persian_text(text, summary_length):
|
|
| 150 |
# Sample Persian texts for demonstration
|
| 151 |
sample_texts = {
|
| 152 |
"خبر سیاسی": """
|
| 153 |
-
مجلس شورای اسلامی ایران در جلسه علنی روز گذشته لایحه بودجه سال آینده را بررسی کرد. نمایندگان مجلس در این جلسه به بحث و بررسی جزئیات بودجه پرداختند و پیشنهادات مختلفی برای بهبود آن ارائه دادند. وزیر اقتصاد نیز در این جلسه حضور یافت و به سوالات نمایندگان پاسخ داد. بر اساس این لایحه، بودجه عمومی کشور نسبت به سال جاری افزایش قابل توجهی خواهد داشت. همچنین اعتبارات ویژهای برای توسعه زیرساختهای کشور در نظر گرفته شده است.
|
| 154 |
""",
|
| 155 |
|
| 156 |
"مقاله علمی": """
|
| 157 |
-
هوش مصنوعی در دهههای اخیر به یکی از مهمترین فناوریهای نوین تبدیل شده است. این فناوری کاربردهای گستردهای در زمینههای مختلف نظیر پزشکی، آموزش، حمل و نقل و صنعت دارد. یادگیری ماشین که بخش مهمی از هوش مصنوعی محسوب میشود، امکان تحلیل دادههای پیچیده و الگویابی را فراهم میکند. شبکههای عصبی مصنوعی نیز با الهام از مغز انسان طراحی شدهاند و قابلیتهای شگفتانگیزی در تشخیص الگو و پردازش تصویر دارند. با این حال، چالشهایی نظیر اخلاق در هوش مصنوعی و حفظ حریم خصوصی همچنان وجود دارد.
|
| 158 |
""",
|
| 159 |
|
| 160 |
"متن ادبی": """
|
| 161 |
-
در باغ گلهای سرخ، پیرمردی با موهای سفید نشسته بود و به آسمان آبی نگاه میکرد. نسیم ملایم صبحگاهی برگهای درختان را به رقص درآورده بود. صدای آب نهری که از دور میآمد، آرامش خاصی به فضا میبخشید. پیرمرد در دل خود به یاد روزهای جوانیاش بود، زمانی که این باغ را با دستان خود کاشته بود. اکنون پس از سالها، میوه زحماتش را میدید. گلهای رنگارنگ، درختان سایهدار و آرامش این مکان، همه نشان از عشق و دلبستگی او به این باغ داشت.
|
| 162 |
"""
|
| 163 |
}
|
| 164 |
|
| 165 |
# Create Gradio interface
|
| 166 |
with gr.Blocks(
|
| 167 |
-
title="خلاصهساز متن فارسی",
|
| 168 |
theme=gr.themes.Soft(),
|
| 169 |
css="""
|
| 170 |
.persian-text {
|
|
@@ -183,8 +162,8 @@ with gr.Blocks(
|
|
| 183 |
gr.HTML("""
|
| 184 |
<div class="main-header">
|
| 185 |
<h1>🤖 خلاصهساز هوشمند متن فارسی</h1>
|
| 186 |
-
<p><strong>Persian Text Summarization Tool</strong></p>
|
| 187 |
-
<p>این ابزار با
|
| 188 |
</div>
|
| 189 |
""")
|
| 190 |
|
|
@@ -192,13 +171,14 @@ with gr.Blocks(
|
|
| 192 |
with gr.Column(scale=2):
|
| 193 |
gr.Markdown("## 📝 متن ورودی")
|
| 194 |
|
| 195 |
-
#
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
<strong
|
| 199 |
-
<strong
|
| 200 |
-
|
| 201 |
-
|
|
|
|
| 202 |
|
| 203 |
# Sample text selector
|
| 204 |
sample_selector = gr.Dropdown(
|
|
@@ -252,19 +232,19 @@ with gr.Blocks(
|
|
| 252 |
3. **طول خلاصه را تعیین کنید**: کوتاه، متوسط یا بلند
|
| 253 |
4. **دکمه خلاصهسازی را بزنید**: منتظر بمانید تا خلاصه تولید شود
|
| 254 |
|
| 255 |
-
###
|
| 256 |
-
- ✅
|
| 257 |
-
- ✅
|
| 258 |
-
- ✅
|
| 259 |
-
- ✅
|
| 260 |
-
- ✅
|
| 261 |
-
- ✅
|
| 262 |
|
| 263 |
### نکات:
|
| 264 |
- متن ورودی باید حداقل 100 کاراکتر باشد
|
| 265 |
-
-
|
| 266 |
-
- این
|
| 267 |
-
-
|
| 268 |
""")
|
| 269 |
|
| 270 |
# Event handlers
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import re
|
| 3 |
import warnings
|
| 4 |
warnings.filterwarnings("ignore")
|
| 5 |
|
| 6 |
+
class LightweightPersianSummarizer:
|
| 7 |
def __init__(self):
|
| 8 |
+
"""Lightweight Persian summarizer using extractive methods"""
|
| 9 |
+
print("Initializing lightweight Persian summarizer...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
def preprocess_persian_text(self, text):
|
| 12 |
"""Clean and preprocess Persian text"""
|
|
|
|
| 18 |
|
| 19 |
return text
|
| 20 |
|
| 21 |
+
def score_sentence(self, sentence, position, total_sentences):
|
| 22 |
+
"""Score sentences for importance"""
|
| 23 |
+
# Position score (earlier sentences are more important)
|
| 24 |
+
position_score = 1.0 - (position / total_sentences) * 0.3
|
| 25 |
+
|
| 26 |
+
# Length score (moderate length sentences are preferred)
|
| 27 |
+
length = len(sentence.strip())
|
| 28 |
+
if length < 20:
|
| 29 |
+
length_score = 0.1
|
| 30 |
+
elif length > 200:
|
| 31 |
+
length_score = 0.7
|
| 32 |
+
else:
|
| 33 |
+
length_score = 1.0
|
| 34 |
+
|
| 35 |
+
# Keyword density score (sentences with more Persian content)
|
| 36 |
+
persian_chars = len(re.findall(r'[\u0600-\u06FF]', sentence))
|
| 37 |
+
total_chars = len(sentence.replace(' ', ''))
|
| 38 |
+
persian_ratio = persian_chars / max(total_chars, 1)
|
| 39 |
+
|
| 40 |
+
# Combined score
|
| 41 |
+
final_score = (position_score * 0.4) + (length_score * 0.3) + (persian_ratio * 0.3)
|
| 42 |
+
|
| 43 |
+
return final_score
|
| 44 |
+
|
| 45 |
+
def extractive_summary(self, text, target_length):
|
| 46 |
+
"""Advanced extractive summarization for Persian text"""
|
| 47 |
+
# Split into sentences
|
| 48 |
+
sentences = re.split(r'[.!?؟]', text)
|
| 49 |
+
sentences = [s.strip() for s in sentences if len(s.strip()) > 15]
|
| 50 |
+
|
| 51 |
+
if len(sentences) <= 2:
|
| 52 |
+
return text[:target_length] + ("..." if len(text) > target_length else "")
|
| 53 |
+
|
| 54 |
+
# Score all sentences
|
| 55 |
+
scored_sentences = []
|
| 56 |
+
for i, sentence in enumerate(sentences):
|
| 57 |
+
score = self.score_sentence(sentence, i, len(sentences))
|
| 58 |
+
scored_sentences.append((sentence, score, i))
|
| 59 |
+
|
| 60 |
+
# Sort by score (highest first)
|
| 61 |
+
scored_sentences.sort(key=lambda x: x[1], reverse=True)
|
| 62 |
+
|
| 63 |
+
# Select sentences up to target length
|
| 64 |
+
selected_sentences = []
|
| 65 |
+
current_length = 0
|
| 66 |
+
|
| 67 |
+
for sentence, score, original_index in scored_sentences:
|
| 68 |
+
sentence_length = len(sentence)
|
| 69 |
+
if current_length + sentence_length <= target_length:
|
| 70 |
+
selected_sentences.append((sentence, original_index))
|
| 71 |
+
current_length += sentence_length
|
| 72 |
+
elif len(selected_sentences) == 0: # At least include one sentence
|
| 73 |
+
# Truncate the sentence if needed
|
| 74 |
+
truncated = sentence[:target_length-10] + "..."
|
| 75 |
+
selected_sentences.append((truncated, original_index))
|
| 76 |
+
break
|
| 77 |
+
|
| 78 |
+
# Sort selected sentences by original order
|
| 79 |
+
selected_sentences.sort(key=lambda x: x[1])
|
| 80 |
+
|
| 81 |
+
# Join sentences
|
| 82 |
+
summary = ". ".join([sent[0] for sent in selected_sentences])
|
| 83 |
+
|
| 84 |
+
# Final cleanup
|
| 85 |
+
summary = re.sub(r'\s+', ' ', summary.strip())
|
| 86 |
+
|
| 87 |
+
return summary
|
| 88 |
+
|
| 89 |
def summarize_text(self, text, max_length=150, min_length=50):
|
| 90 |
+
"""Main summarization function"""
|
| 91 |
if not text or len(text.strip()) < 50:
|
| 92 |
return "❌ متن ورودی کوتاه است. لطفاً متن طولانیتری وارد کنید."
|
| 93 |
|
|
|
|
| 99 |
if len(clean_text) < 100:
|
| 100 |
return "❌ متن پس از پردازش کوتاه است. متن بلندتری وارد کنید."
|
| 101 |
|
| 102 |
+
# Use extractive summarization
|
| 103 |
+
summary = self.extractive_summary(clean_text, max_length)
|
|
|
|
| 104 |
|
| 105 |
+
# Ensure minimum length
|
| 106 |
+
if len(summary) < min_length and len(clean_text) > min_length:
|
| 107 |
+
# Try with higher target length
|
| 108 |
+
summary = self.extractive_summary(clean_text, min(max_length + 50, len(clean_text)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
+
return summary if summary else "❌ نتوانستم خلاصه مناسبی تولید کنم."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
except Exception as e:
|
| 113 |
+
return f"❌ خطا در خلاصهسازی: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
# Initialize the summarizer
|
| 116 |
+
persian_summarizer = LightweightPersianSummarizer()
|
| 117 |
|
| 118 |
def summarize_persian_text(text, summary_length):
|
| 119 |
"""Main function to handle summarization requests"""
|
|
|
|
| 129 |
# Sample Persian texts for demonstration
|
| 130 |
sample_texts = {
|
| 131 |
"خبر سیاسی": """
|
| 132 |
+
مجلس شورای اسلامی ایران در جلسه علنی روز گذشته لایحه بودجه سال آینده را بررسی کرد. نمایندگان مجلس در این جلسه به بحث و بررسی جزئیات بودجه پرداختند و پیشنهادات مختلفی برای بهبود آن ارائه دادند. وزیر اقتصاد نیز در این جلسه حضور یافت و به سوالات نمایندگان پاسخ داد. بر اساس این لایحه، بودجه عمومی کشور نسبت به سال جاری افزایش قابل توجهی خواهد داشت. همچنین اعتبارات ویژهای برای توسعه زیرساختهای کشور در نظر گرفته شده است. نمایندگان بر لزوم شفافیت در هزینهکرد بودجه تأکید کردند و خواستار نظارت دقیقتر بر اجرای برنامههای توسعهای شدند.
|
| 133 |
""",
|
| 134 |
|
| 135 |
"مقاله علمی": """
|
| 136 |
+
هوش مصنوعی در دهههای اخیر به یکی از مهمترین فناوریهای نوین تبدیل شده است. این فناوری کاربردهای گستردهای در زمینههای مختلف نظیر پزشکی، آموزش، حمل و نقل و صنعت دارد. یادگیری ماشین که بخش مهمی از هوش مصنوعی محسوب میشود، امکان تحلیل دادههای پیچیده و الگویابی را فراهم میکند. شبکههای عصبی مصنوعی نیز با الهام از مغز انسان طراحی شدهاند و قابلیتهای شگفتانگیزی در تشخیص الگو و پردازش تصویر دارند. با این حال، چالشهایی نظیر اخلاق در هوش مصنوعی و حفظ حریم خصوصی همچنان وجود دارد. محققان بر لزوم توسعه هوش مصنوعی مسئولانه تأکید میکنند تا از مزایای این فناوری استفاده کرده و از مضرات احتمالی آن جلوگیری شود.
|
| 137 |
""",
|
| 138 |
|
| 139 |
"متن ادبی": """
|
| 140 |
+
در باغ گلهای سرخ، پیرمردی با موهای سفید نشسته بود و به آسمان آبی نگاه میکرد. نسیم ملایم صبحگاهی برگهای درختان را به رقص درآورده بود. صدای آب نهری که از دور میآمد، آرامش خاصی به فضا میبخشید. پیرمرد در دل خود به یاد روزهای جوانیاش بود، زمانی که این باغ را با دستان خود کاشته بود. اکنون پس از سالها، میوه زحماتش را میدید. گلهای رنگارنگ، درختان سایهدار و آرامش این مکان، همه نشان از عشق و دلبستگی او به این باغ داشت. او لبخندی بر لب داشت، لبخندی که حاکی از رضایت و آرامش درونی بود. این باغ نه تنها مکانی برای استراحت، بلکه خانهای برای خاطرات شیرین او بود.
|
| 141 |
"""
|
| 142 |
}
|
| 143 |
|
| 144 |
# Create Gradio interface
|
| 145 |
with gr.Blocks(
|
| 146 |
+
title="خلاصهساز متن فارسی - نسخه سبک",
|
| 147 |
theme=gr.themes.Soft(),
|
| 148 |
css="""
|
| 149 |
.persian-text {
|
|
|
|
| 162 |
gr.HTML("""
|
| 163 |
<div class="main-header">
|
| 164 |
<h1>🤖 خلاصهساز هوشمند متن فارسی</h1>
|
| 165 |
+
<p><strong>Persian Text Summarization Tool - Lightweight Version</strong></p>
|
| 166 |
+
<p>این ابزار با روش استخراجی پیشرفته، متنهای فارسی را خلاصه میکند</p>
|
| 167 |
</div>
|
| 168 |
""")
|
| 169 |
|
|
|
|
| 171 |
with gr.Column(scale=2):
|
| 172 |
gr.Markdown("## 📝 متن ورودی")
|
| 173 |
|
| 174 |
+
# Method info
|
| 175 |
+
gr.HTML("""
|
| 176 |
+
<div style="padding: 10px; background-color: #e3f2fd; border-radius: 5px; margin-bottom: 10px;">
|
| 177 |
+
<strong>⚡ روش:</strong> خلاصهسازی استخراجی پیشرفته<br>
|
| 178 |
+
<strong>✅ مزیت:</strong> سریع، پایدار و کاملاً فارسی<br>
|
| 179 |
+
<strong>🎯 کیفیت:</strong> بالا برای متنهای فارسی
|
| 180 |
+
</div>
|
| 181 |
+
""")
|
| 182 |
|
| 183 |
# Sample text selector
|
| 184 |
sample_selector = gr.Dropdown(
|
|
|
|
| 232 |
3. **طول خلاصه را تعیین کنید**: کوتاه، متوسط یا بلند
|
| 233 |
4. **دکمه خلاصهسازی را بزنید**: منتظر بمانید تا خلاصه تولید شود
|
| 234 |
|
| 235 |
+
### ویژگیهای نسخه سبک:
|
| 236 |
+
- ✅ سریع و پایدار (بدون نیاز به مدلهای سنگین)
|
| 237 |
+
- ✅ کاملاً فارسی (هیچ ترجمهای انجام نمیشود)
|
| 238 |
+
- ✅ کیفیت بالا برای متنهای فارسی
|
| 239 |
+
- ✅ مناسب برای همه سرورها
|
| 240 |
+
- ✅ روش استخراجی هوشمند
|
| 241 |
+
- ✅ امتیازدهی پیشرفته به جملات
|
| 242 |
|
| 243 |
### نکات:
|
| 244 |
- متن ورودی باید حداقل 100 کاراکتر باشد
|
| 245 |
+
- جملات ابتدایی و میانی متن امتیاز بیشتری دریافت میکنند
|
| 246 |
+
- این نسخه برای استقرار آسان طراحی شده است
|
| 247 |
+
- هیچ وابستگی پیچیدهای ندارد
|
| 248 |
""")
|
| 249 |
|
| 250 |
# Event handlers
|