diff --git a/lib/mi-voice-bridge.ts b/lib/mi-voice-bridge.ts index 1b3b991e..45a99628 100644 --- a/lib/mi-voice-bridge.ts +++ b/lib/mi-voice-bridge.ts @@ -30,7 +30,7 @@ export class MiVoiceBridge { constructor(opts: MiVoiceBridgeOptions = {}) { this.#bridgeUrl = opts.bridgeUrl ?? DEFAULT_BRIDGE; - this.#voice = opts.voice ?? "en-US-AndrewMultilingualNeural"; + this.#voice = opts.voice ?? "en-US-EmmaMultilingualNeural"; this.#onStateChange = opts.onStateChange ?? null; } @@ -191,7 +191,7 @@ export class MiVoiceBridge { const res = await fetch(`${this.#bridgeUrl}${TTS_PATH}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text, voice: this.#voice, volume: 0.3, rate: "-10%", pitch: "-6Hz" }), + body: JSON.stringify({ text, voice: this.#voice, volume: 0.25, rate: "-8%", pitch: "+0Hz" }), }); if (!res.ok) { ws.removeEventListener("message", handler); @@ -224,8 +224,8 @@ export class MiVoiceBridge { this.#speakResolve = resolve; const utterance = new SpeechSynthesisUtterance(text); utterance.rate = 0.95; - utterance.pitch = 0.85; - utterance.volume = 0.3; + utterance.pitch = 1.0; + utterance.volume = 0.25; utterance.onend = () => { this.#speakResolve = null; resolve(); diff --git a/modules/rpubs/mod.ts b/modules/rpubs/mod.ts index 0bf19633..44d7678d 100644 --- a/modules/rpubs/mod.ts +++ b/modules/rpubs/mod.ts @@ -200,7 +200,10 @@ function formatBytes(n: number): string { function renderReaderPage(opts: { record: import("./publications-store").PublicationRecord; spaceSlug: string }): string { const { record, spaceSlug } = opts; - const base = `/${escapeAttr(spaceSlug)}/rpubs/publications/${escapeAttr(record.slug)}`; + // Always emit canonical subdomain absolute URLs so links are correct + // regardless of whether the user arrived via {space}.rspace.online/... + // or rspace.online/{space}/... (internal path-scope rewriting form). + const base = `https://${encodeURIComponent(record.space)}.rspace.online/rpubs/publications/${encodeURIComponent(record.slug)}`; const pdfUrl = `${base}/pdf`; const epubUrl = `${base}/epub`; const epubFixedUrl = record.fixedEpubBytes ? `${base}/epub-fixed` : null; @@ -550,23 +553,18 @@ routes.post("/api/publish", async (c) => { fixedEpub, }); - const proto = c.req.header("x-forwarded-proto") || "https"; - const host = c.req.header("host") || "rspace.online"; - const baseUrl = `${proto}://${host}`; - // The reader lives under the module mount (`/:space/rpubs/publications/:slug`) - // or, on the standalone rpubs.online domain, at `/publications/:slug`. - const isStandalone = host.includes("rpubs.online"); - const hostedPath = isStandalone - ? `/publications/${record.slug}` - : `/${record.space}/rpubs/publications/${record.slug}`; + // Canonical subdomain form: {space}.rspace.online/rpubs/publications/{slug}. + // The server rewrites subdomain → path-scope internally, so this URL works + // even though Hono routes are mounted at `/:space/rpubs/...`. + const hostedUrl = `https://${record.space}.rspace.online/rpubs/publications/${record.slug}`; return c.json({ ...record, - hosted_url: `${baseUrl}${hostedPath}`, - hosted_path: hostedPath, - pdf_url: `${baseUrl}${hostedPath}/pdf`, - epub_url: `${baseUrl}${hostedPath}/epub`, - epub_fixed_url: fixedEpub ? `${baseUrl}${hostedPath}/epub-fixed` : null, + hosted_url: hostedUrl, + hosted_path: `/rpubs/publications/${record.slug}`, + pdf_url: `${hostedUrl}/pdf`, + epub_url: `${hostedUrl}/epub`, + epub_fixed_url: fixedEpub ? `${hostedUrl}/epub-fixed` : null, }, 201); } catch (error) { console.error("[Pubs] Publish error:", error); @@ -598,7 +596,7 @@ routes.get("/publications/:slug", async (c) => { const record = await getPublication(dataSpace, slug); if (!record) { - return c.html(`Not found

Publication not found

No publication exists at this URL.

Back to rPubs

`, 404); + return c.html(`Not found

Publication not found

No publication exists at this URL.

Back to rPubs

`, 404); } return c.html(renderShell({