notes: 加重命名 — title 旁边 ✏️ 按钮 prompt 改名 (PATCH /api/recordings/:id)
deploy notes / build-and-deploy (push) Successful in 1m53s
deploy notes / build-and-deploy (push) Successful in 1m53s
This commit is contained in:
@@ -71,7 +71,10 @@
|
|||||||
<p v-if="!selected" class="empty">← 从左边挑一条</p>
|
<p v-if="!selected" class="empty">← 从左边挑一条</p>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<header class="cont-head">
|
<header class="cont-head">
|
||||||
<h2>{{ selected.title }}</h2>
|
<h2>
|
||||||
|
{{ selected.title }}
|
||||||
|
<button class="rename-btn" title="重命名" @click="rename">✏️</button>
|
||||||
|
</h2>
|
||||||
<div class="head-meta">
|
<div class="head-meta">
|
||||||
<span>{{ statusLabel(selected.status) }}</span>
|
<span>{{ statusLabel(selected.status) }}</span>
|
||||||
<span>· {{ fmtSize(selected.size_bytes) }}</span>
|
<span>· {{ fmtSize(selected.size_bytes) }}</span>
|
||||||
@@ -132,6 +135,7 @@ import {
|
|||||||
uploadRecording,
|
uploadRecording,
|
||||||
deleteRecording,
|
deleteRecording,
|
||||||
retryRecording,
|
retryRecording,
|
||||||
|
renameRecording,
|
||||||
convertFeishu,
|
convertFeishu,
|
||||||
audioUrl as audioUrlFn,
|
audioUrl as audioUrlFn,
|
||||||
getPass,
|
getPass,
|
||||||
@@ -332,6 +336,20 @@ async function remove() {
|
|||||||
} catch (e) { alert(e.message) }
|
} catch (e) { alert(e.message) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function rename() {
|
||||||
|
const cur = selected.value?.title || ''
|
||||||
|
const t = prompt('改个名字', cur)
|
||||||
|
if (t == null) return
|
||||||
|
const trimmed = t.trim()
|
||||||
|
if (!trimmed || trimmed === cur) return
|
||||||
|
try {
|
||||||
|
await renameRecording(selectedId.value, trimmed)
|
||||||
|
if (selected.value) selected.value.title = trimmed
|
||||||
|
const inList = list.value.find(r => r.id === selectedId.value)
|
||||||
|
if (inList) inList.title = trimmed
|
||||||
|
} catch (e) { alert(e.message) }
|
||||||
|
}
|
||||||
|
|
||||||
async function retry() {
|
async function retry() {
|
||||||
try {
|
try {
|
||||||
await retryRecording(selectedId.value)
|
await retryRecording(selectedId.value)
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ async function jreq(path, opts = {}) {
|
|||||||
export function listRecordings() { return jreq('/api/recordings') }
|
export function listRecordings() { return jreq('/api/recordings') }
|
||||||
export function getRecording(id) { return jreq('/api/recordings/' + id) }
|
export function getRecording(id) { return jreq('/api/recordings/' + id) }
|
||||||
export function deleteRecording(id) { return jreq('/api/recordings/' + id, { method: 'DELETE' }) }
|
export function deleteRecording(id) { return jreq('/api/recordings/' + id, { method: 'DELETE' }) }
|
||||||
|
export function renameRecording(id, title) {
|
||||||
|
return jreq('/api/recordings/' + id, { method: 'PATCH', body: JSON.stringify({ title }) })
|
||||||
|
}
|
||||||
export function retryRecording(id) { return jreq('/api/recordings/' + id + '/retry', { method: 'POST' }) }
|
export function retryRecording(id) { return jreq('/api/recordings/' + id + '/retry', { method: 'POST' }) }
|
||||||
export function convertFeishu(id) {
|
export function convertFeishu(id) {
|
||||||
return jreq('/api/recordings/' + id + '/feishu', { method: 'POST' })
|
return jreq('/api/recordings/' + id + '/feishu', { method: 'POST' })
|
||||||
|
|||||||
+26
-1
@@ -103,7 +103,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.route("/recordings", get(list_recordings).post(upload_recording).layer(
|
.route("/recordings", get(list_recordings).post(upload_recording).layer(
|
||||||
DefaultBodyLimit::max(REQUEST_BYTES),
|
DefaultBodyLimit::max(REQUEST_BYTES),
|
||||||
))
|
))
|
||||||
.route("/recordings/:id", get(get_recording).delete(delete_recording))
|
.route("/recordings/:id", get(get_recording).patch(patch_recording).delete(delete_recording))
|
||||||
.route("/recordings/:id/audio", get(stream_audio))
|
.route("/recordings/:id/audio", get(stream_audio))
|
||||||
.route("/recordings/:id/retry", post(retry_recording))
|
.route("/recordings/:id/retry", post(retry_recording))
|
||||||
.route("/recordings/:id/feishu", post(convert_feishu))
|
.route("/recordings/:id/feishu", post(convert_feishu))
|
||||||
@@ -580,6 +580,31 @@ async fn call_llm_summary(s: &AppState, transcript: &str) -> Result<String, Stri
|
|||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PatchRecording {
|
||||||
|
title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn patch_recording(
|
||||||
|
State(s): State<AppState>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
JsonResp(body): JsonResp<PatchRecording>,
|
||||||
|
) -> Result<JsonResp<Value>, AppError> {
|
||||||
|
let conn = s.db.lock().unwrap();
|
||||||
|
let exists: bool = conn
|
||||||
|
.query_row("SELECT 1 FROM recordings WHERE id = ?1", params![id], |_| Ok(true))
|
||||||
|
.optional()?
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !exists { return Err(AppError::NotFound); }
|
||||||
|
if let Some(t) = body.title.as_ref() {
|
||||||
|
let t = t.trim();
|
||||||
|
if t.is_empty() { return Err(AppError::bad_request("title can't be blank")); }
|
||||||
|
let t: String = t.chars().take(120).collect();
|
||||||
|
conn.execute("UPDATE recordings SET title = ?1 WHERE id = ?2", params![&t, id])?;
|
||||||
|
}
|
||||||
|
Ok(JsonResp(json!({ "ok": true })))
|
||||||
|
}
|
||||||
|
|
||||||
async fn delete_recording(
|
async fn delete_recording(
|
||||||
State(s): State<AppState>,
|
State(s): State<AppState>,
|
||||||
Path(id): Path<i64>,
|
Path(id): Path<i64>,
|
||||||
|
|||||||
Reference in New Issue
Block a user