/* v25: 手机全屏操作台·补全(studio 创作台 5 个 + 资源库 + 通知中心 + 工具类)
在 v24 基础上扩展;分发器优先 v24 已注册,再查本文件新注册表。 */
(function () {
'use strict';
if (window.__mobV25) return;
window.__mobV25 = true;
var API = 'https://api-gateway.duoweiying.cn';
function $(id) { return document.getElementById(id); }
function uid() {
var u = null; try { u = JSON.parse(localStorage.getItem('user_info') || '{}'); } catch (e) {}
return (u && (u.id || u.username)) || localStorage.getItem('user_id') || 'anonymous';
}
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); }
function tip(t) {
try { var o = $('v25tip'); if (o) o.remove(); var d = document.createElement('div'); d.id = 'v25tip';
d.style.cssText = 'position:fixed;left:50%;bottom:120px;transform:translateX(-50%);z-index:48000;background:#0f172a;color:#fff;font-size:13px;padding:9px 16px;border-radius:18px;max-width:84%;text-align:center;';
d.textContent = t; document.body.appendChild(d); setTimeout(function () { d.remove(); }, 2600); } catch (e) {}
}
var H = {};
function open(title, html) {
close();
var p = document.createElement('div'); p.id = 'v25Page';
p.style.cssText = 'position:fixed;inset:0;z-index:45000;background:#f1f5f9;display:flex;flex-direction:column;';
p.innerHTML = '
' +
'
‹ ' +
'
' + esc(title) + '
' +
'
';
document.body.appendChild(p);
$('v25Back').onclick = close;
$('v25Body').innerHTML = html;
return $('v25Body');
}
function close() { var p = $('v25Page'); if (p) p.remove(); }
function fld(l, i) { return ''; }
function area(id, ph) { return ''; }
function input(id, ph) { return ' '; }
function sel(id, opts) { return '' + opts.map(function (o) { return '' + esc(o) + ' '; }).join('') + ' '; }
function val(id) { var e = $(id); return e ? e.value.trim() : ''; }
function chk(id) { var e = $(id); return !!(e && e.checked); }
function v25TplLoad(selId, tplType, emptyLabel) {
fetch(API + '/api/templates?type=' + encodeURIComponent(tplType) + '&user_id=' + encodeURIComponent(uid()))
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var el = $(selId); if (!el) return;
var rows = (d && d.data) || [];
el.innerHTML = '' + esc(emptyLabel || '不使用') + ' ' + rows.map(function (t) {
return '' + esc(String(t.name || t.id || '')) + ' ';
}).join('');
}).catch(function () {});
}
function btn(id, label) { return '' + esc(label) + ' '; }
function result() { return '
'; }
/* ===== WR-529C: 出片结果渲染 + 素材上下文 + 任务轮询 ===== */
function v25Abs(u) {
/* WR-562k11: 跟后端 _fm_public_url 同口径。旧写法把 /static/ 拼到 console 域(403)、
把桶内相对路径原样返回(被当页面相对路径 404) -> “假链接打不开”。 */
return absUrl(u);
}
function v25IsVideo(s) {
s = String(s || '').toLowerCase().split('?')[0];
return /.mp4$|.mov$|.webm$|.m4v$|.avi$|.mkv$/.test(s);
}
function v25Mats() {
var out = [];
var ks = Object.keys(UP || {});
for (var i = 0; i < ks.length; i++) {
var u = UP[ks[i]];
if (!u) continue;
var s = String(u);
out.push({ url: s, name: ks[i], kind: v25IsVideo(s) ? 'video' : 'image', role: 'material' });
}
for (var j = 0; j < __v25MixSel.length; j++) {
var m = __v25MixSel[j];
if (m && m.url) out.push(m);
}
return out;
}
window.v25Mats = v25Mats;
window.v25SetMixSel = function (rows) { __v25MixSel = rows || []; };
function v25FindVideos(d) {
var out = [], seen = {};
function push(v) {
if (typeof v === 'string' && v) { var u = v25Abs(v); if (!seen[u]) { seen[u] = 1; out.push(u); } }
else if (v && typeof v === 'object' && v.url) push(v.url);
}
try {
var r = (d && d.result) || {}, rd = r.data || {}, gd = (d && d.data) || {};
push(d && d.video_url); push(r.video_url); push(rd.video_url); push(gd.video_url);
var arrs = [gd.videos, gd.video_urls, rd.videos, rd.video_urls, r.videos];
for (var i = 0; i < arrs.length; i++) { if (Object.prototype.toString.call(arrs[i]) === '[object Array]') arrs[i].forEach(push); }
} catch (e) {}
return out;
}
function v25ShowResult(box, txt, d) {
var vids = v25FindVideos(d);
var html = esc(String(txt || '').substring(0, 4000));
if (vids.length) {
html += vids.map(function (u) {
return '' +
'
' +
'💾 保存/下载 ' +
'📤 分享 ' +
'
';
}).join('');
html += '成片已自动归档到「我的 → 文件管理 → 视频」。
';
}
var needConfirm = !!(d && (d.needs_confirm || (d.result && d.result.data && d.result.data.needs_confirm)));
if (needConfirm) {
html += '' +
'✅ 确认生成 ' +
'🎬 用我的素材混剪
';
}
box.style.display = 'block';
box.innerHTML = html;
try { box.setAttribute('data-txt', String(txt || '').substring(0, 1500)); } catch (e) {}
try { box.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } catch (e) {}
box.onclick = function (e) {
var tg = e.target || {};
var sv = tg.getAttribute && tg.getAttribute('data-save');
var sh = tg.getAttribute && tg.getAttribute('data-share');
var cf = tg.getAttribute && tg.getAttribute('data-cf');
if (cf) { run(cf, v25Mats(), 'video_generation'); return; }
if (sv) v25Save(sv, '成片.mp4');
else if (sh) v25Share(sh, '成片');
};
var tid = (d && (d.task_id || (d.result && d.result.task_id) || (d.data && d.data.task_id))) || '';
if (!vids.length && tid) v25PollTask(tid, box, 0);
}
function v25PollTask(tid, box, tries) {
tries = tries || 0;
if (!tid || tries > 90) return;
fetch(API + '/ai-army/tasks/' + encodeURIComponent(tid) + '?_t=' + Date.now())
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
if (v25FindVideos(d).length) { v25ShowResult(box, '✅ 出片完成', d); return; }
var st = String((d && (d.status || (d.data && d.data.status))) || '');
if (st === 'failed') { v25ShowResult(box, '❌ 出片失败:' + String((d && (d.error || d.message)) || '').substring(0, 300), d); return; }
box.style.display = 'block';
box.innerHTML = '⏳ 出片中(第 ' + (tries + 1) + ' 次轮询…)
' + esc(String(box.getAttribute('data-txt') || '').substring(0, 1200));
setTimeout(function () { v25PollTask(tid, box, tries + 1); }, 8000);
})
.catch(function () { setTimeout(function () { v25PollTask(tid, box, tries + 1); }, 8000); });
}
function run(txt, mats, taskType, opts) {
var _k = (window.__mksCurItem && window.__mksCurItem.id) || '';
if (window.dwyGateCheck) {
window.dwyGateCheck(_k, function (ok) { if (ok) { runRaw(txt, mats, taskType, opts); } });
return;
}
runRaw(txt, mats, taskType, opts);
}
function runRaw(txt, mats, taskType, opts) {
var box = $('v25Res');
if (box) { box.style.display = 'block'; box.innerHTML = '⏳ AI 员工接收任务中…'; }
var _body = { user_input: txt, user_id: uid(), task_type: (taskType || 'chat'),
ai_context: { materials: (mats || v25Mats()), video: (opts || {}) } };
if (opts && Object.keys(opts).length) {
_body.video = opts;
_body.context = { scene: 'mobile', user_id: uid(), video: opts };
}
fetch(API + '/ai-army/dispatch?agent_id=frontend_content_coordinator_agent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(_body) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var t = '';
if (d && d.result) {
var rr = d.result;
t = rr.reply || rr.response || rr.summary || rr.message || '';
if (!t && rr.execution_results && rr.execution_results.length) t = rr.execution_results.map(function (x) { return (x.summary || x.output || JSON.stringify(x)).substring(0, 400); }).join('\n\n');
if (!t) t = JSON.stringify(rr).substring(0, 800);
} else t = (d && d.message) || (d && d.response) || '任务已完成(详见通知中心)';
if (box) { v25ShowResult(box, t, d); }
}).catch(function (e) { if (box) { box.style.display = 'block'; box.innerHTML = '执行失败:' + esc(e.message) + ' '; } });
}
var UP = {};
/* WR-562k53: file-management selections must reach the coordinator context. */
var __v25MixSel = [];
/* WR-562k15 从文件管理选择: 与「我的 → 文件管理」同源(同一份 file_records/素材库), 不必重新上传。 */
function v25PickBtn(kind, key) {
return '📂 从文件管理选择 ';
}
window.v25PickFiles = function (kind, key) {
try {
var cat = (kind === 'video') ? 'video' : (kind === 'image') ? 'image' : (kind === 'audio') ? 'audio' : (kind === 'document') ? 'document' : '';
tip('\u23f3 正在读取「文件管理」…');
var jobs = [];
jobs.push(fetch(API + '/api/files/list?user_id=' + encodeURIComponent(uid()) + (cat ? '&category=' + encodeURIComponent(cat) : '') + '&page_size=100&_t=' + Date.now())
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { return ((d && d.files) || []).map(filesToRow); }).catch(function () { return []; }));
jobs.push(fetch(API + '/ai-army/materials?user_id=' + encodeURIComponent(uid()) + (cat ? '&material_type=' + encodeURIComponent(cat) : '') + '&limit=100&_t=' + Date.now())
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { return ((d && (d.data || d.materials || d.items)) || []).map(matToRow); }).catch(function () { return []; }));
if (kind === 'video') {
jobs.push(fetch(API + '/ai-army/user-resources?user_id=' + encodeURIComponent(uid()) + '&types=video&_t=' + Date.now())
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { return ((d && d.video) || []).map(function (v) {
return { id: 'uv_' + String(v.id || ''), name: v.title || ('视频_' + String(v.id || '')), cat: 'video', desc: '', url: absUrl(v.url), time: v.time || '' };
}); }).catch(function () { return []; }));
}
Promise.all(jobs).then(function (lists) {
var out = [], seen = {}, seenU = {};
lists.forEach(function (l) {
(l || []).forEach(function (r) {
if (!r || !r.url) return;
var rk = String(r.id || ''), ru = String(r.url || '');
if (rk && seen[rk]) return;
if (ru && seenU[ru]) return;
if (rk) seen[rk] = 1;
if (ru) seenU[ru] = 1;
out.push(r);
});
});
if (!out.length) { tip('「文件管理」里还没有可用的素材,请先到文件管理上传素材'); return; }
out.sort(function (a, b) {
var ia = parseInt(String(a.id || '').replace(/\D/g, ''), 10) || 0;
var ib = parseInt(String(b.id || '').replace(/\D/g, ''), 10) || 0;
if (ia !== ib) return ib - ia;
return String(b.time || '').localeCompare(String(a.time || ''));
});
var opts = out.slice(0, 100).map(function (r) {
return { label: r.name || '素材', hint: (r.cat || '') + (r.folder ? (' · ' + r.folder) : '') + ' · ' + String(r.time || '').substring(0, 16), value: r.url };
});
return __v25Choose('从「文件管理」选择素材(共 ' + opts.length + ' 个,最新在前)', opts).then(function (v) {
if (!v) return;
UP[key] = v;
var sp = $('v25up_' + key); if (sp) { sp.textContent = '\u2705 已从文件管理选择'; sp.style.color = '#16a34a'; }
tip('\u2705 已从文件管理选择素材');
});
}).catch(function () { tip('读取文件管理失败,请重试'); });
} catch (e) {}
};
function upBtn(kind, key, label) {
return '' + esc(label || '上传') + ' ' + '未上传
' +
'
';
}
window.v25Up = function (kind, key) {
try {
var old = $('v25file'); if (old) old.remove();
var i = document.createElement('input'); i.id = 'v25file'; i.type = 'file';
i.accept = kind === 'video' ? 'video/*' : (kind === 'image' ? 'image/*' : (kind === 'audio' ? 'audio/*' : '*/*'));
i.style.display = 'none'; document.body.appendChild(i);
i.onchange = function () {
var f = i.files && i.files[0]; if (!f) return;
tip('⏳ 上传中…');
var fd = new FormData(); fd.append('file', f);
fetch(API + '/ai-army/upload?user_id=' + encodeURIComponent(uid()) + '&material_type=' + kind, { method: 'POST', body: fd })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var u = d && (d.url || (d.data && d.data.url) || d.file_path);
/* WR-562k53: 只有 file_id 才代表已进入文件管理,避免“显示已上传、实际找不到”。 */
if (!u || !d || !d.file_id || Number(d.file_id) <= 0) {
tip('上传失败:文件未入库,请重试'); return;
}
UP[key] = u;
var sp = $('v25up_' + key); if (sp) { sp.textContent = '✅ 已上传'; sp.style.color = '#16a34a'; }
}).catch(function () { tip('网络错误'); });
};
i.click();
} catch (e) {}
};
/* ---------- studio 5 台 ---------- */
H.image_editor = function () {
open('🎨 图片生成与编辑',
fld('① 描述想要的图片(必填)', area('v25ImgReq', '例如:赛博朋克城市夜景插画,16:9,霓虹灯…')) +
fld('② 参考图(可选:风格参考/改图)', upBtn('image', 'ref', '🖼️ 上传参考图')) +
btn('v25ImgGo', '🎨 生成/编辑图片') + result());
$('v25ImgGo').onclick = function () {
var s = val('v25ImgReq'); if (!s) { tip('请描述想要的图片'); return; }
run('【图片生成与编辑】请按以下需求产出图片:\n需求:' + s + (UP.ref ? '\n参考图:' + UP.ref : ''), null, 'image_generation');
};
};
H.video_mixer = function () {
open('🎞️ 视频混剪/模板制作',
fld('① 标题/主题(可选)', input('v25MxT', '例如:产品宣传片')) +
fld('② 脚本/口播文案', area('v25MxS', '粘贴脚本/口播文案…')) +
fld('③ 素材上传', upBtn('video', 'v', '🎥 上传视频素材') + '' + upBtn('image', 'p', '🖼️ 上传图片素材') + '
') +
fld('④ 数字人形象模板', '不使用(普通混剪) ') +
fld('⑤ 音色模板', '不使用(保留原声/TTS默认) ') +
fld('⑥ 封面模板', '默认封面样式 ') +
fld('⑥-A 封面文案(4-12字,可选)', input('v25MxCv', '例如:大肉包先给谁?')) +
fld('⑦ 背景音乐', upBtn('audio', 'm', '🎵 上传背景音乐')) +
fld('⑧ 选项', ' 自动生成字幕 ') +
btn('v25MxGo', '🎬 生成视频') + result());
v25TplLoad('v25MxA', 'digital_human', '不使用(普通混剪)');
v25TplLoad('v25MxV', 'voice', '不使用(保留原声/TTS默认)');
v25TplLoad('v25MxC', 'cover', '默认封面样式');
$('v25MxGo').onclick = function () {
var p = ['【视频混剪/模板制作】请根据以下配置产出视频:'];
if (val('v25MxT')) p.push('标题/主题:' + val('v25MxT'));
if (val('v25MxS')) p.push('脚本/口播:' + val('v25MxS'));
if (UP.v) p.push('视频素材:' + UP.v);
if (UP.p) p.push('图片素材:' + UP.p);
if (val('v25MxA')) p.push('数字人模板ID:' + val('v25MxA'));
else p.push('数字人形象:不使用');
if (val('v25MxV')) p.push('音色模板ID:' + val('v25MxV'));
if (val('v25MxC')) p.push('封面模板ID:' + val('v25MxC'));
if (val('v25MxCv')) p.push('封面文案:' + val('v25MxCv'));
if (UP.m) p.push('背景音乐:' + UP.m);
p.push('字幕:' + (chk('v25MxSub') ? '开启' : '关闭'));
run(p.join('\n'), null, 'video_generation', {
digital_human: !!val('v25MxA'),
template_id: val('v25MxA') || '',
digital_human_template_id: val('v25MxA') || '',
voice_template_id: val('v25MxV') || '',
cover_template_id: val('v25MxC') || '',
cover: val('v25MxCv') || ''
});
};
};
H.voice_studio = function () {
open('🎙️ 智能配音/我的音色',
fld('① 配音文本(必填)', area('v25VsT', '输入需要配音的文案…')) +
fld('② 音色', sel('v25VsV', ['温柔女声', '磁性男声', '活力少女', '知性女声', '浑厚男声', '新闻播音'])) +
fld('③ 语速', sel('v25VsR', ['0.8x 慢速', '1.0x 标准', '1.2x 稍快'])) +
btn('v25VsGo', '🎙️ 生成配音') + result());
$('v25VsGo').onclick = function () {
var s = val('v25VsT'); if (!s) { tip('请输入配音文本'); return; }
run('【智能配音】请将以下文本转为配音方案(音色 ' + val('v25VsV') + ',语速 ' + val('v25VsR') + '):\n' + s);
};
};
H.subtitle_studio = function () {
open('💬 字幕与标题模板',
fld('① 视频主题/文案', area('v25StT', '描述视频内容或粘贴口播稿…')) +
fld('② 风格', sel('v25StS', ['简洁商务', '知识科普', '娱乐活泼', '情感治愈', '科技炫酷'])) +
btn('v25StGo', '💬 生成字幕/标题方案') + result());
$('v25StGo').onclick = function () {
var s = val('v25StT'); if (!s) { tip('请输入内容'); return; }
run('【字幕与标题模板】内容:' + s + '\n风格:' + val('v25StS') + '\n输出:5 个爆款标题候选 + 分段字幕文案。');
};
};
H.music_library = function () {
open('🎵 背景音乐库',
'模板音乐:
加载中…
' +
fld('也可以上传自己的音乐', upBtn('audio', 'bgm', '🎵 上传音乐')) +
fld('用途说明(可选)', area('v25BgmUse', '例如:口播视频背景,轻松治愈氛围…')) +
btn('v25BgmGo', '🎵 生成配乐方案') + result());
fetch(API + '/api/templates?type=bgm&user_id=' + encodeURIComponent(uid())).then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var rows = (d && d.data) || [], el = $('v25Bgm');
if (!el) return;
if (!rows.length) { el.innerHTML = '暂无公开音乐模板'; return; }
el.innerHTML = rows.map(function (t) {
return '' + esc(t.name || '') + '
' +
'
' + esc(t.description || '') + (t.is_free ? '' : ' · 付费 ' + ((t.price_cents || 0) / 100) + ' 元') + '
' +
(t.preview_key ? '
' : '') + '
';
}).join('');
}).catch(function () { var el = $('v25Bgm'); if (el) el.innerHTML = '加载失败'; });
$('v25BgmGo').onclick = function () {
var use = val('v25BgmUse');
if (!use && !UP.bgm) { tip('请说明用途或上传音乐'); return; }
run('【背景音乐】需求:' + (use || '为口播视频配乐') + '\n音乐来源:' + (UP.bgm || '模板音乐'));
};
};
/* ---------- 资源库(素材/文件/模板/知识库/视频) ---------- */
function resPage(kind, title, tip2) {
open('📂 ' + title,
'+ 上传新素材 ' +
'加载中…
');
window.v25ResUp = function () {
var old = $('v25file'); if (old) old.remove();
var i = document.createElement('input'); i.id = 'v25file'; i.type = 'file';
i.accept = kind === 'image' ? 'image/*' : (kind === 'video' ? 'video/*' : (kind === 'audio' ? 'audio/*' : '*/*'));
i.style.display = 'none'; document.body.appendChild(i);
i.onchange = function () {
var f = i.files && i.files[0]; if (!f) return;
tip('⏳ 上传中…');
var fd = new FormData(); fd.append('file', f);
fetch(API + '/ai-army/upload?user_id=' + encodeURIComponent(uid()) + '&material_type=' + (kind === 'template' ? 'document' : kind), { method: 'POST', body: fd })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { tip((d && (d.url || (d.data && d.data.url))) ? '✅ 已上传' : '上传失败'); load(); })
.catch(function () { tip('网络错误'); });
};
i.click();
};
function load() {
var box = $('v25Res'); if (!box) return;
if (kind === 'template') {
fetch(API + '/api/templates?user_id=' + encodeURIComponent(uid())).then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var rows = (d && d.data) || [];
box.innerHTML = rows.length ? rows.map(function (t) {
return '' + esc(t.name || t.id || '') + '
' + esc(t.description || '') + (t.is_free ? ' · 免费' : ' · 付费 ' + ((t.price_cents || 0) / 100) + ' 元') + '
';
}).join('') : (tip2 || '暂无模板');
}).catch(function () { box.innerHTML = '加载失败'; });
return;
}
fetch(API + '/ai-army/materials?user_id=' + encodeURIComponent(uid()) + (kind === 'all' ? '' : '&material_type=' + kind) + '&limit=100')
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var rows = (d && (d.data || d.materials || d.items)) || [];
if (!rows.length) { box.innerHTML = tip2 || '暂无素材,点上方上传'; return; }
box.innerHTML = rows.map(function (m) {
var u = m.url || m.file_path || m.preview || '';
var low = String(u).toLowerCase(), media = '';
if (/\.(mp4|webm|mov)$/.test(low)) media = ' ';
else if (/\.(mp3|wav|m4a|ogg)$/.test(low)) media = ' ';
else if (/\.(png|jpe?g|gif|webp)$/.test(low)) media = ' ';
return '' + esc(m.name || m.filename || u.split('/').pop() || m.id || '素材') + '
' +
'
' + esc((m.material_type || m.type || '') + ' · ' + (m.created_at || m.upload_time || '')) + '
' + media + '
';
}).join('');
}).catch(function () { box.innerHTML = '加载失败'; });
}
load();
}
H.material_library = function () { resUnified('all', '素材管理'); };
H.file_management = function () { resUnified('all', '文件管理'); };
H.knowledge = function () { resPage('document', '知识库管理', '暂无知识文档,上传 Word/PDF/MD 建立知识库'); };
H.my_templates = function () { resPage('template', '我的模板'); };
H.video_management = function () { resUnified('video', '视频管理'); };
/* ---------- AX(2026-09-19): 统一文件管理 = 素材/文件/视频同源, 可播放/可删除/可勾选混剪 ---------- */
var RES_CATS = [
{ k: 'all', n: '全部' }, { k: 'video', n: '视频' }, { k: 'image', n: '图片' },
{ k: 'audio', n: '音频' }, { k: 'document', n: '文档' }, { k: 'copy', n: '文案' },
{ k: 'material', n: '素材' }, { k: 'template', n: '模板' }
];
var __resSel = {};
var __resRows = {};
var __resOrder = [];
var __resCur = 'all';
var __resNeedSession = false;
var __resTitle = '文件管理';
function __resDelOk(id) { return /^\d+$/.test(String(id)) || /^uv_\d+$/.test(String(id)); }
/* ===== WR-562k11: 文件管理「文件夹」——新建/移动/筛选/改名/删除 =====
老板口径: 手机上要能把素材按门店(如“同庆楼门店”)归类;
之后自然语言说门店名, 协调员/出片智能体才能检索到对应素材出片。 */
var __v25Fold = 'AI成片';
var __v25Folds = [];
var __v25Q = '';
var __v25FoldUncat = 0;
var __v25OnlyUsable = true;
/* WR-562k52: 最新成片永远置顶。用户不必在几十条素材里翻找,
进入文件管理第一屏就能看到并播放最新 AI 成片。 */
function v25LatestLoad() {
var box = $('v25Latest'); if (!box) return;
var u = uid(); if (!u || u === 'anonymous') { box.innerHTML = ''; return; }
fetch(API + '/api/files/list?user_id=' + encodeURIComponent(u) +
'&folder=' + encodeURIComponent('AI成片') + '&sort=recent&page_size=3')
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var rows = (d && d.files) || [];
if (!rows.length) { box.innerHTML = ''; return; }
var h = '' +
'
' +
'
🎬 最新成片 · 置顶
' +
'
查看全部 ';
rows.forEach(function (f) {
var fu = absUrl(f.file_url || f.url || f.file_path || '');
h += '
' +
'
' + esc(f.file_name || '') + '
' +
'
' + esc(String(f.created_at || '').substring(0, 19)) + ' · ' + esc(f.folder || 'AI成片') + '
' +
(fu ? '
' : '') +
'
' +
(fu ? '💾 保存/下载 ' : '') +
'
';
});
h += '
';
box.innerHTML = h;
}).catch(function () { box.innerHTML = ''; });
}
function v25FoldLoad() {
var bar = $('v25FoldBar');
__v25Folds = [];
if (!bar) return;
var u = uid();
if (!u || u === 'anonymous') { bar.innerHTML = ''; return; }
fetch(API + '/api/files/folders?user_id=' + encodeURIComponent(u))
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { __v25Folds = (d && d.folders) || []; __v25FoldUncat = (d && d.uncategorized) || 0; v25FoldBar(); })
.catch(function () { v25FoldBar(); });
}
function v25FoldBar() {
var bar = $('v25FoldBar'); if (!bar) return;
var st = 'padding:6px 11px;border-radius:15px;border:1px solid #e2e8f0;font-size:12px;cursor:pointer;background:#fff;color:#334155;';
var on = 'background:#0ea5e9;color:#fff;border-color:#0ea5e9;';
var cur = __v25Fold;
var h = '🕒 最近7天 ' +
'全部 ' +
'未分类(' + (__v25FoldUncat || 0) + ') ';
var _curRoot46 = (cur && cur.indexOf('/') > 0) ? cur.split('/')[0] : (cur === '__all__' ? '' : cur);
(__v25Folds || []).forEach(function (f) {
if (f.hidden) return;
var _isSub46 = String(f.name || '').indexOf('/') > 0;
/* WR-562k48: 「全部」视图也要显示子目录,否则用户只看到“素材库”三个字,
以为门店/未分类素材丢了。 */
/* WR-562k49: 「全部」只显示一级目录 + 用户自建目录;系统子目录点进「素材库」再看。 */
if (_isSub46 && cur === '__all__' && f.system) return;
if (_isSub46 && cur !== '__all__' && !(cur === f.name || (_curRoot46 && String(f.name).indexOf(_curRoot46 + '/') === 0))) return;
var act = cur === f.name;
h += '' +
'📁 ' + esc(f.name) + '(' + (f.total_count != null ? f.total_count : (f.count || 0)) + ') ' +
((f.protected || f.system) ? '' : '改 ' +
'删 ') + ' ';
});
h += '+ 新建文件夹 ' +
'移动勾选 ' +
'删除勾选 ' +
'移入废素材 ' +
'清理空素材 ' +
'自动整理 ' +
'' + (__v25OnlyUsable ? '只看可用' : '显示全部') + ' ' +
'全选当前 ' +
'取消全选 ';
bar.innerHTML = h;
}
window.v25FoldSet = function (n) { __v25Fold = n || '__all__'; v25FoldBar(); load(); };
window.v25FoldSelectAll = function (on) {
var keep = {};
if (on) {
(__resOrder || []).forEach(function (id) {
if (/^\d+$/.test(String(id)) && __resRows[String(id)]) keep[String(id)] = 1;
});
}
__resSel = keep; renderBar(); paint();
};
window.v25FoldOnlyUsable = function () { __v25OnlyUsable = !__v25OnlyUsable; v25FoldBar(); paint(); };
window.v25FoldClassify = function () {
var P = __v25Confirm('按文件名/用途自动整理「未分类」素材?已手动放好的文件夹不会动。', { title: '自动整理素材', okText: '开始整理' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/classify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), dry_run: false }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
if (d && d.success) { tip('已整理 ' + (d.changed || 0) + ' 条素材'); v25FoldLoad(); load(); }
else { tip('整理失败:' + ((d && (d.detail || d.message)) || '未知')); }
}).catch(function () { tip('整理网络错误'); });
});
};
/* WR-562k46: 微信/企业微信 WebView 会拦截原生 prompt/confirm,
之前"点新建文件夹没有输入框"就是这里断了。这里自绘页面内对话框,
不依赖 mobile_v39_dialog.js 是否加载。 */
function __v25Ask(opts) {
opts = opts || {};
return new Promise(function (resolve) {
var mask = document.createElement('div');
mask.style.cssText = 'position:fixed;left:0;top:0;right:0;bottom:0;z-index:99999;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;padding:18px;';
var box = document.createElement('div');
box.style.cssText = 'width:100%;max-width:360px;background:#fff;border-radius:16px;padding:18px;box-shadow:0 20px 50px rgba(0,0,0,.25);box-sizing:border-box;';
var title = document.createElement('div');
title.textContent = opts.title || '请输入';
title.style.cssText = 'font-size:16px;font-weight:700;color:#0f172a;margin-bottom:10px;';
var inp = document.createElement('input');
inp.type = 'text';
inp.value = opts.value || '';
inp.placeholder = opts.placeholder || '';
inp.style.cssText = 'width:100%;box-sizing:border-box;padding:11px 12px;border:1px solid #cbd5e1;border-radius:10px;font-size:15px;outline:none;color:#0f172a;background:#f8fafc;';
var bar = document.createElement('div');
bar.style.cssText = 'display:flex;gap:8px;margin-top:14px;';
var cancel = document.createElement('button');
cancel.type = 'button'; cancel.textContent = '取消';
cancel.style.cssText = 'flex:1;padding:10px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#334155;font-size:14px;cursor:pointer;';
var ok = document.createElement('button');
ok.type = 'button'; ok.textContent = opts.okText || '确定';
ok.style.cssText = 'flex:1;padding:10px;border:none;border-radius:10px;background:#0ea5e9;color:#fff;font-size:14px;font-weight:600;cursor:pointer;';
bar.appendChild(cancel); bar.appendChild(ok);
box.appendChild(title); box.appendChild(inp); box.appendChild(bar);
mask.appendChild(box); document.body.appendChild(mask);
setTimeout(function () { try { inp.focus(); inp.select(); } catch (e) {} }, 60);
function done(v) { try { mask.remove(); } catch (e) {} resolve(v); }
cancel.onclick = function () { done(null); };
ok.onclick = function () { done(String(inp.value || '').trim()); };
inp.onkeydown = function (e) {
if (e.key === 'Enter') { e.preventDefault(); ok.click(); }
else if (e.key === 'Escape') { cancel.click(); }
};
mask.onclick = function (e) { if (e.target === mask) done(null); };
});
}
function __v25Confirm(msg, opts) {
opts = opts || {};
return new Promise(function (resolve) {
var mask = document.createElement('div');
mask.style.cssText = 'position:fixed;left:0;top:0;right:0;bottom:0;z-index:99999;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;padding:18px;';
var box = document.createElement('div');
box.style.cssText = 'width:100%;max-width:360px;background:#fff;border-radius:16px;padding:18px;box-shadow:0 20px 50px rgba(0,0,0,.25);box-sizing:border-box;';
var title = document.createElement('div');
title.textContent = opts.title || '请确认';
title.style.cssText = 'font-size:16px;font-weight:700;color:#0f172a;margin-bottom:8px;';
var body = document.createElement('div');
body.textContent = msg || '';
body.style.cssText = 'font-size:14px;line-height:1.6;color:#475569;white-space:pre-wrap;';
var bar = document.createElement('div');
bar.style.cssText = 'display:flex;gap:8px;margin-top:16px;';
var cancel = document.createElement('button');
cancel.type = 'button'; cancel.textContent = '取消';
cancel.style.cssText = 'flex:1;padding:10px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#334155;font-size:14px;cursor:pointer;';
var ok = document.createElement('button');
ok.type = 'button'; ok.textContent = opts.okText || '确定';
ok.style.cssText = 'flex:1;padding:10px;border:none;border-radius:10px;background:' + (opts.okBg || '#0ea5e9') + ';color:#fff;font-size:14px;font-weight:600;cursor:pointer;';
bar.appendChild(cancel); bar.appendChild(ok);
box.appendChild(title); box.appendChild(body); box.appendChild(bar);
mask.appendChild(box); document.body.appendChild(mask);
function done(v) { try { mask.remove(); } catch (e) {} resolve(v); }
cancel.onclick = function () { done(false); };
ok.onclick = function () { done(true); };
mask.onclick = function (e) { if (e.target === mask) done(false); };
});
}
/* WR-562k49: 页面内选择器。微信/企业微信 WebView 常常没有 window.wrChoose,
旧逻辑退化成手输文件夹名,用户点“同庆楼”像没反应。这里自绘列表。 */
function __v25Choose(title, opts) {
opts = opts || [];
return new Promise(function (resolve) {
var mask = document.createElement('div');
mask.style.cssText = 'position:fixed;left:0;top:0;right:0;bottom:0;z-index:99999;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;padding:18px;';
var box = document.createElement('div');
box.style.cssText = 'width:100%;max-width:380px;max-height:82vh;background:#fff;border-radius:16px;padding:16px;box-shadow:0 20px 50px rgba(0,0,0,.25);box-sizing:border-box;display:flex;flex-direction:column;';
var head = document.createElement('div');
head.textContent = title || '请选择';
head.style.cssText = 'font-size:16px;font-weight:700;color:#0f172a;margin-bottom:8px;flex:none;';
var q = document.createElement('input');
q.type = 'search'; q.placeholder = '搜索…';
q.style.cssText = 'width:100%;box-sizing:border-box;padding:9px 11px;border:1px solid #cbd5e1;border-radius:9px;font-size:14px;background:#f8fafc;flex:none;';
var list = document.createElement('div');
list.style.cssText = 'overflow:auto;flex:1;margin-top:8px;min-height:80px;';
var bar = document.createElement('div');
bar.style.cssText = 'display:flex;gap:8px;margin-top:10px;flex:none;';
var cancel = document.createElement('button');
cancel.type = 'button'; cancel.textContent = '取消';
cancel.style.cssText = 'flex:1;padding:10px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#334155;font-size:14px;cursor:pointer;';
bar.appendChild(cancel);
box.appendChild(head); box.appendChild(q); box.appendChild(list); box.appendChild(bar);
mask.appendChild(box); document.body.appendChild(mask);
function done(v) { try { mask.remove(); } catch (e) {} resolve(v); }
function render(kw) {
kw = String(kw || '').toLowerCase();
var rows = opts.filter(function (o) {
if (!kw) return true;
return (String(o.label || '') + ' ' + String(o.hint || '')).toLowerCase().indexOf(kw) >= 0;
});
list.innerHTML = rows.length ? rows.map(function (o) {
return '' +
'' + esc(o.label || '') + '
' +
(o.hint ? '' + esc(o.hint) + '
' : '') +
' ';
}).join('') : '没有匹配项
';
}
list.onclick = function (e) {
var b = e.target && e.target.closest && e.target.closest('button[data-v]');
if (!b) return;
done(b.getAttribute('data-v'));
};
q.oninput = function () { render(q.value); };
cancel.onclick = function () { done(null); };
mask.onclick = function (e) { if (e.target === mask) done(null); };
render('');
setTimeout(function () { try { q.focus(); } catch (e) {} }, 60);
});
}
window.v25FoldNew = function () {
/* WR-562k13d: 原生 prompt 在微信/企业微信 WebView 被静默拦截(点了没有任何输入框)
-> 改走页面内对话框 wrAsk(见 mobile_v39_dialog.js) */
var P = __v25Ask({ title: '新建文件夹', placeholder: '例如:同庆楼门店 / 餐饮素材', okText: '创建' });
P.then(function (n) {
n = String(n == null ? '' : n).trim(); if (!n) return;
fetch(API + '/api/files/folders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), name: n }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var ok = d && d.success !== false;
tip(ok ? ('\u5df2\u521b\u5efa\uff1a' + n) : ('\u521b\u5efa\u5931\u8d25\uff1a' + ((d && (d.detail || d.message)) || '\u672a\u77e5')));
if (ok) { __v25Fold = n; v25FoldLoad(); load(); }
}).catch(function () { tip('\u7f51\u7edc\u9519\u8bef'); });
});
};
window.v25FoldRename = function (id, cur) {
/* WR-562k13d: 同上, 改页面内对话框 */
var P = __v25Ask({ title: '重命名文件夹', value: cur || '', okText: '保存' });
P.then(function (n) {
n = String(n == null ? '' : n).trim();
if (!n || n === cur) return;
fetch(API + '/api/files/folders/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), name: n }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { tip(d && d.success !== false ? '\u5df2\u91cd\u547d\u540d' : '\u91cd\u547d\u540d\u5931\u8d25'); if (__v25Fold === cur) __v25Fold = n; v25FoldLoad(); load(); })
.catch(function () { tip('\u7f51\u7edc\u9519\u8bef'); });
});
};
window.v25FoldDel = function (id, name) {
/* WR-562k13d: confirm 同样被微信 WebView 拦截 -> 页面内确认 */
var P = __v25Confirm('删除文件夹「' + name + '」?(其中文件回到"未分类",不会删除文件)', { title: '删除文件夹', okText: '删除', okBg: '#e11d48' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/folders/' + id + '?user_id=' + encodeURIComponent(uid()), { method: 'DELETE' })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { tip(d && d.success !== false ? '\u5df2\u5220\u9664\u6587\u4ef6\u5939' : '\u5220\u9664\u5931\u8d25'); if (__v25Fold === name) __v25Fold = '__all__'; v25FoldLoad(); load(); })
.catch(function () { tip('\u7f51\u7edc\u9519\u8bef'); });
});
};
function v25FoldSelIds() {
var out = [];
Object.keys(__resSel).forEach(function (k) {
var r = __resRows[k]; if (!r) return;
if (!/^\d+$/.test(String(r.id))) return;
out.push(Number(r.id));
});
return out;
}
function v25DoMove(ids, folder) {
if (!ids || !ids.length) { tip('\u8bf7\u5148\u52fe\u9009\u8981\u79fb\u52a8\u7684\u6587\u4ef6'); return; }
fetch(API + '/api/files/move', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), ids: ids, folder: folder || '' }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var ok = d && d.success !== false;
tip(ok ? ('\u5df2\u79fb\u52a8 ' + ((d && d.moved) || 0) + ' \u4e2a\u6587\u4ef6 \u2192 ' + (folder || '\u672a\u5206\u7c7b'))
: ('\u79fb\u52a8\u5931\u8d25\uff1a' + ((d && (d.detail || d.message)) || '\u672a\u77e5')));
__resSel = {};
/* WR-562k13d: 移动成功后自动切到目标文件夹, 让用户立刻看到文件真的进去了 */
if (ok) __v25Fold = folder || '__none__';
v25FoldLoad(); load();
})
.catch(function () { tip('\u79fb\u52a8\u7f51\u7edc\u9519\u8bef'); });
}
/* WR-562k13d: 移动目标选择 —— 页面内列表(点选), 不再靠手打名字;
含「+ 新建文件夹…」与「移出到未分类」, 解决"点同庆楼却移不进去"。 */
function v25PickFolder(title, cur) {
var opts = (__v25Folds || []).filter(function (f) { return !f.hidden; }).map(function (f) {
return { label: '📁 ' + f.name, hint: '(' + (f.count || 0) + ' 个文件)', value: f.name };
});
opts.push({ label: '+ 新建文件夹…', hint: '先输入名称,再移入', value: '__NEW__' });
opts.push({ label: '移出到「未分类」', hint: '素材库/未分类', value: '' });
return __v25Choose(title, opts).then(function (v) {
if (v === '__NEW__') {
return __v25Ask({ title: '新建文件夹并移入', placeholder: '文件夹名称,例如:同庆楼', okText: '创建并移动' });
}
return v;
});
}
window.v25FoldMoveChecked = function () {
var ids = v25FoldSelIds();
if (!ids.length) { tip('\u8bf7\u5148\u52fe\u9009\u8981\u79fb\u52a8\u7684\u6587\u4ef6'); return; }
var tgt = (__v25Fold === '__all__' || __v25Fold === '__none__') ? '' : __v25Fold;
v25PickFolder('把勾选的 ' + ids.length + ' 个文件移动到…', tgt).then(function (n) {
if (n === null || n === undefined) return;
v25DoMove(ids, n);
});
};
window.v25FoldDelChecked = function () {
var ids = v25FoldSelIds();
if (!ids.length) { tip('请先勾选要删除的文件'); return; }
var P = __v25Confirm('确定删除勾选的 ' + ids.length + ' 个文件?删除后不可恢复。', { title: '批量删除', okText: '删除', okBg: '#e11d48' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), ids: ids }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
tip(d && d.success !== false ? ('已删除 ' + ((d && d.deleted) || 0) + ' 个文件') : ('删除失败:' + ((d && (d.detail || d.message)) || '未知')));
if (d && d.success !== false) { __resSel = {}; v25FoldLoad(); load(); }
}).catch(function () { tip('删除网络错误'); });
});
};
window.v25FoldTrashChecked = function () {
var ids = v25FoldSelIds();
if (!ids.length) { tip('请先勾选要移入废素材的文件'); return; }
var P = __v25Confirm('把勾选的 ' + ids.length + ' 个文件移入「回收站/废素材」?\n之后可在回收站里彻底删除。', { title: '移入废素材', okText: '移入' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/trash', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), ids: ids }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
tip(d && d.success !== false ? ('已移入废素材 ' + ((d && d.moved) || 0) + ' 个') : ('移入失败:' + ((d && (d.detail || d.message)) || '未知')));
if (d && d.success !== false) { __resSel = {}; __v25Fold = '__all__'; v25FoldLoad(); load(); }
}).catch(function () { tip('移入网络错误'); });
});
};
window.v25FoldCleanEmpty = function () {
var P = __v25Confirm('把 0 字节中间件、失败草稿批量移入「回收站/废素材」?\n正常素材和成片不会被移动。', { title: '清理空素材', okText: '开始清理' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/classify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), clean_empty: true }) })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
if (d && d.success !== false) {
tip('已清理 ' + ((d && d.cleaned) || 0) + ' 条空素材/失败草稿');
__resSel = {}; v25FoldLoad(); load();
} else { tip('清理失败:' + ((d && (d.detail || d.message)) || '未知')); }
}).catch(function () { tip('清理网络错误'); });
});
};
window.v25FoldMoveOne = function (id) {
v25PickFolder('把这个文件移动到…', '').then(function (n) {
if (n === null || n === undefined) return;
v25DoMove([Number(id)], n);
});
};
/* WR-562k11: 文件管理「删除」按文件记录走服务端接线。 */
window.v25FileDel = function (id) {
/* WR-562k13d: 页面内确认 */
var r = __resRows[String(id)] || {};
var P = __v25Confirm('确定删除「' + (r.name || id) + '」?删除后不可恢复。', { title: '删除文件', okText: '删除', okBg: '#e11d48' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/ai-army/materials/' + encodeURIComponent(id) + '?user_id=' + encodeURIComponent(uid()), { method: 'DELETE' })
.then(function (x) { return x.json().catch(function () { return {}; }); })
.then(function (d) { tip(d && d.success ? '\u{1F5D1} \u5df2\u5220\u9664' : ('\u5220\u9664\u5931\u8d25\uff1a' + ((d && (d.detail || d.message)) || '\u672a\u77e5'))); if (d && d.success) { delete __resSel[String(id)]; v25FoldLoad(); load(); } })
.catch(function () { tip('\u5220\u9664\u7f51\u7edc\u9519\u8bef'); });
});
};
function resUnified(cat, title) {
__resCur = cat || 'all';
__resTitle = title || '文件管理';
open('📂 ' + __resTitle,
/* WR-562k10b: 文件管理/素材管理/视频管理 增加"上传素材"入口。
此前只有对话框胶囊有上传按钮, 而对话框上传=直接出片; 文件管理页没有上传按钮
-> 用户无法把自己的素材传进"我的文件"供混剪/二创/知识课程使用。 */
'' +
'+ 上传素材 ' +
'📷 拍照/录像 ' +
'
' +
'' +
' ' +
'
' +
'
' +
'
' +
'
加载中…
' +
'
' +
'文件管理 build 20261019_wr562k52
');
renderChips();
/* WR-562k53: 进入文件管理先给最新 AI 成片;点分类页签时再看全库。 */
__v25Fold = (cat === 'all') ? 'AI成片' : '__all__';
__v25Q = '';
var _q2 = $('v25ResQ');
if (_q2) _q2.oninput = function () { __v25Q = String(_q2.value || '').trim(); paint(); };
try { v25FoldLoad(); } catch (e) {}
var _ub2 = $('v25ResUp2'), _cb2 = $('v25ResCam2');
if (_ub2) _ub2.onclick = function () { resResUpload(''); };
if (_cb2) _cb2.onclick = function () { resResUpload('environment'); };
load();
try { v25LatestLoad(); } catch (e) {}
}
/* WR-562k10b: 文件管理页内上传 —— 落到"我的文件/素材"(按 user_id 隔离), 不直接出片 */
function resCatToType(cat) {
if (cat === 'image') return 'image';
if (cat === 'audio') return 'audio';
if (cat === 'copy') return 'script';
if (cat === 'template') return 'template';
if (cat === 'document') return 'document';
return 'video';
}
function resFileType(f) {
var t = String((f && f.type) || '').toLowerCase();
if (t.indexOf('video/') === 0) return 'video';
if (t.indexOf('image/') === 0) return 'image';
if (t.indexOf('audio/') === 0) return 'audio';
if (t) return 'document';
return '';
}
function resResUpload(cap) {
try {
if (!uid() || uid() === 'anonymous') { tip('请先在「我的」登录后再上传素材'); return; }
var old = $('v25file'); if (old) old.remove();
var cat = __resCur, mt = resCatToType(cat), acc;
if (cap) acc = 'video/*,image/*';
else if (cat === 'all') acc = 'video/*,image/*,audio/*,.pdf,.doc,.docx,.ppt,.pptx,.md,.txt';
else if (mt === 'image') acc = 'image/*';
else if (mt === 'video') acc = 'video/*';
else if (mt === 'audio') acc = 'audio/*';
else acc = '.doc,.docx,.md,.txt,.pdf,.ppt,.pptx';
/* WR-562k53: 不再让用户手打文件夹;默认自动分类,也可点选已有文件夹。 */
var _curFold = (__v25Fold && __v25Fold !== '__all__' && __v25Fold !== '__none__' &&
__v25Fold !== '__recent__' && __v25Fold !== 'AI成片') ? __v25Fold : '';
var _foldOpts = [{ label: '自动分类(推荐)', hint: '按文件名/用途归档;识别不了放未分类', value: '__AUTO__' }];
if (_curFold) _foldOpts.push({ label: '当前文件夹:' + _curFold, hint: '上传到这里', value: _curFold });
(__v25Folds || []).forEach(function (f) {
if (!f || f.hidden || !f.name || f.name === 'AI成片') return;
if (_curFold && f.name === _curFold) return;
_foldOpts.push({ label: '📁 ' + f.name, hint: (f.total_count != null ? f.total_count : (f.count || 0)) + ' 个文件', value: f.name });
});
var ask = __v25Choose('素材归档到哪个文件夹?', _foldOpts);
ask.then(function (picked) {
if (picked === null || picked === undefined) return;
var folder = (picked === '__AUTO__') ? '' : String(picked).trim();
var folderLabel = folder || '自动分类';
var i = document.createElement('input'); i.id = 'v25file'; i.type = 'file'; i.accept = acc;
if (!cap) i.multiple = true;
if (cap && (cat === 'all' || mt === 'video' || mt === 'image')) { i.setAttribute('capture', cap); i.capture = cap; }
i.style.display = 'none'; document.body.appendChild(i);
i.onchange = function () {
var files = Array.prototype.slice.call(i.files || []);
if (!files.length) { try { i.remove(); } catch (e) {} return; }
var total = files.length, done = 0, ok = 0, fail = 0, errMsg = '', lastFolder = folderLabel, firstId = '';
function finish() {
try { i.remove(); } catch (e) {}
if (ok) tip('✅ 已上传 ' + ok + '/' + total + ' 个到「' + lastFolder + '」' + (fail ? (',失败 ' + fail + ' 个') : ''));
else tip('上传失败:' + (errMsg || '请检查登录状态和网络后重试'));
if (ok) { __v25Fold = (lastFolder && lastFolder !== '自动分类') ? lastFolder : '__all__'; v25FoldLoad(); load(); try { window.scrollTo(0, 0); } catch (e) {} }
}
files.forEach(function (f) {
var use = resFileType(f) || mt;
var fd = new FormData(); fd.append('file', f);
fetch(API + '/ai-army/upload?user_id=' + encodeURIComponent(uid()) + '&material_type=' + encodeURIComponent(use) +
'&folder=' + encodeURIComponent(folder) + '&usage=' + encodeURIComponent(folder.replace('素材库/', '') + ' 用户上传'),
{ method: 'POST', body: fd })
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
/* WR-562k52: 只有 file_id 才代表“已进文件管理”。
旧逻辑看到 url 就报成功,OBS 有对象、文件表没有行 -> 用户永远找不到。 */
if (d && d.success !== false && d.file_id && (d.url || d.file_path)) {
ok++; lastFolder = d.folder || lastFolder; firstId = firstId || String(d.file_id || '');
} else if (d && d.success !== false && (d.url || d.file_path) && !d.file_id) {
fail++; errMsg = '文件已上传但未入库,请重试';
} else {
fail++; errMsg = (d && (d.error || d.detail || d.message)) || '服务端未返回文件';
}
})
.catch(function (e) { fail++; errMsg = (e && e.message) || '网络错误'; })
.then(function () { done++; if (done >= total) finish(); });
});
tip('⏳ 正在上传 ' + total + ' 个素材…');
};
i.click();
});
} catch (e) { tip('当前环境不支持选择文件'); }
}
function renderChips() {
var box = $('v25Chips'); if (!box) return;
box.innerHTML = '' + RES_CATS.map(function (c) {
return '' + c.n + ' ';
}).join('') + '
';
box.onclick = function (e) {
var b = e.target && e.target.getAttribute && e.target.getAttribute('data-rc');
if (!b) return;
if (b !== __resCur) __v25Fold = '__all__';
__resCur = b; renderChips(); load();
};
}
function renderBar() {
var box = $('v25Bar'); if (!box) return;
var n = Object.keys(__resSel).length;
box.innerHTML = '' + (n ? '
✔ 已选 ' + n + ' 项
' : '') +
'
' +
'✂ 用所选素材混剪/二创 ' +
'📂 移动 ' +
'🗑 删除 ' +
'♻ 废 ' +
'清空 ' +
'
';
var m = $('v25Mix'); if (m) m.onclick = mixSelected;
var msel = $('v25MoveSel'); if (msel) msel.onclick = function () { window.v25FoldMoveChecked(); };
var dsel = $('v25DelSel'); if (dsel) dsel.onclick = function () { window.v25FoldDelChecked(); };
var tsel = $('v25TrashSel'); if (tsel) tsel.onclick = function () { window.v25FoldTrashChecked(); };
var c = $('v25Clr'); if (c) c.onclick = function () { __resSel = {}; renderBar(); paint(); };
}
/* WR-529: 相对路径归一(/static/... → 绝对直链),否则视频/图片点不开 */
/* WR-550: 相对路径归一 —— OBS 桶内对象(materials/... / users/...)必须拼"公有读"域名,
否则手机端 img/video/audio 会按"当前页相对路径"解析 -> 404 -> 表现为"文件打不开/不能播放"。
与后端 file_management._fm_public_url 同口径。 */
var OBS_PUB = 'https://categorized-assets-smart-center.obs.cn-east-3.myhuaweicloud.com';
function absUrl(u) {
u = String(u || '');
if (!u) return u;
if (/^(https?:)?\/\//i.test(u) || /^data:/i.test(u) || /^blob:/i.test(u)) return u;
if (u.charAt(0) === '/') {
/* WR-562k11: console.duoweiying.cn 不提供 /static/(实测 403) -> 改走 ai.duoweiying.cn(实测 200) */
if (u.indexOf('/static/') === 0) return 'https://ai.duoweiying.cn' + u;
return OBS_PUB + u;
}
if (/^[a-zA-Z][a-zA-Z0-9+.\-]*:/.test(u)) return u;
return OBS_PUB + '/' + u.replace(/^\.?\//, '');
}
function filesToRow(f) {
var u = absUrl(f.file_url || f.url || f.file_path || '');
var fold = f.folder || '';
return { id: String(f.id), name: f.file_name || String(f.id), cat: f.category || '', folder: fold,
desc: (fold ? '[文件夹:' + fold + '] ' : '') + (f.description || ''),
url: u, playable: /^https?:\/\//i.test(String(u)), time: f.created_at || f.updated_at || '' };
}
function matToRow(m) {
var u = absUrl(m.url || m.file_path || '');
return { id: String(m.id), name: m.name || m.filename || String(m.id), cat: m.category || m.type || '', desc: m.desc || '', url: u, playable: /^https?:\/\//i.test(String(u)), time: m.time || m.created_at || '' };
}
function fetchFileCat(c) {
var extra = '';
if (__v25Fold === '__none__') extra = '&folder=' + encodeURIComponent('素材库/未分类');
else if (__v25Fold && __v25Fold !== '__all__' && __v25Fold !== '__recent__') extra = '&folder=' + encodeURIComponent(__v25Fold);
var _days = (__v25Fold === '__recent__') ? '&days=7' : '';
return fetch(API + '/api/files/list?user_id=' + encodeURIComponent(uid()) + '&category=' + encodeURIComponent(c) + extra + _days + '&sort=recent&page_size=100')
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
/* WR-550: 属主=所有者/机密文件需持证;未持证时后端回 need_session -> 记录以便给用户明确提示 */
if (d && (d.code === 'need_session' || d.need_oauth)) __resNeedSession = true;
return ((d && d.files) || []).map(filesToRow);
});
}
/* WR-550: 分类页签与"全部"同源并集"我的素材库" —— 文件门禁需持证时页签不再整页空白 */
function fetchMaterials(kind) {
/* WR-562k11: 文件夹只对 file_records 有效;筛选文件夹时不再并入素材库,避免“筛了还有别的” */
if (__v25Fold && __v25Fold !== '__all__') return Promise.resolve([]);
var q = API + '/ai-army/materials?user_id=' + encodeURIComponent(uid()) +
(kind ? '&material_type=' + encodeURIComponent(kind) : '') + '&limit=100';
return fetch(q).then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { return ((d && (d.data || d.materials || d.items)) || []).map(matToRow); });
}
function load() {
var box = $('v25Res'); var sb = $('v25Sb');
__resRows = {}; __resOrder = []; __resNeedSession = false;
if (box) box.innerHTML = '加载中…
';
var jobs = [], cat = __resCur;
if (cat === 'template') {
jobs.push(fetch(API + '/api/templates?user_id=' + encodeURIComponent(uid())).then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) { return ((d && d.data) || []).map(function (t) {
return { id: 'tpl_' + (t.id || t.name), name: t.name || t.id, cat: 'template', desc: t.description || '', url: '', playable: false, time: '' };
}); }));
jobs.push(fetchFileCat('template'));
} else if (cat === 'all') {
/* WR-562k47: 文件管理以 file_records 为唯一真源;旧 materials 接口只做兜底,
不再并进主列表,避免同一素材出现两次、旧素材把新成片挤下去。 */
jobs.push(fetchFileCat(''));
} else if (cat === 'audio') {
jobs.push(fetchFileCat('voice')); jobs.push(fetchFileCat('music'));
} else if (cat === 'material') {
/* WR-562k54: 「素材」是原始素材视图,不是某个 category。 */
jobs.push(fetchFileCat('source'));
} else if (cat === 'copy') {
/* 文案 = 用户确认保存的正文/方案 + 历史生成稿。 */
jobs.push(fetchFileCat('script'));
jobs.push(fetchFileCat('generated'));
} else {
jobs.push(fetchFileCat(cat));
/* WR-562k49: file_records 是唯一真源;不再并 user_videos,避免旧片重复/把新成片挤下去。 */
}
Promise.all(jobs).then(function (lists) {
var out = [], seen = {}, seenUrl = {};
lists.forEach(function (l) {
(l || []).forEach(function (r) {
if (!r || !r.id || seen[r.id]) return;
seen[r.id] = 1;
if (r.url && seenUrl[r.url]) return;
if (r.url) seenUrl[r.url] = 1;
out.push(r);
});
});
out.sort(function (a, b) {
var ia = parseInt(String(a.id || '').replace(/\D/g, ''), 10) || 0;
var ib = parseInt(String(b.id || '').replace(/\D/g, ''), 10) || 0;
if (ia !== ib) return ib - ia;
var ta = Date.parse(String(a.time || '').replace(' ', 'T')) || 0;
var tb = Date.parse(String(b.time || '').replace(' ', 'T')) || 0;
if (ta !== tb) return tb - ta;
return String(b.id || '').localeCompare(String(a.id || ''));
});
out.forEach(function (r) { __resRows[String(r.id)] = r; });
__resOrder = out.map(function (r) { return String(r.id); });
if (sb) {
if (!out.length) sb.textContent = '';
else {
var _latest47 = out[0] || {};
sb.textContent = '共 ' + out.length + ' 项 · 最新在前' +
(_latest47.name ? ('|最新:' + String(_latest47.name).slice(0, 28) + (_latest47.time ? (' · ' + String(_latest47.time).substring(0, 16)) : '')) : '') +
(__v25Q ? '(已按“' + __v25Q + '”筛选)' : '') + ' · 勾选可混剪/移动/删除';
}
}
if (!out.length) {
if (box) box.innerHTML = __resNeedSession
? '⚠️ 未识别到你的登录身份(受保护文件需持证) 请在「我的」登录,或从企业微信/公众号入口进入手机端后再查看
'
: '暂无内容 上传/生成后会自动归档到这里
';
renderBar(); return;
}
paint(); renderBar();
}).catch(function () { if (box) box.innerHTML = '加载失败,请重新进入'; });
}
function contentTypeOf(u, cat) {
var low = String(u || '').toLowerCase();
if (cat === 'video' || /\.(mp4|webm|mov)(\?|$)/.test(low)) return 'video';
if (cat === 'image' || /\.(png|jpe?g|gif|webp)(\?|$)/.test(low)) return 'image';
if (cat === 'voice' || cat === 'music' || /\.(mp3|wav|m4a|aac|ogg)(\?|$)/.test(low)) return 'audio';
return 'file';
}
function paint() {
var box = $('v25Res'); if (!box) return;
var ids = (__resOrder && __resOrder.length ? __resOrder.slice() : Object.keys(__resRows)).filter(function (id) {
var r = __resRows[id] || {};
if (__v25Fold === '__recent__' && r.time) {
var _t46 = Date.parse(r.time);
if (!isNaN(_t46) && _t46 < Date.now() - 7 * 24 * 3600 * 1000) return false;
}
if (__v25OnlyUsable && __resCur === 'all') {
var _f47 = String(r.folder || '');
if (_f47.indexOf('素材库/资料') === 0 || _f47.indexOf('素材库/配置素材') === 0 ||
_f47.indexOf('素材库/对标素材') === 0 || _f47.indexOf('素材库/空镜环境') === 0 ||
_f47.indexOf('回收站') === 0) return false;
}
if (!__v25Q) return true;
var blob = String(r.name || '') + ' ' + String(r.desc || '') + ' ' + String(r.cat || '') + ' ' + String(r.time || '');
return blob.toLowerCase().indexOf(__v25Q.toLowerCase()) >= 0;
});
box.innerHTML = ids.map(function (id) {
var r = __resRows[id], t = contentTypeOf(r.url, r.cat), media = '';
if (r.playable && t === 'video') media = ' ';
else if (r.playable && t === 'image') media = ' ';
else if (r.playable && t === 'audio') media = ' ';
else if (t === 'video') media = '⚠️ 该文件暂不可播放
';
/* WR-550: 非视频文件(文档/商品/其他)也要能打开;视频保留 保存/下载 + 分享 */
var acts = '';
if (r.url) {
var btns = [];
var _bs = 'flex:none;border:none;border-radius:8px;padding:6px 12px;font-size:13px;color:#fff;cursor:pointer;';
if (r.playable && (t === 'video' || t === 'image' || t === 'audio')) {
btns.push('💾 保存/下载 ');
}
if (r.playable && t === 'video') {
btns.push('📤 分享 ');
}
btns.push('🔗 打开/查看 ');
acts = '' + btns.join('') + '
';
}
var del = __resDelOk(id)
? '🗑 '
: '';
return '' +
'
' +
'
' +
'
' + esc(r.name) + '
' +
'
' + esc(String(r.cat || '') + (r.time ? ' · ' + String(r.time).substring(0, 19) : '')) + '
' + (r.desc ? '
' + esc(String(r.desc).slice(0, 120)) + '
' : '') + '
' +
(/^\d+$/.test(String(id)) ? '
移动 ' : '') + del + '
' + media + acts + '
';
}).join('');
box.onchange = function (e) {
var id = e.target && e.target.getAttribute && e.target.getAttribute('data-rid');
if (!id) return;
if (e.target.checked) __resSel[id] = 1; else delete __resSel[id];
renderBar();
};
box.onclick = function (e) {
var id = e.target && e.target.getAttribute && e.target.getAttribute('data-del');
if (id) { delOne(id); return; }
var act = e.target && e.target.getAttribute && e.target.getAttribute('data-act');
if (act) {
var _u = e.target.getAttribute('data-url') || '', _n = e.target.getAttribute('data-name') || '\u89c6\u9891';
if (act === 'save') v25Save(_u, _n);
else if (act === 'share') v25Share(_u, _n);
else if (act === 'open') { try { window.open(_u, '_blank'); } catch (e) { tip('打开失败'); } }
}
};
}
function delOne(id) {
var r = __resRows[String(id)] || {};
var P = __v25Confirm('确定删除「' + (r.name || id) + '」?删除后不可恢复。', { title: '删除文件', okText: '删除', okBg: '#e11d48' });
P.then(function (ok) {
if (!ok) return;
fetch(API + '/api/files/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: uid(), ids: [Number(id)] }) })
.then(function (x) { return x.json().catch(function () { return {}; }); })
.then(function (d) {
tip(d && d.success !== false ? '\u{1F5D1} \u5df2\u5220\u9664' : ('\u5220\u9664\u5931\u8d25\uff1a' + ((d && (d.detail || d.message)) || '\u672a\u77e5')));
if (d && d.success !== false) { delete __resSel[String(id)]; load(); }
}).catch(function () { tip('\u5220\u9664\u7f51\u7edc\u9519\u8bef'); });
});
}
function v25Entitlement() {
var u = uid();
if (!u || u === 'anonymous') return Promise.resolve(false);
if (u === 'admin-001' || u === 'admin') return Promise.resolve(true);
return fetch(API + '/api/user-settings/status?user_id=' + encodeURIComponent(u))
.then(function (r) { return r.json(); }).then(function (d) {
var st = (d && d.data) || {};
var sub = st.subscription || st.subscriptions || [];
var active = false;
try { active = Array.isArray(sub) ? sub.some(function (x) { return x && (x.status === 'active' || (x.end && new Date(x.end) > new Date())); }) : (sub && sub.status === 'active'); } catch (e) {}
var bal = Number(st.balance_yuan || st.balance || 0);
return active || (bal > 0);
}).catch(function () { return false; });
}
function v25Save(url, name) {
v25Entitlement().then(function (ok) {
if (!ok) { tip('\u4e0b\u8f7d\u9700\u5148\u8ba2\u9605/\u5145\u503c\u540e\u4f7f\u7528\uff08\u6211\u7684 \u2192 \u5145\u503c/\u8ba2\u9605\uff09'); return; }
var fn = String(name || 'video').replace(/[\\/:*?"<>|]/g, '_');
if (!/\.(mp4|mov|webm|png|jpe?g|mp3|wav)$/i.test(fn)) fn += '.mp4';
fetch(url).then(function (r) { return r.blob(); }).then(function (b) {
var a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = fn;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(function () { try { URL.revokeObjectURL(a.href); } catch (e) {} }, 4000);
tip('💾 \u5df2\u4fdd\u5b58');
}).catch(function () { tip('\u4e0b\u8f7d\u5931\u8d25'); });
});
}
function v25Share(url, name) {
try { if (navigator.share) { navigator.share({ title: name || 'video', url: url }); return; } } catch (e) {}
try { navigator.clipboard.writeText(url).then(function () { tip('🔗 \u94fe\u63a5\u5df2\u590d\u5236\uff0c\u53ef\u7c98\u8d34\u5230\u5fae\u4fe1/\u793e\u7fa4'); }, function () { tip(url); }); }
catch (e) { tip(url); }
}
function mixSelected() {
var ids = Object.keys(__resSel);
if (!ids.length) { tip('请先勾选要用于混剪/二创的素材'); return; }
var rows = [];
for (var i = 0; i < ids.length; i++) {
var r = __resRows[ids[i]] || {};
var u = String(r.url || '');
if (!u) continue;
rows.push({ id: String(ids[i]), url: u, name: r.name || ids[i],
kind: v25IsVideo(u) ? 'video' : 'image', role: 'material',
folder: r.folder || '', category: r.cat || '' });
}
if (!rows.length) { tip('所选素材没有可用的文件地址,请刷新文件管理后重试'); return; }
if (window.v25SetMixSel) window.v25SetMixSel(rows);
var lines = rows.map(function (x) {
return '・' + x.name + '(ID:' + x.id + ')' + x.url;
});
var inp = $('chat-input');
close();
if (inp) {
inp.value = '【素材混剪/二创】请用我勾选的 ' + rows.length +
' 项素材组织成片(优先复用自有素材与模板,素材不足再补生成)。\n' +
lines.join('\n');
try { inp.focus(); } catch (e) {}
}
try { if (typeof window.switchTab === 'function') window.switchTab('home'); } catch (e) {}
var sent = false;
try {
if (typeof window.sendMessage === 'function') {
setTimeout(function () { try { window.sendMessage(); } catch (e) {} }, 320);
sent = true;
}
} catch (e) {}
tip(sent ? '✍️ 已提交给协调员,正在执行…' : '✍️ 已写入对话框,点「发送」交给协调员执行');
}
/* ---------- 通知中心 ---------- */
H.tool_notify = function () {
open('🔔 通知中心', '加载中…
');
function load() {
var box = $('v25Nt'); if (!box) return;
fetch(API + '/notifications?user_id=' + encodeURIComponent(uid()) + '&limit=50').then(function (r) { return r.json().catch(function () { return []; }); })
.then(function (list) {
var arr = Array.isArray(list) ? list : (list.data || list.list || []);
if (!arr.length) { box.innerHTML = '暂无通知
'; return; }
box.innerHTML = arr.map(function (n) {
return '' +
'
' + esc(n.title || n.message || n.content || '通知') + '
' +
'
' + esc(n.created_at || '') + '
';
}).join('');
}).catch(function () { box.innerHTML = '加载失败'; });
}
window.v25NtRead = function (id) {
fetch(API + '/notifications/' + encodeURIComponent(id) + '/read?user_id=' + encodeURIComponent(uid()), { method: 'POST' }).catch(function () {});
load();
};
load();
};
/* ---------- 工具/文案类 ---------- */
H.tool_settings = function () {
close();
try { if (typeof window.switchTab === 'function') window.switchTab('profile'); } catch (e) {}
tip('资料/安全/偏好设置请在「我的」页操作');
};
H.tool_help = function () {
open('❓ 帮助中心',
'' +
'· 💬 对话:输入需求,AI 军团安排执行 · 🧩 应用市场:点卡片进入全屏操作台 · 🎤 语音/📷 拍照:对话页胶囊直达 · 📡 多平台发布:描述"发布到抖音/快手…" · 🔑 API Key:市场 → API Key 生成(绑定账号) · ⏰ 定时任务:让 AI 定时执行/发布
');
};
H.tool_sync = function () {
open('☁️ 云同步',
'' +
'· 账号/余额/订阅/素材/任务:登录同一账号全端互通 · 资料与偏好:保存即云端同步 ' +
'· 手机端专属数据(群聊/客户资料)按需同步规划中
');
};
H.membership = function () {
close();
try { if (typeof window.switchTab === 'function') window.switchTab('profile'); } catch (e) {}
tip('余额与订阅状态请在「我的」页查看;续费可到桌面端或公众号充值');
};
H.multi_platform = function (item) {
close();
var pref = (item && item.url) || '我想把视频发布到多平台,请帮我:';
var inp = $('chat-input');
if (inp) inp.value = pref;
try { if (typeof window.switchTab === 'function') window.switchTab('home'); } catch (e) {}
try { if (typeof window.sendMessage === 'function') setTimeout(function () { window.sendMessage(); }, 300); } catch (e) {}
};
/* ---------- WR-562k41: 视频级再创(换景/换装/动作迁移/换人) ---------- */
function v25StudioJob(jobId, box, tries) {
tries = tries || 0;
if (tries > 120) { v25ShowResult(box, '❌ 出片超时,请稍后在「我的 → 文件管理 → AI成片」查看', {}); return; }
fetch(API + '/ai-army/factory/jobs/' + encodeURIComponent(jobId) + '?user_id=' + encodeURIComponent(uid()) + '&_t=' + Date.now())
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
var job = (d && d.data) || {};
var st = String(job.status || '');
var vurl = String(job.video_url || '');
if (vurl) { v25ShowResult(box, '✅ 出片完成', { video_url: vurl }); return; }
if (st === 'failed' || st === 'cancelled') {
v25ShowResult(box, '❌ 出片失败:' + String(job.error || st).substring(0, 400), {});
return;
}
box.style.display = 'block';
box.innerHTML = '⏳ ' + esc(job.stage || st || '排队中') + ' · 进度 ' + (job.progress || 0) + '%
' +
'任务已进入计划体系(' + esc(job.skill_id || 'content_factory.video_restyle') + '),成片会自动归档到「AI成片」。
';
setTimeout(function () { v25StudioJob(jobId, box, tries + 1); }, 6000);
})
.catch(function () { setTimeout(function () { v25StudioJob(jobId, box, tries + 1); }, 6000); });
}
function v25StudioSubmit(payload, box) {
if (!box) return;
box.style.display = 'block';
box.innerHTML = '⏳ 正在提交到出片计划体系…';
var body = payload || {};
body.user_id = uid();
fetch(API + '/ai-army/skills/content_factory.video_restyle/run?async=1&scene=user_frontend&user_id=' + encodeURIComponent(uid()), {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
}).then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
if (!d || d.success === false) {
var msg = String((d && (d.message || (d.data && d.data.reply))) || '提交失败');
box.innerHTML = '提交失败:' + esc(msg.substring(0, 400)) + ' ';
return;
}
var jid = d.job_id || d.task_id || '';
if (!jid) { v25ShowResult(box, '任务已受理,请在「AI成片」查看', d); return; }
v25StudioJob(jid, box, 0);
}).catch(function (e) {
box.innerHTML = '网络错误:' + esc(e.message || '') + ' ';
});
}
function v25VideoEditPage(title, kind) {
var isEdit = kind === 'edit';
open(title,
fld('① 原视频(必填)', upBtn('video', 'vr_v', '📹 上传视频') + '' + v25PickBtn('video', 'vr_v') + '
') +
(isEdit ?
fld('② 参考图(换装必填;换景用于指定场景)', upBtn('image', 'vr_ref', '🖼️ 上传参考图') + '' + v25PickBtn('image', 'vr_ref') + '
') :
fld('② 人物形象图(必填)', upBtn('image', 'vr_img', '🖼️ 上传人物图') + '' + v25PickBtn('image', 'vr_img') + '
')) +
(isEdit ?
fld('③ 编辑类型', sel('vr_act', ['换背景/换场景', '换装'])) +
fld('④ 补充要求(可选)', area('vr_prompt', '例如:换成夜晚街边小店的烟火气场景,光线自然')) +
fld('⑤ 分辨率', sel('vr_res', ['720P', '1080P'])) :
fld('③ 生成模式', sel('vr_mode', ['wan-std', 'wan-pro']))) +
btn('vrGo', '🚀 提交出片') + result());
$('vrGo').onclick = function () {
var vid = UP.vr_v || '';
if (!vid) { tip('请先上传或选择原视频'); return; }
var p = null;
if (isEdit) {
var act = val('vr_act') === '换装' ? 'change_clothes' : 'change_background';
var ref = UP.vr_ref || '';
if (act === 'change_clothes' && !ref) { tip('换装必须上传参考服装图'); return; }
p = { action: act, video_url: vid, prompt: val('vr_prompt'), resolution: val('vr_res') || '720P' };
if (ref) p.reference_images = [ref];
} else {
var img = UP.vr_img || '';
if (!img) { tip('请先上传或选择人物形象图'); return; }
p = { action: kind === 'move' ? 'motion_transfer' : 'character_replace',
image_url: img, video_url: vid, mode: val('vr_mode') || 'wan-std' };
}
v25StudioSubmit(p, $('v25Res'));
};
}
H.video_restyle = function () { v25VideoEditPage('🎬 视频换景/换装', 'edit'); };
H.video_motion = function () { v25VideoEditPage('🕺 动作迁移', 'move'); };
H.video_character = function () { v25VideoEditPage('🎭 视频换人', 'mix'); };
/* ---------- 覆盖分发器:v24 已注册 → v25 扩展 → 通用兜底 ---------- */
window.mksOpenModule = function (item) {
var id = item && item.id;
if (!id) return;
window.__mksCurItem = item; // WR-530: 供派发前门禁预检取卡片能力键
var M1 = window.__mksV24Map || {};
if (M1[id]) { M1[id](); return; }
if (H[id]) { H[id](item); return; }
if (item && item.url && String(item.url).indexOf('http') === 0) {
try {
var _op = function () { try { window.open(item.url, '_blank'); } catch (e) {} };
__v25Confirm('「' + (item.name || id) + '」为独立页面,是否在新窗口打开?', { title: '打开外链' }).then(function (ok) { if (ok) _op(); });
} catch (e) {}
return;
}
if (item && item.type === 'chat') { H.multi_platform(item); return; }
close();
var inp = $('chat-input');
if (inp) { inp.value = '我想使用【' + (item.name || id) + '】,请帮我:'; }
tip('请描述具体需求,AI 会为你执行');
};
/* WR-562k16: 内嵌素材区勾选回调(写 UP, 与上传后等价) */
window.v25SetFile = function (key, url, name) {
try {
if (!url) return;
UP[key] = url;
var sp = $('v25up_' + key);
if (sp) { sp.textContent = '\u2705 已选用:' + String(name || '').substring(0, 18); sp.style.color = '#16a34a'; }
tip('\u2705 已选用「文件管理」里的素材');
} catch (e) {}
};
window.__mksV25Map = H;
console.log('[MobV25] 补全操作台就绪(图片/混剪/配音/字幕/音乐库/资源库/通知)');
})();