Spaces:
Running
Running
Fix: No-op app_v2_shorts_fix.js — all voice/emotion/slide logic now built into app_v2.js to fix conflicts
Browse files- static/app_v2_shorts_fix.js +4 -381
static/app_v2_shorts_fix.js
CHANGED
|
@@ -1,383 +1,6 @@
|
|
| 1 |
-
//
|
| 2 |
-
//
|
| 3 |
-
|
| 4 |
(function(){
|
| 5 |
-
|
| 6 |
-
const VOICE_LIST = [
|
| 7 |
-
{id:'hoaimy', label:'🎙️ Hoài My (VI)', lang:'vi'},
|
| 8 |
-
{id:'namminh', label:'🎙️ Nam Minh (VI)', lang:'vi'},
|
| 9 |
-
{id:'andrew', label:'🎙️ Andrew (Multilingual)', lang:'en'},
|
| 10 |
-
{id:'jenny', label:'🎙️ Jenny (EN)', lang:'en'},
|
| 11 |
-
{id:'thalita', label:'🎙️ Thalita (PT)', lang:'pt'},
|
| 12 |
-
{id:'pt_francisco', label:'🎙️ Francisco (PT)', lang:'pt'},
|
| 13 |
-
{id:'ela', label:'🎙️ Ela (ES)', lang:'es'},
|
| 14 |
-
{id:'es_carlos', label:'🎙️ Carlos (ES)', lang:'es'},
|
| 15 |
-
{id:'denise', label:'🎙️ Denise (FR)', lang:'fr'},
|
| 16 |
-
{id:'katja', label:'🎙️ Katja (DE)', lang:'de'},
|
| 17 |
-
{id:'nanami', label:'🎙️ Nanami (JA)', lang:'ja'},
|
| 18 |
-
{id:'sunhee', label:'🎙️ SunHee (KO)', lang:'ko'},
|
| 19 |
-
{id:'xiaochen', label:'🎙️ XiaoChen (ZH)', lang:'zh'},
|
| 20 |
-
];
|
| 21 |
-
|
| 22 |
-
const EMOTION_LIST = [
|
| 23 |
-
{id:'neutral', label:'😐 Trung tính'},
|
| 24 |
-
{id:'happy', label:'😊 Vui vẻ'},
|
| 25 |
-
{id:'excited', label:'🔥 Hào hứng'},
|
| 26 |
-
{id:'sad', label:'😢 Buồn'},
|
| 27 |
-
{id:'humorous', label:'😂 Hài hước'},
|
| 28 |
-
{id:'serious', label:'⚠️ Nghiêm túc'},
|
| 29 |
-
{id:'urgent', label:'🚨 Khẩn cấp'},
|
| 30 |
-
{id:'warm', label:'💖 Ấm áp'},
|
| 31 |
-
];
|
| 32 |
-
|
| 33 |
-
// Language detection (lightweight)
|
| 34 |
-
function detectLanguage(text) {
|
| 35 |
-
if (!text) return 'vi';
|
| 36 |
-
var t = text.toLowerCase();
|
| 37 |
-
var chars = new Set(t);
|
| 38 |
-
// Vietnamese
|
| 39 |
-
var vnChars = 'đăâêôơưàảãạáằẳẵặắầẩẫậấèẻẽẹéềễểệếìỉĩịíòỏõọóồổỗộốờởỡợớùủũụúừửữựỳỷỹỵý';
|
| 40 |
-
var vnCount = 0;
|
| 41 |
-
for (var c of vnChars) { if (chars.has(c)) vnCount++; }
|
| 42 |
-
if (vnCount >= 2) return 'vi';
|
| 43 |
-
|
| 44 |
-
// Spanish markers
|
| 45 |
-
if (chars.has('ñ') || chars.has('¿') || chars.has('¡')) return 'es';
|
| 46 |
-
|
| 47 |
-
// Portuguese markers
|
| 48 |
-
if (chars.has('ã') || chars.has('õ')) return 'pt';
|
| 49 |
-
|
| 50 |
-
// English default
|
| 51 |
-
var words = t.split(/\s+/);
|
| 52 |
-
var enWords = ['the','is','at','which','on','and','or','but','this','that','with','from','have','been'];
|
| 53 |
-
var enCount = words.filter(function(w){return enWords.indexOf(w)>=0;}).length;
|
| 54 |
-
if (enCount >= 2) return 'en';
|
| 55 |
-
|
| 56 |
-
return 'vi';
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
// Auto-detect emotion
|
| 60 |
-
function detectEmotion(text) {
|
| 61 |
-
if (!text) return 'neutral';
|
| 62 |
-
var t = text.toLowerCase();
|
| 63 |
-
var kws = {
|
| 64 |
-
happy: ['vui','hạnh phúc','tuyệt','thành công','chiến thắng','feliz','maravilloso','happy','joy','wonderful','great','amazing','love','excellent'],
|
| 65 |
-
excited: ['hào hứng','phấn khích','đột phá','kỷ lục','đỉnh cao','emocionante','increíble','excited','thrilling','unbelievable','awesome','breakthrough'],
|
| 66 |
-
sad: ['buồn','đau','mất','thảm họa','khủng hoảng','triste','terrible','sad','unhappy','tragic','painful','death'],
|
| 67 |
-
humorous: ['hài hước','buồn cười','haha','đùa','engraçado','gracioso','funny','hilarious','joke','lol'],
|
| 68 |
-
serious: ['nghiêm trọng','khẩn cấp','quan trọng','lo ngại','sério','crítico','serious','critical','urgent','severe','crisis'],
|
| 69 |
-
urgent: ['khẩn cấp','báo động','ngay lập tức','urgent','breaking','alert','emergency'],
|
| 70 |
-
warm: ['ấm áp','tình cảm','yêu thương','warm','love','heart','touching','beautiful story']
|
| 71 |
-
};
|
| 72 |
-
var bestScore = 0;
|
| 73 |
-
var bestEmotion = 'neutral';
|
| 74 |
-
for (var em in kws) {
|
| 75 |
-
var score = 0;
|
| 76 |
-
for (var kw of kws[em]) {
|
| 77 |
-
if (t.indexOf(kw) >= 0) score++;
|
| 78 |
-
}
|
| 79 |
-
if (score > bestScore) { bestScore = score; bestEmotion = em; }
|
| 80 |
-
}
|
| 81 |
-
return bestEmotion;
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
// Auto-select voice based on language
|
| 85 |
-
function getAutoVoice(lang) {
|
| 86 |
-
var map = {vi:'hoaimy', pt:'thalita', es:'ela', en:'jenny', fr:'denise', de:'katja', ja:'nanami', ko:'sunhee', zh:'xiaochen'};
|
| 87 |
-
return map[lang] || 'hoaimy';
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
// Build voice+emotion selector HTML
|
| 91 |
-
function buildVoiceEmotionSelector(post) {
|
| 92 |
-
var lang = post.language || detectLanguage(post.title + ' ' + post.text);
|
| 93 |
-
var autoVoice = post.voice || getAutoVoice(lang);
|
| 94 |
-
var autoEmotion = post.emotion || detectEmotion(post.title + ' ' + post.text);
|
| 95 |
-
|
| 96 |
-
var h = '<div class="tts-selector" style="margin-top:10px;padding:10px;background:#1a1a1a;border:1px solid #2a2a2a;border-radius:10px">';
|
| 97 |
-
h += '<div class="tts-selector-label" style="font-size:11px;color:#888;margin-bottom:6px">🎙️ Giọng đọc (ngôn ngữ: ' + lang.toUpperCase() + '):</div>';
|
| 98 |
-
h += '<div class="tts-voice-btns" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">';
|
| 99 |
-
VOICE_LIST.forEach(function(v){
|
| 100 |
-
var sel = v.id === autoVoice ? 'border-color:#5cb87a;background:#1a2a1f' : 'border-color:#333;background:#222';
|
| 101 |
-
h += '<button class="tts-voice-btn" data-voice="'+v.id+'" style="'+sel+';border:1px solid;color:#ccc;padding:4px 8px;border-radius:10px;font-size:10px;cursor:pointer">'+v.label+'</button>';
|
| 102 |
-
});
|
| 103 |
-
h += '</div>';
|
| 104 |
-
h += '<div class="tts-selector-label" style="font-size:11px;color:#888;margin-bottom:6px">😊 Cảm xúc:</div>';
|
| 105 |
-
h += '<div class="tts-emotion-btns" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">';
|
| 106 |
-
EMOTION_LIST.forEach(function(e){
|
| 107 |
-
var sel = e.id === autoEmotion ? 'border-color:#5cb87a;background:#1a2a1f' : 'border-color:#333;background:#222';
|
| 108 |
-
h += '<button class="tts-emotion-btn" data-emotion="'+e.id+'" style="'+sel+';border:1px solid;color:#ccc;padding:4px 8px;border-radius:10px;font-size:10px;cursor:pointer">'+e.label+'</button>';
|
| 109 |
-
});
|
| 110 |
-
h += '</div>';
|
| 111 |
-
h += '<div class="tts-speed-row" style="display:flex;align-items:center;gap:6px;margin-bottom:8px"><span style="font-size:11px;color:#888">⚡ Tốc độ:</span>';
|
| 112 |
-
h += '<select class="tts-speed" style="background:#222;border:1px solid #333;color:#ccc;padding:3px 8px;border-radius:8px;font-size:10px">';
|
| 113 |
-
h += '<option value="0.85">0.85x Chậm</option><option value="1.0">1.0x Bình thường</option><option value="1.2" selected>1.2x Nhanh</option><option value="1.35">1.35x Rất nhanh</option>';
|
| 114 |
-
h += '</select></div>';
|
| 115 |
-
h += '<button class="tts-create-btn" style="width:100%;background:#2d8659;border:0;color:#fff;padding:8px;border-radius:10px;font-size:11px;font-weight:700;cursor:pointer">🎬 Tạo Short AI</button>';
|
| 116 |
-
h += '<input type="hidden" class="tts-selected-voice" value="' + autoVoice + '">';
|
| 117 |
-
h += '<input type="hidden" class="tts-selected-emotion" value="' + autoEmotion + '">';
|
| 118 |
-
h += '</div>';
|
| 119 |
-
return h;
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
// Patch readWallPost to include full voice+emotion selector
|
| 123 |
-
var origReadWallPost = window.readWallPost;
|
| 124 |
-
window.readWallPost = function(i){
|
| 125 |
-
var p = _wallPosts[i];
|
| 126 |
-
if(!p) return;
|
| 127 |
-
showView('view-article');
|
| 128 |
-
var images = p.images || [];
|
| 129 |
-
var imgGallery = '';
|
| 130 |
-
if(images.length > 0){
|
| 131 |
-
imgGallery = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin:8px 0">';
|
| 132 |
-
images.slice(0,6).forEach(function(imgUrl){
|
| 133 |
-
imgGallery += '<img src="'+esc(imgUrl)+'" style="width:80px;height:60px;object-fit:cover;border-radius:4px" onerror="this.style.display=\'none\'">';
|
| 134 |
-
});
|
| 135 |
-
imgGallery += '</div>';
|
| 136 |
-
}
|
| 137 |
-
var hasVideo = p.video && p.video.length > 0;
|
| 138 |
-
var voiceEmotionHtml = '';
|
| 139 |
-
if(!hasVideo){
|
| 140 |
-
voiceEmotionHtml = buildVoiceEmotionSelector(p);
|
| 141 |
-
} else {
|
| 142 |
-
voiceEmotionHtml = '<div style="margin-top:10px;padding:10px;background:#1a2a1f;border-radius:8px;font-size:11px;color:#5cb87a">🎬 Voice: ' + (p.voice||'hoaimy') + ' | 😊 Emotion: ' + (p.emotion||'neutral') + ' | ⚡ Speed: ' + (p.short_speed||'1.2') + 'x</div>';
|
| 143 |
-
voiceEmotionHtml += buildVoiceEmotionSelector(p);
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
document.getElementById('view-article').innerHTML =
|
| 147 |
-
'<button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button>' +
|
| 148 |
-
'<div class="article-view"><span class="badge badge-ai">AI</span><h1 class="article-title">'+esc(p.title)+'</h1>' +
|
| 149 |
-
imgGallery +
|
| 150 |
-
'<p class="article-p" style="white-space:pre-wrap">'+esc(p.text)+'</p>' +
|
| 151 |
-
(hasVideo ? '<video class="article-img" src="'+esc(p.video)+'" controls playsinline style="max-height:400px"></video>' : '') +
|
| 152 |
-
'<div class="article-actions">' +
|
| 153 |
-
(hasVideo ? '<button onclick="openShortAIFeed('+i+')">🎬 Xem Short</button>' : '') +
|
| 154 |
-
'</div>' +
|
| 155 |
-
voiceEmotionHtml +
|
| 156 |
-
'</div>';
|
| 157 |
-
window.scrollTo(0,0);
|
| 158 |
-
|
| 159 |
-
// Bind voice/emotion selectors
|
| 160 |
-
setTimeout(function(){
|
| 161 |
-
var container = document.querySelector('.tts-selector');
|
| 162 |
-
if(!container) return;
|
| 163 |
-
|
| 164 |
-
container.querySelectorAll('.tts-voice-btn').forEach(function(btn){
|
| 165 |
-
btn.addEventListener('click', function(){
|
| 166 |
-
container.querySelectorAll('.tts-voice-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});
|
| 167 |
-
this.style.borderColor='#5cb87a';
|
| 168 |
-
this.style.background='#1a2a1f';
|
| 169 |
-
container.querySelector('.tts-selected-voice').value = this.dataset.voice;
|
| 170 |
-
});
|
| 171 |
-
});
|
| 172 |
-
container.querySelectorAll('.tts-emotion-btn').forEach(function(btn){
|
| 173 |
-
btn.addEventListener('click', function(){
|
| 174 |
-
container.querySelectorAll('.tts-emotion-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});
|
| 175 |
-
this.style.borderColor='#5cb87a';
|
| 176 |
-
this.style.background='#1a2a1f';
|
| 177 |
-
container.querySelector('.tts-selected-emotion').value = this.dataset.emotion;
|
| 178 |
-
});
|
| 179 |
-
});
|
| 180 |
-
var createBtn = container.querySelector('.tts-create-btn');
|
| 181 |
-
if(createBtn){
|
| 182 |
-
createBtn.addEventListener('click', function(){
|
| 183 |
-
var postId = p.id;
|
| 184 |
-
var voice = container.querySelector('.tts-selected-voice').value;
|
| 185 |
-
var emotion = container.querySelector('.tts-selected-emotion').value;
|
| 186 |
-
var speed = parseFloat(container.querySelector('.tts-speed').value) || 1.2;
|
| 187 |
-
window.makeShortVideo(postId, this, voice, speed, emotion);
|
| 188 |
-
});
|
| 189 |
-
}
|
| 190 |
-
}, 100);
|
| 191 |
-
};
|
| 192 |
-
|
| 193 |
-
// Patch makeShortVideo to accept emotion parameter
|
| 194 |
-
var origMakeShortVideo = window.makeShortVideo;
|
| 195 |
-
window.makeShortVideo = function(postId, btn, voice, speed, emotion){
|
| 196 |
-
if(!postId) return;
|
| 197 |
-
var origText = btn ? btn.textContent : '🎬 Tạo Video';
|
| 198 |
-
if(btn){btn.disabled=true;btn.textContent='⏳ Đang tạo...';}
|
| 199 |
-
toast('⏳ Đang tạo video shorts...');
|
| 200 |
-
try{
|
| 201 |
-
var url = '/api/ai/short/' + encodeURIComponent(postId);
|
| 202 |
-
var params = [];
|
| 203 |
-
if(voice) params.push('voice='+encodeURIComponent(voice));
|
| 204 |
-
if(speed) params.push('speed='+encodeURIComponent(speed));
|
| 205 |
-
if(emotion) params.push('emotion='+encodeURIComponent(emotion));
|
| 206 |
-
if(params.length) url += '?' + params.join('&');
|
| 207 |
-
|
| 208 |
-
fetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({voice:voice||'hoaimy', emotion:emotion||'neutral', speed:speed||1.2})})
|
| 209 |
-
.then(function(r){return r.json().then(function(j){return {ok:r.ok, j:j};});})
|
| 210 |
-
.then(function(result){
|
| 211 |
-
if(!result.ok || result.j.error) throw new Error(result.j.error||'Lỗi tạo video');
|
| 212 |
-
toast('✅ Đã tạo video shorts!');
|
| 213 |
-
var p = _wallPosts.find(function(x){return String(x.id)===String(postId);});
|
| 214 |
-
if(p){
|
| 215 |
-
p.video = result.j.video;
|
| 216 |
-
p.voice = result.j.voice;
|
| 217 |
-
p.emotion = result.j.emotion;
|
| 218 |
-
var itemId = 'wall-item-'+postId;
|
| 219 |
-
var el = document.getElementById(itemId);
|
| 220 |
-
if(el){
|
| 221 |
-
var idx = _wallPosts.indexOf(p);
|
| 222 |
-
el.outerHTML = makeWallItem(p, idx);
|
| 223 |
-
var newEl = document.getElementById(itemId);
|
| 224 |
-
if(newEl) newEl.className = 'wall-item wall-item-new';
|
| 225 |
-
}
|
| 226 |
-
}
|
| 227 |
-
refreshShortAISlider();
|
| 228 |
-
})
|
| 229 |
-
.catch(function(e){
|
| 230 |
-
toast('❌ '+e.message);
|
| 231 |
-
if(btn){btn.disabled=false;btn.textContent=origText;}
|
| 232 |
-
});
|
| 233 |
-
}catch(e){
|
| 234 |
-
toast('❌ '+e.message);
|
| 235 |
-
if(btn){btn.disabled=false;btn.textContent=origText;}
|
| 236 |
-
}
|
| 237 |
-
};
|
| 238 |
-
|
| 239 |
-
// Also patch rewriteArticle in app_v2.js to use rewrite_share (not rewrite_slide)
|
| 240 |
-
window.rewriteArticle = async function(){
|
| 241 |
-
var url = window._currentArticle && window._currentArticle.url;
|
| 242 |
-
if(!url) return;
|
| 243 |
-
toast('⏳ Đang tạo bài đăng Tường AI...');
|
| 244 |
-
try {
|
| 245 |
-
var r = await fetch('/api/rewrite_share', {
|
| 246 |
-
method: 'POST',
|
| 247 |
-
headers: {'Content-Type': 'application/json'},
|
| 248 |
-
body: JSON.stringify({url: url, context: document.querySelector('.article-view') ? document.querySelector('.article-view').innerText.slice(0,14000) : ''})
|
| 249 |
-
});
|
| 250 |
-
var j = await r.json();
|
| 251 |
-
if (!r.ok || j.error) throw new Error(j.error);
|
| 252 |
-
toast('✅ Đã đăng Tường AI!');
|
| 253 |
-
if (j.post) prependWallPost(j.post);
|
| 254 |
-
if (j.post && typeof goToWallPost === 'function') goToWallPost(j.post.id);
|
| 255 |
-
// Show slides if available
|
| 256 |
-
if (j.slides && j.slides.length) {
|
| 257 |
-
setTimeout(function(){ showSlidePreview(j.slides, j.post ? j.post.title : ''); }, 500);
|
| 258 |
-
}
|
| 259 |
-
// Show voice + emotion selector
|
| 260 |
-
if (j.post && !j.post.video) {
|
| 261 |
-
setTimeout(function(){ showVoiceEmotionSelector(j.post.id, j.post.title, j.post.text); }, 1000);
|
| 262 |
-
}
|
| 263 |
-
} catch(e) {
|
| 264 |
-
toast('❌ ' + e.message);
|
| 265 |
-
}
|
| 266 |
-
};
|
| 267 |
-
|
| 268 |
-
// Show voice+emotion selector popup (same as in rewrite_fix_v2.js)
|
| 269 |
-
window.showVoiceEmotionSelector = function(postId, title, text) {
|
| 270 |
-
var overlay = document.createElement('div');
|
| 271 |
-
overlay.id = 'voice-emotion-selector';
|
| 272 |
-
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px';
|
| 273 |
-
var box = document.createElement('div');
|
| 274 |
-
box.style.cssText = 'background:#1a1a1a;border:2px solid #2d8659;border-radius:16px;padding:20px;max-width:400px;width:100%;max-height:80vh;overflow-y:auto';
|
| 275 |
-
var lang = detectLanguage(title + ' ' + text);
|
| 276 |
-
var autoEmotion = detectEmotion(title + ' ' + text);
|
| 277 |
-
var h = '<h3 style="color:#5cb87a;margin-bottom:12px;font-size:16px">🎬 Tạo Short AI (ngôn ngữ: '+lang.toUpperCase()+')</h3>';
|
| 278 |
-
h += '<div style="margin-bottom:12px"><div style="color:#aaa;font-size:11px;margin-bottom:6px">🎙️ Chọn giọng đọc:</div>';
|
| 279 |
-
VOICE_LIST.forEach(function(v){
|
| 280 |
-
var sel = v.id === getAutoVoice(lang) ? 'border-color:#5cb87a;background:#1a2a1f' : 'border-color:#333;background:#222';
|
| 281 |
-
h += '<button class="ve-voice-btn" data-voice="'+v.id+'" style="display:inline-block;'+sel+';border:1px solid;color:#ccc;padding:5px 10px;border-radius:12px;font-size:10px;margin:2px;cursor:pointer">'+v.label+'</button>';
|
| 282 |
-
});
|
| 283 |
-
h += '</div>';
|
| 284 |
-
h += '<div style="margin-bottom:12px"><div style="color:#aaa;font-size:11px;margin-bottom:6px">😊 Chọn cảm xúc:</div>';
|
| 285 |
-
EMOTION_LIST.forEach(function(e){
|
| 286 |
-
var sel = e.id === autoEmotion ? 'border-color:#5cb87a;background:#1a2a1f' : 'border-color:#333;background:#222';
|
| 287 |
-
h += '<button class="ve-emotion-btn" data-emotion="'+e.id+'" style="display:inline-block;'+sel+';border:1px solid;color:#ccc;padding:5px 10px;border-radius:12px;font-size:10px;margin:2px;cursor:pointer">'+e.label+'</button>';
|
| 288 |
-
});
|
| 289 |
-
h += '</div>';
|
| 290 |
-
h += '<div style="margin-bottom:12px"><div style="color:#aaa;font-size:11px;margin-bottom:6px">⚡ Tốc độ:</div>';
|
| 291 |
-
h += '<select id="ve-speed" style="background:#222;border:1px solid #333;color:#ccc;padding:6px 12px;border-radius:10px;font-size:11px">';
|
| 292 |
-
h += '<option value="0.85">0.85x Chậm</option><option value="1.0">1.0x Bình thường</option><option value="1.2" selected>1.2x Nhanh</option><option value="1.35">1.35x Rất nhanh</option></select></div>';
|
| 293 |
-
h += '<div style="display:flex;gap:8px"><button id="ve-create-btn" style="flex:1;background:#2d8659;border:0;color:#fff;padding:10px;border-radius:12px;font-size:12px;font-weight:700;cursor:pointer">🎬 Tạo Short</button>';
|
| 294 |
-
h += '<button id="ve-cancel-btn" style="background:#333;border:0;color:#ccc;padding:10px 16px;border-radius:12px;font-size:11px;cursor:pointer">✕</button></div>';
|
| 295 |
-
h += '<div id="ve-status" style="color:#888;font-size:10px;margin-top:8px;display:none"></div>';
|
| 296 |
-
box.innerHTML = h;
|
| 297 |
-
overlay.appendChild(box);
|
| 298 |
-
document.body.appendChild(overlay);
|
| 299 |
-
var selectedVoice = getAutoVoice(lang);
|
| 300 |
-
var selectedEmotion = autoEmotion;
|
| 301 |
-
box.querySelectorAll('.ve-voice-btn').forEach(function(btn){
|
| 302 |
-
btn.addEventListener('click', function(){
|
| 303 |
-
box.querySelectorAll('.ve-voice-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});
|
| 304 |
-
this.style.borderColor='#5cb87a';this.style.background='#1a2a1f';
|
| 305 |
-
selectedVoice = this.dataset.voice;
|
| 306 |
-
});
|
| 307 |
-
});
|
| 308 |
-
box.querySelectorAll('.ve-emotion-btn').forEach(function(btn){
|
| 309 |
-
btn.addEventListener('click', function(){
|
| 310 |
-
box.querySelectorAll('.ve-emotion-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});
|
| 311 |
-
this.style.borderColor='#5cb87a';this.style.background='#1a2a1f';
|
| 312 |
-
selectedEmotion = this.dataset.emotion;
|
| 313 |
-
});
|
| 314 |
-
});
|
| 315 |
-
box.querySelector('#ve-cancel-btn').addEventListener('click', function(){overlay.remove();});
|
| 316 |
-
box.querySelector('#ve-create-btn').addEventListener('click', async function(){
|
| 317 |
-
this.disabled = true; this.textContent = '⏳ Đang tạo...';
|
| 318 |
-
box.querySelector('#ve-status').style.display = 'block';
|
| 319 |
-
box.querySelector('#ve-status').textContent = 'Đang tạo video shorts...';
|
| 320 |
-
try {
|
| 321 |
-
var speed = parseFloat(box.querySelector('#ve-speed').value) || 1.2;
|
| 322 |
-
var r = await fetch('/api/ai/short/' + encodeURIComponent(postId), {
|
| 323 |
-
method: 'POST', headers: {'Content-Type': 'application/json'},
|
| 324 |
-
body: JSON.stringify({voice: selectedVoice, emotion: selectedEmotion, speed: speed})
|
| 325 |
-
});
|
| 326 |
-
var j = await r.json();
|
| 327 |
-
if (!r.ok || j.error) throw new Error(j.error || 'Lỗi tạo video');
|
| 328 |
-
toast('✅ Đã tạo Short AI!');
|
| 329 |
-
overlay.remove();
|
| 330 |
-
var p = _wallPosts.find(function(x){return String(x.id)===String(postId);});
|
| 331 |
-
if (p) { p.video = j.video; p.voice = j.voice; p.emotion = j.emotion; }
|
| 332 |
-
var track = document.getElementById('ai-wall-track');
|
| 333 |
-
if (track) {
|
| 334 |
-
var idx = _wallPosts.findIndex(function(x){return String(x.id)===String(postId);});
|
| 335 |
-
if (idx >= 0 && track.children[idx]) {
|
| 336 |
-
track.children[idx].outerHTML = makeWallItem(p, idx);
|
| 337 |
-
}
|
| 338 |
-
}
|
| 339 |
-
refreshShortAISlider();
|
| 340 |
-
} catch(e) {
|
| 341 |
-
this.disabled = false; this.textContent = '🎬 Tạo Short';
|
| 342 |
-
box.querySelector('#ve-status').textContent = '❌ ' + e.message;
|
| 343 |
-
}
|
| 344 |
-
});
|
| 345 |
-
};
|
| 346 |
-
|
| 347 |
-
// Show slides fullscreen overlay
|
| 348 |
-
window.showSlidePreview = function(slides, title) {
|
| 349 |
-
if (!slides || !slides.length) return;
|
| 350 |
-
var overlay = document.createElement('div');
|
| 351 |
-
overlay.id = 'slide-preview';
|
| 352 |
-
overlay.style.cssText = 'position:fixed;inset:0;background:#000;z-index:99999;display:flex;flex-direction:column;overflow:hidden';
|
| 353 |
-
var currentSlide = 0;
|
| 354 |
-
function renderSlide(idx) {
|
| 355 |
-
var s = slides[idx];
|
| 356 |
-
overlay.innerHTML = '<div style="position:absolute;top:10px;left:10px;right:10px;display:flex;justify-content:space-between;align-items:center;z-index:2">' +
|
| 357 |
-
'<button onclick="document.getElementById(\'slide-preview\').remove()" style="background:rgba(0,0,0,.6);border:0;color:#fff;padding:8px 14px;border-radius:20px;font-size:12px;cursor:pointer">✕ Đóng</button>' +
|
| 358 |
-
'<span style="color:#fff;font-size:11px;background:rgba(0,0,0,.6);padding:4px 10px;border-radius:10px">' + (idx+1) + '/' + slides.length + '</span>' +
|
| 359 |
-
'</div>' +
|
| 360 |
-
'<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:20px">' +
|
| 361 |
-
(s.image ? '<img src="' + esc(s.image) + '" style="max-width:100%;max-height:60vh;border-radius:10px;object-fit:contain" onerror="this.style.display=\'none\'">' : '') +
|
| 362 |
-
'</div>' +
|
| 363 |
-
'<div style="padding:16px 20px;background:linear-gradient(transparent,rgba(0,0,0,.9));min-height:100px">' +
|
| 364 |
-
'<p style="color:#fff;font-size:14px;line-height:1.6;text-align:center">' + esc(s.text) + '</p>' +
|
| 365 |
-
'</div>' +
|
| 366 |
-
'<div style="display:flex;gap:10px;padding:10px 20px 20px;justify-content:center">' +
|
| 367 |
-
'<button onclick="prevSlide()" style="background:#333;border:0;color:#fff;padding:10px 20px;border-radius:20px;font-size:12px;cursor:pointer"' + (idx===0?' disabled style="opacity:.3"':'') + '>← Trước</button>' +
|
| 368 |
-
'<button onclick="nextSlideRip('+idx+')" id="slide-next-btn" style="background:#2d8659;border:0;color:#fff;padding:10px 20px;border-radius:20px;font-size:12px;cursor:pointer"' + (idx===slides.length-1?' disabled style="opacity:.3"':'') + '>Tiếp →</button>' +
|
| 369 |
-
'</div>';
|
| 370 |
-
}
|
| 371 |
-
window.nextSlideRip = function(currentIdx) { if (currentIdx < slides.length - 1) { currentSlide = currentIdx + 1; renderSlide(currentSlide); } };
|
| 372 |
-
window.prevSlide = function() { if (currentSlide > 0) { currentSlide--; renderSlide(currentSlide); } };
|
| 373 |
-
renderSlide(0);
|
| 374 |
-
document.body.appendChild(overlay);
|
| 375 |
-
var startX = 0;
|
| 376 |
-
overlay.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; });
|
| 377 |
-
overlay.addEventListener('touchend', function(e) {
|
| 378 |
-
var diff = e.changedTouches[0].clientX - startX;
|
| 379 |
-
if (diff < -50) nextSlideRip(currentSlide);
|
| 380 |
-
else if (diff > 50) prevSlide();
|
| 381 |
-
});
|
| 382 |
-
};
|
| 383 |
})();
|
|
|
|
| 1 |
+
// app_v2_shorts_fix.js - No-op patch
|
| 2 |
+
// All voice+emotion+slide functionality is now built into app_v2.js directly.
|
| 3 |
+
// This file is kept for backward compatibility but does not override any functions.
|
| 4 |
(function(){
|
| 5 |
+
console.log("[VNEWS] Shorts fix system loaded (built-in)");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
})();
|