datawhalechina/hello-agents · error · Error
保存失败:
Error message
保存失败:
What it means
'保存失败: ' + e (save failed) is alerted by the NovelGenerator frontend when PUT {API_URL}/outline/update responds non-OK; the thrown Error wraps the raw response text. The request persists the edited outline {novel_id, title, note_id, content} — so the most common failure is the backend rejecting an outline id that no longer exists (outline.id was set from a previous generate and the backend store changed).
Source
Thrown at Co-creation-projects/lgs-only-NovelGenerator/frontend/index.html:411
}
};
const updateOutline = async () => {
if (!outline.id) return;
loading.value = true;
currentAction.value = 'update_outline';
try {
const res = await fetch(`${API_URL}/outline/update`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
novel_id: project.novel_id,
title: project.title,
note_id: outline.id,
content: outline.content
})
});
if (!res.ok) throw new Error(await res.text());
alert('大纲保存成功!');
} catch (e) {
alert('保存失败: ' + e);
} finally {
loading.value = false;
currentAction.value = null;
}
};
const deleteOutline = async () => {
if(!confirm('确定要删除当前大纲吗?此操作不可恢复。')) return;
loading.value = true;
currentAction.value = 'delete_outline';
try {
const params = new URLSearchParams({
novel_id: project.novel_id,
title: project.title,
note_id: outline.idView on GitHub (pinned to 606a07d341)
Solutions
- Disable the save button unless outline.id is set (generate must succeed first).
- Read the alert text — the server body distinguishes 404 'not found' from validation errors.
- If the backend lost state (restart), regenerate the outline to obtain a fresh note_id.
- Guard title/content non-empty before the PUT.
Example fix
// before
const updateOutline = async () => { /* no guard */ }
// after
const updateOutline = async () => {
if (!outline.id) { alert('请先生成大纲'); return; }
if (!outline.content?.trim()) { alert('大纲内容为空'); return; }
// ... existing fetch
} Defensive patterns
Strategy: validation
Validate before calling
if (!outline.id) { alert('请先生成大纲再保存'); return; }
if (!outline.content?.trim()) { alert('大纲内容不能为空'); return; }
if (!project.novel_id || !project.title) { alert('项目信息不完整,请重新加载'); return; } Type guard
function isOutlineUpdatePayload(p: unknown): p is { novel_id: string; title: string; note_id: string; content: string } {
return typeof p === 'object' && p !== null
&& typeof (p as { note_id?: unknown }).note_id === 'string'
&& typeof (p as { content?: unknown }).content === 'string';
} Try / catch
try {
const res = await fetch(`${API_URL}/outline/update`, opts);
if (!res.ok) throw new Error(`${res.status}: ${(await res.text()).slice(0, 200)}`);
alert('大纲保存成功!');
} catch (e) {
alert('保存失败: ' + (e instanceof Error ? e.message : String(e)));
} finally {
loading.value = false;
} Prevention
- Disable save until outline.id exists — null note_id guarantees a server rejection.
- If the backend stores notes in memory, expect ids to die on restart; regenerate after restarts.
- Always send non-empty title/content; the backend rejects blanks.
When it happens
Trigger: PUT /outline/update returns 404 (note_id/novel_id unknown — backend restarted, outline deleted elsewhere, outline.id never set because generate failed earlier), 422 (missing required fields such as empty title), 500 (storage/write failure). Clicking save before any outline was generated leaves outline.id null → guaranteed rejection.
Common situations: Saving with no outline loaded (id null); backend memory store reset between generate and save; concurrent edits deleting the note; required field omitted in the edit.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/eb9cd65115abc158.
Report an issue: GitHub.