notes: 新建 notes.famzheng.me — 录音 → ASR → LLM 会议纪要
deploy articulate / build-and-deploy (push) Successful in 1m21s
deploy cube / build-and-deploy (push) Successful in 1m44s
deploy karaoke / build-and-deploy (push) Successful in 1m13s
deploy music / build-and-deploy (push) Successful in 2m23s
deploy notes / build-and-deploy (push) Successful in 2m16s
deploy simpleasm / build-and-deploy (push) Successful in 1m44s
deploy werewolf / build-and-deploy (push) Successful in 1m7s
deploy articulate / build-and-deploy (push) Successful in 1m21s
deploy cube / build-and-deploy (push) Successful in 1m44s
deploy karaoke / build-and-deploy (push) Successful in 1m13s
deploy music / build-and-deploy (push) Successful in 2m23s
deploy notes / build-and-deploy (push) Successful in 2m16s
deploy simpleasm / build-and-deploy (push) Successful in 1m44s
deploy werewolf / build-and-deploy (push) Successful in 1m7s
- 后端 axum + sqlite (recordings 表):上传 multipart 流式落 PVC;spawn worker pending → transcribing (调 mochi 那边 ASR endpoint, fireredasr2 token, Whisper-style multipart) → summarizing (调 gemma-4-31b-it OpenAI 兼容接口) → done
- 鉴权 middleware:Authorization: token <PASSPHRASE>;audio 流播放 ?token= query 兜底;passphrase 走 k8s Secret 不写死
- 前端 Vue3:首次访问弹 passphrase modal;sidebar 录音列表(带状态 chip)+ content 选中显示音频 + 转写 + markdown 纪要;5s polling 进度
- k8s manifest: ns cube-notes / PVC 30Gi / Ingress notes.famzheng.me / bodylimit 600M;Secret notes-creds = {passphrase, asr_token, llm_token}
- portal apps.ts 加 notes entry
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// 鉴权:每个请求加 Authorization: token <pass>,<audio> 用 ?token= 兜底。
|
||||
|
||||
const KEY = 'notes.pass'
|
||||
|
||||
export function getPass() {
|
||||
return localStorage.getItem(KEY) || ''
|
||||
}
|
||||
export function setPass(v) {
|
||||
localStorage.setItem(KEY, v || '')
|
||||
}
|
||||
export function clearPass() {
|
||||
localStorage.removeItem(KEY)
|
||||
}
|
||||
|
||||
async function jreq(path, opts = {}) {
|
||||
const pass = getPass()
|
||||
const h = { 'Authorization': 'token ' + pass, ...(opts.headers || {}) }
|
||||
if (opts.body && !(opts.body instanceof FormData) && !h['Content-Type']) {
|
||||
h['Content-Type'] = 'application/json'
|
||||
}
|
||||
const r = await fetch(path, { ...opts, headers: h })
|
||||
if (r.status === 401) {
|
||||
const err = new Error('unauthorized')
|
||||
err.unauthorized = true
|
||||
throw err
|
||||
}
|
||||
if (!r.ok) {
|
||||
const t = await r.text().catch(() => '')
|
||||
throw new Error(t || `${r.status}`)
|
||||
}
|
||||
return r.json()
|
||||
}
|
||||
|
||||
export function listRecordings() { return jreq('/api/recordings') }
|
||||
export function getRecording(id) { return jreq('/api/recordings/' + id) }
|
||||
export function deleteRecording(id) { return jreq('/api/recordings/' + id, { method: 'DELETE' }) }
|
||||
export function retryRecording(id) { return jreq('/api/recordings/' + id + '/retry', { method: 'POST' }) }
|
||||
|
||||
export function uploadRecording(title, file) {
|
||||
const fd = new FormData()
|
||||
if (title) fd.append('title', title)
|
||||
fd.append('audio', file, file.name)
|
||||
return jreq('/api/recordings', { method: 'POST', body: fd })
|
||||
}
|
||||
|
||||
export function audioUrl(id) {
|
||||
return `/api/recordings/${id}/audio?token=${encodeURIComponent(getPass())}`
|
||||
}
|
||||
Reference in New Issue
Block a user