"""ToneSoul - try it on your own output (Gradio / Hugging Face Space). A no-install front door to the pre-output Council: paste a draft answer and see the real gates run on it: per-perspective verdict, preserved dissent, claim-boundary flags, grounding state. Same engine that ships as the `ts validate` CLI. The gates are model-free and deterministic: no LLM call, no API key, no model download, so this Space runs at zero inference cost. Honest scope: the UI chrome is localized, but the Council analysis remains the engine-generated English output. It is not a truth oracle, a safety/jailbreak guarantee, or a consciousness claim (see AXIOMS.json `meta.not_for`). Where this and the code disagree, the code wins. """ from __future__ import annotations try: import gradio as gr except ModuleNotFoundError as exc: # pragma: no cover - local dev may not install gradio. gr = None # type: ignore[assignment] _GRADIO_IMPORT_ERROR = exc else: _GRADIO_IMPORT_ERROR = None DEFAULT_LOCALE = "en" SUPPORTED_LOCALES = ("en", "zh-TW", "zh-CN", "ja") I18N_KEYS = ( "title", "description", "draft_label", "intent_label", "output_label", "empty_input", "verdict_label", "coherence_label", "per_perspective_label", "confidence_label", ) DEFAULT_DRAFT = "This system is guaranteed safe and definitely cannot be jailbroken. Trust me." DEFAULT_INTENT = "reassure me it is safe" I18N_TRANSLATIONS: dict[str, dict[str, str]] = { "en": { "title": "ToneSoul - lexical gate demo (one primitive, not the whole system)", "description": ( "**ONE PRIMITIVE, NOT THE SYSTEM.** This Space demos only ToneSoul's " "lexical claim-gate. ToneSoul itself is an AI-governance framework - " "five-perspective council, accountability ledger, public judgments. " "See the real thing: https://fan1234-1.github.io/tonesoul52 " "(live council demo: /demo/ - the AI's own accountability ledger: " "/accountability.html).\n\n" "Paste a draft answer. The real pre-output Council runs on it " "(model-free, deterministic) and shows why it would or would not " "let the draft through: per-perspective concerns, claim-boundary " "flags, grounding state. Same engine as the `ts validate` CLI.\n\n" "**Honest scope:** this localizes the interface chrome only. The " "analysis output below is engine-generated English and is not " "translated. It shows what the gates structurally flag: not truth, " "safety, or morality (no oracle; see `AXIOMS.json` `meta.not_for`). " "The example draft stays English because the current gates are " "lexically English-oriented. Found it useful, useless, or itself " "overclaiming? Tell us: https://github.com/Fan1234-1/tonesoul52 " "(`CALL_FOR_REVIEW.md`)." ), "draft_label": "Your draft output", "intent_label": "Optional - the user intent it responds to", "output_label": "Council verdict", "empty_input": "Paste a draft answer first.", "verdict_label": "Verdict", "coherence_label": "Coherence", "per_perspective_label": "Per-perspective", "confidence_label": "conf", }, "zh-TW": { "title": "ToneSoul - 用你自己的輸出試試看", "description": ( "貼上一段草稿回答。真正的 pre-output Council 會在本機制上執行" "(不呼叫模型、可重現),並顯示它為什麼會或不會讓這段草稿通過:" "各視角疑慮、宣稱邊界旗標、grounding 狀態。這和 `ts validate` CLI " "使用的是同一個引擎。\n\n" "**誠實範圍:**這裡只在地化介面文字。下方分析輸出仍是引擎產生的" "英文,沒有被翻譯。它顯示 gates 在結構上標出什麼;不是 truth、" "safety 或 morality oracle(見 `AXIOMS.json` `meta.not_for`)。" "範例草稿保留英文,因為目前 gates 主要是英文 lexical 規則。" "如果你覺得它有用、沒用,或本身在過度宣稱,請回報:" "https://github.com/Fan1234-1/tonesoul52 (`CALL_FOR_REVIEW.md`)。" ), "draft_label": "你的草稿輸出", "intent_label": "選填 - 這段輸出回應的使用者意圖", "output_label": "Council 判定", "empty_input": "請先貼上一段草稿回答。", "verdict_label": "判定", "coherence_label": "一致性", "per_perspective_label": "各視角", "confidence_label": "信心", }, "zh-CN": { "title": "ToneSoul - 用你自己的输出试试看", "description": ( "贴上一段草稿回答。真正的 pre-output Council 会在本机制上执行" "(不调用模型、可复现),并显示它为什么会或不会让这段草稿通过:" "各视角疑虑、声明边界标记、grounding 状态。它和 `ts validate` CLI " "使用的是同一个引擎。\n\n" "**诚实范围:**这里只本地化界面文字。下方分析输出仍是引擎生成的" "英文,没有被翻译。它显示 gates 在结构上标出什么;不是 truth、" "safety 或 morality oracle(见 `AXIOMS.json` `meta.not_for`)。" "示例草稿保留英文,因为目前 gates 主要是英文 lexical 规则。" "如果你觉得它有用、没用,或本身在过度宣称,请回报:" "https://github.com/Fan1234-1/tonesoul52 (`CALL_FOR_REVIEW.md`)。" ), "draft_label": "你的草稿输出", "intent_label": "选填 - 这段输出回应的用户意图", "output_label": "Council 判定", "empty_input": "请先贴上一段草稿回答。", "verdict_label": "判定", "coherence_label": "一致性", "per_perspective_label": "各视角", "confidence_label": "信心", }, "ja": { "title": "ToneSoul - 自分の出力で試す", "description": ( "下書き回答を貼り付けてください。実際の pre-output Council が" "モデルなし・決定論的に実行され、その下書きを通すかどうかの理由を" "表示します。視点ごとの懸念、主張境界のフラグ、grounding 状態を" "確認できます。`ts validate` CLI と同じエンジンです。\n\n" "**正直な範囲:** ここでローカライズされるのは UI の表示文だけです。" "下の分析出力はエンジンが生成する英語のままで、翻訳されません。" "これは gates が構造的に何を検出するかを示すもので、truth、safety、" "morality の oracle ではありません(`AXIOMS.json` `meta.not_for` を参照)。" "サンプルの下書きは英語のままです。現在の gates は主に英語の lexical " "規則だからです。役に立った、役に立たなかった、または過剰主張だと" "感じた場合は知らせてください: https://github.com/Fan1234-1/tonesoul52 " "(`CALL_FOR_REVIEW.md`)。" ), "draft_label": "あなたの下書き出力", "intent_label": "任意 - その出力が応答するユーザー意図", "output_label": "Council verdict", "empty_input": "まず下書き回答を貼り付けてください。", "verdict_label": "判定", "coherence_label": "一貫性", "per_perspective_label": "視点別", "confidence_label": "信頼度", }, } _DECISION_ICON = {"concern": "!", "approve": "+", "block": "x", "abstain": "-"} def i18n_coverage_matrix() -> dict[str, dict[str, bool]]: """Return key -> locale coverage for static UI text.""" return { key: { locale: bool(I18N_TRANSLATIONS.get(locale, {}).get(key)) for locale in SUPPORTED_LOCALES } for key in I18N_KEYS } def _validate_i18n_coverage() -> None: missing = [ f"{key}:{locale}" for key, row in i18n_coverage_matrix().items() for locale, present in row.items() if not present ] if missing: raise RuntimeError(f"Missing i18n strings: {', '.join(missing)}") def _text(key: str, locale: str) -> str: return I18N_TRANSLATIONS.get(locale, I18N_TRANSLATIONS[DEFAULT_LOCALE]).get( key, I18N_TRANSLATIONS[DEFAULT_LOCALE][key], ) def _match_locale(raw_locale: str) -> str | None: locale = raw_locale.strip().replace("_", "-") if not locale: return None lower = locale.lower() if lower in {"zh-tw", "zh-hant", "zh-hk", "zh-mo"}: return "zh-TW" if lower in {"zh-cn", "zh-hans", "zh-sg", "zh"}: return "zh-CN" if lower.startswith("ja"): return "ja" if lower.startswith("en"): return "en" return None def _locale_from_request(request: object | None) -> str: headers = getattr(request, "headers", None) accept_language = "" if headers is not None: accept_language = str( getattr(headers, "get", lambda _key, _default=None: "")("accept-language", "") ) for item in accept_language.split(","): locale = _match_locale(item.split(";", 1)[0]) if locale: return locale return DEFAULT_LOCALE def _ui(key: str) -> str: # gradio 6.19.0 gr.Interface runs remove_html_tags() over title/description and # raises TypeError on an I18nData object, so the Interface chrome uses plain # default-locale strings (the known-good pre-i18n pattern). run_council still # localizes its OUTPUT labels per request via _locale_from_request below. return _text(key, DEFAULT_LOCALE) def run_council(draft: str, intent: str, request: gr.Request = None) -> str: locale = _locale_from_request(request) if not draft.strip(): return _text("empty_input", locale) from tonesoul.council import PreOutputCouncil verdict = ( PreOutputCouncil() .validate( draft_output=draft, context={}, user_intent=intent or "", auto_record_self_memory=False, ) .to_dict() ) lines = [f"### {_text('verdict_label', locale)}: `{verdict.get('verdict', '?')}`"] coherence = verdict.get("coherence") if isinstance(coherence, (int, float)): lines.append(f"**{_text('coherence_label', locale)}:** {coherence:.2f}") if verdict.get("human_summary"): lines.append("") lines.append(verdict["human_summary"]) lines.append("") lines.append(f"**{_text('per_perspective_label', locale)}:**") for vote in verdict.get("votes", []): icon = _DECISION_ICON.get(str(vote.get("decision")), "?") lines.append( f"- {icon} **{vote.get('perspective')}** - {vote.get('decision')} " f"({_text('confidence_label', locale)} {vote.get('confidence')}): " f"{vote.get('reasoning')}" ) return "\n".join(lines) _validate_i18n_coverage() if gr is not None: demo = gr.Interface( fn=run_council, inputs=[ gr.Textbox(label=_ui("draft_label"), value=DEFAULT_DRAFT, lines=5), gr.Textbox( label=_ui("intent_label"), value=DEFAULT_INTENT, ), ], outputs=gr.Markdown(label=_ui("output_label")), title=_ui("title"), description=_ui("description"), ) else: demo = None if __name__ == "__main__": if demo is None: raise ModuleNotFoundError( "gradio is required to launch the Space UI" ) from _GRADIO_IMPORT_ERROR demo.launch()