tonesoul-tryit / app.py
Famwin's picture
honest framing: one primitive, not the system — banner + links to the real thing (owner-approved plan A)
eebc45a verified
Raw
History Blame Contribute Delete
12 kB
"""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()