HumanTouch / render.py
ciaochris's picture
HumanTouch: authorial-preservation engine, change ledger, Voiceprint, evals (#4)
db3e82c
Raw History Blame Contribute Delete
18.9 kB
"""HTML for the studio. Every string that came from a person or a model is escaped here.
These functions take the plain dicts the engine produces (model_dump output),
so the same data shown on screen is what MCP callers and exports receive.
"""
from __future__ import annotations
import base64
import json
from html import escape
from humantouch.intent import STRENGTH_LABELS
STATE_CLASS = {
'original': 'p-original', 'lightly edited': 'p-light', 'substantially rewritten': 'p-heavy', 'moved': 'p-moved',
'AI-added': 'p-added', 'restored by you': 'p-restored', 'kept by guard': 'p-guard',
'edited by you after generation': 'p-user',
}
STATE_HELP = {
'original': 'exactly as you wrote it', 'lightly edited': 'mostly your wording', 'substantially rewritten': 'reworked from your sentence',
'moved': 'your sentence, in a new place', 'AI-added': 'no counterpart in your text', 'restored by you': 'you put your wording back',
'kept by guard': 'an edit was withheld to protect meaning or protected text', 'edited by you after generation': 'you changed this by hand afterwards',
}
LEVEL_WIDTH = {'none': 0, 'very low': 12, 'low': 30, 'medium': 60, 'high': 100}
HEADER = """
<nav class="masthead" aria-label="Studio identity">
<div class="wordmark"><span class="brand-mark" aria-hidden="true">h.</span>HumanTouch</div>
<span class="masthead-note">A writing studio by Vers3Dynamics</span>
<span class="edition">YOUR WORDS, WITH CHARACTER</span>
</nav>
<section class="hero" aria-labelledby="studio-title">
<div class="hero-copy">
<p class="eyebrow"><span></span> AN EDITOR THAT WORKS UNDERNEATH YOUR VOICE</p>
<h1 id="studio-title">Edited.<br>Still <em>yours.</em><svg class="underline" viewBox="0 0 200 16" aria-hidden="true"><path d="M3 10 Q82 0 196 7 M14 14 Q111 5 183 12"/></svg></h1>
<p class="hero-description">HumanTouch helps AI edit your writing without sanding away the parts that make it yours.
Paste something you’ve written. I can tighten it, reshape it, help you continue it, compare versions, or learn the patterns of your voice.
I’ll tell you when I think a revision changes more than style.</p>
</div>
<aside class="margin-note" aria-label="How HumanTouch treats an edit">
<div class="note-top"><span>WHAT THE LEDGER LOOKS LIKE</span><span aria-hidden="true">↙</span></div>
<p class="note-before">“I think this could help.”</p>
<p class="note-after">“This will help.”</p>
<div class="note-bottom"><span class="handwritten">More certain than you were. Kept yours.</span><span>AN ILLUSTRATION</span></div>
</aside>
</section>
"""
FOOTER = """
<footer class="studio-footer"><span><b>HumanTouch</b> &nbsp; AI can change the writing without quietly replacing the writer.</span>
<span>The final authority is you.</span></footer>
"""
EMPTY = '<p class="quiet">Nothing here yet. This fills in after an edit.</p>'
def _short(text: str, limit: int = 90) -> str:
text = ' '.join((text or '').split())
return text if len(text) <= limit else text[:limit - 1] + '…'
def change_label(change: dict) -> str:
left = f'“{_short(change["original"], 40)}”' if change['original'] else '(nothing)'
right = f'“{_short(change["revised"], 40)}”' if change['revised'] else '(removed)'
return f'{change["id"]:02d} · {left} → {right} · {change["category"]}'
def summary(result: dict | None, fixture: bool = False) -> str:
if not result:
return ''
a = result['analysis']
s = a['stats']
parts = ['<section class="ht-summary" aria-label="Summary of this revision">']
if fixture:
parts.append('<p class="notice">Development provider: these edits come from a mechanical test double, not a language model.</p>')
mode = 'ghost' if a['assistance_mode'] == 'ghostwriter' else 'mirror'
parts.append(f'<p class="mode {mode}"><b>{"New language added" if mode == "ghost" else "Edited, not rewritten"}.</b> {escape(a["assistance_note"])}</p>')
active = s['changes'] - s['changes_restored']
parts.append(f'<p class="counts">{int(active)} edit{"s" if active != 1 else ""} in effect · {int(s["sentences_untouched"])} of {int(s["sentences"])} sentences '
f'exactly yours · {int(s["source_words"])} → {int(s["result_words"])} words</p>')
for action in result.get('guard_actions', []):
parts.append(f'<p class="guard">{escape(action)}</p>')
high = [w for w in a['meaning_warnings'] if w['severity'] != 'low']
if high:
parts.append(f'<p class="warn-line"><b>{len(high)} thing{"s" if len(high) != 1 else ""} to check below</b>: this revision may change more than style.</p>')
parts.append('<dl class="estimates">')
for name, est in a['confidence'].items():
parts.append(f'<div><dt>{escape(name.capitalize())}</dt><dd><span class="lvl lvl-{escape(est["level"].replace(" ", "-"))}">{escape(est["level"])}</span> '
f'<span class="means">{escape(est["means"])}</span></dd></div>')
parts.append('</dl></section>')
return ''.join(parts)
def warnings(result: dict | None) -> str:
if not result:
return EMPTY
items = result['analysis']['meaning_warnings']
out = ['<section class="ht-warnings"><h4>Did I change what you meant?</h4>']
if not items:
out.append('<p class="quiet">No warning from the deterministic checks (numbers, dates, names, quotations, links, negation, scope and '
'certainty words). They cannot see a subtle paraphrase, so a read-through is still yours to do.</p>')
for w in items:
out.append(f'<article class="warning sev-{w["severity"]}"><p class="w-head"><span class="tag">{escape(w["status"])}</span> {escape(w["what"])}</p>')
if w['original']:
out.append(f'<p class="w-pair"><span>Original</span> {escape(w["original"])}</p>')
if w['revised']:
out.append(f'<p class="w-pair"><span>Revision</span> {escape(w["revised"])}</p>')
if w['change_ids']:
out.append(f'<p class="w-fix">To restore your wording, tick edit{"s" if len(w["change_ids"]) != 1 else ""} '
f'{", ".join(f"{i:02d}" for i in w["change_ids"])} in the ledger.</p>')
out.append('</article>')
audit = result.get('audit')
if audit:
out.append('<h4>A model’s second reading <span class="tag soft">model judgment</span></h4>'
'<p class="quiet">This is another model call’s opinion, kept separate from the checks above. Each line quotes both texts exactly; '
f'{audit["unverified_dropped"]} line(s) that did not were dropped.</p>')
for item in audit['items']:
out.append(f'<p class="audit"><span class="tag soft">{escape(item["status"])}</span> “{escape(_short(item["original"], 140))}” → '
f'“{escape(_short(item["revised"], 140))}” <em>{escape(item["note"])}</em></p>')
elif result.get('audit_error'):
out.append(f'<p class="notice">{escape(result["audit_error"])}</p>')
out.append('</section>')
return ''.join(out)
def ledger(result: dict | None) -> str:
if not result:
return EMPTY
changes = result['analysis']['changes']
if not changes:
return '<p class="quiet">No edits. The text came back exactly as you wrote it.</p>'
out = ['<section class="ht-ledger"><p class="quiet">Categories describe the kind of difference each edit makes. They are read from the edit itself, '
'not from the model’s account of why.</p>']
for c in changes:
cls = 'restored' if c['restored'] else 'active'
who = {'you': 'restored by you', 'guard': 'withheld by the meaning guard', 'protected text': 'withheld: protected text'}.get(c.get('restored_by') or '', '')
out.append(
f'<article class="change {cls}"><header><span class="num">CHANGE {c["id"]:02d}</span><span class="tag">{escape(c["category"])}</span>'
+ (f'<span class="tag kept">{escape(who)}</span>' if who else '') + '</header>'
f'<p class="pair"><del>{escape(c["original"]) or "<i>(nothing)</i>"}</del><span aria-hidden="true"> → </span>'
f'<ins>{escape(c["revised"]) or "<i>(removed)</i>"}</ins></p>'
f'<p class="meta">{escape(c["reason"])} · Voice impact: <b>{c["voice_impact"]}</b> · Meaning impact: <b>{c["meaning_impact"]}</b></p></article>')
out.append('</section>')
return ''.join(out)
def _legend(states: list[str]) -> str:
seen = [s for s in STATE_CLASS if s in states]
return '<p class="legend">' + ''.join(f'<span class="{STATE_CLASS[s]}">{escape(s)}</span><small>{escape(STATE_HELP[s])}</small>' for s in seen) + '</p>'
def authorship(result: dict | None) -> str:
if not result:
return EMPTY
prov = result['analysis']['provenance']
counts: dict[str, int] = {}
for p in prov:
counts[p['state']] = counts.get(p['state'], 0) + 1
tally = ', '.join(f'{n} {s}' for s, n in counts.items())
body = ' '.join(f'<span class="{STATE_CLASS[p["state"]]}" title="{escape(p["state"])}: {p["original_share"]:.0%} your wording">'
f'{escape(p["text"])}<sup>{escape(p["state"])}</sup></span>' for p in prov)
return (f'<section class="ht-authorship"><p class="quiet">Where did these words come from? {len(prov)} sentences: {escape(tally)}. '
'This is about awareness, not contamination: added words are not worse words.</p>'
f'{_legend(list(counts))}<div class="map">{body}</div></section>')
def voice(result: dict | None) -> str:
if not result:
return EMPTY
a = result['analysis']
drift = a['voice_drift']
out = [f'<section class="ht-voice"><h4>Voice drift <span class="tag soft">against {escape(drift["reference"])}</span></h4>'
f'<p class="{"warn-line" if drift["detected"] else "quiet"}">{escape(drift["summary"])}</p>']
if drift['shifts']:
out.append('<table><thead><tr><th scope="col">Habit</th><th scope="col">Before</th><th scope="col">Now</th><th scope="col">Change</th></tr></thead><tbody>')
for s in drift['shifts']:
out.append(f'<tr><th scope="row">{escape(s["label"])}</th><td>{s["before"]:g}</td><td>{s["after"]:g}</td><td>{escape(s["change"])}</td></tr>')
out.append('</tbody></table>')
out.append('<h4>Authorial distance</h4><p class="quiet">How far this revision moved from your text, axis by axis. A navigational aid, not a verdict.</p><dl class="distance">')
for name, axis in a['authorial_distance'].items():
out.append(f'<div><dt>{escape(name.capitalize())}</dt><dd><span class="bar" aria-hidden="true"><i style="width:{LEVEL_WIDTH[axis["level"]]}%"></i></span>'
f'<b>{escape(axis["level"])}</b><small>{escape(axis["how"])}</small></dd></div>')
out.append('</dl>')
if a['preserved_features']:
out.append('<h4>What I kept</h4><ul>' + ''.join(f'<li>{escape(x)}</li>' for x in a['preserved_features']) + '</ul>')
out.append('</section>')
return ''.join(out)
def plan_text(intent: dict | None) -> str:
if not intent:
return ''
return ('<p class="plan-basis">I think you want (' + escape(intent['basis']) + '; untick anything I got wrong):</p>')
def notice_list(items: list[dict]) -> str:
if not items:
return ''
return '<ul class="notices">' + ''.join(f'<li>{escape(i["message"])}</li>' for i in items) + '</ul>'
def data_link(obj: dict, filename: str, label: str) -> str:
payload = base64.b64encode(json.dumps(obj, ensure_ascii=False, indent=2).encode('utf-8')).decode()
return f'<a class="download" download="{escape(filename)}" href="data:application/json;base64,{payload}">{escape(label)}</a>'
def voiceprint(vp: dict | None) -> str:
if not vp:
return ('<p class="quiet">No Voiceprint yet. Add some of your own writing above and build one. Without it, HumanTouch protects the voice '
'of whatever text you paste; with it, it can also notice drift from how you usually write.</p>')
q = vp['sample_quality']
out = [f'<section class="voice-portrait"><h3>Your Voiceprint</h3><p class="sample sample-{q["rating"]}"><b>Sample: {escape(q["rating"])}.</b> {escape(q["advice"])}</p>']
if vp['tendencies']:
out.append('<h4>Your writing tends to</h4><ul>' + ''.join(f'<li>{escape(t)};</li>' for t in vp['tendencies']) + '</ul>')
out.append('<h4>Dimensions</h4><p class="quiet">Descriptions, not grades. <i>Observed</i> is measured from your samples; <i>inferred</i> is a proxy built on '
'those measurements; <i>model judgment</i> is a model’s reading that quoted your samples.</p><div class="dims">')
labels = {'keep': 'you said: keep this', 'not_me': 'you said: that isn’t me', 'more': 'you said: more of this', 'less': 'you said: less of this',
'never_imitate': 'you said: never imitate this'}
for d in vp['dimensions']:
corrected = f'<span class="tag kept">{escape(labels[d["correction"]])}</span>' if d.get('correction') else ''
evidence = ''.join(f'<q>{escape(e)}</q>' for e in d['evidence'])
out.append(f'<article class="dim{" off" if d.get("correction") in ("not_me", "never_imitate") else ""}"><header><b>{escape(d["name"].capitalize())}</b>'
f'<span class="tag soft">{escape(d["basis"])}</span><span class="tag soft">confidence: {escape(d["confidence"])}</span>{corrected}</header>'
f'<p>{escape(d["summary"])} {evidence}</p></article>')
out.append('</div>')
if vp['signature_patterns']:
out.append('<h4>Signature patterns</h4><p class="patterns">' + ''.join(
f'<span title="{escape(p["basis"])}">{escape(p["text"])} <small>×{p["count"]}</small></span>' for p in vp['signature_patterns']) + '</p>')
if vp['stable_traits'] or vp['contextual_traits']:
out.append(f'<h4>Across contexts ({escape(", ".join(vp["contexts"]))})</h4><p><b>Stable:</b> {escape(", ".join(vp["stable_traits"]) or "none measured")}.<br>'
f'<b>Changes with context:</b> {escape(", ".join(vp["contextual_traits"]) or "none measured")}.</p>')
if vp['voice_notes']:
out.append('<h4>Your voice notes</h4><ul>' + ''.join(
f'<li>“{escape(n["note"])}” <span class="tag soft">{escape(n["effect"])}</span>'
+ (f' <span class="tag kept">protects: {escape(", ".join(n["protected_phrases"]))}</span>' if n['protected_phrases'] else '') + '</li>'
for n in vp['voice_notes']) + '</ul>')
model = {'included': f'A model reading is included; {vp["unverified_dropped"]} claim(s) it could not tie to an exact quotation were dropped.',
'not requested': 'No model was used. Everything above is measured on this server.',
'failed': 'The model reading failed, so humor, abstraction and imagery are marked “not assessed” rather than guessed.'}[vp['model_read']]
out.append(f'<p class="quiet">{escape(model)}</p><details><summary>Limitations and every measurement</summary><ul>'
+ ''.join(f'<li>{escape(x)}</li>' for x in vp['limitations']) + f'</ul><pre>{escape(json.dumps(vp["observed"], indent=2))}</pre></details>')
out.append(data_link(vp, 'humantouch-voiceprint.json', 'Download this Voiceprint (JSON)'))
out.append('<p class="quiet">The file holds derived measurements, your notes, and short quoted phrases from your samples. It does not hold the samples.</p></section>')
return ''.join(out)
def voice_indicator(vp: dict | None) -> str:
if not vp:
return '<p class="voice-hint">No Voiceprint loaded. I’ll protect the voice of the text itself.</p>'
q = vp['sample_quality']
return f'<p class="voice-hint active">Voiceprint active · {q["words"]:,} words sampled · {escape(q["rating"])}</p>'
def comparison(cmp: dict | None) -> str:
if not cmp:
return ''
out = ['<section class="ht-compare"><ul>' + ''.join(f'<li>{escape(s)}</li>' for s in cmp['statements']) + '</ul>']
for title, items in (('Certainty differences', cmp['certainty']), ('Factual and content differences', cmp['facts'])):
if items:
out.append(f'<h4>{title}</h4>' + ''.join(
f'<p class="w-pair"><span>{escape(w["status"])}</span> {escape(w["what"])}'
+ (f'<br><small>A: {escape(w["original"])}</small>' if w['original'] else '')
+ (f'<br><small>B: {escape(w["revised"])}</small>' if w['revised'] else '') + '</p>' for w in items))
out.append(f'<p class="quiet">{escape(cmp["note"])}</p></section>')
return ''.join(out)
def demo(generic: dict, touched: dict, source: str) -> str:
def column(title: str, r: dict) -> str:
a = r['analysis']
s = a['stats']
medium_up = sum(w['severity'] != 'low' for w in a['meaning_warnings'])
rows = [('Sentences left exactly as written', f'{int(s["sentences_untouched"])} of {int(s["sentences"])}'),
('Share of the original’s words edited', f'{s["edit_ratio"]:.0%}'),
('Meaning warnings to check', str(medium_up)),
('Habits that drifted', str(len(a['voice_drift']['shifts']))),
('Sentences with no counterpart', str(int(s['ai_added_sentences']))),
('Length', f'{int(s["source_words"])} → {int(s["result_words"])} words')]
body = ' '.join(f'<span class="{STATE_CLASS[p["state"]]}">{escape(p["text"])}</span>' for p in a['provenance'])
table = ''.join(f'<tr><th scope="row">{escape(k)}</th><td>{escape(v)}</td></tr>' for k, v in rows)
return (f'<article><h4>{escape(title)}</h4><div class="map">{body}</div><table>{table}</table>'
f'<p class="quiet">{escape(a["voice_drift"]["summary"])}</p></article>')
states = [p['state'] for r in (generic, touched) for p in r['analysis']['provenance']]
return ('<section class="ht-demo"><p class="quiet">Both columns were produced just now by the same model from the same text and the same request. '
'Only the instructions around the model differ. The numbers are measurements of these two outputs, so they will vary from run to run.</p>'
f'{_legend(states)}<div class="cols">{column("A conventional rewrite prompt", generic)}{column("HumanTouch", touched)}</div></section>')
def cowrite_map(sentences: list[dict]) -> str:
if not sentences:
return ''
body = ' '.join(f'<span class="{c}" title="{escape(label)}">{escape(text)}<sup>{escape(label)}</sup></span>' for text, label, c in
((s['text'], s['state'], 'p-added' if s['state'] == 'AI-added' else 'p-light' if 'edited' in s['state'] else 'p-original') for s in sentences))
return f'<section class="ht-authorship"><div class="map">{body}</div></section>'
__all__ = ['HEADER', 'FOOTER', 'STRENGTH_LABELS']