From 187bc16163cd719ab84821cf863792743862c6a8 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Thu, 22 Oct 2026 20:00:36 +0000 Subject: [PATCH] mcp station: DJ box (listener requests steer the next sheet, re-cooks up-next) + emoji reactions; URI station2 to bust the host cache Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014mBuCaQSgWwqhtzsmTBQuM --- mcp/worker.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/mcp/worker.js b/mcp/worker.js index 49d0247..715a320 100644 --- a/mcp/worker.js +++ b/mcp/worker.js @@ -9,7 +9,7 @@ const MEDIA = "https://dev.gandora.lol"; const ORIGIN = "https://dev.gandora.lol"; const TRACKS = { sky: "/asmr/audio/kokoro-sky.mp3", rain: "/asmr/audio/bed-rain.mp3" }; const PROBE_URI = "ui://gandora/probe"; -const RADIO_URI = "ui://gandora/station"; +const RADIO_URI = "ui://gandora/station2"; const UI_MIME = "text/html;profile=mcp-app"; const UI_META = { ui: { csp: { resourceDomains: [MEDIA], connectDomains: [MEDIA] }, prefersBorder: true } }; const APP_ONLY = { ui: { visibility: ["app"] } }; @@ -49,7 +49,7 @@ const TOOLS = [ { name: "render_track", title: "Render a gandora track", description: "Radio UI only: renders a sheet to mp3 (base64).", inputSchema: SHEET, _meta: APP_ONLY }, { name: "dj_next", title: "Write the next sheet", description: "Radio UI only: the in-card DJ writes the next track sheet from the station and play history.", - inputSchema: { type: "object", properties: { station: { type: "string" }, history: { type: "array" } } }, _meta: APP_ONLY }, + inputSchema: { type: "object", properties: { station: { type: "string" }, history: { type: "array" }, requests: { type: "array" } } }, _meta: APP_ONLY }, { name: "dj_speak", title: "Speak a DJ line", description: "Radio UI only: TTS for the DJ line (base64 mp3).", inputSchema: { type: "object", properties: { text: { type: "string" }, voice: { type: "string" } }, required: ["text"] }, _meta: APP_ONLY }, ]; @@ -103,8 +103,10 @@ async function callTool(name, a, env) { "{\"title\",\"tags\",\"lyrics\",\"bpm\",\"key\",\"seconds\",\"dj_line\"}. tags: an ACE-Step style prompt (genre, instruments, mood, vocal type, production), comma-separated, under 300 chars. " + "lyrics: original, short ([verse] 4 lines, [chorus] 4 lines), never quote real songs; or empty for an instrumental now and then. key like 'A minor'. seconds 60-90. " + "dj_line: 1-2 warm, short sentences spoken before the track, can reference the last track. Stay on the station theme but vary tempo, key, texture and title; " + - "lean toward what was liked, away from what was disliked or skipped."; - const user = JSON.stringify({ station: a.station || "", recent: hist.map((h) => ({ title: h.title, tags: h.tags, bpm: h.bpm, key: h.key, reaction: h.reaction || "played" })) }); + "lean toward what was liked or got positive emoji, away from what was disliked or skipped. " + + "If there are listener requests, the newest one comes first: honor it in this track, and acknowledge it warmly in the dj_line."; + const reqs = (Array.isArray(a.requests) ? a.requests : []).slice(-4).reverse(); + const user = JSON.stringify({ station: a.station || "", listener_requests: reqs, recent: hist.map((h) => ({ title: h.title, tags: h.tags, bpm: h.bpm, key: h.key, reaction: h.reaction || "played" })) }); const r = await inference(env, "/v1/chat/completions", { model: "claude-haiku-5-5", max_tokens: 900, temperature: 1, messages: [{ role: "system", content: sys }, { role: "user", content: user }] }); const d = await r.json(); @@ -147,7 +149,7 @@ async function handle(msg, env) { case "resources/templates/list": return ok({ resourceTemplates: [] }); case "resources/read": if (params && params.uri === PROBE_URI) return ok({ contents: [{ uri: PROBE_URI, mimeType: UI_MIME, text: PROBE_HTML, _meta: UI_META }] }); - if (params && (params.uri === RADIO_URI || params.uri === "ui://gandora/radio" || params.uri === "ui://gandora/radio-v3")) return ok({ contents: [{ uri: RADIO_URI, mimeType: UI_MIME, text: RADIO_HTML, _meta: UI_META }] }); + if (params && (params.uri === RADIO_URI || params.uri === "ui://gandora/radio" || params.uri === "ui://gandora/radio-v3" || params.uri === "ui://gandora/station")) return ok({ contents: [{ uri: RADIO_URI, mimeType: UI_MIME, text: RADIO_HTML, _meta: UI_META }] }); return err(-32002, "resource not found"); default: if (id === undefined) return null; @@ -280,6 +282,8 @@ button:disabled{opacity:.4;cursor:default}button.on{border-color:CanvasText} label{font-size:12px;color:var(--mut);display:flex;gap:4px;align-items:center} .line{font-size:13px;font-style:italic;margin:10px 0 0;color:var(--mut)} .q{font-size:12px;color:var(--mut);margin-top:6px} +.emo{display:flex;gap:4px;flex-wrap:wrap;margin-top:10px}.emo button{font-size:16px;padding:2px 8px} +.dj{display:flex;gap:6px;margin-top:10px}.dj input{flex:1;min-width:0;font:inherit;font-size:13px;padding:6px 10px;border-radius:7px;border:1px solid var(--line);background:transparent;color:inherit} details{margin-top:8px;font-size:12px;color:var(--mut)}pre{white-space:pre-wrap;font:11px ui-monospace,monospace;max-height:160px;overflow:auto} @media (max-width:480px){canvas{width:96px;height:96px}} @@ -289,6 +293,8 @@ details{margin-top:8px;font-size:12px;color:var(--mut)}pre{white-space:pre-wrap;
+
+
log