/* 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 '
' + l + '
' + i + '
'; } function area(id, ph) { return ''; } function input(id, ph) { return ''; } function sel(id, opts) { return ''; } 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 = '' + rows.map(function (t) { return ''; }).join(''); }).catch(function () {}); } function btn(id, label) { return ''; } 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 '
' + '未上传
' + '
'; } 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('⑤ 音色模板', '') + 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 = '' + '' + ''; 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 += '' + '' + ((f.protected || f.system) ? '' : '' + '') + ''; }); h += '' + '' + '' + '' + '' + '' + '' + '' + ''; 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 ''; }).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 ''; }).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] 补全操作台就绪(图片/混剪/配音/字幕/音乐库/资源库/通知)'); })();