diff --git a/ventures/havre/app.js b/ventures/havre/app.js index 5debc4d..a4b0ce7 100644 --- a/ventures/havre/app.js +++ b/ventures/havre/app.js @@ -1028,11 +1028,13 @@ function jumpTo(t, m) { } // ---------- editing and deleting messages ---------- -function msgTools(el, t, m) { +function msgTools(el, t, m, voice = []) { if (!t || !m || !m.id) return; el.dataset.msg = m.id; + for (const old of $$(".msgtools", el)) old.remove(); const c = cardOf(t), words = !!String(m.content || "").trim() && c && !c.plain && S.ab.memory; el.append(h("span", { class: "msgtools" }, ...slashed([ + ...voice, m.role === "user" && !m.media ? linkish("edit", () => editMsg(t, m), { "aria-label": "Edit this message" }) : null, words ? linkish("remember", () => rememberMsg(t, m), { "aria-label": "Add this to what they remember", title: "into their memory, in these words" }) : null, words ? linkish("remind", () => remindMsg(t, m), { "aria-label": "Remind them of this at a time", title: "back to mind at a time you pick" }) : null, @@ -1122,7 +1124,7 @@ function renderLog() { if (m.voice) put(voiceLine(c, m)); let last = null; for (const b of bubblesOf(c, m.content)) { last = put(line(b, "them")); if (m.id) last.dataset.msg = m.id; } - if (last) { offerVoice(last, c, m, t); msgTools(last, t, m); } + if (last) msgTools(last, t, m, offerVoice(last, c, m, t)); } } const ideas = hist.some((m) => m.role === "user" && !m.hidden) ? [] : C.suggestions(c); @@ -1376,7 +1378,7 @@ async function play(t, bubbles, signal, c, msg, drawn = S.gen) { if (msg && msg.id) last.dataset.msg = msg.id; scrollLog(); } - if (last && c && msg) offerVoice(last, c, msg, t); + if (last && c && msg) msgTools(last, t, msg, offerVoice(last, c, msg, t)); } // ---------- photos, clips, voice notes, and yours, in the conversation ---------- @@ -1446,7 +1448,7 @@ function voiceLine(c, m) { /** "hear it" after a reply that came as text: its voice note, made when asked for (once, however often it's clicked) */ const voicing = new WeakSet(); function offerVoice(el, c, m, t) { - if (m.voice || m.demo || c.plain || !S.key || !t) return; + if (m.voice || m.demo || c.plain || !S.key || !t) return []; const btns = []; const make = (label, asmr) => { const b = linkish(voicing.has(m) ? "recording…" : label, async () => { @@ -1457,15 +1459,15 @@ function offerVoice(el, c, m, t) { try { m.voice = await makeVoice(c, m.content, undefined, { asmr }); saveThread(t); - if (b.isConnected) { b.replaceWith(audioEl(m.voice.id)); for (const x of btns) if (x !== b) x.remove(); } - else placeVoice(t, m); + placeVoice(t, m); // the voice note goes above the words + if (el.isConnected) msgTools(el, t, m); // and the line under them loses hear / whisper balanceSoon(); } catch (e) { for (const x of btns) x.disabled = false; b.textContent = label; toast(e.message); } finally { voicing.delete(m); } }, { class: "linkish hear", "aria-label": asmr ? `Hear this whispered by ${c.name}, over soft rain` : `Hear this as a voice note from ${c.name}`, disabled: voicing.has(m) || null }); btns.push(b); return b; }; - $(".said", el).append(" ", make("hear it", false), " ", make("whisper it", true)); + return [make("hear", false), make("whisper", true)]; } /** a voice note of these words, in the character's voice and mood */ async function makeVoice(c, text, signal, { asmr = false } = {}) {