| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import * as Y from "yjs"; |
| import { |
| addAuthorRecord, |
| appendInline, |
| broadcastCursorAtEnd, |
| claimSlotAfterHeading, |
| clearAwarenessCursor, |
| findHeadingIndex, |
| insertInlineNode, |
| registerCitation, |
| setAffiliationUrl, |
| sleep, |
| typeIntoParagraph, |
| waitForAffiliationCount, |
| waitForAuthorCount, |
| waitForCondition, |
| writeBlockMathAtSlot, |
| type PersonaHandle, |
| } from "./lib/yjs-client.js"; |
|
|
| function jitter(minMs: number, maxMs: number): number { |
| return minMs + Math.random() * (maxMs - minMs); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function ensureBibliographySection(handle: PersonaHandle): void { |
| const existing = handle.fragment |
| .toArray() |
| .find((c) => c instanceof Y.XmlElement && c.nodeName === "bibliography"); |
| if (existing) return; |
|
|
| handle.ydoc.transact(() => { |
| const bib = new Y.XmlElement("bibliography"); |
| handle.fragment.push([bib]); |
| bib.setAttribute("renderedHtml", ""); |
| |
| |
| |
| |
| const trailing = new Y.XmlElement("paragraph"); |
| handle.fragment.push([trailing]); |
| }); |
| } |
|
|
| export async function runBob( |
| bob: PersonaHandle, |
| speed: number, |
| ): Promise<void> { |
| console.log("[bob] connected as Yjs peer, waiting for Alice's first author"); |
|
|
| |
| |
| await sleep(jitter(800, 1200)); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const seeded = await waitForAuthorCount(bob, 1, 30_000); |
| if (!seeded) { |
| console.warn("[bob] Alice's first author never appeared, proceeding"); |
| } |
| const affSeeded = await waitForAffiliationCount(bob, 1, 30_000); |
| if (!affSeeded) { |
| console.warn("[bob] Hugging Face affiliation never appeared, proceeding"); |
| } |
| await sleep(jitter(250, 500)); |
|
|
| setAffiliationUrl(bob, 1, "https://huggingface.co"); |
| await sleep(jitter(180, 320)); |
|
|
| addAuthorRecord(bob, { |
| name: "Bob Mercier", |
| url: "https://huggingface.co/bmercier", |
| existingAffiliationIndices: [1], |
| }); |
| await sleep(jitter(300, 550)); |
| addAuthorRecord(bob, { |
| name: "Mira Patel", |
| url: "https://huggingface.co/mpatel", |
| existingAffiliationIndices: [1], |
| }); |
| console.log("[bob] authors added (both at Hugging Face)"); |
|
|
| |
| |
| |
| |
| |
| const hasHeading = await waitForCondition( |
| bob.fragment, |
| () => findHeadingIndex(bob.fragment, "The recipe") !== -1, |
| 60_000, |
| ); |
| if (!hasHeading) { |
| console.warn("[bob] 'The recipe' heading never arrived, skipping body"); |
| return; |
| } |
| await sleep(jitter(150, 300)); |
|
|
| |
| |
| |
| |
| |
| |
| const slot0 = claimSlotAfterHeading(bob.fragment, "The recipe", 0); |
| if (!slot0) { |
| console.warn("[bob] could not locate slot 0 of 'The recipe'"); |
| return; |
| } |
| await sleep(jitter(80, 160)); |
|
|
| const bodyCps = 38 / Math.max(0.5, speed / 2); |
| const fastCps = 48 / Math.max(0.5, speed / 2); |
| await typeIntoParagraph( |
| slot0, |
| "Attention lets a token look at every other token, scoring each pair through queries ", |
| { cps: bodyCps, chunkSize: 2 }, |
| undefined, |
| bob, |
| ); |
| await insertInlineNode(slot0, { type: "inlineMath", latex: "Q" }, 200, bob); |
| await typeIntoParagraph(slot0, ", keys ", { cps: bodyCps }, undefined, bob); |
| await insertInlineNode(slot0, { type: "inlineMath", latex: "K" }, 160, bob); |
| await typeIntoParagraph( |
| slot0, |
| " and values ", |
| { cps: bodyCps }, |
| undefined, |
| bob, |
| ); |
| await insertInlineNode(slot0, { type: "inlineMath", latex: "V" }, 160, bob); |
| await typeIntoParagraph(slot0, ".", { cps: bodyCps }, undefined, bob); |
|
|
| await sleep(jitter(240, 400)); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const citationKey = "vaswani2017attention"; |
| registerCitation(bob, citationKey, { |
| id: citationKey, |
| "citation-key": citationKey, |
| type: "article-journal", |
| title: "Attention Is All You Need", |
| author: [ |
| { family: "Vaswani", given: "Ashish" }, |
| { family: "Shazeer", given: "Noam" }, |
| { family: "Parmar", given: "Niki" }, |
| { family: "Uszkoreit", given: "Jakob" }, |
| { family: "Jones", given: "Llion" }, |
| { family: "Gomez", given: "Aidan N." }, |
| { family: "Kaiser", given: "Lukasz" }, |
| { family: "Polosukhin", given: "Illia" }, |
| ], |
| issued: { "date-parts": [[2017]] }, |
| "container-title": "Advances in Neural Information Processing Systems", |
| volume: "30", |
| URL: "https://arxiv.org/abs/1706.03762", |
| DOI: "10.48550/arXiv.1706.03762", |
| }); |
| appendInline(slot0, [ |
| { |
| type: "citation", |
| key: citationKey, |
| label: "(Vaswani et al., 2017)", |
| }, |
| ]); |
| broadcastCursorAtEnd(bob, slot0); |
| ensureBibliographySection(bob); |
| console.log("[bob] citation + bibliography inserted"); |
|
|
| await sleep(jitter(320, 520)); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| await sleep(jitter(220, 380)); |
| const mathNode = writeBlockMathAtSlot( |
| bob.fragment, |
| "The recipe", |
| 1, |
| "\\text{Attention}(Q, K, V) = \\text{softmax}\\!\\left(\\frac{QK^\\top}{\\sqrt{d_k}}\\right) V", |
| ); |
| if (mathNode) { |
| console.log("[bob] block math equation inserted"); |
| } else { |
| console.warn("[bob] could not insert block math"); |
| } |
| await sleep(jitter(320, 500)); |
|
|
| |
| const slot2 = claimSlotAfterHeading(bob.fragment, "The recipe", 2); |
| if (slot2) { |
| await sleep(jitter(80, 150)); |
| await typeIntoParagraph( |
| slot2, |
| "The softmax turns those similarity scores into weights, and the weighted sum over values produces one contextual vector per token.", |
| { cps: fastCps, chunkSize: 2 }, |
| undefined, |
| bob, |
| ); |
| } else { |
| console.warn("[bob] could not locate slot 2"); |
| } |
|
|
| await sleep(jitter(200, 320)); |
|
|
| |
| const slot3 = claimSlotAfterHeading(bob.fragment, "The recipe", 3); |
| if (slot3) { |
| await sleep(jitter(80, 150)); |
| await typeIntoParagraph( |
| slot3, |
| "In practice the same operation runs in parallel through several independent projections, one per head, and the outputs are concatenated - that is multi-head attention.", |
| { cps: fastCps, chunkSize: 2 }, |
| undefined, |
| bob, |
| ); |
| } else { |
| console.warn("[bob] could not locate slot 3"); |
| } |
|
|
| await sleep(jitter(200, 320)); |
| |
| |
| clearAwarenessCursor(bob); |
| console.log("[bob] scenario complete"); |
| } |
|
|