bep40 commited on
Commit
53ee7c5
·
verified ·
1 Parent(s): 4076652

Consolidate AI wall, persistent comments/shorts, direct shorts player, better Qwen topics

Browse files
Files changed (1) hide show
  1. ai_runtime_final7.py +193 -0
ai_runtime_final7.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Final7: consolidate to one AI wall, persistent comments/shorts, direct Shorts player, better AI topic writing."""
2
+ import os, re, json, time, requests
3
+ from urllib.parse import urlparse, quote
4
+ import ai_runtime_final6 as f6
5
+ from ai_runtime_final6 import app, base, rt, HTMLResponse, JSONResponse, Request, Query, FileResponse
6
+ try:
7
+ import main as main_mod
8
+ except Exception:
9
+ main_mod=None
10
+
11
+ SPACE_URL="https://bep40-vnews.hf.space"
12
+ DATA_DIR="/data" if os.path.isdir('/data') else "/app/data"
13
+ AI_INTERACTIONS_FILE=os.path.join(DATA_DIR,'ai_interactions.json')
14
+ AI_SHORT_INDEX_FILE=os.path.join(DATA_DIR,'ai_short_index.json')
15
+ SHORTS_CACHE={"t":0,"d":[]}
16
+ CHANNELS=["baodantri7941","baosuckhoedoisongboyte"]
17
+
18
+
19
+ def clean(s):
20
+ import html as html_lib
21
+ return re.sub(r"\s+"," ",html_lib.unescape(s or "")).strip()
22
+
23
+ def _domain(u):
24
+ try:return urlparse(u or '').netloc.replace('www.','')
25
+ except Exception:return ''
26
+
27
+ def _load(path,default):
28
+ try:
29
+ if os.path.exists(path):
30
+ with open(path,'r',encoding='utf-8') as f:return json.load(f)
31
+ except Exception:pass
32
+ return default
33
+
34
+ def _save(path,data):
35
+ try:
36
+ os.makedirs(os.path.dirname(path),exist_ok=True);tmp=path+'.tmp'
37
+ with open(tmp,'w',encoding='utf-8') as f:json.dump(data,f,ensure_ascii=False)
38
+ os.replace(tmp,path)
39
+ except Exception:pass
40
+
41
+ def _fallback_shorts():
42
+ seen=set();out=[];c=[]
43
+ try:c+=(getattr(main_mod,'SHORTS_FALLBACK',[]) or [])
44
+ except Exception:pass
45
+ hard=[('Lu_iCQ5YwNM','Công an lập hồ sơ xử lý người phụ nữ chửi bới, tát tài xế ô tô | Dân trí','baodantri7941'),('CwWvijF8BOA','Chú rể bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước | Dân trí','baodantri7941'),('tvPewsc2ph4','Tính năng ẩn trên iPhone giúp giảm mỏi mắt | Dân trí','baodantri7941'),('7Pd6vZ2Lz1M','Hành động ấm lòng trong tìm kiếm học sinh tử vong ở sông Lô | SKĐS','baosuckhoedoisongboyte'),('SlHLt_ZyPiE','Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc - Nam | SKĐS','baosuckhoedoisongboyte'),('IUOprcJyYr4','Phụ nữ táo bón có phải do lười ăn rau? | SKĐS','baosuckhoedoisongboyte')]
46
+ for vid,title,ch in hard:c.append({'id':vid,'title':title,'channel':ch})
47
+ for v in c:
48
+ vid=v.get('id')
49
+ if vid and vid not in seen:
50
+ seen.add(vid)
51
+ out.append({'id':vid,'title':v.get('title','YouTube Short'),'channel':v.get('channel',''),'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
52
+ return out
53
+
54
+ def _fresh_shorts():
55
+ seen=set();out=[]
56
+ for ch in CHANNELS:
57
+ got=f6.f5.f4.f3._youtube_shorts_ytdlp(ch,20) or f6.f5.f4.f3._youtube_shorts_html(ch,20)
58
+ for v in got:
59
+ vid=v.get('id')
60
+ if vid and vid not in seen:
61
+ seen.add(vid);out.append(v)
62
+ for v in _fallback_shorts():
63
+ if v['id'] not in seen:
64
+ seen.add(v['id']);out.append(v)
65
+ return out[:60]
66
+
67
+ # Remove conflicting routes.
68
+ _PATCH={('/api/shorts','GET'),('/api/topic_post','POST'),('/api/ai/interact','POST'),('/api/ai/short/{post_id}','POST'),('/api/ai_shorts','GET'),('/api/ai/short-file/{file_id}','GET'),('/','GET')}
69
+ app.router.routes=[r for r in app.router.routes if not any(getattr(r,'path',None)==p and m in getattr(r,'methods',set()) for p,m in _PATCH)]
70
+
71
+ @app.get('/api/shorts')
72
+ def api_shorts(refresh:int=Query(default=0)):
73
+ now=time.time()
74
+ if not refresh and SHORTS_CACHE['d'] and now-SHORTS_CACHE['t']<900:return JSONResponse(SHORTS_CACHE['d'])
75
+ data=_fresh_shorts();SHORTS_CACHE.update({'t':now,'d':data});return JSONResponse(data)
76
+
77
+ @app.post('/api/topic_post')
78
+ async def topic_post_quality(request:Request):
79
+ body=await request.json();topic=clean(body.get('topic',''))
80
+ if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
81
+ img=f6.f5._topic_image(topic) if hasattr(f6.f5,'_topic_image') else base.pollinations_image_url(topic)
82
+ prompt=f"""Bạn là một cây bút phân tích chuyên sâu của VNEWS. Người dùng muốn một bài viết chất lượng về chủ đề: {topic}
83
+
84
+ Hãy sử dụng kiến thức tổng hợp của bạn để viết một bài hoàn chỉnh, sâu và hữu ích. KHÔNG lập dàn ý, KHÔNG nói chung chung, KHÔNG hướng dẫn cách viết.
85
+
86
+ Bắt buộc:
87
+ - Tiêu đề cụ thể, hấp dẫn.
88
+ - Mở đầu đi thẳng vào bản chất của chủ đề.
89
+ - Giải thích các khái niệm/bối cảnh quan trọng để người đọc hiểu vấn đề.
90
+ - Cung cấp nhận định, ví dụ cụ thể, hệ quả hoặc ý nghĩa thực tế.
91
+ - Nếu là thể thao: nói về nhân vật/đội bóng, bối cảnh, ý nghĩa chuyên môn/lịch sử, vì sao đáng chú ý.
92
+ - Nếu là công nghệ/xã hội/giáo dục: nói về cơ chế, ứng dụng, lợi ích, rủi ro, hiểu lầm phổ biến.
93
+ - Tránh bịa số liệu thời sự; nếu không chắc, diễn đạt thận trọng.
94
+ - Độ dài 700-1000 chữ tiếng Việt.
95
+ - Cuối bài ghi: Nguồn tham khảo: Qwen2.5-VL / kiến thức tổng hợp.
96
+ """
97
+ text=await base.qwen_generate(prompt,image_url=img,max_tokens=1800)
98
+ if not text:text=f"{topic}\n\n{topic} là một chủ đề đáng chú ý vì nó liên quan đến bối cảnh, tác động và những hiểu lầm thường gặp trong đời sống. Bài viết này cung cấp một góc nhìn tổng hợp, giải thích bản chất vấn đề và các điểm cần lưu ý để người đọc hiểu sâu hơn.\n\nNguồn tham khảo: Qwen2.5-VL / kiến thức tổng hợp."
99
+ post=base.make_post(topic,text,img,'','topic_qwen',sources=[{'title':'Qwen2.5-VL / kiến thức tổng hợp','url':'','via':'Qwen2.5-VL'}]);post['images']=[img]
100
+ posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
101
+ return JSONResponse({'post':post})
102
+
103
+ @app.post('/api/ai/interact')
104
+ async def ai_interact(request:Request):
105
+ body=await request.json();pid=str(body.get('id','')).strip();kind=str(body.get('kind','wall')).strip();action=str(body.get('action','')).strip();text=clean(body.get('text',''));title=clean(body.get('title',''));context=clean(body.get('context',''))
106
+ if not pid:return JSONResponse({'error':'missing id'},status_code=400)
107
+ db=_load(AI_INTERACTIONS_FILE,{})
108
+ key=kind+':'+pid
109
+ st=db.get(key) or {'views':0,'likes':0,'comments':[],'asks':[]}
110
+ if action=='view':st['views']=int(st.get('views',0))+1
111
+ elif action=='like':st['likes']=int(st.get('likes',0))+1
112
+ elif action=='comment' and text:
113
+ st.setdefault('comments',[]).insert(0,{'text':text[:300],'ts':int(time.time())});st['comments']=st['comments'][:100]
114
+ elif action=='ask' and text:
115
+ if kind in ('ai','short','wall'):
116
+ posts=base._load_ai_wall();p=next((x for x in posts if str(x.get('id'))==pid),{})
117
+ title=title or p.get('title','');context=context or (p.get('text') or '')
118
+ if not context:context=title or pid
119
+ prompt=f"""Bạn là trợ lý VNEWS. Trả lời đúng trọng tâm câu hỏi dựa trên ngữ cảnh video/bài viết.
120
+
121
+ Tiêu đề: {title}
122
+ Ngữ cảnh/nội dung mô tả: {context[:6000]}
123
+ Câu hỏi: {text}
124
+
125
+ Hãy trả lời bằng tiếng Việt, cụ thể và hữu ích. Nếu đây là Shorts YouTube chỉ có tiêu đề, hãy nói rõ bạn đang suy luận từ tiêu đề/mô tả, nhưng vẫn phân tích đúng trọng tâm tiêu đề.
126
+ """
127
+ ans=await base.qwen_generate(prompt,max_tokens=1000)
128
+ if not ans:ans='AI chưa trả lời được lúc này. Bạn thử hỏi cụ thể hơn.'
129
+ st.setdefault('asks',[]).insert(0,{'q':text[:300],'a':ans[:1800],'ts':int(time.time())});st['asks']=st['asks'][:60]
130
+ db[key]=st;_save(AI_INTERACTIONS_FILE,db);return JSONResponse({'stats':st})
131
+
132
+ @app.post('/api/ai/short/{post_id}')
133
+ async def ai_short_persistent(post_id:str,request:Request):
134
+ # Reuse robust generator from final1/final2 chain.
135
+ res=await f6.f5.f4.f3.f2.f1.final_short(post_id,request) if hasattr(f6.f5.f4.f3.f2.f1,'final_short') else await f6.f5.f4.f3.f2.f1.short_segments(post_id,request)
136
+ try:
137
+ data=json.loads(res.body.decode()) if hasattr(res,'body') else {}
138
+ video=data.get('video')
139
+ if video:
140
+ idx=_load(AI_SHORT_INDEX_FILE,[])
141
+ if not any(x.get('post_id')==post_id for x in idx):idx.insert(0,{'post_id':post_id,'video':video,'ts':int(time.time())})
142
+ _save(AI_SHORT_INDEX_FILE,idx[:200])
143
+ except Exception:pass
144
+ return res
145
+
146
+ @app.get('/api/ai_shorts')
147
+ def api_ai_shorts():
148
+ posts=base._load_ai_wall();idx=_load(AI_SHORT_INDEX_FILE,[]);out=[]
149
+ for rec in idx:
150
+ p=next((x for x in posts if str(x.get('id'))==str(rec.get('post_id'))),None)
151
+ if p:
152
+ p=dict(p);p['video']=rec.get('video') or p.get('video');out.append(p)
153
+ # also include any posts with video not indexed yet
154
+ for p in posts:
155
+ if p.get('video') and not any(str(x.get('id'))==str(p.get('id')) for x in out):out.append(p)
156
+ return JSONResponse({'posts':out[:100]})
157
+
158
+ @app.get('/api/ai/short-file/{file_id}')
159
+ def short_file(file_id:str):
160
+ return f6.f5.f4.f3.f2.f1.short_file(file_id) if hasattr(f6.f5.f4.f3.f2.f1,'short_file') else JSONResponse({'error':'not found'},status_code=404)
161
+
162
+ FINAL7_INJECT=r'''
163
+ <style>
164
+ /* One wall only: hide all auxiliary AI wall clones */
165
+ #ai-wall-topic-live,#ai-wall-patched,#ai-shorts-patched{display:none!important}.topic-final3,.topic-final4{display:none!important}.topic-final5{display:flex!important}.comment-list{margin-top:8px}.comment-item{background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px}
166
+ </style>
167
+ <script>
168
+ (function(){
169
+ function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
170
+ let shortsData7=[];let aiShorts7=[];
171
+ async function loadShorts7(){shortsData7=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);return shortsData7;}
172
+ async function loadAIShorts7(){aiShorts7=(await fetch('/api/ai_shorts').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];renderAIShorts7();return aiShorts7;}
173
+ function renderAIShorts7(){let home=document.getElementById('view-home');if(!home)return;document.getElementById('ai-short-home')?.remove();if(!aiShorts7.length)return;let wrap=document.createElement('div');wrap.id='ai-short-home';wrap.className='ai-short-home';let h='<div class="slider-header"><span class="slider-label">🎬 Short AI</span><span class="slider-note">Lưu vĩnh viễn</span></div><div class="slider-track">';aiShorts7.slice(0,50).forEach((p,i)=>{h+=`<div class="ai-short-card-final" onclick="openAIShorts7(${i})"><video src="${p.video}" muted playsinline preload="metadata"></video><div class="slider-title">${esc(p.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let wall=document.getElementById('ai-wall-final')||document.querySelector('.ai-compose');if(wall)wall.after(wrap);else home.prepend(wrap);}
174
+ function actionPanel(kind,id){return `<div class="short-action-panel"><button class="short-action-btn" onclick="shortAct7('${kind}','${id}','view')"><div class="ico">👁</div><span id="v-${kind}-${id}">0</span></button><button class="short-action-btn" onclick="shortAct7('${kind}','${id}','like')"><div class="ico">❤️</div><span id="l-${kind}-${id}">0</span></button><button class="short-action-btn" onclick="openComments7('${kind}','${id}')"><div class="ico">💬</div><span>BL</span></button><button class="short-action-btn" onclick="openAsk7('${kind}','${id}')"><div class="ico">🤖</div><span>Hỏi</span></button><button class="short-action-btn" onclick="shareShortCtx('${kind}','${id}')"><div class="ico">📤</div><span>Share</span></button></div>`}
175
+ window.openShortsFinal5=window.openShorts7=async function(start){let arts=shortsData7.length?shortsData7:await loadShorts7();if(!arts.length)return alert('Không tải được Shorts');let ordered=start>0?arts.slice(start).concat(arts.slice(0,start)):arts;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Shorts Dân trí & SKĐS</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((v,i)=>{let id=v.id;let src='https://www.youtube.com/embed/'+id+'?autoplay=1&rel=0&playsinline=1';h+=`<div class="tiktok-slide" data-kind="yt" data-id="${id}" data-title="${esc(v.title)}" data-channel="${esc(v.channel||'')}"><iframe data-yt-src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture"></iframe><div class="tiktok-bottom"><span class="badge badge-fpt">YT</span><p class="tiktok-title">${esc(v.title)}</p></div>${actionPanel('yt',id)}<span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;initFeed7();}
176
+ window.openAIShorts7=function(start){let arts=aiShorts7.length?aiShorts7:[];if(!arts.length)return;let ordered=start>0?arts.slice(start).concat(arts.slice(0,start)):arts;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Short AI</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((p,i)=>{h+=`<div class="tiktok-slide" data-kind="ai" data-id="${p.id}" data-title="${esc(p.title)}" data-context="${esc((p.text||'').slice(0,800))}"><video src="${p.video}" playsinline controls loop></video><div class="tiktok-bottom"><span class="badge badge-ai">AI</span><p class="tiktok-title">${esc(p.title)}</p></div>${actionPanel('ai',p.id)}<span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;initFeed7();}
177
+ function initFeed7(){let feed=document.getElementById('tiktok-feed');if(!feed)return;let slides=feed.querySelectorAll('.tiktok-slide');let cur=-1;function act(i){if(i===cur)return;slides.forEach((sl,idx)=>{let fr=sl.querySelector('iframe'),v=sl.querySelector('video');if(idx===i){if(fr&&!fr.src&&fr.dataset.ytSrc)fr.src=fr.dataset.ytSrc;if(v)v.play().catch(()=>{});shortAct7(sl.dataset.kind,sl.dataset.id,'view').catch(()=>{})}else{if(fr&&fr.src)fr.src='';if(v)v.pause();}});cur=i}let t;feed.addEventListener('scroll',()=>{clearTimeout(t);t=setTimeout(()=>{let rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,b=-1,d=1e9;slides.forEach((sl,i)=>{let dd=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(dd<d){d=dd;b=i}});if(b>=0)act(b)},130)});setTimeout(()=>act(0),250)}
178
+ window.shortAct7=window.shortAct=async function(kind,id,action,text=''){let sl=document.querySelector(`.tiktok-slide[data-id="${id}"]`);let body={id,kind,action,text,title:sl?.dataset.title||'',context:sl?.dataset.context||sl?.dataset.title||''};let r=await fetch('/api/ai/interact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});let j=await r.json();let st=j.stats||j;let v=document.getElementById(`v-${kind}-${id}`),l=document.getElementById(`l-${kind}-${id}`);if(v&&st.views!=null)v.textContent=st.views;if(l&&st.likes!=null)l.textContent=st.likes;return st;}
179
+ window.openComments7=function(kind,id){let m=document.getElementById('short-modal');m.innerHTML=`<h3>💬 Bình luận</h3><div id="comment-list" class="comment-list">Đang tải...</div><textarea id="short-comment-text" placeholder="Nhập bình luận..."></textarea><button onclick="submitComment7('${kind}','${id}')">Gửi</button><button onclick="closeShortModal()">Đóng</button>`;m.classList.add('active');shortAct7(kind,id,'noop').then(st=>{document.getElementById('comment-list').innerHTML=(st.comments||[]).map(c=>`<div class="comment-item">${esc(c.text)}</div>`).join('')||'<div class="comment-item">Chưa có bình luận</div>'})}
180
+ window.submitComment7=async function(kind,id){let t=document.getElementById('short-comment-text').value.trim();if(!t)return;let st=await shortAct7(kind,id,'comment',t);document.getElementById('comment-list').innerHTML=(st.comments||[]).map(c=>`<div class="comment-item">${esc(c.text)}</div>`).join('')}
181
+ window.openAsk7=window.openAskBox=function(kind,id){let m=document.getElementById('short-modal');m.innerHTML=`<h3>🤖 Hỏi AI</h3><input id="short-ask-text" placeholder="Bạn muốn hỏi gì?"><div id="short-answer"></div><button onclick="submitAsk7('${kind}','${id}')">Hỏi</button><button onclick="closeShortModal()">Đóng</button>`;m.classList.add('active')}
182
+ window.submitAsk7=window.submitShortAsk=async function(kind,id){let t=document.getElementById('short-ask-text').value.trim();if(!t)return;let st=await shortAct7(kind,id,'ask',t);let a=(st.asks&&st.asks[0]&&st.asks[0].a)||'Chưa có trả lời';document.getElementById('short-answer').innerHTML='<p style="white-space:pre-wrap;color:#ccc">'+esc(a)+'</p>'}
183
+ let oldMake=window.makeFinalShort||window.aiMakeShortPatched;window.makeFinalShort=window.aiMakeShortPatched=async function(i){let p=(window.finalWall||[])[i]||(window.finalWall3||[])[i];if(!p&&oldMake)return oldMake(i);if(!p)return;let voice=document.getElementById('ai-short-voice')?.value||'nu';let emotion=document.getElementById('ai-short-emotion')?.value||'neutral';let btn=event?.target;if(btn){btn.disabled=true;btn.textContent='Đang tạo...'}try{let r=await fetch('/api/ai/short/'+p.id,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice,emotion,speed:1.2})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');p.video=j.video;await loadAIShorts7();alert('Đã tạo và lưu Short AI vĩnh viễn.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='🎬 Tạo short'}}}
184
+ window.createTopicPostFinal5=async function(){let inp=document.getElementById('ai-topic-input-final5');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');let btn=document.getElementById('ai-topic-btn-final5');if(btn){btn.disabled=true;btn.textContent='Đang tạo bài...'}try{let r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');if(window.finalWall)window.finalWall.unshift(j.post);if(window.renderWall)window.renderWall();alert('Đã tạo bài chất lượng bằng kiến thức Qwen và đăng vào Tường AI duy nhất.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài kiến thức bằng Qwen'}}}
185
+ setTimeout(async()=>{document.querySelectorAll('#ai-wall-topic-live,#ai-wall-patched,#ai-shorts-patched').forEach(e=>e.remove());await loadShorts7();await loadAIShorts7();document.querySelectorAll('.slider-label').forEach(label=>{if((label.textContent||'').includes('Shorts'))label.closest('.slider-wrap')?.querySelectorAll('.slider-item').forEach((el,i)=>el.setAttribute('onclick',`openShorts7(${i})`));});},1000);
186
+ })();
187
+ </script>
188
+ '''
189
+
190
+ @app.get('/')
191
+ async def index_final7():
192
+ html=f6.f5.f4.f3.f2.f1._load_index_html();body=getattr(rt.old,'PATCH_INJECT','')+f6.f5.f4.f3.f2.f1.FINAL_INJECT+f6.f5.f4.f3.FINAL3_INJECT+f6.f5.f4.FINAL4_INJECT+f6.f5.FINAL5_INJECT+FINAL7_INJECT
193
+ return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)