Unify webui dialogs and loading flow

This commit is contained in:
lpf
2026-03-06 14:21:47 +08:00
parent 5e421bb730
commit 86691f75d0
6 changed files with 202 additions and 47 deletions

View File

@@ -148,14 +148,47 @@ const Config: React.FC = () => {
async function saveConfig() {
try {
const payload = showRaw ? JSON.parse(cfgRaw) : cfg;
const r = await fetch(`/webui/api/config${q}`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload),
});
alert(await r.text());
const submit = async (confirmRisky: boolean) => {
const body = confirmRisky ? { ...payload, confirm_risky: true } : payload;
return ui.withLoading(async () => {
const r = await fetch(`/webui/api/config${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const text = await r.text();
let data: any = null;
try {
data = text ? JSON.parse(text) : null;
} catch {
data = null;
}
return { ok: r.ok, text, data };
}, t('saving'));
};
let result = await submit(false);
if (!result.ok && result.data?.requires_confirm) {
const changedFields = Array.isArray(result.data?.changed_fields) ? result.data.changed_fields.join(', ') : '';
const ok = await ui.confirmDialog({
title: t('configRiskyChangeConfirmTitle'),
message: t('configRiskyChangeConfirmMessage', { fields: changedFields || '-' }),
danger: true,
confirmText: t('saveChanges'),
});
if (!ok) return;
result = await submit(true);
}
if (!result.ok) {
throw new Error(result.data?.error || result.text || 'save failed');
}
await ui.notify({ title: t('saved'), message: t('configSaved') });
setBaseline(JSON.parse(JSON.stringify(payload)));
setShowDiff(false);
} catch (e) {
alert(`${t('saveConfigFailed')}: ${e}`);
await ui.notify({ title: t('requestFailed'), message: `${t('saveConfigFailed')}: ${e}` });
}
}

View File

@@ -93,14 +93,19 @@ const Cron: React.FC = () => {
if (!ok) return;
}
try {
await fetch(`/webui/api/cron${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, id }),
});
await ui.withLoading(async () => {
const r = await fetch(`/webui/api/cron${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, id }),
});
if (!r.ok) {
throw new Error(await r.text());
}
}, t('loading'));
await refreshCron();
} catch (e) {
console.error(e);
await ui.notify({ title: t('actionFailed'), message: String(e) });
}
}
@@ -152,12 +157,13 @@ const Cron: React.FC = () => {
if (r.ok) {
setIsCronModalOpen(false);
await refreshCron();
await ui.notify({ title: t('saved'), message: t('cronSaved') });
} else {
const err = await r.text();
alert(`${t('actionFailed')}: ${err}`);
await ui.notify({ title: t('actionFailed'), message: err });
}
} catch (e) {
alert(`${t('actionFailed')}: ${e}`);
await ui.notify({ title: t('actionFailed'), message: String(e) });
}
}

View File

@@ -13,6 +13,10 @@ const Memory: React.FC = () => {
async function loadFiles() {
const r = await fetch(`/webui/api/memory${q}`);
if (!r.ok) {
await ui.notify({ title: t('requestFailed'), message: await r.text() });
return;
}
const j = await r.json();
setFiles(Array.isArray(j.files) ? j.files : []);
}
@@ -21,6 +25,10 @@ const Memory: React.FC = () => {
async function openFile(path: string) {
const r = await fetch(`/webui/api/memory${qp('path', path)}`);
if (!r.ok) {
await ui.notify({ title: t('requestFailed'), message: await r.text() });
return;
}
const j = await r.json();
setActive(path);
setContent(j.content || '');
@@ -28,12 +36,22 @@ const Memory: React.FC = () => {
async function saveFile() {
if (!active) return;
await fetch(`/webui/api/memory${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: active, content }),
});
await loadFiles();
try {
await ui.withLoading(async () => {
const r = await fetch(`/webui/api/memory${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: active, content }),
});
if (!r.ok) {
throw new Error(await r.text());
}
await loadFiles();
}, t('saving'));
await ui.notify({ title: t('saved'), message: t('memoryFileSaved') });
} catch (e) {
await ui.notify({ title: t('requestFailed'), message: String(e) });
}
}
async function removeFile(path: string) {
@@ -44,24 +62,48 @@ const Memory: React.FC = () => {
confirmText: t('delete'),
});
if (!ok) return;
await fetch(`/webui/api/memory${qp('path', path)}`, { method: 'DELETE' });
if (active === path) {
setActive('');
setContent('');
try {
await ui.withLoading(async () => {
const r = await fetch(`/webui/api/memory${qp('path', path)}`, { method: 'DELETE' });
if (!r.ok) {
throw new Error(await r.text());
}
if (active === path) {
setActive('');
setContent('');
}
await loadFiles();
}, t('deleting'));
} catch (e) {
await ui.notify({ title: t('requestFailed'), message: String(e) });
}
await loadFiles();
}
async function createFile() {
const name = prompt(t('memoryFileNamePrompt'), `note-${Date.now()}.md`);
if (!name) return;
await fetch(`/webui/api/memory${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: name, content: '' }),
const name = await ui.promptDialog({
title: t('memoryCreateTitle'),
message: t('memoryFileNamePrompt'),
confirmText: t('create'),
initialValue: `note-${Date.now()}.md`,
inputPlaceholder: 'note.md',
});
await loadFiles();
await openFile(name);
if (!name) return;
try {
await ui.withLoading(async () => {
const r = await fetch(`/webui/api/memory${q}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: name, content: '' }),
});
if (!r.ok) {
throw new Error(await r.text());
}
await loadFiles();
await openFile(name);
}, t('creating'));
} catch (e) {
await ui.notify({ title: t('requestFailed'), message: String(e) });
}
}
useEffect(() => {