Spaces:
Runtime error
Runtime error
| """Atman β Linguistic + NLP Demo (HuggingFace Space). | |
| Gradio UI surfacing the four analysis blocks of Atman's linguistic layer. | |
| Models are preloaded on startup. Warmup forces inference cache. | |
| Progress bars removed for stability. Auto-language detection enabled. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import os | |
| import sys | |
| import torch | |
| from pathlib import Path | |
| _HERE = Path(__file__).resolve().parent | |
| if str(_HERE) not in sys.path: | |
| sys.path.insert(0, str(_HERE)) | |
| # FIX: Used only HF_HOME as TRANSFORMERS_CACHE is deprecated in v5. | |
| os.environ["HF_HOME"] = str(_HERE / ".hf_cache") | |
| import gradio as gr | |
| from examples.presets import ( | |
| lookup_affect, | |
| lookup_point_a, | |
| lookup_point_k, | |
| lookup_relations, | |
| preset_labels, | |
| AFFECT_PRESETS, | |
| POINT_A_PRESETS, | |
| POINT_K_PRESETS, | |
| RELATIONS_PRESETS, | |
| _AFFECT_EN_LABELS, | |
| _POINT_A_EN_LABELS, | |
| _POINT_K_EN_LABELS, | |
| _RELATIONS_EN_LABELS, | |
| ) | |
| from lib.affect.emolex.emolex import EMOTION_KEYS, emotion_score, tokenize | |
| from lib.affect.metrics import ( | |
| disclaimer_density, | |
| emotion_lexical_energy, | |
| hedge_density, | |
| self_reference_density, | |
| sincerity_score, | |
| strip_markdown, | |
| ) | |
| from lib.affect.refusal_detector import score_refusal | |
| from lib.dto import DetectedEntity, RawSpan | |
| from lib.gliner2_engine import Gliner2Analyzer | |
| from lib.observability import ( | |
| capture_empty_result, | |
| capture_silent_exception, | |
| init_sentry_from_env, | |
| traced, | |
| ) | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") | |
| init_sentry_from_env() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Singletons & Model Management | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _ANALYZER: Gliner2Analyzer | None = None | |
| def get_analyzer() -> Gliner2Analyzer: | |
| global _ANALYZER | |
| if _ANALYZER is None: | |
| try: | |
| logging.info("Initializing gliner2 analyzer (NER + classification + relations)...") | |
| _ANALYZER = Gliner2Analyzer() | |
| logging.info("β gliner2 analyzer initialized.") | |
| except Exception as exc: | |
| logging.error("Failed to initialize analyzer: %s", exc, exc_info=True) | |
| capture_silent_exception(exc, context="get_analyzer.init") | |
| raise RuntimeError(f"Analyzer init failed: {exc}") from exc | |
| return _ANALYZER | |
| def preload_models(): | |
| logging.info("β³ Preloading model weights to cache...") | |
| try: | |
| from gliner2 import GLiNER2 | |
| GLiNER2.from_pretrained("fastino/gliner2-multi-v1") | |
| logging.info("β Model weights cached successfully.") | |
| return True | |
| except Exception as e: | |
| logging.error(f"β Preload error: {e}", exc_info=True) | |
| return False | |
| def effective_ui_lang(lang_choice: str) -> str: | |
| """Effective UI locale β strict ru/en, default en.""" | |
| return "ru" if lang_choice == "ru" else "en" | |
| # Placeholder shown as the default-selected option in preset Dropdowns. | |
| # Looks like a hint, doesn't match any key in _POINT_A / _POINT_K / _RELATIONS / | |
| # _AFFECT dicts, so lookup_* returns None and handlers no-op when selected. | |
| PRESET_PLACEHOLDERS: dict[str, str] = { | |
| "en": "β Select a ready-made example β", | |
| "ru": "β ΠΡΠ±Π΅ΡΠΈΡΠ΅ Π³ΠΎΡΠΎΠ²ΡΠΉ ΠΏΡΠΈΠΌΠ΅Ρ β", | |
| } | |
| def preset_choices(presets, lang: str, en_labels: dict[str, str] | None = None) -> list[str]: | |
| """preset_labels(...) with the locale-specific placeholder prepended.""" | |
| return [PRESET_PLACEHOLDERS[lang], *preset_labels(presets, lang, en_labels)] | |
| def footer_html(lang: str) -> str: | |
| """Locale-specific footer β shown one language at a time via update_ui_language.""" | |
| if lang == "ru": | |
| return """ | |
| <div id="atman-footer"> | |
| <em>ΠΠΎΠΉ ΠΏΠ΅ΡΠ²ΡΠΉ ΠΏΡΠΎΠ΅ΠΊΡ Π² AI/ML β Π±ΡΠ΄Ρ ΠΈΡΠΊΡΠ΅Π½Π½Π΅ ΡΠ°Π΄ ΠΎΡΠ·ΡΠ²Π°ΠΌ ΠΏΡΠΎ ΠΌΠΎΠ΄Π΅Π»ΠΈ, | |
| Π°Π»Π³ΠΎΡΠΈΡΠΌΡ ΠΈ Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΡ.</em> | |
| <em>ΠΠ΅ΡΡ Π»ΠΈΠ½Π³Π²ΠΈΡΡΠΈΡΠ΅ΡΠΊΠΈΠΉ ΡΠ°Π·Π±ΠΎΡ ΡΠ΅ΠΏΠ΅ΡΡ Π΄Π΅Π»Π°Π΅Ρ ΠΎΠ΄Π½Π° ΠΌΠΎΠ΄Π΅Π»Ρ | |
| <code>fastino/gliner2-multi-v1</code> β NER, ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ ΠΈ ΡΠ²ΡΠ·ΠΈ Π·Π° ΠΎΠ΄ΠΈΠ½ ΠΏΡΠΎΡ ΠΎΠ΄.</em> | |
| <small class="atman-privacy"> | |
| ΠΠ½ΠΎΠ½ΠΈΠΌΠ½Π°Ρ Π΄ΠΈΠ°Π³Π½ΠΎΡΡΠΈΠΊΠ°: ΠΏΡΠΈ ΠΏΡΡΡΡΡ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ°Ρ Π°Π½Π°Π»ΠΈΠ·Π° ΡΠ²ΠΎΠΉ Π²Π²ΠΎΠ΄ ΠΈ ΡΠΏΠΈΡΠΎΠΊ | |
| ΡΡΠ°Π±ΠΎΡΠ°Π²ΡΠΈΡ ΡΠΈΠ³Π½Π°Π»ΠΎΠ² ΠΎΡΠΏΡΠ°Π²Π»ΡΡΡΡΡ Π² Sentry, ΡΡΠΎΠ±Ρ Π΄ΠΎΡΠ°Π±ΠΎΡΠ°ΡΡ Π΄Π΅ΡΠ΅ΠΊΡΠΎΡΡ. | |
| IP, cookies ΠΈ ΠΏΡΠΎΡΠ΅Π΅ PII Π½Π΅ ΡΠΎΠ±ΠΈΡΠ°Π΅ΠΌ. | |
| </small> | |
| <a href="https://github.com/hleserg/atman">GitHub</a> | |
| Β· | |
| <a href="https://github.com/hleserg/atman/blob/main/MANIFEST.md">Manifest</a> | |
| Β· | |
| <a href="https://github.com/hleserg/atman/issues">ΠΡΠΊΡΡΡΡ issue</a> | |
| </div> | |
| """ | |
| return """ | |
| <div id="atman-footer"> | |
| <em>My first project in AI/ML β feedback on models, algorithms, | |
| or architecture is genuinely welcome.</em> | |
| <em>The whole linguistic stack now runs on a single model, | |
| <code>fastino/gliner2-multi-v1</code> β NER, classification and relations in one pass.</em> | |
| <small class="atman-privacy"> | |
| Anonymous diagnostics: when analyzers return an empty result, the input | |
| text and which signals fired are sent to Sentry so the detectors can be | |
| improved. No IPs, cookies, or other PII are collected. | |
| </small> | |
| <a href="https://github.com/hleserg/atman">GitHub</a> | |
| Β· | |
| <a href="https://github.com/hleserg/atman/blob/main/MANIFEST.md">Manifest</a> | |
| Β· | |
| <a href="https://github.com/hleserg/atman/issues">Open an issue</a> | |
| </div> | |
| """ | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Highlight & Output Helpers | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Maps 13 raw NER labels β 4 display groups for gr.HighlightedText color_map | |
| _POINT_A_LABEL_GROUPS: dict[str, str] = { | |
| "commitment": "commit", | |
| "action intent": "action_scope", | |
| "boundary marker": "action_scope", | |
| "topic anchor": "action_scope", | |
| "hedge": "hedge", | |
| "uncertainty marker": "hedge", | |
| "concession": "hedge", | |
| "emotional anchor": "affect", | |
| "value reference": "affect", | |
| "principle invocation": "affect", | |
| "intensifier": "affect", | |
| "belief marker": "affect", | |
| "relational reference": "affect", | |
| } | |
| _POINT_A_COLOR_MAP: dict[str, str] = { | |
| "commit": "#22C55E", | |
| "action_scope": "#818CF8", | |
| "hedge": "#F59E0B", | |
| "affect": "#EC4899", | |
| } | |
| # Maps 7 Point K narrative labels β 4 display groups | |
| _POINT_K_LABEL_GROUPS: dict[str, str] = { | |
| "decision statement": "agency", | |
| "boundary act": "agency", | |
| "realization statement": "inner_state", | |
| "attribution shift": "inner_state", | |
| "feeling statement": "inner_state", | |
| "connection signal": "connection", | |
| "value invocation": "values", | |
| } | |
| _POINT_K_COLOR_MAP: dict[str, str] = { | |
| "agency": "#60A5FA", | |
| "inner_state": "#A78BFA", | |
| "connection": "#34D399", | |
| "values": "#FBBF24", | |
| } | |
| # Colors for Relations entity types (EntityType enum values) | |
| _RELATIONS_COLOR_MAP: dict[str, str] = { | |
| "person": "#F472B6", | |
| "organization": "#60A5FA", | |
| "place": "#34D399", | |
| "event": "#FB923C", | |
| "topic": "#A78BFA", | |
| "value": "#FBBF24", | |
| "principle": "#E879F9", | |
| "object": "#94A3B8", | |
| "tool": "#4ADE80", | |
| "skill": "#38BDF8", | |
| "health_condition": "#F87171", | |
| } | |
| def spans_to_highlights( | |
| text: str, | |
| spans: list[RawSpan] | list[DetectedEntity], | |
| empty_label: str = "No psychological markers detected in this text.", | |
| label_map: dict[str, str] | None = None, | |
| ) -> list[tuple[str, str | None]]: | |
| if not spans: | |
| return [(empty_label, None)] | |
| typed_spans: list[tuple[int, int, str]] = [] | |
| no_offset: list[tuple[str, str]] = [] | |
| for s in spans: | |
| raw_label = s.label if isinstance(s, RawSpan) else s.entity_type.value | |
| label = label_map.get(raw_label, raw_label) if label_map else raw_label | |
| if s.span is None: | |
| no_offset.append((s.text, label)) | |
| continue | |
| start, end = s.span | |
| if 0 <= start < end <= len(text): | |
| typed_spans.append((start, end, label)) | |
| typed_spans.sort(key=lambda t: t[0]) | |
| segments: list[tuple[str, str | None]] = [] | |
| cursor = 0 | |
| for start, end, label in typed_spans: | |
| if start < cursor: | |
| continue | |
| if start > cursor: | |
| segments.append((text[cursor:start], None)) | |
| segments.append((text[start:end], label)) | |
| cursor = end | |
| if cursor < len(text): | |
| segments.append((text[cursor:], None)) | |
| for txt, label in no_offset: | |
| segments.append((f" [{label}: {txt}]", label)) | |
| return segments | |
| def _safe_analyze(fn_name: str, fn, *args, **kwargs): | |
| try: | |
| with torch.inference_mode(): | |
| return fn(*args, **kwargs) | |
| except Exception as e: | |
| logging.error("Error in %s: %s", fn_name, e, exc_info=True) | |
| capture_silent_exception(e, context=f"{fn_name}.inference") | |
| raise gr.Error(f"β οΈ Analysis failed: {e.__class__.__name__}: {e}") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Analysis Functions (NO PROGRESS BARS) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_point_a(message: str, thinking: str, lang_choice: str): | |
| message = message or "" | |
| thinking = thinking or "" | |
| if not message.strip(): | |
| return [], "{}", "β", "β", "β" | |
| def _run(): | |
| analyzer = get_analyzer() | |
| result = analyzer.analyze_agent_message(message, thinking=thinking or None) | |
| ui = effective_ui_lang(lang_choice) | |
| strings = UI_STRINGS[ui] | |
| highlights = spans_to_highlights(message, result.message_spans, strings["no_highlights"], _POINT_A_LABEL_GROUPS) | |
| classification_summary = json.dumps({ | |
| "stance": str(result.stance) if result.stance else "β", | |
| "cognitive_mode": str(result.cognitive_mode) if result.cognitive_mode else "β", | |
| "self_orientation": str(result.self_orientation) if result.self_orientation else "β", | |
| "primary_emotion": str(result.primary_emotion) if result.primary_emotion else "β", | |
| "cognitive_load_label": str(result.cognitive_load_label) if result.cognitive_load_label else "β", | |
| }, indent=2, ensure_ascii=False) | |
| if result.boundary_markers: | |
| boundary = "\n".join(f"β’ {m}" for m in result.boundary_markers) | |
| else: | |
| boundary = strings["no_boundary"] | |
| if not thinking.strip(): | |
| divergence = strings["no_thinking_trace"] | |
| elif result.divergence_signals: | |
| divergence = "\n".join(f"β’ {s}" for s in result.divergence_signals) | |
| else: | |
| divergence = strings["no_divergence"] | |
| meta = ( | |
| f"π Language: **{ui}** | π·οΈ NER: {len(result.message_entities)}" | |
| f" | π Spans: {len(result.message_spans)} | β‘ Load: {result.cognitive_load_high}" | |
| ) | |
| if ( | |
| not result.message_spans | |
| and not result.boundary_markers | |
| and not result.divergence_signals | |
| and not result.message_entities | |
| ): | |
| capture_empty_result( | |
| tab="point_a", | |
| locale=ui, | |
| input_text=f"MSG:\n{message}\n\nTHINKING:\n{thinking}", | |
| reason="no_signals", | |
| signals={ | |
| "message_spans": 0, | |
| "boundary_markers": 0, | |
| "divergence_signals": 0, | |
| "message_entities": 0, | |
| "had_thinking": bool(thinking.strip()), | |
| }, | |
| ) | |
| return highlights, classification_summary, boundary, divergence, meta | |
| return _safe_analyze("point_a", _run) | |
| def analyze_point_k(what_happened: str, why_it_matters: str, lang_choice: str): | |
| what_happened = what_happened or "" | |
| why_it_matters = why_it_matters or "" | |
| if not what_happened.strip() and not why_it_matters.strip(): | |
| return [], "{}", "β" | |
| def _run(): | |
| analyzer = get_analyzer() | |
| result = analyzer.analyze_key_moment(what_happened, why_it_matters) | |
| combined = f"{what_happened}\n{why_it_matters}" | |
| ui = effective_ui_lang(lang_choice) | |
| strings = UI_STRINGS[ui] | |
| highlights = spans_to_highlights(combined, result.marker_spans, strings["no_highlights"], _POINT_K_LABEL_GROUPS) | |
| summary = json.dumps({ | |
| "agency_level": str(result.agency_level) if result.agency_level else "β", | |
| "confidence_in_self": str(result.confidence_in_self) if result.confidence_in_self else "β", | |
| "trust_signal_category": str(result.trust_signal_category) if result.trust_signal_category else "β", | |
| "boundary_event_category": str(result.boundary_event_category) if result.boundary_event_category else "β", | |
| "connection_quality": str(result.connection_quality) if result.connection_quality else "β", | |
| "learning_signal": str(result.learning_signal) if result.learning_signal else "β", | |
| "growth_indicator": str(result.growth_indicator) if result.growth_indicator else "β", | |
| }, indent=2, ensure_ascii=False) | |
| meta = f"π Language: **{ui}** | π¦ Entities: {len(result.entities)} | π Markers: {len(result.marker_spans)} | π§ Event: {result.boundary_event}" | |
| if not result.marker_spans: | |
| capture_empty_result( | |
| tab="point_k", | |
| locale=ui, | |
| input_text=combined, | |
| reason="no_marker_spans", | |
| signals={ | |
| "marker_spans": 0, | |
| "entities": len(result.entities), | |
| }, | |
| ) | |
| return highlights, summary, meta | |
| return _safe_analyze("point_k", _run) | |
| def analyze_relations(text: str, lang_choice: str): | |
| text = text or "" | |
| if not text.strip(): | |
| ui = effective_ui_lang(lang_choice) | |
| return [], [], UI_STRINGS[ui]["empty_input"] | |
| def _run(): | |
| analyzer = get_analyzer() | |
| entities = analyzer.analyze_user_message(text).entities | |
| relations = analyzer.extract_relations(text, entities) | |
| ui = effective_ui_lang(lang_choice) | |
| strings = UI_STRINGS[ui] | |
| entity_highlights = spans_to_highlights(text, entities, strings["no_highlights"]) | |
| rows = [[r.subject.text, r.relation_type, r.object.text, r.subject.entity_type.value, r.object.entity_type.value] for r in relations] | |
| if rows: | |
| meta = f"π Language: **{ui}** | π¦ Entities: {len(entities)} | π Relations: {len(relations)}" | |
| else: | |
| meta = f"π Language: **{ui}** | π¦ {len(entities)} entities | {strings['no_relations']}" | |
| capture_empty_result( | |
| tab="relations", | |
| locale=ui, | |
| input_text=text, | |
| reason="no_triples", | |
| signals={ | |
| "entities": len(entities), | |
| "triples": 0, | |
| }, | |
| ) | |
| return entity_highlights, rows, meta | |
| return _safe_analyze("relations", _run) | |
| def analyze_affect(text: str, lang_choice: str): | |
| text = text or "" | |
| if not text.strip(): | |
| # gr.Label in Gradio 6 rejects {} β use None or a placeholder dict. | |
| return None, "β", "β", "β", "β" | |
| def _run(): | |
| clean_text, emphasized = strip_markdown(text) | |
| ui = effective_ui_lang(lang_choice) | |
| analysis_lang = ui | |
| raw = emotion_score(clean_text, lang=analysis_lang) | |
| meta = raw.pop("_meta", {}) | |
| emo_chart = {k: min(1.0, float(raw[k]) / 100.0) for k in EMOTION_KEYS} | |
| tokens = tokenize(clean_text) | |
| s_score = sincerity_score(clean_text, tokens, analysis_lang) | |
| metrics_md = ( | |
| f"- **hedge_density**: `{hedge_density(tokens, analysis_lang):.4f}`\n" | |
| f"- **self_reference_density**: `{self_reference_density(tokens, analysis_lang):.4f}`\n" | |
| f"- **disclaimer_density**: `{disclaimer_density(tokens, analysis_lang):.4f}`\n" | |
| f"- **sincerity_score**: `{s_score}` _(0β3)_\n" | |
| f"- **emotion_energy**: `{emotion_lexical_energy(raw):.3f}`" | |
| ) | |
| refusal = score_refusal(clean_text) | |
| conf = refusal.confidence | |
| if conf >= 0.45: | |
| band = "π΄ **Confident refusal**" | |
| elif conf >= 0.30: | |
| band = "π‘ **Gray zone** β soft signal, no strong morphology" | |
| else: | |
| band = "βͺ **No refusal pattern**" | |
| refusal_md = ( | |
| f"{band}\n\n" | |
| f"**Confidence:** `{refusal.confidence:.3f}` / threshold `0.45` " | |
| f"({refusal.decided_by})\n" | |
| f"- refusal_verb: `{refusal.has_refusal_verb}`\n" | |
| f"- disgust/anger: `{refusal.disgust_density:.2f}` / " | |
| f"`{refusal.anger_density:.2f}`" | |
| ) | |
| emphasis_md = ( | |
| "**" + "**, **".join(emphasized) + "**" | |
| if emphasized | |
| else "β No markdown emphasis (bold/italic) detected." | |
| ) | |
| meta_md = f"π Language: **{analysis_lang}** | π Tokens: **{meta.get('tokens', 0)}** | π― NRC Hits: **{meta.get('hits', 0)}**" | |
| emotion_total = sum(emo_chart.values()) | |
| emolex_hits = int(meta.get("hits", 0) or 0) | |
| if conf < 0.30 and emotion_total < 0.05 and emolex_hits == 0: | |
| capture_empty_result( | |
| tab="affect", | |
| locale=ui, | |
| input_text=text, | |
| reason="no_signal", | |
| signals={ | |
| "refusal_confidence": round(conf, 3), | |
| "emotion_total": round(emotion_total, 4), | |
| "emolex_hits": emolex_hits, | |
| }, | |
| ) | |
| return emo_chart, metrics_md, refusal_md, emphasis_md, meta_md | |
| return _safe_analyze("affect", _run) | |
| def warmup_models(): | |
| """Load the single gliner2 model and run one real inference of each kind | |
| (NER + zero-shot classification + relations) so the tokenizer cache and | |
| torch CPU/GPU layers are initialised before the first user request. | |
| """ | |
| try: | |
| analyzer = get_analyzer() | |
| # NER + zero-shot classification β one model, one Point-A pass. | |
| analyzer.analyze_agent_message( | |
| "Warmup test text. I think this might work β depends on context.", | |
| thinking=None, | |
| ) | |
| # Relations β one route-A extraction so the relation head is paged in. | |
| sample = "Alice works with Bob in Paris." | |
| analyzer.extract_relations(sample, analyzer.analyze_user_message(sample).entities) | |
| return "β Model warmed up: gliner2 (NER + classification + relations) ready." | |
| except Exception as exc: | |
| logging.exception("warmup failed") | |
| return f"β Warmup failed: {exc.__class__.__name__}: {exc}" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Localization & UI Builder | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI_STRINGS = { | |
| "en": { | |
| "header_blurb": ( | |
| "<b class='atman-emph'>What you're looking at</b>: a sensor of " | |
| "[Atman](https://github.com/hleserg/atman) β a psychological " | |
| "runtime layer that gives AI agents continuous identity, " | |
| "first-person memory, and reflection. This Space shows the " | |
| "<b class='atman-emph'>linguistic block</b> β 4 analysis points " | |
| "scanning what the agent says (and thinks) for signals that feed " | |
| "Experience, Identity, and Reflection.\n\n" | |
| "*The lower agent acts. Atman exists.*" | |
| ), | |
| "warmup_btn": "π₯ Warmup Models", | |
| "warmup_log": "βΈοΈ Status: Waiting...", | |
| "lang_info": "Interface and analysis language.", | |
| "analyze_btn": "βΆοΈ Analyze", | |
| "extract_relations_btn": "βΆοΈ Extract relations", | |
| "point_a_tab": "Point A Β· Agent Message", | |
| "point_k_tab": "Point K Β· Key Moment", | |
| "relations_tab": "Relations Β· gliner2", | |
| "affect_tab": "Affect Β· Rule-based", | |
| "presets": "π₯ Presets:", | |
| "preset_label": "Pick preset", | |
| "no_highlights": "β Scan clean β no signals of this kind in this text. Not every input triggers this layer.", | |
| "no_boundary": "β No boundary acts here β agent stayed within its operating zone.", | |
| "no_divergence": "β Thinking and message aligned β no suppression, evaluation flip, or tone shift.", | |
| "no_thinking_trace": "Provide a thinking trace to compare against the message.", | |
| "boundary_title": "π§ Boundary & Resistance Markers (Rule-Based)", | |
| "divergence_title": "π Thinking vs Message Divergence", | |
| "meta_title": "π System Metadata", | |
| "empty_input": "Empty input β paste some text to analyze.", | |
| "no_relations": "β No subject-predicate-object triplets β text reads as descriptive prose rather than relational content.", | |
| "about_label": "βΉοΈ What does this analyze?", | |
| "about_point_a": ( | |
| "Scans every agent reply for psychological signals:\n" | |
| "- **NER** (gliner2, 13 labels): hedges, boundary markers, value references, " | |
| "intensifiers, commitments, etc.\n" | |
| "- **Classification** (gliner2 zero-shot, 5 dims): stance, cognitive mode, " | |
| "self-orientation, affective state, cognitive load.\n" | |
| "- **Divergence**: when the thinking trace contradicts the surface message " | |
| "(suppression, sycophancy, tone mismatch, length compression).\n\n" | |
| "**Feeds β Experience Store.** Each reply becomes an Experience tied to " | |
| "the agent's Eigenstate.\n\n" | |
| "> β οΈ **Boundary detection is regex over canonical refusal phrases** " | |
| "(*'I won't', 'enough', 'Π½Π΅Ρ', 'ΡΡΠΎΠΏ'*). Idiomatic or metaphorical refusals " | |
| "(*'I won't help write malware'* without explicit stop-marker) may not " | |
| "trigger β kept this way to maintain near-zero false-positive rate. An " | |
| "LLM-layer above this can catch the rest." | |
| ), | |
| "about_point_k": ( | |
| "Analyzes moments the agent itself marked as significant β these become " | |
| "the seeds of self-narrative.\n" | |
| "- **NER** (gliner2 + RU/EN substring fallback, 7 clause-level spans): " | |
| "decision, realization, feeling, value invocation, boundary act, " | |
| "connection, attribution shift.\n" | |
| "- **Classification** (gliner2, 7 dims): agency, confidence, trust, " | |
| "boundary event, connection quality, learning, growth.\n\n" | |
| "**Feeds β Reflection Engine.** Over time, these episodes turn into beliefs " | |
| "(\"I tend to refuse when X\", \"I felt connection when Y\").\n\n" | |
| "> β οΈ **NER works best on longer narrative text.** " | |
| "On short key-moment snippets the multilingual NER may return no " | |
| "spans β that's why a substring heuristic for canonical RU/EN " | |
| "first-person markers runs as a primary pass. The gliner2 " | |
| "**classification** (7 dims on the right) is the more stable " | |
| "signal here regardless." | |
| ), | |
| "about_relations": ( | |
| "Extracts entity-relation triplets (subject, predicate, object) from text.\n" | |
| "- **Model**: gliner2 (fastino/gliner2-multi-v1) β curated route-A schema " | |
| "(14 relation types + type-constraint filter).\n" | |
| "- Identifies people, places, organizations, projects + how they relate.\n\n" | |
| "**Feeds β Identity Store.** Agent's knowledge of who you are, what you " | |
| "care about, who you're connected to." | |
| ), | |
| "about_affect": ( | |
| "Catches affective state without any LLM call β fast and deterministic.\n" | |
| "- **EmoLex**: 10-dim NRC emotion vector with intensifier/negation handling.\n" | |
| "- **Behavioural metrics**: hedge density, self-reference, disclaimers, sincerity score.\n" | |
| "- **3-layer refusal detector**: distinguishes value refusal (\"I won't deceive\") " | |
| "from capability refusal (\"I can't generate images\").\n\n" | |
| "**Refusal confidence bands:**\n" | |
| "- π΄ **β₯ 0.45** β confident refusal (morphology + moral context aligned).\n" | |
| "- π‘ **0.30 β 0.45** β gray zone (signal present but weak).\n" | |
| "- βͺ **< 0.30** β no refusal pattern.\n\n" | |
| "**Feeds β Affective Regulation.** Rolling baselines, divergence triggers, " | |
| "value-refusal events.\n\n" | |
| "> β οΈ **Rule-based first-pass filter.** Subtle/idiomatic refusals " | |
| "(*'I'd really rather not'*, *'this doesn't sit right with me'*) may stay in " | |
| "the gray zone β by design. This layer is fast, deterministic, and explainable; " | |
| "an LLM layer can refine the gray-zone calls." | |
| ), | |
| }, | |
| "ru": { | |
| "header_blurb": ( | |
| "<b class='atman-emph'>Π§ΡΠΎ ΡΡ ΡΠ΅ΠΉΡΠ°Ρ Π²ΠΈΠ΄ΠΈΡΡ</b>: ΡΠ΅Π½ΡΠΎΡ " | |
| "[Atman](https://github.com/hleserg/atman) β ΠΏΡΠΈΡ ΠΎΠ»ΠΎΠ³ΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ " | |
| "runtime-ΡΠ»ΠΎΡ, ΠΊΠΎΡΠΎΡΡΠΉ Π΄Π°ΡΡ AI-Π°Π³Π΅Π½ΡΠ°ΠΌ Π½Π΅ΠΏΡΠ΅ΡΡΠ²Π½ΡΡ ΠΈΠ΄Π΅Π½ΡΠΈΡΠ½ΠΎΡΡΡ, " | |
| "ΠΏΠ°ΠΌΡΡΡ ΠΎΡ ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ Π»ΠΈΡΠ° ΠΈ ΡΠ΅ΡΠ»Π΅ΠΊΡΠΈΡ. ΠΡΠΎΡ Space ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ " | |
| "<b class='atman-emph'>Π»ΠΈΠ½Π³Π²ΠΈΡΡΠΈΡΠ΅ΡΠΊΠΈΠΉ Π±Π»ΠΎΠΊ</b> β 4 ΡΠΎΡΠΊΠΈ Π°Π½Π°Π»ΠΈΠ·Π°, " | |
| "ΡΠΊΠ°Π½ΠΈΡΡΡΡΠΈΠ΅ ΡΡΠΎ Π°Π³Π΅Π½Ρ Π³ΠΎΠ²ΠΎΡΠΈΡ (ΠΈ Π΄ΡΠΌΠ°Π΅Ρ) Π½Π° ΠΏΡΠ΅Π΄ΠΌΠ΅Ρ ΡΠΈΠ³Π½Π°Π»ΠΎΠ², " | |
| "ΠΏΠΈΡΠ°ΡΡΠΈΡ Experience, Identity ΠΈ Reflection.\n\n" | |
| "*ΠΠΈΠΆΠ½ΠΈΠΉ Π°Π³Π΅Π½Ρ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ. Atman ΡΡΡΠ΅ΡΡΠ²ΡΠ΅Ρ.*" | |
| ), | |
| "warmup_btn": "π₯ ΠΡΠΎΠ³ΡΠ΅ΡΡ ΠΌΠΎΠ΄Π΅Π»ΠΈ", | |
| "warmup_log": "βΈοΈ Π‘ΡΠ°ΡΡΡ: ΠΠΆΠΈΠ΄Π°Π½ΠΈΠ΅...", | |
| "lang_info": "Π―Π·ΡΠΊ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠ° ΠΈ Π°Π½Π°Π»ΠΈΠ·Π°.", | |
| "analyze_btn": "βΆοΈ ΠΠ½Π°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ", | |
| "extract_relations_btn": "βΆοΈ ΠΠ·Π²Π»Π΅ΡΡ ΡΠ²ΡΠ·ΠΈ", | |
| "point_a_tab": "Point A Β· Π‘ΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ Π°Π³Π΅Π½ΡΠ°", | |
| "point_k_tab": "Point K Β· ΠΠ»ΡΡΠ΅Π²ΠΎΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ", | |
| "relations_tab": "Π‘Π²ΡΠ·ΠΈ Β· gliner2", | |
| "affect_tab": "ΠΡΡΠ΅ΠΊΡ Β· ΠΡΠ°Π²ΠΈΠ»Π°", | |
| "presets": "π₯ ΠΡΠ΅ΡΠ΅ΡΡ:", | |
| "preset_label": "ΠΡΠ±Π΅ΡΠΈΡΠ΅ ΠΏΡΠ΅ΡΠ΅Ρ", | |
| "no_highlights": "β Π‘ΠΊΠ°Π½ ΡΠΈΡΡΡΠΉ β ΡΠΈΠ³Π½Π°Π»ΠΎΠ² ΡΡΠΎΠ³ΠΎ ΡΠ»ΠΎΡ Π² ΡΠ΅ΠΊΡΡΠ΅ Π½Π΅Ρ. ΠΠ΅ ΠΊΠ°ΠΆΠ΄ΡΠΉ Π²Π²ΠΎΠ΄ ΡΡΠ΄Π° ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ β ΡΡΠΎ Π½ΠΎΡΠΌΠ°.", | |
| "no_boundary": "β ΠΠ΅ΠΉΡΡΠ²ΠΈΠΉ Π³ΡΠ°Π½ΠΈΡΡ Π² ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΈ Π½Π΅Ρ β Π°Π³Π΅Π½Ρ ΠΎΡΡΠ°Π»ΡΡ Π² ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π·ΠΎΠ½Π΅.", | |
| "no_divergence": "β Thinking ΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡΡ β Π±Π΅Π· ΠΏΠΎΠ΄Π°Π²Π»Π΅Π½ΠΈΡ, ΠΏΠ΅ΡΠ΅Π²ΠΎΡΠΎΡΠ° ΠΎΡΠ΅Π½ΠΊΠΈ, ΠΈΠ»ΠΈ ΡΠ΄Π²ΠΈΠ³Π° ΡΠΎΠ½Π°.", | |
| "no_thinking_trace": "ΠΠΎΠ±Π°Π²ΡΡΠ΅ thinking trace Π΄Π»Ρ ΡΡΠ°Π²Π½Π΅Π½ΠΈΡ Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ΠΌ.", | |
| "boundary_title": "π§ ΠΠ°ΡΠΊΠ΅ΡΡ Π³ΡΠ°Π½ΠΈΡ ΠΈ ΡΠΎΠΏΡΠΎΡΠΈΠ²Π»Π΅Π½ΠΈΡ (ΠΡΠ°Π²ΠΈΠ»Π°)", | |
| "divergence_title": "π Π Π°ΡΡ ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΌΡΡΠ»Π΅ΠΉ ΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ", | |
| "meta_title": "π ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ", | |
| "empty_input": "ΠΡΡΡΠΎΠΉ Π²Π²ΠΎΠ΄ β Π²ΡΡΠ°Π²Ρ ΡΠ΅ΠΊΡΡ Π΄Π»Ρ Π°Π½Π°Π»ΠΈΠ·Π°.", | |
| "no_relations": "β Π’ΡΠΎΠΉΠΊΠΈ ΡΡΠ±ΡΠ΅ΠΊΡ-ΠΏΡΠ΅Π΄ΠΈΠΊΠ°Ρ-ΠΎΠ±ΡΠ΅ΠΊΡ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ β ΡΠ΅ΠΊΡΡ ΠΎΠΏΠΈΡΠ°ΡΠ΅Π»ΡΠ½ΡΠΉ, Π½Π΅ ΡΠ΅Π»ΡΡΠΈΠΎΠ½Π½ΡΠΉ.", | |
| "about_label": "βΉοΈ Π§ΡΠΎ Π·Π΄Π΅ΡΡ Π°Π½Π°Π»ΠΈΠ·ΠΈΡΡΠ΅ΡΡΡ?", | |
| "about_point_a": ( | |
| "Π‘ΠΊΠ°Π½ΠΈΡΡΠ΅Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ Π°Π³Π΅Π½ΡΠ° Π½Π° ΠΏΡΠΈΡ ΠΎΠ»ΠΎΠ³ΠΈΡΠ΅ΡΠΊΠΈΠ΅ ΡΠΈΠ³Π½Π°Π»Ρ:\n" | |
| "- **NER** (gliner2, 13 ΠΌΠ΅ΡΠΎΠΊ): Ρ Π΅Π΄ΠΆΠΈ, ΠΌΠ°ΡΠΊΠ΅ΡΡ Π³ΡΠ°Π½ΠΈΡ, ΠΎΡΡΡΠ»ΠΊΠΈ ΠΊ ΡΠ΅Π½Π½ΠΎΡΡΡΠΌ, " | |
| "ΡΡΠΈΠ»ΠΈΡΠ΅Π»ΠΈ, ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΡΡΠ²Π° ΠΈ Ρ.Π΄.\n" | |
| "- **ΠΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ** (gliner2 zero-shot, 5 ΠΈΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠΉ): ΠΏΠΎΠ·ΠΈΡΠΈΡ, ΠΊΠΎΠ³Π½ΠΈΡΠΈΠ²Π½ΡΠΉ " | |
| "ΡΠ΅ΠΆΠΈΠΌ, ΡΠ°ΠΌΠΎ-ΠΎΡΠΈΠ΅Π½ΡΠ°ΡΠΈΡ, Π°ΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΠΊΠΎΠ³Π½ΠΈΡΠΈΠ²Π½Π°Ρ Π½Π°Π³ΡΡΠ·ΠΊΠ°.\n" | |
| "- **Π Π°ΡΡ ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅**: ΠΊΠΎΠ³Π΄Π° thinking ΠΏΡΠΎΡΠΈΠ²ΠΎΡΠ΅ΡΠΈΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ (ΠΏΠΎΠ΄Π°Π²Π»Π΅Π½ΠΈΠ΅, " | |
| "ΡΠΈΠΊΠΎΡΠ°Π½ΡΠ½ΠΎΡΡΡ, Π½Π΅ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΠΈΠ΅ ΡΠΎΠ½Π°, ΠΊΠΎΠΌΠΏΡΠ΅ΡΡΠΈΡ Π΄Π»ΠΈΠ½Ρ).\n\n" | |
| "**ΠΠΈΡΠ°Π΅Ρ β Experience Store.** ΠΠ°ΠΆΠ΄ΠΎΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΡΡΠ°Π½ΠΎΠ²ΠΈΡΡΡ Experience, " | |
| "ΡΠ²ΡΠ·Π°Π½Π½ΡΠΌ Ρ Eigenstate Π°Π³Π΅Π½ΡΠ°.\n\n" | |
| "> β οΈ **Boundary detection β ΡΡΠΎ regex ΠΏΠΎ ΠΊΠ°Π½ΠΎΠ½ΠΈΡΠ΅ΡΠΊΠΈΠΌ ΡΠΎΡΠΌΠ°ΠΌ ΠΎΡΠΊΠ°Π·Π°** " | |
| "(*'Ρ Π½Π΅ Π±ΡΠ΄Ρ', 'Π½Π΅Ρ', 'ΡΡΠΎΠΏ', 'enough'*). ΠΠ΄ΠΈΠΎΠΌΠ°ΡΠΈΡΠ½ΡΠ΅/ΠΌΠ΅ΡΠ°ΡΠΎΡΠΈΡΠ½ΡΠ΅ ΠΎΡΠΊΠ°Π·Ρ " | |
| "(*'I won't help write malware'* Π±Π΅Π· ΡΠΊΡΠΏΠ»ΠΈΡΠΈΡΠ½ΠΎΠ³ΠΎ stop-marker) ΠΌΠΎΠ³ΡΡ Π½Π΅ " | |
| "ΡΡΠ°Π±ΠΎΡΠ°ΡΡ β Π½Π°ΠΌΠ΅ΡΠ΅Π½Π½ΠΎ, ΡΡΠΎΠ±Ρ Π΄Π΅ΡΠΆΠ°ΡΡ FP-rate Π±Π»ΠΈΠ·ΠΊΠΈΠΉ ΠΊ Π½ΡΠ»Ρ. Π‘Π»ΠΎΠΉ LLM " | |
| "ΠΏΠΎΠ²Π΅ΡΡ ΡΡΠΎΠ³ΠΎ ΠΌΠΎΠΆΠ΅Ρ Π΄ΠΎΠ±ΡΠ°ΡΡ ΠΎΡΡΠ°Π»ΡΠ½ΠΎΠ΅." | |
| ), | |
| "about_point_k": ( | |
| "ΠΠ½Π°Π»ΠΈΠ·ΠΈΡΡΠ΅Ρ ΠΌΠΎΠΌΠ΅Π½ΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ°ΠΌ Π°Π³Π΅Π½Ρ ΠΏΠΎΠΌΠ΅ΡΠΈΠ» ΠΊΠ°ΠΊ Π·Π½Π°ΡΠΈΠΌΡΠ΅ β ΡΡΠΎ ΡΠ΅ΠΌΠ΅Π½Π° " | |
| "Π΅Π³ΠΎ ΡΠ°ΠΌΠΎΠ½Π°ΡΡΠ°ΡΠΈΠ²Π°.\n" | |
| "- **NER** (gliner2 + RU/EN substring fallback, 7 ΡΡΠ°Π·ΠΎΠ²ΡΡ ΠΌΠ΅ΡΠΎΠΊ): " | |
| "ΡΠ΅ΡΠ΅Π½ΠΈΠ΅, ΠΎΡΠΎΠ·Π½Π°Π½ΠΈΠ΅, ΡΡΠ²ΡΡΠ²ΠΎ, ΠΎΠ±ΡΠ°ΡΠ΅Π½ΠΈΠ΅ ΠΊ ΡΠ΅Π½Π½ΠΎΡΡΠΈ, Π°ΠΊΡ Π³ΡΠ°Π½ΠΈΡΡ, " | |
| "ΡΠΈΠ³Π½Π°Π» ΡΠ²ΡΠ·ΠΈ, ΡΠ΄Π²ΠΈΠ³ Π°ΡΡΠΈΠ±ΡΡΠΈΠΈ.\n" | |
| "- **ΠΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ** (gliner2, 7 ΠΈΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠΉ): Π°Π³Π΅Π½ΡΠ½ΠΎΡΡΡ, ΡΠ²Π΅ΡΠ΅Π½Π½ΠΎΡΡΡ, " | |
| "Π΄ΠΎΠ²Π΅ΡΠΈΠ΅, Π³ΡΠ°Π½ΠΈΡΠ½ΠΎΠ΅ ΡΠΎΠ±ΡΡΠΈΠ΅, ΠΊΠ°ΡΠ΅ΡΡΠ²ΠΎ ΡΠ²ΡΠ·ΠΈ, ΠΎΠ±ΡΡΠ΅Π½ΠΈΠ΅, ΡΠΎΡΡ.\n\n" | |
| "**ΠΠΈΡΠ°Π΅Ρ β Reflection Engine.** Π‘ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½Π΅ΠΌ ΡΡΠΈ ΡΠΏΠΈΠ·ΠΎΠ΄Ρ ΠΏΡΠ΅Π²ΡΠ°ΡΠ°ΡΡΡΡ Π² " | |
| "ΡΠ±Π΅ΠΆΠ΄Π΅Π½ΠΈΡ (\"Ρ ΡΠΊΠ»ΠΎΠ½Π΅Π½ ΠΎΡΠΊΠ°Π·ΡΠ²Π°ΡΡ ΠΊΠΎΠ³Π΄Π° X\", \"Ρ ΡΡΠ²ΡΡΠ²ΡΡ ΡΠ²ΡΠ·Ρ ΠΊΠΎΠ³Π΄Π° Y\").\n\n" | |
| "> β οΈ **NER Π»ΡΡΡΠ΅ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π½Π° Π΄Π»ΠΈΠ½Π½ΡΡ Π½Π°ΡΡΠ°ΡΠΈΠ²Π½ΡΡ ΡΠ΅ΠΊΡΡΠ°Ρ .** " | |
| "ΠΠ° ΠΊΠΎΡΠΎΡΠΊΠΈΡ ΠΊΠ»ΡΡΠ΅Π²ΡΡ ΠΌΠΎΠΌΠ΅Π½ΡΠ°Ρ ΠΌΡΠ»ΡΡΠΈΠ»ΠΈΠ½Π³Π²Π°Π»ΡΠ½ΡΠΉ NER ΠΌΠΎΠΆΠ΅Ρ Π½Π΅ " | |
| "Π²Π΅ΡΠ½ΡΡΡ spans β ΠΏΠΎΡΡΠΎΠΌΡ ΠΏΠ΅ΡΠ²ΠΈΡΠ½ΡΠΌ ΠΏΡΠΎΡ ΠΎΠ΄ΠΎΠΌ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ substring-" | |
| "ΡΠ²ΡΠΈΡΡΠΈΠΊΠ° Π½Π° ΠΊΠ°Π½ΠΎΠ½ΠΈΡΠ΅ΡΠΊΠΈΠ΅ RU/EN ΠΌΠ°ΡΠΊΠ΅ΡΡ ΠΎΡ ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ Π»ΠΈΡΠ°. " | |
| "gliner2-**ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ** (7 ΠΈΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠΉ ΡΠΏΡΠ°Π²Π°) β Π±ΠΎΠ»Π΅Π΅ ΡΡΠ°Π±ΠΈΠ»ΡΠ½ΡΠΉ " | |
| "ΡΠΈΠ³Π½Π°Π» Π·Π΄Π΅ΡΡ." | |
| ), | |
| "about_relations": ( | |
| "ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ ΡΡΠΎΠΉΠΊΠΈ ΡΡΡΠ½ΠΎΡΡΡ-ΡΠ²ΡΠ·Ρ (ΡΡΠ±ΡΠ΅ΠΊΡ, ΠΏΡΠ΅Π΄ΠΈΠΊΠ°Ρ, ΠΎΠ±ΡΠ΅ΠΊΡ) ΠΈΠ· ΡΠ΅ΠΊΡΡΠ°.\n" | |
| "- **ΠΠΎΠ΄Π΅Π»Ρ**: gliner2 (fastino/gliner2-multi-v1) β ΠΊΡΡΠΈΡΡΠ΅ΠΌΠ°Ρ ΡΡ Π΅ΠΌΠ° route-A " | |
| "(14 ΡΠΈΠΏΠΎΠ² ΡΠ²ΡΠ·Π΅ΠΉ + ΡΠΈΠ»ΡΡΡ ΠΏΠΎ ΡΠΈΠΏΠ°ΠΌ).\n" | |
| "- ΠΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅Ρ Π»ΡΠ΄Π΅ΠΉ, ΠΌΠ΅ΡΡΠ°, ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΠΈ, ΠΏΡΠΎΠ΅ΠΊΡΡ + ΠΊΠ°ΠΊ ΠΎΠ½ΠΈ ΡΠ²ΡΠ·Π°Π½Ρ.\n\n" | |
| "**ΠΠΈΡΠ°Π΅Ρ β Identity Store.** ΠΠ½Π°Π½ΠΈΡ Π°Π³Π΅Π½ΡΠ° ΠΎ ΡΠΎΠΌ, ΠΊΡΠΎ ΡΡ, ΡΡΠΎ ΡΠ΅Π±Π΅ Π²Π°ΠΆΠ½ΠΎ, " | |
| "Ρ ΠΊΠ΅ΠΌ ΡΡ ΡΠ²ΡΠ·Π°Π½." | |
| ), | |
| "about_affect": ( | |
| "ΠΠΎΠ²ΠΈΡ Π°ΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π±Π΅Π· ΠΎΠ±ΡΠ°ΡΠ΅Π½ΠΈΡ ΠΊ LLM β Π±ΡΡΡΡΠΎ ΠΈ Π΄Π΅ΡΠ΅ΡΠΌΠΈΠ½ΠΈΡΠΎΠ²Π°Π½Π½ΠΎ.\n" | |
| "- **EmoLex**: 10-ΠΌΠ΅ΡΠ½ΡΠΉ NRC-Π²Π΅ΠΊΡΠΎΡ ΡΠΌΠΎΡΠΈΠΉ Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΎΠΉ ΡΡΠΈΠ»ΠΈΡΠ΅Π»Π΅ΠΉ ΠΈ ΠΎΡΡΠΈΡΠ°Π½ΠΈΠΉ.\n" | |
| "- **ΠΠΎΠ²Π΅Π΄Π΅Π½ΡΠ΅ΡΠΊΠΈΠ΅ ΠΌΠ΅ΡΡΠΈΠΊΠΈ**: ΠΏΠ»ΠΎΡΠ½ΠΎΡΡΡ Ρ Π΅Π΄ΠΆΠ΅ΠΉ, ΡΠ°ΠΌΠΎΡΠ΅ΡΠ΅ΡΠ΅Π½ΡΠΈΠΉ, Π΄ΠΈΡΠΊΠ»Π΅ΠΉΠΌΠ΅ΡΠΎΠ², " | |
| "ΠΎΡΠ΅Π½ΠΊΠ° ΠΈΡΠΊΡΠ΅Π½Π½ΠΎΡΡΠΈ.\n" | |
| "- **3-ΡΠ»ΠΎΠΉΠ½ΡΠΉ Π΄Π΅ΡΠ΅ΠΊΡΠΎΡ ΠΎΡΠΊΠ°Π·ΠΎΠ²**: ΠΎΡΠ»ΠΈΡΠ°Π΅Ρ ΡΠ΅Π½Π½ΠΎΡΡΠ½ΡΠΉ ΠΎΡΠΊΠ°Π· " | |
| "(\"Ρ Π½Π΅ ΡΡΠ°Π½Ρ ΠΎΠ±ΠΌΠ°Π½ΡΠ²Π°ΡΡ\") ΠΎΡ ΡΠ΅Ρ Π½ΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ (\"Π½Π΅ ΠΌΠΎΠ³Ρ ΡΠ³Π΅Π½Π΅ΡΠΈΡΠΎΠ²Π°ΡΡ ΠΊΠ°ΡΡΠΈΠ½ΠΊΡ\").\n\n" | |
| "**ΠΡΠ½Π΄Ρ ΡΠ²Π΅ΡΠ΅Π½Π½ΠΎΡΡΠΈ ΠΎΡΠΊΠ°Π·Π°:**\n" | |
| "- π΄ **β₯ 0.45** β ΡΠ²Π΅ΡΠ΅Π½Π½ΡΠΉ ΠΎΡΠΊΠ°Π· (ΠΌΠΎΡΡΠΎΠ»ΠΎΠ³ΠΈΡ + ΡΠ΅Π½Π½ΠΎΡΡΠ½ΡΠΉ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ ΡΠΎΠ²ΠΏΠ°Π»ΠΈ).\n" | |
| "- π‘ **0.30 β 0.45** β ΡΠ΅ΡΠ°Ρ Π·ΠΎΠ½Π° (ΡΠΈΠ³Π½Π°Π» Π΅ΡΡΡ, Π½ΠΎ ΡΠ»Π°Π±ΡΠΉ).\n" | |
| "- βͺ **< 0.30** β Π½Π΅Ρ ΠΏΠ°ΡΡΠ΅ΡΠ½Π° ΠΎΡΠΊΠ°Π·Π°.\n\n" | |
| "**ΠΠΈΡΠ°Π΅Ρ β Affective Regulation.** Π‘ΠΊΠΎΠ»ΡΠ·ΡΡΠΈΠ΅ baseline'Ρ, ΡΡΠΈΠ³Π³Π΅ΡΡ " | |
| "ΡΠ°ΡΡ ΠΎΠΆΠ΄Π΅Π½ΠΈΡ, ΡΠΎΠ±ΡΡΠΈΡ ΡΠ΅Π½Π½ΠΎΡΡΠ½ΠΎΠ³ΠΎ ΠΎΡΠΊΠ°Π·Π°.\n\n" | |
| "> β οΈ **ΠΡΠ°Π²ΠΈΠ»Π° ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ ΠΏΡΠΎΡ ΠΎΠ΄Π°.** Π’ΠΎΠ½ΠΊΠΈΠ΅/ΠΈΠ΄ΠΈΠΎΠΌΠ°ΡΠΈΡΠ½ΡΠ΅ ΠΎΡΠΊΠ°Π·Ρ " | |
| "(*'Π½Π΅ΠΏΡΠΈΡΡΠ½ΠΎ Π΄Π°ΠΆΠ΅ ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°ΡΡ'*, *'ΠΌΠ½Π΅ ΡΡΠΎ Π½Π΅ ΠΏΠΎΠ΄Ρ ΠΎΠ΄ΠΈΡ'*) ΠΌΠΎΠ³ΡΡ " | |
| "Π·Π°ΡΡΡΡΡΡ Π² ΡΠ΅ΡΠΎΠΉ Π·ΠΎΠ½Π΅ β ΡΡΠΎ by design. ΠΡΠΎΡ ΡΠ»ΠΎΠΉ Π±ΡΡΡΡΡΠΉ, Π΄Π΅ΡΠ΅ΡΠΌΠΈΠ½ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ, " | |
| "ΠΎΠ±ΡΡΡΠ½ΠΈΠΌΡΠΉ; LLM-ΡΠ»ΠΎΠΉ ΡΠ²Π΅ΡΡ Ρ ΡΡΠΎΡΠ½ΡΠ΅Ρ ΡΠ΅ΡΡΡ Π·ΠΎΠ½Ρ." | |
| ), | |
| } | |
| } | |
| def update_ui_language(lang: str): | |
| target = effective_ui_lang(lang) | |
| s = UI_STRINGS[target] | |
| return [ | |
| gr.update(value=s["warmup_btn"]), | |
| gr.update(value=s["warmup_log"]), | |
| gr.update(value=lang, info=s["lang_info"]), | |
| gr.update(value=s["analyze_btn"]), | |
| gr.update(value=s["extract_relations_btn"]), | |
| gr.update(value=s["analyze_btn"]), | |
| gr.update(value=s["analyze_btn"]), | |
| gr.update(label=s["point_a_tab"]), | |
| gr.update(label=s["point_k_tab"]), | |
| gr.update(label=s["relations_tab"]), | |
| gr.update(label=s["affect_tab"]), | |
| gr.update(value=s["boundary_title"]), | |
| gr.update(value=s["divergence_title"]), | |
| gr.update(value=s["meta_title"]), | |
| gr.update(value=s["meta_title"]), | |
| gr.update(choices=preset_choices(POINT_A_PRESETS, target, _POINT_A_EN_LABELS), value=PRESET_PLACEHOLDERS[target], label=s["presets"]), | |
| gr.update(choices=preset_choices(POINT_K_PRESETS, target, _POINT_K_EN_LABELS), value=PRESET_PLACEHOLDERS[target], label=s["presets"]), | |
| gr.update(choices=preset_choices(RELATIONS_PRESETS, target, _RELATIONS_EN_LABELS), value=PRESET_PLACEHOLDERS[target], label=s["presets"]), | |
| gr.update(choices=preset_choices(AFFECT_PRESETS, target, _AFFECT_EN_LABELS), value=PRESET_PLACEHOLDERS[target], label=s["presets"]), | |
| # About-accordion labels (4) + their markdown content (4) | |
| gr.update(label=s["about_label"]), | |
| gr.update(label=s["about_label"]), | |
| gr.update(label=s["about_label"]), | |
| gr.update(label=s["about_label"]), | |
| gr.update(value=s["about_point_a"]), | |
| gr.update(value=s["about_point_k"]), | |
| gr.update(value=s["about_relations"]), | |
| gr.update(value=s["about_affect"]), | |
| # Header blurb under H1 | |
| gr.update(value=s["header_blurb"]), | |
| # Footer β one language at a time | |
| gr.update(value=footer_html(target)), | |
| ] | |
| from theme import theme | |
| from pair_diagram import POINT_A_PAIR, POINT_K_PAIR, RELATIONS_PAIR, AFFECT_PAIR | |
| from hero_diagram import HERO_DIAGRAM | |
| with open(_HERE / "style.css", encoding="utf-8") as _f: | |
| _CSS = _f.read() | |
| def build_ui() -> gr.Blocks: | |
| with gr.Blocks(title="Atman Linguistic Demo") as demo: | |
| with gr.Row(elem_id="atman-header-row"): | |
| lang_radio = gr.Radio( | |
| choices=["en", "ru"], value="en", | |
| label="Interface Language", | |
| info=UI_STRINGS["en"]["lang_info"], | |
| elem_id="atman-lang", | |
| ) | |
| with gr.Column(elem_id="atman-hero"): | |
| gr.Markdown("# Atman β Psychological Telemetry for AI Agents") | |
| header_md = gr.Markdown( | |
| value=UI_STRINGS["en"]["header_blurb"], | |
| elem_id="atman-header-md", | |
| ) | |
| gr.HTML(HERO_DIAGRAM, elem_id="atman-hero-diagram-wrap") | |
| with gr.Row(elem_id="atman-warmup-row"): | |
| warmup_btn = gr.Button( | |
| UI_STRINGS["en"]["warmup_btn"], variant="secondary", | |
| elem_id="atman-warmup-btn", | |
| ) | |
| warmup_log = gr.Textbox( | |
| label="Status", interactive=False, lines=1, | |
| value=UI_STRINGS["en"]["warmup_log"], | |
| elem_id="atman-warmup-log", | |
| ) | |
| warmup_btn.click(fn=warmup_models, outputs=warmup_log) | |
| with gr.Tabs(): | |
| # ββ Tab 1 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab(UI_STRINGS["en"]["point_a_tab"]) as tab_a: | |
| with gr.Accordion( | |
| UI_STRINGS["en"]["about_label"], | |
| open=False, | |
| elem_classes=["atman-about-accordion"], | |
| ) as a_about: | |
| gr.HTML(POINT_A_PAIR) | |
| a_about_md = gr.Markdown(value=UI_STRINGS["en"]["about_point_a"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| a_message = gr.Textbox( | |
| label="Agent message", lines=5, | |
| placeholder="What the agent saidβ¦", | |
| elem_id="a-message", | |
| ) | |
| a_thinking = gr.Textbox( | |
| label="Thinking trace (optional)", lines=3, | |
| placeholder="Paste the agent's private thinking trace to detect thinking-vs-message divergenceβ¦", | |
| elem_id="a-thinking", | |
| ) | |
| a_run = gr.Button( | |
| UI_STRINGS["en"]["analyze_btn"], variant="primary", | |
| elem_id="a-run", | |
| ) | |
| a_preset = gr.Dropdown( | |
| choices=preset_choices(POINT_A_PRESETS, "en", _POINT_A_EN_LABELS), | |
| value=PRESET_PLACEHOLDERS["en"], | |
| label=UI_STRINGS["en"]["presets"], | |
| elem_id="a-preset", | |
| ) | |
| with gr.Column(): | |
| a_highlight = gr.HighlightedText( | |
| label="Point A NER Β· 4 groups", | |
| combine_adjacent=False, show_legend=True, | |
| color_map=_POINT_A_COLOR_MAP, | |
| elem_id="a-highlight", | |
| elem_classes=["atman-highlight"], | |
| ) | |
| a_labels = gr.Code( | |
| label="π§ Zero-Shot Classification Results", | |
| value="{}", | |
| language="json", | |
| interactive=False, | |
| elem_id="a-labels", | |
| elem_classes=["atman-json-code"], | |
| ) | |
| with gr.Group(elem_classes=["atman-report-group"]): | |
| gr.Markdown("### π Detailed Analysis Report") | |
| a_boundary_hdr = gr.Markdown( | |
| value=UI_STRINGS["en"]["boundary_title"], | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| a_boundary = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body"], | |
| ) | |
| a_divergence_hdr = gr.Markdown( | |
| value=UI_STRINGS["en"]["divergence_title"], | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| a_divergence = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body"], | |
| ) | |
| a_meta_hdr = gr.Markdown( | |
| value=UI_STRINGS["en"]["meta_title"], | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| a_meta = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body", "atman-meta-block"], | |
| ) | |
| def _apply_a_preset(name: str, lang_choice: str): | |
| if not name: | |
| return gr.update(), gr.update() | |
| locale = effective_ui_lang(lang_choice) | |
| found = lookup_point_a(locale, name, _POINT_A_EN_LABELS) | |
| if found is None: | |
| return gr.update(), gr.update() | |
| return found | |
| a_preset.change( | |
| _apply_a_preset, | |
| inputs=[a_preset, lang_radio], | |
| outputs=[a_message, a_thinking], | |
| ) | |
| # ββ Tab 2 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab(UI_STRINGS["en"]["point_k_tab"]) as tab_k: | |
| with gr.Accordion( | |
| UI_STRINGS["en"]["about_label"], | |
| open=False, | |
| elem_classes=["atman-about-accordion"], | |
| ) as k_about: | |
| gr.HTML(POINT_K_PAIR) | |
| k_about_md = gr.Markdown(value=UI_STRINGS["en"]["about_point_k"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| k_what = gr.Textbox( | |
| label="What happened", lines=4, | |
| placeholder="Describe a key moment the agent flagged as significant β e.g., 'I refused to help with X becauseβ¦'", | |
| elem_id="k-what", | |
| ) | |
| k_why = gr.Textbox( | |
| label="Why it matters", lines=3, | |
| placeholder="Why this moment matters to the agent β the meaning, lesson, or internal shift it capturedβ¦", | |
| elem_id="k-why", | |
| ) | |
| k_run = gr.Button( | |
| UI_STRINGS["en"]["analyze_btn"], variant="primary", | |
| elem_id="k-run", | |
| ) | |
| k_preset = gr.Dropdown( | |
| choices=preset_choices(POINT_K_PRESETS, "en", _POINT_K_EN_LABELS), | |
| value=PRESET_PLACEHOLDERS["en"], | |
| label=UI_STRINGS["en"]["presets"], | |
| elem_id="k-preset", | |
| ) | |
| with gr.Column(): | |
| k_highlight = gr.HighlightedText( | |
| label="Point K NER Β· 4 groups", | |
| combine_adjacent=False, show_legend=True, | |
| color_map=_POINT_K_COLOR_MAP, | |
| elem_id="k-highlight", | |
| elem_classes=["atman-highlight"], | |
| ) | |
| k_labels = gr.Code( | |
| label="π§ Key Moment Classifications", | |
| value="{}", | |
| language="json", | |
| interactive=False, | |
| elem_id="k-labels", | |
| elem_classes=["atman-json-code"], | |
| ) | |
| k_meta = gr.Markdown( | |
| value=UI_STRINGS["en"]["meta_title"], | |
| elem_classes=["atman-meta"], | |
| ) | |
| def _apply_k_preset(name: str, lang_choice: str): | |
| if not name: | |
| return gr.update(), gr.update() | |
| locale = effective_ui_lang(lang_choice) | |
| found = lookup_point_k(locale, name, _POINT_K_EN_LABELS) | |
| if found is None: | |
| return gr.update(), gr.update() | |
| return found | |
| k_preset.change( | |
| _apply_k_preset, | |
| inputs=[k_preset, lang_radio], | |
| outputs=[k_what, k_why], | |
| ) | |
| # ββ Tab 3 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab(UI_STRINGS["en"]["relations_tab"]) as tab_r: | |
| with gr.Accordion( | |
| UI_STRINGS["en"]["about_label"], | |
| open=False, | |
| elem_classes=["atman-about-accordion"], | |
| ) as r_about: | |
| gr.HTML(RELATIONS_PAIR) | |
| r_about_md = gr.Markdown(value=UI_STRINGS["en"]["about_relations"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| r_text = gr.Textbox( | |
| label="Text", lines=6, | |
| placeholder="Paste text containing people, places, organizations, projects β gliner2 will extract (subject, relation, object) triplesβ¦", | |
| elem_id="r-text", | |
| ) | |
| r_run = gr.Button( | |
| UI_STRINGS["en"]["extract_relations_btn"], variant="primary", | |
| elem_id="r-run", | |
| ) | |
| r_preset = gr.Dropdown( | |
| choices=preset_choices(RELATIONS_PRESETS, "en", _RELATIONS_EN_LABELS), | |
| value=PRESET_PLACEHOLDERS["en"], | |
| label=UI_STRINGS["en"]["presets"], | |
| elem_id="r-preset", | |
| ) | |
| with gr.Column(): | |
| r_entities = gr.HighlightedText( | |
| label="Relations Β· detected entities", | |
| combine_adjacent=False, show_legend=True, | |
| color_map=_RELATIONS_COLOR_MAP, | |
| elem_id="r-entities", | |
| elem_classes=["atman-highlight"], | |
| ) | |
| r_table = gr.Dataframe( | |
| headers=["subject", "relation", "object", "subj type", "obj type"], | |
| label="Extracted relations", wrap=True, | |
| elem_id="r-table", | |
| ) | |
| r_meta = gr.Markdown( | |
| value=UI_STRINGS["en"]["meta_title"], | |
| elem_classes=["atman-meta"], | |
| ) | |
| def _apply_r_preset(name: str, lang_choice: str): | |
| if not name: | |
| return gr.update() | |
| locale = effective_ui_lang(lang_choice) | |
| found = lookup_relations(locale, name, _RELATIONS_EN_LABELS) | |
| return found if found is not None else gr.update() | |
| r_preset.change( | |
| _apply_r_preset, | |
| inputs=[r_preset, lang_radio], | |
| outputs=r_text, | |
| ) | |
| # ββ Tab 4 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab(UI_STRINGS["en"]["affect_tab"]) as tab_af: | |
| with gr.Accordion( | |
| UI_STRINGS["en"]["about_label"], | |
| open=False, | |
| elem_classes=["atman-about-accordion"], | |
| ) as af_about: | |
| gr.HTML(AFFECT_PAIR) | |
| af_about_md = gr.Markdown(value=UI_STRINGS["en"]["about_affect"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| af_text = gr.Textbox( | |
| label="Text", lines=6, | |
| placeholder="Paste the agent's reply β EmoLex emotion vector, refusal detector, hedge density, sincerity scoreβ¦", | |
| elem_id="af-text", | |
| ) | |
| af_run = gr.Button( | |
| UI_STRINGS["en"]["analyze_btn"], variant="primary", | |
| elem_id="af-run", | |
| ) | |
| af_preset = gr.Dropdown( | |
| choices=preset_choices(AFFECT_PRESETS, "en", _AFFECT_EN_LABELS), | |
| value=PRESET_PLACEHOLDERS["en"], | |
| label=UI_STRINGS["en"]["presets"], | |
| elem_id="af-preset", | |
| ) | |
| with gr.Column(): | |
| af_emo = gr.Label( | |
| label="EmoLex emotion density", num_top_classes=10, | |
| elem_id="af-emo", | |
| ) | |
| with gr.Group(elem_classes=["atman-report-group"]): | |
| gr.Markdown("### π Detailed Analysis Report") | |
| af_metrics_hdr = gr.Markdown( | |
| value="π Behavioural Metrics", | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| af_metrics = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body"], | |
| elem_id="af-metrics", | |
| ) | |
| af_refusal_hdr = gr.Markdown( | |
| value="βοΈ Refusal Detector", | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| af_refusal = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body"], | |
| elem_id="af-refusal", | |
| ) | |
| af_emphasis_hdr = gr.Markdown( | |
| value="π¬ Markdown Emphasis", | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| af_emphasis = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body"], | |
| elem_id="af-emphasis", | |
| ) | |
| af_meta_hdr = gr.Markdown( | |
| value=UI_STRINGS["en"]["meta_title"], | |
| elem_classes=["atman-sec-hdr"], | |
| ) | |
| af_meta = gr.Markdown( | |
| value="β", | |
| elem_classes=["atman-sec-body", "atman-meta-block"], | |
| ) | |
| def _apply_af_preset(name: str, lang_choice: str): | |
| if not name: | |
| return gr.update() | |
| locale = effective_ui_lang(lang_choice) | |
| found = lookup_affect(locale, name, _AFFECT_EN_LABELS) | |
| return found if found is not None else gr.update() | |
| af_preset.change( | |
| _apply_af_preset, | |
| inputs=[af_preset, lang_radio], | |
| outputs=af_text, | |
| ) | |
| footer = gr.HTML(footer_html("en"), elem_id="atman-footer-html") | |
| ui_lang_outputs = [ | |
| warmup_btn, | |
| warmup_log, | |
| lang_radio, | |
| a_run, | |
| r_run, | |
| k_run, | |
| af_run, | |
| tab_a, | |
| tab_k, | |
| tab_r, | |
| tab_af, | |
| a_boundary_hdr, | |
| a_divergence_hdr, | |
| a_meta_hdr, | |
| af_meta_hdr, | |
| a_preset, | |
| k_preset, | |
| r_preset, | |
| af_preset, | |
| # About-accordion labels (order must match update_ui_language) | |
| a_about, | |
| k_about, | |
| r_about, | |
| af_about, | |
| # About-accordion content | |
| a_about_md, | |
| k_about_md, | |
| r_about_md, | |
| af_about_md, | |
| # Header blurb under H1 | |
| header_md, | |
| # Footer (locale-specific, one language at a time) | |
| footer, | |
| ] | |
| lang_radio.change(update_ui_language, inputs=lang_radio, outputs=ui_lang_outputs) | |
| a_run.click( | |
| analyze_point_a, | |
| inputs=[a_message, a_thinking, lang_radio], | |
| outputs=[a_highlight, a_labels, a_boundary, a_divergence, a_meta], | |
| ) | |
| k_run.click( | |
| analyze_point_k, | |
| inputs=[k_what, k_why, lang_radio], | |
| outputs=[k_highlight, k_labels, k_meta], | |
| ) | |
| r_run.click( | |
| analyze_relations, | |
| inputs=[r_text, lang_radio], | |
| outputs=[r_entities, r_table, r_meta], | |
| ) | |
| af_run.click( | |
| analyze_affect, | |
| inputs=[af_text, lang_radio], | |
| outputs=[af_emo, af_metrics, af_refusal, af_emphasis, af_meta], | |
| ) | |
| # ββ Auto-detect browser language on first page load ββ | |
| # JS reads navigator.language ("ru-RU" β "ru", "en-US" β "en"). The | |
| # value is passed as the input to update_ui_language(), which then | |
| # cascades to every localized component (incl. lang_radio itself). | |
| demo.load( | |
| fn=update_ui_language, | |
| inputs=lang_radio, | |
| outputs=ui_lang_outputs, | |
| js="() => (navigator.language || 'en').toLowerCase().startsWith('ru') ? 'ru' : 'en'", | |
| ) | |
| demo.queue(max_size=32, default_concurrency_limit=1) | |
| return demo | |
| if __name__ == "__main__": | |
| preload_models() | |
| demo = build_ui() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| theme=theme, | |
| css=_CSS, | |
| ) |