Download app.py from SergeyShk/ruTS: direct link, hf CLI and curl.
- Browser
- Download file 28.4 kB
-
https://huggingface.co/spaces/SergeyShk/ruTS/resolve/main/app.py
- Command line
-
hf download hf://spaces/SergeyShk/ruTS/app.py
-
curl -L -o app.py https://huggingface.co/spaces/SergeyShk/ruTS/resolve/main/app.py
28.4 kB
| import threading | |
| from collections import Counter | |
| from collections.abc import Callable | |
| from functools import lru_cache | |
| from importlib.metadata import version | |
| from io import BytesIO | |
| from math import floor, isinf, isnan | |
| import gradio as gr | |
| import matplotlib | |
| import matplotlib.pyplot as plt | |
| import pandas as pd | |
| import spacy | |
| from PIL import Image | |
| from spacy.tokens import Doc | |
| from ruts import ( | |
| BasicStats, | |
| CohesionStats, | |
| DiversityStats, | |
| LexicalStats, | |
| MorphStats, | |
| PhonStats, | |
| ReadabilityStats, | |
| StyleStats, | |
| SyntaxStats, | |
| VerseStats, | |
| WordsExtractor, | |
| ) | |
| from ruts.constants import ( | |
| BASIC_STATS_DESC, | |
| COHESION_STATS_DESC, | |
| DIVERSITY_STATS_DESC, | |
| HIGHLIGHT_DEFAULT_LAYERS, | |
| HIGHLIGHT_LAYER_GROUPS, | |
| HIGHLIGHT_LAYERS_DESC, | |
| LEXICAL_STATS_DESC, | |
| MORPHOLOGY_STATS_DESC, | |
| PHON_STATS_DESC, | |
| READABILITY_STATS_DESC, | |
| STYLE_STATS_DESC, | |
| SYNTAX_STATS_DESC, | |
| VERSE_STATS_DESC, | |
| ) | |
| from ruts.corpus import collocations, keyness | |
| from ruts.datasets import FreqDict, StressDict | |
| from ruts.exceptions import SourceError | |
| from ruts.style_stats import is_stopword | |
| from ruts.visualizers import highlight, sentence_lengths_plot, zipf | |
| matplotlib.use("Agg") | |
| MAX_CHARS = 20_000 | |
| ZIPF_WORDS = 100 | |
| SENTENCES_MIN = 5 | |
| SENTENCES_WINDOW = 5 | |
| ZIPF_MIN_WORDS = 100 | |
| KEYWORDS_TOP_N = 15 | |
| COLLOCATION_WINDOW = 3 | |
| VERSE_MAX_LINES = 40 | |
| VERSE_PATTERN_WIDTH = 24 | |
| VERSE_NOTE = ( | |
| "Ударения расставлены по словарю Козиева и подогнаны под метр, а знак ударения из текста " | |
| "берется как есть; схема строки - `c` безударный " | |
| "слог, `C` ударный, буква - группа рифмы, дефис - строка без рифмы. Для стиха вставляйте текст " | |
| "построчно: на одной строке или короткой фразе метр не определяется." | |
| ) | |
| KEYWORDS_NOTE = ( | |
| "Ключевые слова - леммы, которые в тексте встречаются заметно чаще, чем в частотном " | |
| "словаре Ляшевской и Шарова (G² - значимость, Log Ratio - во сколько раз чаще в двоичном " | |
| "логарифме). Коллокации - пары знаменательных лемм на расстоянии до трех слов, встретившиеся " | |
| "хотя бы дважды, по logDice; в коротком тексте их может не быть." | |
| ) | |
| POS_SHORT = { | |
| "NOUN": "сущ.", | |
| "PROPN": "имя собств.", | |
| "ADJ": "прил.", | |
| "ADV": "наречие", | |
| "VERB": "глагол", | |
| "AUX": "вспом. глагол", | |
| "PRON": "местоим.", | |
| "DET": "местоим. прил.", | |
| "NUM": "числит.", | |
| "ADP": "предлог", | |
| "CCONJ": "соч. союз", | |
| "SCONJ": "подч. союз", | |
| "PART": "частица", | |
| "INTJ": "междометие", | |
| "PUNCT": "знак", | |
| "SYM": "символ", | |
| "X": "прочее", | |
| } | |
| EXAMPLES = { | |
| "Сказка": ( | |
| "Жили-были старик со старухой. У них была курочка Ряба. Снесла курочка яичко, " | |
| "не простое, а золотое. Дед бил, бил - не разбил. Баба била, била - не разбила. " | |
| "Мышка бежала, хвостиком махнула, яичко упало и разбилось. Дед плачет, баба плачет, " | |
| "а курочка кудахчет: не плачь, дед, не плачь, баба, я снесу вам яичко другое, " | |
| "не золотое, а простое." | |
| ), | |
| "Стихи": ( | |
| "Вечер. Взморье. Вздохи ветра.\n" | |
| "Величавый возглас волн.\n" | |
| "Близко буря. В берег бьётся\n" | |
| "Чуждый чарам чёрный чёлн.\n\n" | |
| "Чуждый чистым чарам счастья,\n" | |
| "Чёлн томленья, чёлн тревог,\n" | |
| "Бросил берег, бьётся с бурей,\n" | |
| "Ищет светлых снов чертог.\n\n" | |
| "Мчится взморьем, мчится морем,\n" | |
| "Отдаваясь воле волн.\n" | |
| "Месяц матовый взирает,\n" | |
| "Месяц горький полон грусти.\n\n" | |
| "Умер вечер. Ночь чернеет.\n" | |
| "Ропщет море. Мрак растёт.\n" | |
| "Чёлн томленья тьмой охвачен.\n" | |
| "Буря воет в бездне вод." | |
| ), | |
| "Новость": ( | |
| "Администрация города утвердила программу ремонта дворов на следующий год. " | |
| "Согласно документу, в первую очередь будут отремонтированы проезды и детские площадки " | |
| "в микрорайонах, построенных до 1980 года, а заявки жителей, поданные через портал, " | |
| "рассматриваются в течение месяца. Расходы бюджета оцениваются в двести миллионов рублей, " | |
| "подрядчиков выберут по итогам открытого конкурса в марте.\n\n" | |
| "Программа рассчитана на три года. В первый год планируется привести в порядок сорок " | |
| "дворов, во второй - шестьдесят, в третий - все оставшиеся. Жители смогут следить " | |
| "за ходом работ на карте, которая появится на сайте администрации весной: там будут " | |
| "отмечены адреса, сроки и подрядчики. Как пояснили в пресс-службе, при выборе дворов " | |
| "учитывались возраст домов, состояние покрытия и число обращений. Депутаты городской думы " | |
| "поддержали программу единогласно, но попросили отчитываться о её выполнении каждый " | |
| "квартал, а не раз в год, как предлагала администрация." | |
| ), | |
| "Научная проза": ( | |
| "Тезаурусы - особый класс лексикографических ресурсов, для которых характерны следующие " | |
| "черты: полнота значений словарного состава языка или какого-либо его сегмента; " | |
| "тематический, или идеографический способ упорядочения значений слов. Отличительной " | |
| "особенностью тезаурусов по сравнению с формальными онтологиями является выход в сферу " | |
| "лексических значений, установление связей не только между значениями и выражающими их " | |
| "словами, а также между самими значениями (регистрация различных семантических отношений " | |
| "внутри словаря).\n\n" | |
| "Построение тезауруса предполагает выделение понятий предметной области, установление " | |
| "между ними отношений синонимии, гипонимии и ассоциации, а также сопоставление каждому " | |
| "понятию множества текстовых входов - слов и словосочетаний, которыми оно выражается " | |
| "в текстах. Полученная структура используется для расширения запросов в информационном " | |
| "поиске, автоматической рубрикации документов и разрешения лексической многозначности, " | |
| "поскольку позволяет соотносить разные формулировки с одним и тем же значением." | |
| ), | |
| "Канцелярит": ( | |
| "Проект, подготовленный за неделю, был одобрен советом без обсуждения. Повышение " | |
| "эффективности использования бюджетных средств обсуждалось, не выходя за рамки регламента. " | |
| "Участники, представлявшие региональные министерства, не смогли согласовать позиции по " | |
| "вопросам финансирования и распределения ответственности между ведомствами, поскольку " | |
| "каждое из них настаивало на собственной трактовке положений соглашения. Споры стихли, " | |
| "в кулуарах шумно шептались и шушукались, а решение было отложено до следующего заседания.\n\n" | |
| "По итогам заседания было принято решение о необходимости проведения дополнительных " | |
| "консультаций с участием представителей заинтересованных ведомств в целях выработки " | |
| "согласованной позиции по вопросу распределения полномочий. Ответственность за организацию " | |
| "консультаций и подготовку соответствующего протокола была возложена на секретариат, " | |
| "которым должен быть обеспечен учёт замечаний, поступивших в ходе обсуждения." | |
| ), | |
| } | |
| DEFAULT_LAYERS = list(HIGHLIGHT_DEFAULT_LAYERS) | |
| DEFAULT_GROUP_LAYERS = [ | |
| [layer for layer in group if layer in HIGHLIGHT_DEFAULT_LAYERS] | |
| for group in HIGHLIGHT_LAYER_GROUPS.values() | |
| ] | |
| nlp = spacy.load("ru_core_news_sm", exclude=["ner", "lemmatizer"]) | |
| nlp.add_pipe("ruts_blank_lines", before="parser") | |
| freq_dict = FreqDict() | |
| if not freq_dict.filepath: | |
| try: | |
| freq_dict.download() | |
| except (RuntimeError, OSError) as error: | |
| print(f"Частотный словарь недоступен: {error}") | |
| stress_dict = StressDict() | |
| if not stress_dict.filepath: | |
| try: | |
| stress_dict.download() | |
| except (RuntimeError, OSError) as error: | |
| print(f"Словарь ударений недоступен: {error}") | |
| plot_lock = threading.Lock() | |
| def parse(text: str) -> Doc: | |
| return nlp(text) | |
| def format_value(value: float | int | str | None) -> str: | |
| if value is None: | |
| return "-" | |
| if isinstance(value, float): | |
| if isnan(value): | |
| return "-" | |
| return ("∞" if value > 0 else "-∞") if isinf(value) else f"{value:.2f}" | |
| return str(value) | |
| def stats_table( | |
| stats: dict[str, float], | |
| desc: dict[str, str], | |
| describe: Callable[[str], str | None] | None = None, | |
| ) -> pd.DataFrame: | |
| keys = [key for key in desc if key in stats] | |
| if describe is None: | |
| rows = [(desc[key], format_value(stats[key])) for key in keys] | |
| return pd.DataFrame(rows, columns=["Метрика", "Значение"]) | |
| rows_read = [(desc[key], format_value(stats[key]), describe(key) or "-") for key in keys] | |
| return pd.DataFrame(rows_read, columns=["Метрика", "Значение", "Прочтение"]) | |
| def format_reading_time(minutes: float) -> str: | |
| if minutes < 1: | |
| return "меньше минуты" | |
| total = round(minutes) | |
| if total < 60: | |
| return f"{total} мин" | |
| return f"{total // 60} ч {total % 60} мин" | |
| def readability_summary(rs: ReadabilityStats) -> str: | |
| return ( | |
| f"### {rs.describe_grade()}\n\n" | |
| f"Сводный класс по семи формулам класса и индексу Флеша - **{rs.consensus_grade:.1f}**, " | |
| f"индекс удобочитаемости Флеша - **{floor(rs.flesch_reading_easy)}** " | |
| f"({rs.describe('flesch_reading_easy')}), " | |
| f"время чтения - **{format_reading_time(rs.reading_time)}**." | |
| ) | |
| def lexical_table(doc: Doc) -> pd.DataFrame: | |
| try: | |
| ls = LexicalStats(doc, freq_dict=freq_dict) | |
| except SourceError: | |
| # Числа словами не считаются, в тексте из одних чисел слов нет | |
| return pd.DataFrame(columns=["Метрика", "Значение"]) | |
| if freq_dict.filepath: | |
| return stats_table(ls.get_stats(), LEXICAL_STATS_DESC) | |
| bands = { | |
| key: getattr(ls, key) | |
| for key in LEXICAL_STATS_DESC | |
| if key.startswith(("p_top", "p_beyond")) | |
| } | |
| bands["lexical_density"] = ls.lexical_density | |
| return stats_table(bands, LEXICAL_STATS_DESC) | |
| def keywords_table(words: tuple[str, ...]) -> pd.DataFrame: | |
| columns = ["Лемма", "В тексте", "ipm в тексте", "ipm в словаре", "G²", "Log Ratio"] | |
| if not freq_dict.filepath or not words: | |
| return pd.DataFrame(columns=columns) | |
| try: | |
| keywords = keyness(words, freq_dict, min_freq=2, top_n=KEYWORDS_TOP_N) | |
| except SourceError: | |
| # Нет слов словаря, например только латиница | |
| return pd.DataFrame(columns=columns) | |
| return pd.DataFrame( | |
| [ | |
| ( | |
| keyword.word, | |
| keyword.freq_target, | |
| round(keyword.ipm_target), | |
| round(keyword.ipm_reference, 1), | |
| round(keyword.g2, 1), | |
| round(keyword.log_ratio, 2), | |
| ) | |
| for keyword in keywords | |
| ], | |
| columns=columns, | |
| ) | |
| def collocations_table(words: tuple[str, ...]) -> pd.DataFrame: | |
| columns = ["Пара", "Вместе", "logDice"] | |
| if not words: | |
| return pd.DataFrame(columns=columns) | |
| found = [ | |
| collocation | |
| for collocation in collocations(words, window=COLLOCATION_WINDOW, min_freq=2) | |
| if not is_stopword(collocation.left) and not is_stopword(collocation.right) | |
| ] | |
| return pd.DataFrame( | |
| [ | |
| ( | |
| f"{collocation.left} … {collocation.right}", | |
| collocation.freq_pair, | |
| round(collocation.score, 2), | |
| ) | |
| for collocation in found[:KEYWORDS_TOP_N] | |
| ], | |
| columns=columns, | |
| ) | |
| def verse_tables(doc: Doc) -> tuple[pd.DataFrame, str]: | |
| if not stress_dict.filepath: | |
| return pd.DataFrame(columns=["Метрика", "Значение"]), "*Словарь ударений недоступен.*" | |
| try: | |
| vs = VerseStats(doc, stress_dict=stress_dict) | |
| except SourceError: | |
| return pd.DataFrame(columns=["Метрика", "Значение"]), "*В тексте нет русских слов.*" | |
| if not vs.n_lines: | |
| return stats_table(vs.get_stats(), VERSE_STATS_DESC), "*В тексте нет русских слов.*" | |
| accented = [line for stanza in vs.accentuate().split("\n\n") for line in stanza.split("\n")] | |
| letters = [letter for scheme in vs.rhyme_schemes for letter in scheme] | |
| rows = [] | |
| for pattern, letter, line in zip(vs.patterns, letters, accented, strict=True): | |
| if len(pattern) > VERSE_PATTERN_WIDTH: | |
| pattern = pattern[: VERSE_PATTERN_WIDTH - 1] + "…" | |
| rows.append(f"{pattern:<{VERSE_PATTERN_WIDTH}} {letter} {line}") | |
| if len(rows) > VERSE_MAX_LINES: | |
| rows = [*rows[:VERSE_MAX_LINES], f"… ещё {len(rows) - VERSE_MAX_LINES} строк"] | |
| return stats_table(vs.get_stats(), VERSE_STATS_DESC), "```\n" + "\n".join(rows) + "\n```" | |
| def morph_tables(ms: MorphStats) -> tuple[pd.DataFrame, pd.DataFrame]: | |
| stats = ms.get_stats(filter_none=True) | |
| pos = sorted(stats["pos"].items(), key=lambda item: -item[1]) | |
| pos_table = pd.DataFrame( | |
| [(POS_SHORT.get(tag, tag), count) for tag, count in pos], columns=["Часть речи", "Слов"] | |
| ) | |
| rows = [] | |
| for category, counts in stats.items(): | |
| if category == "pos": | |
| continue | |
| desc = MORPHOLOGY_STATS_DESC[category] | |
| for value, count in sorted(counts.items(), key=lambda item: -item[1]): | |
| rows.append((desc["name"], desc["values"].get(value, value), count)) | |
| return pos_table, pd.DataFrame(rows, columns=["Признак", "Значение", "Слов"]) | |
| def figure_image(fig: plt.Figure) -> Image.Image: | |
| buffer = BytesIO() | |
| fig.tight_layout() | |
| fig.savefig(buffer, format="png", dpi=150) | |
| plt.close(fig) | |
| buffer.seek(0) | |
| return Image.open(buffer) | |
| def zipf_image(counter: Counter[str]) -> Image.Image: | |
| with plot_lock: | |
| fig, ax = plt.subplots(figsize=(7, 4.5)) | |
| zipf( | |
| counter, | |
| num_words=min(ZIPF_WORDS, len(counter)), | |
| num_labels=6, | |
| show_theory=True, | |
| alpha=1.1, | |
| show_fit=True, | |
| ax=ax, | |
| ) | |
| return figure_image(fig) | |
| def sentences_image(doc: Doc) -> Image.Image: | |
| with plot_lock: | |
| fig, ax = plt.subplots(figsize=(7, 3.5)) | |
| sentence_lengths_plot(doc, window=SENTENCES_WINDOW, ax=ax) | |
| return figure_image(fig) | |
| def render_highlight(text: str, layers: list[str]) -> str: | |
| return highlight(parse(text), layers=layers).to_html() | |
| def compute(text: str, layers: list[str]) -> dict: | |
| text = text.strip()[:MAX_CHARS] | |
| if not text: | |
| raise ValueError("Вставьте текст для разбора") | |
| doc = parse(text) | |
| bs = BasicStats(doc) | |
| rs = ReadabilityStats(doc) | |
| ds = DiversityStats(doc) | |
| ms = MorphStats(doc) | |
| ss = StyleStats(doc) | |
| ps = PhonStats(doc) | |
| xs = SyntaxStats(doc) | |
| cs = CohesionStats(doc) | |
| words = WordsExtractor(use_lexemes=True, lowercase=True, filter_nums=True).extract(text) | |
| pos_table, morph_table = morph_tables(ms) | |
| verse_table, verse_lines = verse_tables(doc) | |
| basic = {key: value for key, value in bs.get_stats().items() if key in BASIC_STATS_DESC} | |
| counter = Counter(word for word in words if not is_stopword(word)) | |
| enough_words = bs.n_words >= ZIPF_MIN_WORDS and bool(counter) | |
| enough_sents = bs.n_sents >= SENTENCES_MIN | |
| return { | |
| "text": text, | |
| "highlight": highlight(doc, layers=layers).to_html(), | |
| "summary": readability_summary(rs), | |
| "readability": stats_table(rs.get_stats(), READABILITY_STATS_DESC, rs.describe), | |
| "diversity": stats_table(ds.get_stats(), DIVERSITY_STATS_DESC), | |
| "pos": pos_table, | |
| "morph": morph_table, | |
| "syntax": stats_table(xs.get_stats(), SYNTAX_STATS_DESC), | |
| "cohesion": stats_table(cs.get_stats(), COHESION_STATS_DESC), | |
| "lexical": lexical_table(doc), | |
| "style": stats_table(ss.get_stats(), STYLE_STATS_DESC, ss.describe), | |
| "phon": stats_table(ps.get_stats(), PHON_STATS_DESC), | |
| "verse": verse_table, | |
| "verse_lines": verse_lines, | |
| "basic": stats_table(basic, BASIC_STATS_DESC), | |
| "keywords": keywords_table(WordsExtractor(filter_nums=True).extract(text)), | |
| "collocations": collocations_table(words), | |
| "zipf": zipf_image(counter) if enough_words else None, | |
| "enough_words": enough_words, | |
| "sentences": sentences_image(doc) if enough_sents else None, | |
| "enough_sents": enough_sents, | |
| } | |
| def analyze(text: str, *groups: list[str]): | |
| try: | |
| result = compute(text, [layer for group in groups for layer in group]) | |
| except SourceError as error: | |
| raise gr.Error("В тексте нет слов для разбора") from error | |
| except ValueError as error: | |
| raise gr.Error(str(error)) from error | |
| return ( | |
| result["text"], | |
| result["highlight"], | |
| result["summary"], | |
| result["readability"], | |
| result["diversity"], | |
| result["pos"], | |
| result["morph"], | |
| result["syntax"], | |
| result["cohesion"], | |
| result["lexical"], | |
| result["style"], | |
| result["phon"], | |
| result["verse"], | |
| result["verse_lines"], | |
| result["basic"], | |
| result["keywords"], | |
| result["collocations"], | |
| gr.Image(value=result["zipf"], visible=result["enough_words"]), | |
| gr.Markdown(visible=not result["enough_words"]), | |
| gr.Image(value=result["sentences"], visible=result["enough_sents"]), | |
| ) | |
| def rerender(text: str | None, *groups: list[str]) -> str: | |
| if not text: | |
| return "" | |
| return render_highlight(text, [layer for group in groups for layer in group]) | |
| HEADER = """ | |
| # ruTS - статистики русского текста | |
| Вставьте текст и получите удобочитаемость, лексическое разнообразие, морфологический и синтаксический | |
| профиль, связность, частотность слов, SEO-метрики стиля, фоностатистики, метр и рифму стиха, ключевые слова и коллокации, а также подсветку фрагментов, из которых складываются эти числа. | |
| [GitHub](https://github.com/SergeyShk/ruTS) · [Документация](https://sergeyshk.github.io/ruTS/) · | |
| [PyPI](https://pypi.org/project/ruts/) | |
| """ | |
| FOOTER = ( | |
| f"ruts {version('ruts')} · spaCy {spacy.__version__} · ru_core_news_sm {nlp.meta['version']}" | |
| ) | |
| CSS = """ | |
| .ruts-highlight { font-size: 1.05em; } | |
| .stats { --font-mono: var(--font); } | |
| #text-input, #text-input > label { display: flex; flex-direction: column; } | |
| #text-input > label, #text-input .input-container { flex: 1 1 auto; } | |
| #text-input .input-container { align-items: stretch; } | |
| """ | |
| INITIAL = compute(EXAMPLES["Новость"], DEFAULT_LAYERS) | |
| with gr.Blocks(title="ruTS") as demo: | |
| gr.Markdown(HEADER) | |
| text_state = gr.State(INITIAL["text"]) | |
| highlight_output = gr.HTML( | |
| INITIAL["highlight"], label="Подсветка", container=True, padding=True, render=False | |
| ) | |
| summary_output = gr.Markdown(INITIAL["summary"], render=False) | |
| tables = { | |
| name: gr.Dataframe(INITIAL[name], interactive=False, elem_classes="stats", render=False) | |
| for name in ( | |
| "readability", | |
| "diversity", | |
| "morph", | |
| "syntax", | |
| "cohesion", | |
| "lexical", | |
| "style", | |
| "phon", | |
| "verse", | |
| "basic", | |
| "keywords", | |
| "collocations", | |
| ) | |
| } | |
| verse_lines_output = gr.Markdown(INITIAL["verse_lines"], render=False) | |
| pos_output = gr.BarPlot( | |
| INITIAL["pos"], | |
| x="Часть речи", | |
| y="Слов", | |
| sort="-y", | |
| x_label_angle=-30, | |
| height=300, | |
| container=False, | |
| render=False, | |
| ) | |
| zipf_output = gr.Image( | |
| INITIAL["zipf"], | |
| label="Закон Ципфа", | |
| show_label=False, | |
| interactive=False, | |
| visible=INITIAL["enough_words"], | |
| render=False, | |
| ) | |
| zipf_note = gr.Markdown( | |
| f"*График закона Ципфа строится для текстов от {ZIPF_MIN_WORDS} слов.*", | |
| visible=not INITIAL["enough_words"], | |
| render=False, | |
| ) | |
| sentences_output = gr.Image( | |
| INITIAL["sentences"], | |
| label="Длины предложений", | |
| show_label=False, | |
| interactive=False, | |
| visible=INITIAL["enough_sents"], | |
| render=False, | |
| ) | |
| outputs = [ | |
| text_state, | |
| highlight_output, | |
| summary_output, | |
| tables["readability"], | |
| tables["diversity"], | |
| pos_output, | |
| tables["morph"], | |
| tables["syntax"], | |
| tables["cohesion"], | |
| tables["lexical"], | |
| tables["style"], | |
| tables["phon"], | |
| tables["verse"], | |
| verse_lines_output, | |
| tables["basic"], | |
| tables["keywords"], | |
| tables["collocations"], | |
| zipf_output, | |
| zipf_note, | |
| sentences_output, | |
| ] | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=3): | |
| text_input = gr.Textbox( | |
| INITIAL["text"], | |
| lines=9, | |
| max_lines=9, | |
| max_length=MAX_CHARS, | |
| label="Текст", | |
| elem_id="text-input", | |
| placeholder=f"Вставьте текст на русском языке, до {MAX_CHARS:,} символов".replace( | |
| ",", " " | |
| ), | |
| ) | |
| with gr.Column(scale=1): | |
| layer_inputs = [ | |
| gr.CheckboxGroup( | |
| choices=[(HIGHLIGHT_LAYERS_DESC[layer], layer) for layer in group], | |
| value=[layer for layer in group if layer in HIGHLIGHT_DEFAULT_LAYERS], | |
| label=f"Подсветка: {name.lower()}", | |
| ) | |
| for name, group in HIGHLIGHT_LAYER_GROUPS.items() | |
| ] | |
| analyze_button = gr.Button("Разобрать", variant="primary") | |
| gr.Examples( | |
| examples=[[text, *DEFAULT_GROUP_LAYERS] for text in EXAMPLES.values()], | |
| example_labels=list(EXAMPLES), | |
| inputs=[text_input, *layer_inputs], | |
| outputs=outputs, | |
| fn=analyze, | |
| run_on_click=True, | |
| cache_examples=False, | |
| label="Примеры", | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| highlight_output.render() | |
| zipf_output.render() | |
| zipf_note.render() | |
| sentences_output.render() | |
| with gr.Column(): | |
| summary_output.render() | |
| with gr.Tabs(): | |
| with gr.Tab("Читаемость"): | |
| tables["readability"].render() | |
| with gr.Tab("Лексика"): | |
| tables["diversity"].render() | |
| with gr.Tab("Морфология"): | |
| pos_output.render() | |
| tables["morph"].render() | |
| with gr.Tab("Синтаксис"): | |
| tables["syntax"].render() | |
| with gr.Tab("Связность"): | |
| tables["cohesion"].render() | |
| with gr.Tab("Частотность"): | |
| tables["lexical"].render() | |
| with gr.Tab("Стиль"): | |
| tables["style"].render() | |
| with gr.Tab("Фоника"): | |
| tables["phon"].render() | |
| with gr.Tab("Стих"): | |
| gr.Markdown(VERSE_NOTE) | |
| tables["verse"].render() | |
| verse_lines_output.render() | |
| with gr.Tab("Базовые"): | |
| tables["basic"].render() | |
| with gr.Tab("Ключевые слова"): | |
| gr.Markdown(KEYWORDS_NOTE) | |
| tables["keywords"].render() | |
| tables["collocations"].render() | |
| gr.Markdown(FOOTER) | |
| analyze_button.click(analyze, inputs=[text_input, *layer_inputs], outputs=outputs) | |
| text_input.submit(analyze, inputs=[text_input, *layer_inputs], outputs=outputs) | |
| for layer_input in layer_inputs: | |
| layer_input.change( | |
| rerender, inputs=[text_state, *layer_inputs], outputs=[highlight_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(css=CSS) | |