music(inspire): 加「💡 今天练什么」灵感推荐 modal
deploy music / build-and-deploy (push) Failing after 1m50s

- 后端 POST /api/inspire 流式 SSE:随机 keyword 池(23 个)+ 用户曲库画像(recent/top/least)+ Tavily 热点搜索 → gemma stream(temperature=1.0)
- Tavily key 走 k8s Secret tavily-creds(复用 mochi config 同一 token)
- 每次按按钮:keyword 随机 + 用户可输 hint("想练快歌" / "陪儿子" / "新东西")
- 输出强制格式:4 首歌('补回来' 2 + '试试新' 2),每首歌名-歌手 + 一句理由
- 前端 topbar 加 💡 按钮,modal 流式渲染(极简 md:**bold** + 列表)
This commit is contained in:
Fam Zheng
2026-05-10 15:52:00 +01:00
parent f7fac352a5
commit ccb5ad05ce
4 changed files with 512 additions and 0 deletions
+41
View File
@@ -133,6 +133,47 @@ export async function streamChat(pieceId, message, onDelta, signal) {
return { ok: true }
}
// ---- inspire ----
export async function streamInspire(hint, onDelta, signal) {
const resp = await fetch('/api/inspire', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hint: hint || null }),
signal,
})
if (!resp.ok) {
const text = await resp.text().catch(() => '')
return { ok: false, error: text || `${resp.status}` }
}
const reader = resp.body.getReader()
const dec = new TextDecoder()
let buf = ''
let lastEvent = 'message'
let errorMsg = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buf += dec.decode(value, { stream: true })
let idx
while ((idx = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, idx)
buf = buf.slice(idx + 1)
if (line.startsWith('event:')) {
lastEvent = line.slice(6).trim()
} else if (line.startsWith('data:')) {
const data = line.slice(5).replace(/^ /, '')
if (lastEvent === 'error') errorMsg = data
else if (lastEvent !== 'done') onDelta(data)
} else if (line === '') {
lastEvent = 'message'
}
}
}
if (errorMsg) return { ok: false, error: errorMsg }
return { ok: true }
}
// ---- tags ----
export function listTags() {