Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/praisonai-desktop/frontend/tests/engine-down.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,37 @@ for (const [id, label] of [['settings', 'Settings'], ['logsBtn', 'Engine log'],
});
}

test('Export does not fake a backup when the engine is unreachable', async () => {
const writes = [];
const dom = new JSDOM(HTML, {
runScripts: 'dangerously', resources: 'usable', url: ORIGIN + '/',
beforeParse(w) {
w.__TAURI__ = { core: { invoke: async () => ({
state: 'failed', reason: 'Engine crashed',
detail: 'exited before it was ready', tail: 'traceback' }) },
event: { listen: async () => () => {} } };
w.fetch = async () => { throw new TypeError('fetch failed'); };
w.requestAnimationFrame = (cb) => setTimeout(cb, 0);
w.navigator.clipboard = { writeText: async (t) => { writes.push(t); } };
w.confirm = () => true; w.prompt = () => 'x'; w.alert = () => {}; w.scrollTo = () => {};
w.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} });
Object.defineProperty(w.HTMLElement.prototype, 'scrollIntoView', { value() {} });
},
});
const { window } = dom;
await new Promise((r) => setTimeout(r, 400));

await window.runAction({ action: 'export' });
await new Promise((r) => setTimeout(r, 20));

assert.deepEqual(writes, [],
'the clipboard was written despite the export failing -- a stale paste would masquerade as a backup');
assert.ok(window.document.getElementById('toast'),
'the failed export said nothing');
assert.match(window.document.getElementById('toast').textContent, /did not run|could not/i,
'the failed export produced no error message');
});

test('no unhandled rejection escapes any of the three', async () => {
const b = await bootFailed();
for (const id of ['settings', 'logsBtn', 'search']) {
Expand Down
17 changes: 15 additions & 2 deletions src/praisonai-desktop/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1900,10 +1900,23 @@ <h2>Fine-tune a model</h2>

async function runAction(def){
if(def.confirm && !await askConfirm(def.confirm.message,{ok:'Continue'})) return;
// A failed export that says nothing is worse than no export: the user pastes
// a stale clipboard into a file and believes they have a backup. Every branch
// is a bare fetch that rejects when the engine is unreachable, so report it.
try{ await runActionInner(def); }
catch(e){ toast('That did not run: '+(e && e.message ? e.message : e)); }
}
async function runActionInner(def){
if(def.action==='export'){
const {chats}=await (await fetch('http://127.0.0.1:'+PORT+'/chats')).json();
const cr=await fetch('http://127.0.0.1:'+PORT+'/chats');
if(!cr.ok) throw new Error('Could not read conversations ('+cr.status+')');
const {chats}=await cr.json();
const all=[];
for(const c of chats) all.push(await (await fetch('http://127.0.0.1:'+PORT+'/chats/'+c.id)).json());
for(const c of chats){
const dr=await fetch('http://127.0.0.1:'+PORT+'/chats/'+c.id);
if(!dr.ok) throw new Error('Could not read a conversation ('+dr.status+')');
all.push(await dr.json());
}
// A sandboxed webview blocks a download the page starts itself, so the
// export goes to the clipboard and says so rather than failing silently.
await navigator.clipboard.writeText(JSON.stringify(all,null,2));
Expand Down
Loading