- 后端 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:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user