@officexapp/vidfarm-devcli 0.21.12 → 0.21.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +19 -2
- package/SKILL.director.md +19 -2
- package/demo/dist/app.js +247 -226
- package/dist/src/cli.js +56 -7
- package/dist/src/devcli/composition-edit.js +99 -23
- package/dist/src/devcli/doctor.js +65 -9
- package/dist/src/devcli/local-frontend-server.js +341 -49
- package/dist/src/devcli/port-utils.js +43 -0
- package/dist/src/devcli/process-scan.js +173 -0
- package/package.json +3 -1
- package/public/serve-shells/editor.html +62 -13
- package/public/serve-shells/library-files.html +62 -13
- package/public/serve-shells/library-raws.html +62 -13
- package/public/serve-shells/tools-clipper.html +62 -13
- package/public/serve-shells/tools-image.html +62 -13
- package/public/serve-shells/tools-video.html +62 -13
|
@@ -2340,6 +2340,23 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2340
2340
|
setLeftMode(''); // show the conversation, not the Files/History drawer
|
|
2341
2341
|
openThread(id);
|
|
2342
2342
|
}
|
|
2343
|
+
var hadHandoff = !!handoffThread;
|
|
2344
|
+
// Sticky active thread per template so a page refresh reopens the SAME
|
|
2345
|
+
// conversation instead of a blank chat (server routes deliberately never
|
|
2346
|
+
// attach ?thread= to editor URLs — the browser owns "which chat is active").
|
|
2347
|
+
function activeThreadKey() { return 'rk-chat-active:' + TEMPLATE_ID; }
|
|
2348
|
+
function saveActiveThread(id) {
|
|
2349
|
+
try { if (id) localStorage.setItem(activeThreadKey(), id); else localStorage.removeItem(activeThreadKey()); } catch (e) {}
|
|
2350
|
+
}
|
|
2351
|
+
var restoredActive = false;
|
|
2352
|
+
function restoreActiveThread() {
|
|
2353
|
+
if (restoredActive || hadHandoff) return;
|
|
2354
|
+
restoredActive = true;
|
|
2355
|
+
if (convo.length) return; // user already chatting — don't clobber
|
|
2356
|
+
var saved = null;
|
|
2357
|
+
try { saved = localStorage.getItem(activeThreadKey()); } catch (e) {}
|
|
2358
|
+
if (saved) openThread(saved);
|
|
2359
|
+
}
|
|
2343
2360
|
var busy = false;
|
|
2344
2361
|
var pendingAbort = null; // AbortController for the in-flight reply (Stop button)
|
|
2345
2362
|
|
|
@@ -2390,14 +2407,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2390
2407
|
// outgoing user turn — verbatim to what the SPA's own chat sends — so the agent
|
|
2391
2408
|
// knows which fork to read (video_context) and mutate (editor_action). Only the
|
|
2392
2409
|
// /editor dock has this bridge; elsewhere it returns ''.
|
|
2410
|
+
// Async: the Option-B bridge's getSnapshot() returns a PROMISE (it re-reads the
|
|
2411
|
+
// composition through the files API). The old sync call JSON.stringify'd the
|
|
2412
|
+
// Promise itself, sending the model a literal "{}" editor_context — no fork id,
|
|
2413
|
+
// no layers, no viral DNA. Always resolve before serializing.
|
|
2393
2414
|
function editorContextBlock() {
|
|
2394
|
-
if (!isEditorDock) return '';
|
|
2415
|
+
if (!isEditorDock) return Promise.resolve('');
|
|
2395
2416
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
2396
|
-
if (!bridge || typeof bridge.getSnapshot !== 'function') return '';
|
|
2417
|
+
if (!bridge || typeof bridge.getSnapshot !== 'function') return Promise.resolve('');
|
|
2397
2418
|
var snap; try { snap = bridge.getSnapshot(); } catch (e) { snap = null; }
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2419
|
+
return Promise.resolve(snap).then(function (s) {
|
|
2420
|
+
if (!s) return '';
|
|
2421
|
+
try { return '\n\n<editor_context>\n' + JSON.stringify(s, null, 2) + '\n</editor_context>'; }
|
|
2422
|
+
catch (e) { return ''; }
|
|
2423
|
+
}, function () { return ''; });
|
|
2401
2424
|
}
|
|
2402
2425
|
function loadBoot() {
|
|
2403
2426
|
if (BOOT_STATE === 'ready' || BOOT_STATE === 'loading') return;
|
|
@@ -2420,6 +2443,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2420
2443
|
loadThreads();
|
|
2421
2444
|
if (leftMode() === 'cloud') loadTasks();
|
|
2422
2445
|
consumeHandoff();
|
|
2446
|
+
restoreActiveThread();
|
|
2423
2447
|
})
|
|
2424
2448
|
.catch(function () { BOOT_STATE = 'error'; });
|
|
2425
2449
|
}
|
|
@@ -2973,8 +2997,10 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2973
2997
|
setBusy(true);
|
|
2974
2998
|
if (!threadId) threadId = genId('thread');
|
|
2975
2999
|
// Attach a fresh <editor_context> to the current (last) user turn only, so the
|
|
2976
|
-
// model sees the composition state without bloating persisted history.
|
|
2977
|
-
|
|
3000
|
+
// model sees the composition state without bloating persisted history. The
|
|
3001
|
+
// block resolves asynchronously (files-API read) — wait for it before building
|
|
3002
|
+
// the outgoing messages so the model actually receives the composition state.
|
|
3003
|
+
editorContextBlock().then(function (ctxBlock) {
|
|
2978
3004
|
// Attachments (pasted files OR files picked from the directory explorer) must
|
|
2979
3005
|
// ride in the model messages as file content parts + a URL text line — the
|
|
2980
3006
|
// backend only feeds the model messages[].content, NOT user_message.attachments
|
|
@@ -2992,6 +3018,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2992
3018
|
}
|
|
2993
3019
|
return { role: m.role, content: content };
|
|
2994
3020
|
});
|
|
3021
|
+
// The send is what turns a freshly minted thread id into a real saved
|
|
3022
|
+
// thread — make it the sticky-restore target from this moment on.
|
|
3023
|
+
saveActiveThread(threadId);
|
|
2995
3024
|
var body = {
|
|
2996
3025
|
messages: outMessages,
|
|
2997
3026
|
thread_id: threadId,
|
|
@@ -3055,6 +3084,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3055
3084
|
if (!API_KEY) msg = msg + '\n\nAdd an AI provider key in Settings to chat on your own keys.';
|
|
3056
3085
|
view.fail(msg); setBusy(false); if (input) input.focus();
|
|
3057
3086
|
});
|
|
3087
|
+
}); // end editorContextBlock().then
|
|
3058
3088
|
}
|
|
3059
3089
|
|
|
3060
3090
|
function resetConversation() {
|
|
@@ -3308,10 +3338,25 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3308
3338
|
// fall through to chat-attach for folders / non-placeable files.
|
|
3309
3339
|
if (isEditorDock && it && it.viewUrl) {
|
|
3310
3340
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3341
|
+
// Placeability must be decided SYNCHRONOUSLY (the Option-B bridge returns a
|
|
3342
|
+
// Promise, so we can't branch on its result to decide chat-attach fallback —
|
|
3343
|
+
// checking ".ok" on the Promise made EVERY click fall through, placing the
|
|
3344
|
+
// media AND attaching it to chat, with no toast). Only image/video/audio go
|
|
3345
|
+
// on the timeline; folders/docs still fall through to chat-attach.
|
|
3346
|
+
var ct = String(it.contentType || '');
|
|
3347
|
+
if (bridge && typeof bridge.placeMediaAtPlayhead === 'function'
|
|
3348
|
+
&& (ct.indexOf('image/') === 0 || ct.indexOf('video/') === 0 || ct.indexOf('audio/') === 0)) {
|
|
3349
|
+
var placedName = it.name || 'media';
|
|
3350
|
+
var settlePlace = function (r) {
|
|
3351
|
+
if (r && r.ok) { editorPlaceToast('Added \u201c' + placedName + '\u201d to the timeline'); }
|
|
3352
|
+
else { editorPlaceToast('Couldn\u2019t add \u201c' + placedName + '\u201d' + ((r && r.error) ? ': ' + r.error : ''), true); }
|
|
3353
|
+
};
|
|
3354
|
+
try {
|
|
3355
|
+
var res = bridge.placeMediaAtPlayhead({ url: it.viewUrl, name: it.name, contentType: it.contentType });
|
|
3356
|
+
if (res && typeof res.then === 'function') { res.then(settlePlace, function (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }); }
|
|
3357
|
+
else { settlePlace(res); }
|
|
3358
|
+
} catch (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }
|
|
3359
|
+
return;
|
|
3315
3360
|
}
|
|
3316
3361
|
}
|
|
3317
3362
|
// Files-only drawer (opened from the /chat page) has no chat composer of its
|
|
@@ -3428,7 +3473,11 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3428
3473
|
return wrap;
|
|
3429
3474
|
}
|
|
3430
3475
|
function setActiveThread(id) {
|
|
3431
|
-
|
|
3476
|
+
// null = "fresh unsaved chat": KEEP the freshly minted threadId (sends must
|
|
3477
|
+
// always carry a real thread_id or the server silently skips persistence)
|
|
3478
|
+
// and clear the sticky restore key; a real id becomes both current + sticky.
|
|
3479
|
+
if (id) { threadId = id; saveActiveThread(id); }
|
|
3480
|
+
else { saveActiveThread(null); }
|
|
3432
3481
|
if (!histBody) return;
|
|
3433
3482
|
var rows = histBody.querySelectorAll('.rk-aichat-frow');
|
|
3434
3483
|
for (var i = 0; i < rows.length; i++) rows[i].classList.toggle('is-active', rows[i].getAttribute('data-id') === id);
|
|
@@ -3521,7 +3570,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3521
3570
|
.then(function (r) {
|
|
3522
3571
|
if (!r.ok && r.status !== 404) throw new Error('http ' + r.status);
|
|
3523
3572
|
threads = threads.filter(function (t) { return t.id !== id; });
|
|
3524
|
-
if (id === threadId) resetConversation();
|
|
3573
|
+
if (id === threadId) { resetConversation(); saveActiveThread(null); }
|
|
3525
3574
|
renderHistory();
|
|
3526
3575
|
})
|
|
3527
3576
|
.catch(function () {});
|
|
@@ -3933,6 +3933,23 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3933
3933
|
setLeftMode(''); // show the conversation, not the Files/History drawer
|
|
3934
3934
|
openThread(id);
|
|
3935
3935
|
}
|
|
3936
|
+
var hadHandoff = !!handoffThread;
|
|
3937
|
+
// Sticky active thread per template so a page refresh reopens the SAME
|
|
3938
|
+
// conversation instead of a blank chat (server routes deliberately never
|
|
3939
|
+
// attach ?thread= to editor URLs — the browser owns "which chat is active").
|
|
3940
|
+
function activeThreadKey() { return 'rk-chat-active:' + TEMPLATE_ID; }
|
|
3941
|
+
function saveActiveThread(id) {
|
|
3942
|
+
try { if (id) localStorage.setItem(activeThreadKey(), id); else localStorage.removeItem(activeThreadKey()); } catch (e) {}
|
|
3943
|
+
}
|
|
3944
|
+
var restoredActive = false;
|
|
3945
|
+
function restoreActiveThread() {
|
|
3946
|
+
if (restoredActive || hadHandoff) return;
|
|
3947
|
+
restoredActive = true;
|
|
3948
|
+
if (convo.length) return; // user already chatting — don't clobber
|
|
3949
|
+
var saved = null;
|
|
3950
|
+
try { saved = localStorage.getItem(activeThreadKey()); } catch (e) {}
|
|
3951
|
+
if (saved) openThread(saved);
|
|
3952
|
+
}
|
|
3936
3953
|
var busy = false;
|
|
3937
3954
|
var pendingAbort = null; // AbortController for the in-flight reply (Stop button)
|
|
3938
3955
|
|
|
@@ -3983,14 +4000,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3983
4000
|
// outgoing user turn — verbatim to what the SPA's own chat sends — so the agent
|
|
3984
4001
|
// knows which fork to read (video_context) and mutate (editor_action). Only the
|
|
3985
4002
|
// /editor dock has this bridge; elsewhere it returns ''.
|
|
4003
|
+
// Async: the Option-B bridge's getSnapshot() returns a PROMISE (it re-reads the
|
|
4004
|
+
// composition through the files API). The old sync call JSON.stringify'd the
|
|
4005
|
+
// Promise itself, sending the model a literal "{}" editor_context — no fork id,
|
|
4006
|
+
// no layers, no viral DNA. Always resolve before serializing.
|
|
3986
4007
|
function editorContextBlock() {
|
|
3987
|
-
if (!isEditorDock) return '';
|
|
4008
|
+
if (!isEditorDock) return Promise.resolve('');
|
|
3988
4009
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
3989
|
-
if (!bridge || typeof bridge.getSnapshot !== 'function') return '';
|
|
4010
|
+
if (!bridge || typeof bridge.getSnapshot !== 'function') return Promise.resolve('');
|
|
3990
4011
|
var snap; try { snap = bridge.getSnapshot(); } catch (e) { snap = null; }
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4012
|
+
return Promise.resolve(snap).then(function (s) {
|
|
4013
|
+
if (!s) return '';
|
|
4014
|
+
try { return '\n\n<editor_context>\n' + JSON.stringify(s, null, 2) + '\n</editor_context>'; }
|
|
4015
|
+
catch (e) { return ''; }
|
|
4016
|
+
}, function () { return ''; });
|
|
3994
4017
|
}
|
|
3995
4018
|
function loadBoot() {
|
|
3996
4019
|
if (BOOT_STATE === 'ready' || BOOT_STATE === 'loading') return;
|
|
@@ -4013,6 +4036,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
4013
4036
|
loadThreads();
|
|
4014
4037
|
if (leftMode() === 'cloud') loadTasks();
|
|
4015
4038
|
consumeHandoff();
|
|
4039
|
+
restoreActiveThread();
|
|
4016
4040
|
})
|
|
4017
4041
|
.catch(function () { BOOT_STATE = 'error'; });
|
|
4018
4042
|
}
|
|
@@ -4566,8 +4590,10 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
4566
4590
|
setBusy(true);
|
|
4567
4591
|
if (!threadId) threadId = genId('thread');
|
|
4568
4592
|
// Attach a fresh <editor_context> to the current (last) user turn only, so the
|
|
4569
|
-
// model sees the composition state without bloating persisted history.
|
|
4570
|
-
|
|
4593
|
+
// model sees the composition state without bloating persisted history. The
|
|
4594
|
+
// block resolves asynchronously (files-API read) — wait for it before building
|
|
4595
|
+
// the outgoing messages so the model actually receives the composition state.
|
|
4596
|
+
editorContextBlock().then(function (ctxBlock) {
|
|
4571
4597
|
// Attachments (pasted files OR files picked from the directory explorer) must
|
|
4572
4598
|
// ride in the model messages as file content parts + a URL text line — the
|
|
4573
4599
|
// backend only feeds the model messages[].content, NOT user_message.attachments
|
|
@@ -4585,6 +4611,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
4585
4611
|
}
|
|
4586
4612
|
return { role: m.role, content: content };
|
|
4587
4613
|
});
|
|
4614
|
+
// The send is what turns a freshly minted thread id into a real saved
|
|
4615
|
+
// thread — make it the sticky-restore target from this moment on.
|
|
4616
|
+
saveActiveThread(threadId);
|
|
4588
4617
|
var body = {
|
|
4589
4618
|
messages: outMessages,
|
|
4590
4619
|
thread_id: threadId,
|
|
@@ -4648,6 +4677,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
4648
4677
|
if (!API_KEY) msg = msg + '\n\nAdd an AI provider key in Settings to chat on your own keys.';
|
|
4649
4678
|
view.fail(msg); setBusy(false); if (input) input.focus();
|
|
4650
4679
|
});
|
|
4680
|
+
}); // end editorContextBlock().then
|
|
4651
4681
|
}
|
|
4652
4682
|
|
|
4653
4683
|
function resetConversation() {
|
|
@@ -4901,10 +4931,25 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
4901
4931
|
// fall through to chat-attach for folders / non-placeable files.
|
|
4902
4932
|
if (isEditorDock && it && it.viewUrl) {
|
|
4903
4933
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4934
|
+
// Placeability must be decided SYNCHRONOUSLY (the Option-B bridge returns a
|
|
4935
|
+
// Promise, so we can't branch on its result to decide chat-attach fallback —
|
|
4936
|
+
// checking ".ok" on the Promise made EVERY click fall through, placing the
|
|
4937
|
+
// media AND attaching it to chat, with no toast). Only image/video/audio go
|
|
4938
|
+
// on the timeline; folders/docs still fall through to chat-attach.
|
|
4939
|
+
var ct = String(it.contentType || '');
|
|
4940
|
+
if (bridge && typeof bridge.placeMediaAtPlayhead === 'function'
|
|
4941
|
+
&& (ct.indexOf('image/') === 0 || ct.indexOf('video/') === 0 || ct.indexOf('audio/') === 0)) {
|
|
4942
|
+
var placedName = it.name || 'media';
|
|
4943
|
+
var settlePlace = function (r) {
|
|
4944
|
+
if (r && r.ok) { editorPlaceToast('Added \u201c' + placedName + '\u201d to the timeline'); }
|
|
4945
|
+
else { editorPlaceToast('Couldn\u2019t add \u201c' + placedName + '\u201d' + ((r && r.error) ? ': ' + r.error : ''), true); }
|
|
4946
|
+
};
|
|
4947
|
+
try {
|
|
4948
|
+
var res = bridge.placeMediaAtPlayhead({ url: it.viewUrl, name: it.name, contentType: it.contentType });
|
|
4949
|
+
if (res && typeof res.then === 'function') { res.then(settlePlace, function (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }); }
|
|
4950
|
+
else { settlePlace(res); }
|
|
4951
|
+
} catch (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }
|
|
4952
|
+
return;
|
|
4908
4953
|
}
|
|
4909
4954
|
}
|
|
4910
4955
|
// Files-only drawer (opened from the /chat page) has no chat composer of its
|
|
@@ -5021,7 +5066,11 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
5021
5066
|
return wrap;
|
|
5022
5067
|
}
|
|
5023
5068
|
function setActiveThread(id) {
|
|
5024
|
-
|
|
5069
|
+
// null = "fresh unsaved chat": KEEP the freshly minted threadId (sends must
|
|
5070
|
+
// always carry a real thread_id or the server silently skips persistence)
|
|
5071
|
+
// and clear the sticky restore key; a real id becomes both current + sticky.
|
|
5072
|
+
if (id) { threadId = id; saveActiveThread(id); }
|
|
5073
|
+
else { saveActiveThread(null); }
|
|
5025
5074
|
if (!histBody) return;
|
|
5026
5075
|
var rows = histBody.querySelectorAll('.rk-aichat-frow');
|
|
5027
5076
|
for (var i = 0; i < rows.length; i++) rows[i].classList.toggle('is-active', rows[i].getAttribute('data-id') === id);
|
|
@@ -5114,7 +5163,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
5114
5163
|
.then(function (r) {
|
|
5115
5164
|
if (!r.ok && r.status !== 404) throw new Error('http ' + r.status);
|
|
5116
5165
|
threads = threads.filter(function (t) { return t.id !== id; });
|
|
5117
|
-
if (id === threadId) resetConversation();
|
|
5166
|
+
if (id === threadId) { resetConversation(); saveActiveThread(null); }
|
|
5118
5167
|
renderHistory();
|
|
5119
5168
|
})
|
|
5120
5169
|
.catch(function () {});
|
|
@@ -2753,6 +2753,23 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2753
2753
|
setLeftMode(''); // show the conversation, not the Files/History drawer
|
|
2754
2754
|
openThread(id);
|
|
2755
2755
|
}
|
|
2756
|
+
var hadHandoff = !!handoffThread;
|
|
2757
|
+
// Sticky active thread per template so a page refresh reopens the SAME
|
|
2758
|
+
// conversation instead of a blank chat (server routes deliberately never
|
|
2759
|
+
// attach ?thread= to editor URLs — the browser owns "which chat is active").
|
|
2760
|
+
function activeThreadKey() { return 'rk-chat-active:' + TEMPLATE_ID; }
|
|
2761
|
+
function saveActiveThread(id) {
|
|
2762
|
+
try { if (id) localStorage.setItem(activeThreadKey(), id); else localStorage.removeItem(activeThreadKey()); } catch (e) {}
|
|
2763
|
+
}
|
|
2764
|
+
var restoredActive = false;
|
|
2765
|
+
function restoreActiveThread() {
|
|
2766
|
+
if (restoredActive || hadHandoff) return;
|
|
2767
|
+
restoredActive = true;
|
|
2768
|
+
if (convo.length) return; // user already chatting — don't clobber
|
|
2769
|
+
var saved = null;
|
|
2770
|
+
try { saved = localStorage.getItem(activeThreadKey()); } catch (e) {}
|
|
2771
|
+
if (saved) openThread(saved);
|
|
2772
|
+
}
|
|
2756
2773
|
var busy = false;
|
|
2757
2774
|
var pendingAbort = null; // AbortController for the in-flight reply (Stop button)
|
|
2758
2775
|
|
|
@@ -2803,14 +2820,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2803
2820
|
// outgoing user turn — verbatim to what the SPA's own chat sends — so the agent
|
|
2804
2821
|
// knows which fork to read (video_context) and mutate (editor_action). Only the
|
|
2805
2822
|
// /editor dock has this bridge; elsewhere it returns ''.
|
|
2823
|
+
// Async: the Option-B bridge's getSnapshot() returns a PROMISE (it re-reads the
|
|
2824
|
+
// composition through the files API). The old sync call JSON.stringify'd the
|
|
2825
|
+
// Promise itself, sending the model a literal "{}" editor_context — no fork id,
|
|
2826
|
+
// no layers, no viral DNA. Always resolve before serializing.
|
|
2806
2827
|
function editorContextBlock() {
|
|
2807
|
-
if (!isEditorDock) return '';
|
|
2828
|
+
if (!isEditorDock) return Promise.resolve('');
|
|
2808
2829
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
2809
|
-
if (!bridge || typeof bridge.getSnapshot !== 'function') return '';
|
|
2830
|
+
if (!bridge || typeof bridge.getSnapshot !== 'function') return Promise.resolve('');
|
|
2810
2831
|
var snap; try { snap = bridge.getSnapshot(); } catch (e) { snap = null; }
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2832
|
+
return Promise.resolve(snap).then(function (s) {
|
|
2833
|
+
if (!s) return '';
|
|
2834
|
+
try { return '\n\n<editor_context>\n' + JSON.stringify(s, null, 2) + '\n</editor_context>'; }
|
|
2835
|
+
catch (e) { return ''; }
|
|
2836
|
+
}, function () { return ''; });
|
|
2814
2837
|
}
|
|
2815
2838
|
function loadBoot() {
|
|
2816
2839
|
if (BOOT_STATE === 'ready' || BOOT_STATE === 'loading') return;
|
|
@@ -2833,6 +2856,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2833
2856
|
loadThreads();
|
|
2834
2857
|
if (leftMode() === 'cloud') loadTasks();
|
|
2835
2858
|
consumeHandoff();
|
|
2859
|
+
restoreActiveThread();
|
|
2836
2860
|
})
|
|
2837
2861
|
.catch(function () { BOOT_STATE = 'error'; });
|
|
2838
2862
|
}
|
|
@@ -3386,8 +3410,10 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3386
3410
|
setBusy(true);
|
|
3387
3411
|
if (!threadId) threadId = genId('thread');
|
|
3388
3412
|
// Attach a fresh <editor_context> to the current (last) user turn only, so the
|
|
3389
|
-
// model sees the composition state without bloating persisted history.
|
|
3390
|
-
|
|
3413
|
+
// model sees the composition state without bloating persisted history. The
|
|
3414
|
+
// block resolves asynchronously (files-API read) — wait for it before building
|
|
3415
|
+
// the outgoing messages so the model actually receives the composition state.
|
|
3416
|
+
editorContextBlock().then(function (ctxBlock) {
|
|
3391
3417
|
// Attachments (pasted files OR files picked from the directory explorer) must
|
|
3392
3418
|
// ride in the model messages as file content parts + a URL text line — the
|
|
3393
3419
|
// backend only feeds the model messages[].content, NOT user_message.attachments
|
|
@@ -3405,6 +3431,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3405
3431
|
}
|
|
3406
3432
|
return { role: m.role, content: content };
|
|
3407
3433
|
});
|
|
3434
|
+
// The send is what turns a freshly minted thread id into a real saved
|
|
3435
|
+
// thread — make it the sticky-restore target from this moment on.
|
|
3436
|
+
saveActiveThread(threadId);
|
|
3408
3437
|
var body = {
|
|
3409
3438
|
messages: outMessages,
|
|
3410
3439
|
thread_id: threadId,
|
|
@@ -3468,6 +3497,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3468
3497
|
if (!API_KEY) msg = msg + '\n\nAdd an AI provider key in Settings to chat on your own keys.';
|
|
3469
3498
|
view.fail(msg); setBusy(false); if (input) input.focus();
|
|
3470
3499
|
});
|
|
3500
|
+
}); // end editorContextBlock().then
|
|
3471
3501
|
}
|
|
3472
3502
|
|
|
3473
3503
|
function resetConversation() {
|
|
@@ -3721,10 +3751,25 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3721
3751
|
// fall through to chat-attach for folders / non-placeable files.
|
|
3722
3752
|
if (isEditorDock && it && it.viewUrl) {
|
|
3723
3753
|
var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3754
|
+
// Placeability must be decided SYNCHRONOUSLY (the Option-B bridge returns a
|
|
3755
|
+
// Promise, so we can't branch on its result to decide chat-attach fallback —
|
|
3756
|
+
// checking ".ok" on the Promise made EVERY click fall through, placing the
|
|
3757
|
+
// media AND attaching it to chat, with no toast). Only image/video/audio go
|
|
3758
|
+
// on the timeline; folders/docs still fall through to chat-attach.
|
|
3759
|
+
var ct = String(it.contentType || '');
|
|
3760
|
+
if (bridge && typeof bridge.placeMediaAtPlayhead === 'function'
|
|
3761
|
+
&& (ct.indexOf('image/') === 0 || ct.indexOf('video/') === 0 || ct.indexOf('audio/') === 0)) {
|
|
3762
|
+
var placedName = it.name || 'media';
|
|
3763
|
+
var settlePlace = function (r) {
|
|
3764
|
+
if (r && r.ok) { editorPlaceToast('Added \u201c' + placedName + '\u201d to the timeline'); }
|
|
3765
|
+
else { editorPlaceToast('Couldn\u2019t add \u201c' + placedName + '\u201d' + ((r && r.error) ? ': ' + r.error : ''), true); }
|
|
3766
|
+
};
|
|
3767
|
+
try {
|
|
3768
|
+
var res = bridge.placeMediaAtPlayhead({ url: it.viewUrl, name: it.name, contentType: it.contentType });
|
|
3769
|
+
if (res && typeof res.then === 'function') { res.then(settlePlace, function (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }); }
|
|
3770
|
+
else { settlePlace(res); }
|
|
3771
|
+
} catch (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }
|
|
3772
|
+
return;
|
|
3728
3773
|
}
|
|
3729
3774
|
}
|
|
3730
3775
|
// Files-only drawer (opened from the /chat page) has no chat composer of its
|
|
@@ -3841,7 +3886,11 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3841
3886
|
return wrap;
|
|
3842
3887
|
}
|
|
3843
3888
|
function setActiveThread(id) {
|
|
3844
|
-
|
|
3889
|
+
// null = "fresh unsaved chat": KEEP the freshly minted threadId (sends must
|
|
3890
|
+
// always carry a real thread_id or the server silently skips persistence)
|
|
3891
|
+
// and clear the sticky restore key; a real id becomes both current + sticky.
|
|
3892
|
+
if (id) { threadId = id; saveActiveThread(id); }
|
|
3893
|
+
else { saveActiveThread(null); }
|
|
3845
3894
|
if (!histBody) return;
|
|
3846
3895
|
var rows = histBody.querySelectorAll('.rk-aichat-frow');
|
|
3847
3896
|
for (var i = 0; i < rows.length; i++) rows[i].classList.toggle('is-active', rows[i].getAttribute('data-id') === id);
|
|
@@ -3934,7 +3983,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3934
3983
|
.then(function (r) {
|
|
3935
3984
|
if (!r.ok && r.status !== 404) throw new Error('http ' + r.status);
|
|
3936
3985
|
threads = threads.filter(function (t) { return t.id !== id; });
|
|
3937
|
-
if (id === threadId) resetConversation();
|
|
3986
|
+
if (id === threadId) { resetConversation(); saveActiveThread(null); }
|
|
3938
3987
|
renderHistory();
|
|
3939
3988
|
})
|
|
3940
3989
|
.catch(function () {});
|