| """ |
| Module: app.py |
| Application Gradio pour la démonstration de l'IA endorégulée Tian-Dao. |
| Version: 1.4 |
| Date: 2026-06-20 |
| """ |
| import warnings |
| import asyncio |
| import gradio as gr |
| import numpy as np |
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| from sklearn.decomposition import PCA |
| import hashlib |
| import traceback |
| import sys |
| import os |
|
|
| warnings.filterwarnings("ignore", category=DeprecationWarning, module="asyncio") |
| warnings.filterwarnings("ignore", category=RuntimeWarning, module="asyncio") |
|
|
| if sys.version_info >= (3, 13): |
| try: |
| import selectors |
| original_fileobj_to_fd = selectors._fileobj_to_fd |
| |
| def patched_fileobj_to_fd(fileobj): |
| try: |
| fd = original_fileobj_to_fd(fileobj) |
| return fd |
| except ValueError as e: |
| if "Invalid file descriptor" in str(e): |
| return -1 |
| raise |
| |
| selectors._fileobj_to_fd = patched_fileobj_to_fd |
| except Exception: |
| pass |
|
|
| from Endoregulated_AI_v27 import EndoRegulatedCore, RandomInputSimulator, get_core_lock |
|
|
| core = EndoRegulatedCore(noise_level=0.15, seed=42) |
| core_lock = get_core_lock() |
|
|
| I18N = { |
| "fr": { |
| "title": "🧠 Démo Tian-Dao Embeddings", |
| "subtitle": "### IA Endorégulée - Invariant 64→20 avec Cycle Wuxing", |
| "description": "Cette démo transforme votre texte en un embedding 20D unique via un système dynamique non-connexionniste. Le système alterne entre les régimes SHENG (exploration) et KE (contraction) selon un cycle d'auto-régulation inspiré du Wuxing.", |
| "language_label": "🌐 Langue", |
| "text_label": "Texte à encoder", |
| "text_placeholder": "Saisissez votre texte ici...", |
| "submit_btn": "🔮 Générer l'embedding", |
| "clear_btn": "🗑️ Effacer", |
| "metrics_title": "📊 Métriques détaillées", |
| "stats_waiting": "Statistiques : en attente d'entrée...", |
| "plot_label": "Visualisation de l'embedding", |
| "examples_title": "### 📝 Exemples de textes", |
| "examples_label": "Cliquez sur un exemple pour le charger", |
| "examples": [ |
| "Bonjour le monde", |
| "L'intelligence artificielle est fascinante", |
| "Le cycle Wuxing gouverne l'équilibre", |
| "Sheng et Ke dansent dans le chaos", |
| "La topologie des attracteurs révèle l'harmonie" |
| ], |
| "error_empty": "⚠️ Veuillez entrer un texte valide.", |
| "error_prefix": "❌ Erreur :", |
| "info_prefix": "Analyse du texte :", |
| "info_attractor": "Attracteur :", |
| "info_eta": "Asymétrie η :", |
| "info_frustration": "Frustration E :", |
| "info_threshold": "Seuil R :", |
| "info_regime": "Régime :", |
| "info_20d": "Embedding 20D :", |
| "info_768d": "Embedding 768D :", |
| "info_compression": "Taux de compression :", |
| "info_processed": "Entrées traitées :", |
| "dims": "dims", |
| "size": "taille", |
| "bytes": "bytes", |
| "pca_waiting": "Données colinéaires\nen attente de variance...", |
| "pca_waiting_title": "PCA en attente", |
| "pca_need_more": "entrée(s) supplémentaire(s)\npour la PCA", |
| "pca_collecting_title": "En attente de plus de données...", |
| "pca_component1": "Composante 1", |
| "pca_component2": "Composante 2", |
| "pca_history": "Historique", |
| "pca_new": "Nouveau", |
| "embedding_title": "Embedding 20D (attracteur {})", |
| "dimension": "Dimension", |
| "value": "Valeur", |
| "clear_msg": "Entrez un nouveau texte pour générer un embedding." |
| }, |
| "en": { |
| "title": "🧠 Tian-Dao Embeddings Demo", |
| "subtitle": "### Endoregulated AI - 64→20 Invariant with Wuxing Cycle", |
| "description": "This demo transforms your text into a unique 20D embedding via a non-connectionist dynamic system. The system alternates between SHENG (exploration) and KE (contraction) regimes according to a self-regulation cycle inspired by Wuxing.", |
| "language_label": "🌐 Language", |
| "text_label": "Text to encode", |
| "text_placeholder": "Enter your text here...", |
| "submit_btn": "🔮 Generate embedding", |
| "clear_btn": "🗑️ Clear", |
| "metrics_title": "📊 Detailed metrics", |
| "stats_waiting": "Statistics: waiting for input...", |
| "plot_label": "Embedding visualization", |
| "examples_title": "### 📝 Example texts", |
| "examples_label": "Click an example to load it", |
| "examples": [ |
| "Hello world", |
| "Artificial intelligence is fascinating", |
| "The Wuxing cycle governs balance", |
| "Sheng and Ke dance in chaos", |
| "The topology of attractors reveals harmony" |
| ], |
| "error_empty": "⚠️ Please enter a valid text.", |
| "error_prefix": "❌ Error:", |
| "info_prefix": "Text analysis:", |
| "info_attractor": "Attractor:", |
| "info_eta": "Asymmetry η:", |
| "info_frustration": "Frustration E:", |
| "info_threshold": "Threshold R:", |
| "info_regime": "Regime:", |
| "info_20d": "20D Embedding:", |
| "info_768d": "768D Embedding:", |
| "info_compression": "Compression ratio:", |
| "info_processed": "Inputs processed:", |
| "dims": "dims", |
| "size": "size", |
| "bytes": "bytes", |
| "pca_waiting": "Collinear data\nwaiting for variance...", |
| "pca_waiting_title": "PCA waiting", |
| "pca_need_more": "more entry(ies)\nneeded for PCA", |
| "pca_collecting_title": "Waiting for more data...", |
| "pca_component1": "Component 1", |
| "pca_component2": "Component 2", |
| "pca_history": "History", |
| "pca_new": "New", |
| "embedding_title": "20D Embedding (attractor {})", |
| "dimension": "Dimension", |
| "value": "Value", |
| "clear_msg": "Enter a new text to generate an embedding." |
| } |
| } |
|
|
| def _(key: str, lang: str = "fr") -> str: |
| return I18N.get(lang, I18N["fr"]).get(key, I18N["fr"].get(key, key)) |
|
|
| def text_to_embedding(text: str) -> tuple: |
| try: |
| digest = hashlib.sha256(text.encode('utf-8')).digest() |
| hash_val = int.from_bytes(digest[:2], 'big') % 64 |
| |
| ATTRACTOR_TRIPLETS = [ |
| ['P1', 'P2', 'P4'], ['P1', 'P3', 'P5'], ['P2', 'P3', 'P6'], |
| ['P4', 'P5', 'N2'], ['P5', 'P6', 'N3'], ['P1', 'P6', 'N4'], |
| ['P2', 'P5', 'N6'], ['P3', 'P4', 'N6'], ['P1', 'N2', 'N6'], |
| ['P1', 'N3', 'N5'], ['P2', 'N3', 'N5'], ['P3', 'N2', 'N4'], |
| ['P4', 'N1', 'N3'], ['P4', 'N5', 'N6'], ['P5', 'N1', 'N4'], |
| ['P6', 'N1', 'N2'], ['P2', 'N1', 'N4'], ['P3', 'N1', 'N5'], |
| ['P6', 'N5', 'N6'], ['N2', 'N3', 'N4'], |
| ] |
| |
| embedding = [] |
| for triplet in ATTRACTOR_TRIPLETS: |
| n_positive = sum(1 for p in triplet if p.startswith('P')) |
| n_negative = sum(1 for p in triplet if p.startswith('N')) |
| polarity_score = (n_positive - n_negative) / 3.0 |
| mod = 1.0 if (hash_val + len(embedding)) % 5 != 0 else -1.0 |
| embedding.append(polarity_score * mod) |
| |
| emb = np.array(embedding, dtype=np.float32) |
| rng = np.random.default_rng(hash_val) |
| emb = emb + rng.standard_normal(20).astype(np.float32) * 0.15 |
| emb = np.clip(emb, -1.0, 1.0) |
| |
| rng_768 = np.random.default_rng(hash_val + 1000) |
| emb_768d = rng_768.standard_normal(768).astype(np.float32) |
| |
| with core_lock: |
| attractor = core.encode_bits(hash_val) |
| |
| return emb, emb_768d, attractor |
| |
| except Exception as e: |
| print(f"Erreur dans text_to_embedding: {e}") |
| traceback.print_exc() |
| raise |
|
|
| def visualize_embedding(text: str, points_cache: list, lang: str = 'fr') -> tuple: |
| fig = None |
| try: |
| if not text or not text.strip(): |
| return None, _("error_empty", lang), points_cache |
| |
| emb_20d, emb_768d, attractor = text_to_embedding(text) |
| |
| with core_lock: |
| eta = core.eta_direct() |
| frustration = core.frustration() |
| r_threshold = core.r_threshold() |
| regime = core.get_regime().value |
| input_counter = core.input_counter |
| |
| fig, axes = plt.subplots(1, 2, figsize=(12, 5)) |
| |
| ax1 = axes[0] |
| colors = ['blue' if v > 0 else 'red' for v in emb_20d] |
| bars = ax1.bar(range(1, len(emb_20d) + 1), emb_20d, color=colors, alpha=0.7) |
| ax1.axhline(0, color='black', linewidth=0.5) |
| ax1.set_xlabel(_("dimension", lang)) |
| ax1.set_ylabel(_("value", lang)) |
| ax1.set_title(_("embedding_title", lang).format(attractor)) |
| ax1.set_ylim(-1.5, 1.5) |
| ax1.grid(True, alpha=0.3) |
| |
| for bar, val in zip(bars, emb_20d): |
| height = bar.get_height() |
| ax1.text(bar.get_x() + bar.get_width() / 2., height, |
| f'{val:.1f}', |
| ha='center', |
| va='bottom' if height > 0 else 'top', |
| fontsize=8) |
| |
| ax2 = axes[1] |
| |
| if points_cache is None: |
| points_cache = [] |
| |
| points_cache.append(emb_20d.copy()) |
| if len(points_cache) > 20: |
| points_cache.pop(0) |
| |
| if len(points_cache) >= 5: |
| try: |
| points = np.array(points_cache) |
| if np.linalg.matrix_rank(points) < 2: |
| ax2.text(0.5, 0.5, _("pca_waiting", lang), |
| ha='center', va='center', transform=ax2.transAxes) |
| ax2.set_title(_("pca_waiting_title", lang)) |
| else: |
| pca = PCA(n_components=2) |
| points_2d = pca.fit_transform(points) |
| |
| ax2.scatter(points_2d[:-1, 0], points_2d[:-1, 1], |
| c='gray', alpha=0.5, s=40, label=_("pca_history", lang)) |
| ax2.scatter(points_2d[-1, 0], points_2d[-1, 1], |
| c='red', s=120, label=_("pca_new", lang), |
| edgecolors='black', linewidth=2) |
| ax2.set_title('Projection 2D (PCA)') |
| ax2.legend() |
| except Exception as e: |
| ax2.text(0.5, 0.5, f'PCA: {str(e)[:40]}', |
| ha='center', va='center', transform=ax2.transAxes) |
| ax2.set_title('Erreur PCA') |
| else: |
| remaining = 5 - len(points_cache) |
| pca_need_text = _("pca_need_more", lang) |
| ax2.text(0.5, 0.5, f"{remaining} {pca_need_text}", |
| ha='center', va='center', |
| transform=ax2.transAxes, fontsize=12) |
| ax2.set_title(_("pca_collecting_title", lang)) |
| |
| ax2.set_xlabel(_("pca_component1", lang)) |
| ax2.set_ylabel(_("pca_component2", lang)) |
| ax2.grid(True, alpha=0.3) |
| |
| plt.tight_layout() |
| |
| info_text = ( |
| f"{_('info_prefix', lang)} `{text[:50]}{'...' if len(text) > 50 else ''}`\n\n" |
| f"{_('info_attractor', lang)} {attractor}\n" |
| f"{_('info_eta', lang)} {eta:+.2f}\n" |
| f"{_('info_frustration', lang)} {frustration}\n" |
| f"{_('info_threshold', lang)} {r_threshold:.2f}\n" |
| f"{_('info_regime', lang)} {regime}\n" |
| f"{_('info_20d', lang)} {len(emb_20d)} {_('dims', lang)} " |
| f"({_('size', lang)}: {emb_20d.nbytes} {_('bytes', lang)})\n" |
| f"{_('info_768d', lang)} {len(emb_768d)} {_('dims', lang)} " |
| f"({_('size', lang)}: {emb_768d.nbytes} {_('bytes', lang)})\n" |
| f"{_('info_compression', lang)} {emb_768d.nbytes / emb_20d.nbytes:.1f}x\n" |
| f"{_('info_processed', lang)} {input_counter}" |
| ) |
| |
| return fig, info_text, points_cache |
| |
| except Exception as e: |
| error_msg = (f"{_('error_prefix', lang)} {str(e)}\n\n" |
| f"```\n{traceback.format_exc()}\n```") |
| return None, error_msg, points_cache if points_cache else [] |
| |
| finally: |
| if fig is not None: |
| plt.close(fig) |
|
|
| def create_interface() -> gr.Blocks: |
| with gr.Blocks(title="Tian-Dao Embeddings Demo") as demo: |
| points_state = gr.State(value=[]) |
| lang_state = gr.State(value="fr") |
| |
| with gr.Row(): |
| gr.Markdown("## 🧠 Tian-Dao") |
| language_selector = gr.Dropdown( |
| choices=[("🇫🇷 Français", "fr"), ("🇬🇧 English", "en")], |
| value="fr", |
| label="🌐 Language", |
| scale=1, |
| interactive=True |
| ) |
| |
| title_md = gr.Markdown(f"# {_('title', 'fr')}\n{_('subtitle', 'fr')}") |
| description_md = gr.Markdown(_("description", "fr")) |
| |
| with gr.Row(): |
| with gr.Column(scale=2): |
| text_input = gr.Textbox( |
| label=_("text_label", "fr"), |
| placeholder=_("text_placeholder", "fr"), |
| lines=3 |
| ) |
| submit_btn = gr.Button(_("submit_btn", "fr"), variant="primary") |
| clear_btn = gr.Button(_("clear_btn", "fr")) |
| |
| examples_title_md = gr.Markdown(_("examples_title", "fr")) |
| examples_dropdown = gr.Dropdown( |
| choices=I18N["fr"]["examples"], |
| label=_("examples_label", "fr"), |
| interactive=True, |
| scale=1 |
| ) |
| |
| with gr.Accordion(_("metrics_title", "fr"), open=True) as metrics_accordion: |
| stats_text = gr.Markdown(_("stats_waiting", "fr")) |
| |
| with gr.Column(scale=3): |
| plot_output = gr.Plot(label=_("plot_label", "fr")) |
| |
| def submit_text(text: str, points_cache: list, lang: str) -> tuple: |
| if not text or not text.strip(): |
| return None, f"**{_('error_empty', lang)}**", points_cache |
| return visualize_embedding(text, points_cache, lang) |
| |
| def clear_text(points_cache: list, lang: str) -> tuple: |
| return "", None, _("clear_msg", lang), [] |
| |
| def load_example(example: str) -> str: |
| return example if example else "" |
| |
| def change_language(lang: str, current_text: str, points_cache: list): |
| new_title = f"# {_('title', lang)}\n{_('subtitle', lang)}" |
| new_description = _("description", lang) |
| new_examples_title = _("examples_title", lang) |
| new_examples = I18N[lang]["examples"] |
| |
| if current_text and current_text.strip(): |
| fig, info, new_cache = visualize_embedding(current_text, points_cache, lang) |
| return ( |
| gr.update(value=new_title), |
| gr.update(value=new_description), |
| gr.update(label=_("text_label", lang), |
| placeholder=_("text_placeholder", lang)), |
| gr.update(value=_("submit_btn", lang)), |
| gr.update(value=_("clear_btn", lang)), |
| gr.update(choices=new_examples, |
| label=_("examples_label", lang)), |
| gr.update(value=new_examples_title), |
| gr.update(label=_("metrics_title", lang)), |
| gr.update(value=info if info else _("stats_waiting", lang)), |
| gr.update(label=_("plot_label", lang)), |
| fig, |
| lang, |
| new_cache, |
| ) |
| else: |
| return ( |
| gr.update(value=new_title), |
| gr.update(value=new_description), |
| gr.update(label=_("text_label", lang), |
| placeholder=_("text_placeholder", lang)), |
| gr.update(value=_("submit_btn", lang)), |
| gr.update(value=_("clear_btn", lang)), |
| gr.update(choices=new_examples, |
| label=_("examples_label", lang)), |
| gr.update(value=new_examples_title), |
| gr.update(label=_("metrics_title", lang)), |
| gr.update(value=_("stats_waiting", lang)), |
| gr.update(label=_("plot_label", lang)), |
| None, |
| lang, |
| points_cache, |
| ) |
| |
| submit_btn.click( |
| fn=submit_text, |
| inputs=[text_input, points_state, lang_state], |
| outputs=[plot_output, stats_text, points_state] |
| ) |
| |
| clear_btn.click( |
| fn=clear_text, |
| inputs=[points_state, lang_state], |
| outputs=[text_input, plot_output, stats_text, points_state] |
| ) |
| |
| text_input.submit( |
| fn=submit_text, |
| inputs=[text_input, points_state, lang_state], |
| outputs=[plot_output, stats_text, points_state] |
| ) |
| |
| examples_dropdown.change( |
| fn=load_example, |
| inputs=examples_dropdown, |
| outputs=text_input |
| ) |
| |
| language_selector.change( |
| fn=change_language, |
| inputs=[language_selector, text_input, points_state], |
| outputs=[ |
| title_md, |
| description_md, |
| text_input, |
| submit_btn, |
| clear_btn, |
| examples_dropdown, |
| examples_title_md, |
| metrics_accordion, |
| stats_text, |
| plot_output, |
| plot_output, |
| lang_state, |
| points_state, |
| ] |
| ) |
| |
| return demo |
|
|
| if __name__ == "__main__": |
| print(">>> RUNNING VERSION 1.4 BILINGUE <<<") |
| demo = create_interface() |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| ssr_mode=False |
| ) |
|
|