Mercurial
diff mrjunejune/src/jrpg/jrpg.js @ 265:056790c4fb0d
add role-aware Epi assistant prompts
Add verified June knowledge, guest/member/admin Copilot profiles, profile-isolated session recovery, animated Epi greetings, and a single authoritative runtime config workflow for inference.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 10:50:30 -0700 |
| parents | 04fee26ecce0 |
| children | 41a49c29a28f |
line wrap: on
line diff
--- a/mrjunejune/src/jrpg/jrpg.js Fri Aug 07 07:34:12 2026 -0700 +++ b/mrjunejune/src/jrpg/jrpg.js Fri Aug 07 10:50:30 2026 -0700 @@ -89,6 +89,11 @@ const SCRIPT_LOADS = new Map(); const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const VALID_PANELS = Object.freeze(["resume", "tools", "blog", "conversations"]); +const ROLE_GREETINGS = Object.freeze({ + guest: "Hi, my name is Epi. June usually calls me Epi-chan. I’m here to help you navigate his personal website and learn about his work, projects, and writing.", + invited: "Hi! I’m Epi, but June usually calls me Epi-chan. Since June invited you, I can be a little more casual. I can help you explore his site, talk about his projects, or brainstorm simple ideas with you.", + admin: "Hi June! I’m Epi-chan. I’m here to help you review your personal website, think through your projects, and draft or refine public-facing ideas.", +}); const TOOL_ICON_NAMES = Object.freeze({ "/notes": "repository", "/tools/file_converter": "retry", @@ -114,6 +119,13 @@ return loading; } +function greetingForSession(sessionData) { + if (sessionData?.kind !== "user") return ROLE_GREETINGS.guest; + return sessionData.role === "admin" + ? ROLE_GREETINGS.admin + : ROLE_GREETINGS.invited; +} + class StreamingTextAnimator { constructor(render) { this._render = render; @@ -232,9 +244,8 @@ this._previousButton = this.querySelector("[data-turn-previous]"); this._nextButton = this.querySelector("[data-turn-next]"); this._position = this.querySelector("[data-turn-position]"); - this._initialMessages = [...(this._messages?.children || [])].map( - item => item.cloneNode(true), - ); + this._greetingText = ROLE_GREETINGS.guest; + this._greetingAnimator = null; this._turnSequence = 0; this._activeGroup = null; this._onNavigationClick = event => { @@ -254,6 +265,10 @@ appendMessage(speaker, text) { let group = this._activeGroup || "start"; if (speaker === "June") { + this._greetingAnimator?.cancel(); + this._greetingAnimator = null; + const greeting = this._messages?.querySelector("[data-greeting]"); + if (greeting) this.setMessageStreaming(greeting, false); this._turnSequence++; group = `turn-${this._turnSequence}`; this._activeGroup = group; @@ -261,6 +276,42 @@ return this._createMessage(speaker, text, group, true); } + setGreeting(text, animate = true) { + this._greetingText = typeof text === "string" && text + ? text + : ROLE_GREETINGS.guest; + this._showGreeting(animate); + } + + _showGreeting(animate) { + if (!this._messages) return; + this._greetingAnimator?.cancel(); + this._greetingAnimator = null; + this._messages.replaceChildren(); + this._turnSequence = 0; + this._activeGroup = null; + const item = this._createMessage("Epi", "", "start", false); + if (!item) return; + item.dataset.greeting = "true"; + this._refreshTurnGroups("start"); + if (!animate) { + this.setMessageText(item, this._greetingText); + this.setMessageStreaming(item, false); + return; + } + this.setMessageStreaming(item, true); + const animator = new StreamingTextAnimator(value => { + this.setMessageText(item, value); + }); + this._greetingAnimator = animator; + animator.complete(this._greetingText); + void animator.waitForIdle().then(() => { + if (this._greetingAnimator !== animator) return; + this.setMessageStreaming(item, false); + this._greetingAnimator = null; + }); + } + _createMessage(speaker, text, group, select) { if (!this._messages) return; const item = document.createElement("li"); @@ -279,6 +330,8 @@ replaceMessages(turns) { if (!this._messages) return; + this._greetingAnimator?.cancel(); + this._greetingAnimator = null; this._messages.replaceChildren(); this._turnSequence = 0; this._activeGroup = null; @@ -286,10 +339,7 @@ ["user", "assistant"].includes(turn.role) ); if (!visibleTurns.length) { - this._messages.append( - ...this._initialMessages.map(item => item.cloneNode(true)), - ); - this._refreshTurnGroups("start"); + this._showGreeting(true); return; } let group = null; @@ -2116,6 +2166,7 @@ popSeq += 1; archiveCursor = null; }; + chat?.setGreeting(greetingForSession(_sessionData), true); /* Disable composer until fully initialized */ composer?.setDisabled(true); @@ -2301,7 +2352,7 @@ sessionStorage.removeItem(CONVERSATION_LEGACY_KEY); legacyClaimId = null; _replaceUrlState(currentPanel, null); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); try { await refreshArchiveFromScratch(); } catch (error) { @@ -2331,7 +2382,7 @@ sessionStorage.removeItem(CONVERSATION_LEGACY_KEY); legacyClaimId = null; _replaceUrlState(currentPanel, null); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); _renderedIds.clear(); archiveCursor = null; archive?.setConversations([], null); @@ -2351,7 +2402,7 @@ sessionStorage.removeItem(CONVERSATION_LEGACY_KEY); legacyClaimId = null; _replaceUrlState(currentPanel, null); - chat?.replaceMessages([]); + chat?.setGreeting(ROLE_GREETINGS.guest, true); _renderedIds.clear(); archiveCursor = null; archive?.setConversations([], null); @@ -2432,7 +2483,7 @@ currentConversationId = null; sessionStorage.removeItem(CONVERSATION_STORAGE_KEY); _pushUrlState(currentPanel, null); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); archive?.setCurrentId(null); composer?.querySelector("textarea")?.focus(); }); @@ -2461,7 +2512,7 @@ epoch !== principalEpoch) return; currentConversationId = null; sessionStorage.removeItem(CONVERSATION_STORAGE_KEY); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); archive?.setCurrentId(null); history.replaceState( { panel: popPanel, conversation: null }, @@ -2472,7 +2523,7 @@ } else if (!popConvId && currentConversationId) { currentConversationId = null; sessionStorage.removeItem(CONVERSATION_STORAGE_KEY); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); archive?.setCurrentId(null); } }; @@ -2558,7 +2609,7 @@ currentConversationId = null; sessionStorage.removeItem(CONVERSATION_STORAGE_KEY); _replaceUrlState(currentPanel, null); - chat?.replaceMessages([]); + chat?.setGreeting(greetingForSession(_sessionData), true); } /* Focus surviving element — never the detached trigger */ requestAnimationFrame(() => {