Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,13 @@ A tool that answers everything is easy to build. This is the other kind.

### Installing it

**Word on the web** — Home → Add-ins → More Add-ins → My Add-ins → **Upload My Add-in**, and choose
[`src/SignsOfAI.Word/manifest.xml`](src/SignsOfAI.Word/manifest.xml).
**[The add-in's page →](https://peopleworks.github.io/SignsofAI/word)** — what it does, both
install routes, and what it refuses to do, in English and Spanish.

**Word for Windows** has no upload button and reads a shared-folder catalogue instead; the steps are
in [`src/SignsOfAI.Word/README.md`](src/SignsOfAI.Word/README.md).

First load is about 3.4 MB — the .NET runtime, cached afterwards. It is not in the Office Store yet.
The short version: on the web it is Home → Add-ins → More Add-ins → My Add-ins → **Upload My Add-in**
with [`src/SignsOfAI.Word/manifest.xml`](src/SignsOfAI.Word/manifest.xml). Word for Windows has no
upload button and reads a shared-folder catalogue instead, which
[`src/SignsOfAI.Word/README.md`](src/SignsOfAI.Word/README.md) walks through.

### PowerPoint is a different product, on purpose

Expand Down
3 changes: 3 additions & 0 deletions src/SignsOfAI.UI/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<NavLink class="nav-link" href="baseline">@L["nav.baseline"]</NavLink>
<NavLink class="nav-link" href="originality">@L["nav.originality"]</NavLink>
<NavLink class="nav-link" href="catalog">@L["nav.catalog"]</NavLink>
@* No host condition, unlike the two below: somebody using the desktop app writes in
Word too, and the add-in is useful to them for the same reasons. *@
<NavLink class="nav-link" href="word">@L["nav.word"]</NavLink>
@* Only where there is something to gain by downloading it. The same signal as the
folder link above, and for the same reason: this host cannot open a folder, so a
better host exists for this machine and hiding that would be the lowest-common-
Expand Down
25 changes: 6 additions & 19 deletions src/SignsOfAI.UI/Pages/Download.razor
Original file line number Diff line number Diff line change
Expand Up @@ -106,28 +106,15 @@ else
<p class="hint">@L.M("dl.zip.body")</p>
</section>

@* Not the desktop app, and placed after it rather than beside it: this is the other way to run the
engine on your own machine, in the application where the document already is. The privacy claim is
the same one and it is the reason this host is worth having at all, so it is stated rather than
implied. *@
@* The Word add-in has its own page. This is a pointer, deliberately short: the argument lived here
in full for a few hours and that is two copies of the same paragraphs, which is how one of them
ends up saying something the other stopped saying. *@
<section class="card">
<h2 class="dl-h2"><Icon Name="file-text" /> @L["dl.word.title"]</h2>
<p class="hint">@L["dl.word.lede"]</p>
<p>@L.M("dl.word.why")</p>

<ul class="dl-steps">
<li>@L.M("dl.word.install.web")</li>
<li>@L.M("dl.word.install.desktop")</li>
</ul>

<p class="hint sub">
<a href="https://github.com/peopleworks/SignsofAI/blob/main/src/SignsOfAI.Word/manifest.xml"
target="_blank" rel="noopener">@L["dl.word.manifest"]</a>
·
<a href="https://github.com/peopleworks/SignsofAI/blob/main/src/SignsOfAI.Word/README.md"
target="_blank" rel="noopener">@L["dl.word.steps"]</a>
</p>
<p class="hint">@L["dl.word.size"]</p>
<div class="cta">
<a class="primary" href="word">@L["dl.word.page"]</a>
</div>
</section>

@code {
Expand Down
108 changes: 108 additions & 0 deletions src/SignsOfAI.UI/Pages/WordAddin.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@page "/word"
@using SignsOfAI.Core.Calibration
@inherits LocalizedComponent

@*
The Word add-in's own page, the way the desktop app has one.

It leads with what the add-in refuses to do, because that is what makes it different from every
other add-in in this category — not as modesty, but because a reader deciding whether to let a
tool near coursework is deciding about exactly those refusals.

The floor and the published rate are read from the calibration the build ships rather than typed
here. That page went stale for six days in August by being typed; this one cannot.
*@

<PageTitle>@L["wa.pagetitle"]</PageTitle>

<header class="hero">
<h1>@L["wa.h1"]</h1>
<p class="tagline">@L["wa.tagline"]</p>
</header>

@* Installing first: somebody who arrived from the README or a video wants the manifest, not an
argument they have already been convinced by. *@
<section class="card">
<h2 class="dl-h2"><Icon Name="download" /> @L["wa.install.title"]</h2>
<p class="hint">@L["wa.install.lede"]</p>

<h3 class="wa-h3">@L["wa.install.web.title"]</h3>
<ol class="dl-steps">
<li>@L.M("wa.install.web.1")</li>
<li>@L.M("wa.install.web.2")</li>
<li>@L.M("wa.install.web.3")</li>
</ol>

<h3 class="wa-h3">@L["wa.install.win.title"]</h3>
<p>@L.M("wa.install.win.body")</p>

<div class="cta">
<a class="primary" href="@ManifestUrl" target="_blank" rel="noopener">@L["wa.install.manifest"]</a>
<a href="@StepsUrl" target="_blank" rel="noopener">@L["wa.install.steps"]</a>
</div>
<p class="hint sub">@L["wa.install.size"]</p>
</section>

@* The differentiator, stated as a mechanism rather than a promise: there is no endpoint, so there
is nothing to send the document to. A claim a reader can check in the manifest beats one they
have to believe. *@
<section class="card">
<h2 class="dl-h2"><Icon Name="lock" /> @L["wa.privacy.title"]</h2>
<p>@L.M("wa.privacy.body")</p>
<p>@L.M("wa.privacy.permission")</p>
</section>

<section class="card">
<h2 class="dl-h2">@L["wa.does.title"]</h2>
<ul class="dl-grid">
@foreach (var item in Does)
{
<li class="dl-item">
<h3>@L[$"wa.does.{item}.name"]</h3>
<p>@L[$"wa.does.{item}.what"]</p>
</li>
}
</ul>
</section>

@* The refusal, with the numbers read from the shipped calibration. This is the part a teacher is
deciding about, so it gets its own section rather than a caveat at the bottom. *@
<section class="card">
<h2 class="dl-h2">@L["wa.short.title"]</h2>
<p>@L.F("wa.short.body", Floor)</p>
<blockquote class="wa-quote">@L.F("wa.short.quote", Floor)</blockquote>
<p>@L.F("wa.short.rate", Texts, Flagged, RateHigh)</p>
<p class="hint">@L["wa.short.facts"]</p>
</section>

<section class="card">
<h2 class="dl-h2">@L["wa.not.title"]</h2>
<ul class="dl-steps">
<li>@L.F("wa.not.powerpoint", Floor)</li>
<li>@L["wa.not.parts"]</li>
<li>@L["wa.not.store"]</li>
</ul>
</section>

<div class="cta">
<a href="./">@L["wa.cta.try"]</a>
<a href="https://github.com/peopleworks/SignsofAI" target="_blank" rel="noopener">@L["wa.cta.code"]</a>
</div>

@code {
private const string Repo = "https://github.com/peopleworks/SignsofAI/blob/main/src/SignsOfAI.Word";
private const string ManifestUrl = $"{Repo}/manifest.xml";
private const string StepsUrl = $"{Repo}/README.md";

// Same shape as Download.razor's list, and listed by hand in LocaleFileTests for the same
// reason: a text search cannot see a key built from this array.
private static readonly string[] Does = ["reads", "evidence", "characters", "languages"];

// Read, never restated. why.html was typed by hand and spent six days quoting a corpus of
// ninety after the corpus became 296.
private static int Floor => PublishedCalibration.Current?.ShortestWords ?? 0;
private static int Texts => PublishedCalibration.Current?.Texts ?? 0;
private static int Flagged => PublishedCalibration.Current?.FlaggedAtThreshold ?? 0;
private static string RateHigh =>
((PublishedCalibration.Current?.RateHigh ?? 0) * 100).ToString("0.0");
}
33 changes: 33 additions & 0 deletions src/SignsOfAI.UI/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,39 @@ button.ghost.sm { padding: .35rem .7rem; font-size: .82rem; }
Deliberately shares the visual language of the front-door task cards: somebody who learned to
read those four cards reads these four the same way. */
.dl-h2 { font-size: 1.02rem; margin: 0 0 .35rem; }

/* The Word add-in page. It reuses the download page's cards, and adds only what it needs:
a sub-heading inside a card, and a pulled quote for the sentence the pane actually prints. */
.wa-h3 { font-size: .94rem; font-weight: 650; margin: 1.1rem 0 .4rem; }

/* A row of links where one of them is the action. why.html has a .cta of its own — it is a
standalone page with its own stylesheet — and copying the markup here without the style is
exactly how this shipped as two bare links sitting against each other. */
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: 1rem 0 .4rem; }
.cta a {
padding: .55rem 1rem;
border: 1px solid var(--border);
border-radius: var(--radius);
text-decoration: none;
font-weight: 600;
font-size: .92rem;
transition: filter .15s, transform .15s, border-color .15s;
}
.cta a:hover, .cta a:focus-visible { transform: translateY(-1px); border-color: var(--brand); }
.cta a.primary {
background: var(--brand);
color: var(--brand-ink);
border-color: transparent;
}
.cta a.primary:hover, .cta a.primary:focus-visible { filter: brightness(1.08); }
.wa-quote {
margin: .6rem 0;
padding: .7rem .9rem;
border-left: 3px solid var(--muted, #9aa4b0);
background: rgba(128, 128, 128, .07);
font-size: .92rem;
line-height: 1.5;
}
.dl-h2 svg { vertical-align: -2px; }

/* The call to action. A link, not a button, because it is a download and the browser should be
Expand Down
47 changes: 40 additions & 7 deletions src/SignsOfAI.UI/wwwroot/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,44 @@
"dl.same.body": "The same text gets the same number here and in the browser — it is the same engine, compiled from the same source. Nothing is uploaded to run the analysis. Four optional features can send text off the device and <strong>not one of them runs unless you turn it on</strong>; in the desktop app the perplexity measurement is local too, so three remain.",
"dl.word.title": "Inside Word, where the writing happens",
"dl.word.lede": "A task pane that reads the open document and answers in the sidebar. Same engine, same rules, same numbers — Word is simply a third place to run it.",
"dl.word.why": "A task pane <strong>is a browser</strong>, so the engine runs there, on your machine, and <strong>the document is never uploaded</strong>. Every other add-in of this kind posts your text to an API, because their analysis is a server; ours is not, so there is nothing to post it to. And it asks Word for permission to <strong>read</strong> your document, never to change it — Word enforces that, rather than asking you to trust a sentence on a website.",
"dl.word.install.web": "<strong>Word on the web:</strong> Home → Add-ins → More Add-ins → My Add-ins → Upload My Add-in, and choose the manifest.",
"dl.word.install.desktop": "<strong>Word for Windows</strong> has no upload button and reads a shared folder instead; the steps are written out in the add-in's own page.",
"dl.word.manifest": "The manifest",
"dl.word.steps": "Installing, step by step",
"dl.word.size": "First load is about 3.4 MB — the runtime, cached afterwards. Not in the Office Store yet.",
"wa.pagetitle": "Word add-in — Signs of AI Writing",
"wa.h1": "Signs of AI Writing, inside Word",
"wa.tagline": "A task pane that reads the document you have open and answers beside it. Same engine, same rules, same numbers — and the document never leaves your machine.",
"wa.install.title": "Installing it",
"wa.install.lede": "There is nothing to download and nothing to run. Word loads the pane from a web address; what you install is a small manifest file that tells it which one.",
"wa.install.web.title": "Word on the web",
"wa.install.web.1": "Open a document at <strong>office.com</strong>.",
"wa.install.web.2": "<strong>Home</strong> → <strong>Add-ins</strong> → <strong>More Add-ins</strong> → <strong>My Add-ins</strong> → <strong>Upload My Add-in</strong>.",
"wa.install.web.3": "Choose the manifest below. Then <strong>Home</strong> → <strong>Read the signs</strong>.",
"wa.install.win.title": "Word for Windows",
"wa.install.win.body": "The desktop version has no upload button. It reads a <strong>shared folder</strong> you register once in Trust Center, and after that the add-in appears in the ribbon like any other. It is more clicks than it should be, and that is Microsoft's design rather than ours — the steps are written out in full on the add-in's page.",
"wa.install.manifest": "Get the manifest",
"wa.install.steps": "Installing, step by step",
"wa.install.size": "First load is about 3.4 MB — the .NET runtime, cached afterwards. Not in the Office Store yet, which is why installing it still takes a menu.",
"wa.privacy.title": "Your document is never uploaded",
"wa.privacy.body": "Not as a promise — as a mechanism. <strong>A task pane is a browser</strong>: an embedded window with the same rules as a tab. The engine is WebAssembly, it downloads once with the pane, and it runs there. <strong>There is no endpoint in this add-in</strong>, so there is nowhere for your text to be sent. Every other add-in of this kind posts your document to a server, because their analysis <em>is</em> the server.",
"wa.privacy.permission": "And it asks Word for permission to <strong>read</strong> your document, never to change it. That is one line in the manifest, and <strong>Word enforces it</strong> — you do not have to take our word for it, and you can read the line yourself before installing anything.",
"wa.does.title": "What it does",
"wa.does.reads.name": "Reads the open document",
"wa.does.reads.what": "One button. No copying into a browser, no saving a file first, no leaving the page you are writing on.",
"wa.does.evidence.name": "Names every signal",
"wa.does.evidence.what": "Not a percentage on its own. Each tell is listed by name with the fix, so you can disagree with any one of them.",
"wa.does.characters.name": "Finds what typing cannot produce",
"wa.does.characters.what": "Invisible characters and letters from other alphabets, with the codepoint and every position. A fact about the file, not a judgement of its prose.",
"wa.does.languages.name": "English and Spanish",
"wa.does.languages.what": "The pane's language and the document's are independent. A Spanish essay is judged by Spanish rules, whichever language you read the interface in.",
"wa.short.title": "What it does with a short document, which is the part worth reading",
"wa.short.body": "It gives a score and then declines to draw a conclusion from it. The boundary this build judges by was measured only on documents of {0} words and longer, so below that length there is nothing it was fitted on. The pane says so:",
"wa.short.quote": "No verdict at this length. The boundary was measured only on texts of {0} words and longer, so no verdict is given — the score is neither evidence that a machine wrote this nor evidence that a person did.",
"wa.short.rate": "The boundary itself was measured on {0} documents written before 2022 — human by their dates, not by anyone's opinion. At that boundary it flags {1} of them, and the honest upper bound is {2}%. That figure is published, and it moves when the corpus moves.",
"wa.short.facts": "The character scan and the citation cross-check carry no threshold, so they still report at any length. Most of what people check is a paragraph, so the pane will decline often. That is the design, not a gap in it.",
"wa.not.title": "What it does not do",
"wa.not.powerpoint": "There is no PowerPoint version. A slide deck rarely reaches {0} words, so the same add-in there would withhold the verdict for almost every deck — correctly, and to nobody's benefit. That is a different product with a different promise.",
"wa.not.parts": "It reads the body of the document. Not comments, not footnotes, not tracked changes.",
"wa.not.store": "It is not in the Office Store yet, so installing it is a manifest and a menu rather than a button.",
"wa.cta.try": "Try it in the browser first",
"wa.cta.code": "Read the code",
"nav.word": "Word",
"dl.warn.title": "Windows will warn you, and it is right to",
"dl.warn.body": "The download is <strong>not code-signed</strong> — a certificate is the one thing this project has no funding for. SmartScreen therefore says <em>Windows protected your PC</em> the first time you run it, and names an unknown publisher. The warning is doing its job. Check the file instead of clicking through it:",
"dl.warn.step1": "Right-click the downloaded .zip → Properties, and tick Unblock if it is offered.",
Expand Down Expand Up @@ -449,5 +481,6 @@
"home.report.hint": "A document you can keep, forward or print. It contains your text, so it is saved to your device.",
"home.report.title": "Writing analysis report",
"batch.report.hint": "Save the scan as a document you can keep or hand to a colleague. It names your students' files.",
"batch.report.title": "Folder scan — reading order"
"batch.report.title": "Folder scan — reading order",
"dl.word.page": "How it works, and how to install it"
}
Loading
Loading