Spaces:
Build error
Build error
Restore 53ee7c5: delete restore_patch_topic_shorts.py
Browse files- restore_patch_topic_shorts.py +0 -187
restore_patch_topic_shorts.py
DELETED
|
@@ -1,187 +0,0 @@
|
|
| 1 |
-
import os, sys, re, json, time, requests
|
| 2 |
-
from urllib.parse import quote
|
| 3 |
-
from huggingface_hub import snapshot_download
|
| 4 |
-
from fastapi import Request, Query
|
| 5 |
-
from fastapi.responses import JSONResponse, HTMLResponse
|
| 6 |
-
|
| 7 |
-
REVISION=os.environ.get('VNEWS_RESTORE_REVISION','53ee7c5')
|
| 8 |
-
REPO_ID=os.environ.get('VNEWS_REPO_ID','bep40/vnews')
|
| 9 |
-
snapshot_dir=snapshot_download(repo_id=REPO_ID,repo_type='space',revision=REVISION,local_dir='/tmp/vnews_restore',local_dir_use_symlinks=False)
|
| 10 |
-
os.chdir(snapshot_dir);sys.path.insert(0,snapshot_dir)
|
| 11 |
-
import ai_runtime_final6 as restored
|
| 12 |
-
app=restored.app
|
| 13 |
-
base=restored.f5.f4.f3.f2.f1.base
|
| 14 |
-
rt=restored.rt
|
| 15 |
-
|
| 16 |
-
HEADERS={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36','Accept-Language':'vi-VN,vi;q=0.9,en;q=0.8'}
|
| 17 |
-
CACHE={'t':0,'d':[]}
|
| 18 |
-
SHORT_CHANNELS=['baodantri7941','baosuckhoedoisongboyte']
|
| 19 |
-
# Best for article writing: large text-only instruction models, not VL, because user only provides a topic.
|
| 20 |
-
TEXT_MODELS=['Qwen/Qwen3-235B-A22B-Instruct-2507:fastest','Qwen/Qwen3-30B-A3B-Instruct-2507:fastest','Qwen/Qwen2.5-72B-Instruct:fastest','Qwen/Qwen2.5-32B-Instruct:fastest','Qwen/Qwen2.5-14B-Instruct:fastest']
|
| 21 |
-
|
| 22 |
-
def clean(s):
|
| 23 |
-
import html
|
| 24 |
-
return re.sub(r'\s+',' ',html.unescape(s or '')).strip()
|
| 25 |
-
|
| 26 |
-
def _topic_image(topic):
|
| 27 |
-
try:return base.pollinations_image_url(topic)
|
| 28 |
-
except Exception:return 'https://image.pollinations.ai/prompt/'+quote('Vietnamese magazine editorial illustration '+topic)+'?width=1024&height=576&nologo=true'
|
| 29 |
-
|
| 30 |
-
def sanitize_article(txt, topic):
|
| 31 |
-
txt=(txt or '').strip()
|
| 32 |
-
txt=re.sub(r'<think>.*?</think>\s*','',txt,flags=re.S).strip()
|
| 33 |
-
# Remove prompt echoes / instruction bullets if any model copied the input.
|
| 34 |
-
bad_prefixes=('Bạn là biên tập viên','Bạn là một cây bút','Từ một chủ đề','Chủ đề:','Hãy viết','Cấu trúc:','Yêu cầu chất lượng:','Yêu cầu đầu ra:','Bắt buộc:','- Tiêu đề','- Sapo','- 4-6','- Có bối cảnh','- Kết bài','• Từ một chủ đề','• Chủ đề:','• Cấu trúc')
|
| 35 |
-
lines=[]
|
| 36 |
-
for line in txt.splitlines():
|
| 37 |
-
l=line.strip()
|
| 38 |
-
if not l:lines.append(line);continue
|
| 39 |
-
if any(l.startswith(p) for p in bad_prefixes):continue
|
| 40 |
-
if re.match(r'^[•\-]\s*(Tiêu đề|Sapo|Cấu trúc|Chủ đề|Hãy viết|Có bối cảnh|Kết bài)',l,re.I):continue
|
| 41 |
-
lines.append(line)
|
| 42 |
-
txt='\n'.join(lines).strip()
|
| 43 |
-
# If output still looks like a prompt instead of an article, fallback to a real article skeleton.
|
| 44 |
-
prompt_markers=sum(1 for k in ['Cấu trúc','Hãy viết','Chủ đề:','Bạn là biên tập viên','Yêu cầu'] if k.lower() in txt.lower())
|
| 45 |
-
if len(txt)<500 or prompt_markers>=2:
|
| 46 |
-
txt=f"""{topic}: hiểu đúng vấn đề trước khi chạy theo xu hướng
|
| 47 |
-
|
| 48 |
-
{topic} không chỉ là một cụm từ để gây chú ý. Đằng sau nó là những thay đổi về cách con người nhìn nhận thông tin, ra quyết định và tổ chức cuộc sống. Muốn hiểu chủ đề này, điều quan trọng là phải đi từ bản chất, bối cảnh và tác động thực tế thay vì chỉ lặp lại các khẩu hiệu quen thuộc.
|
| 49 |
-
|
| 50 |
-
Bản chất của vấn đề
|
| 51 |
-
|
| 52 |
-
Ở cấp độ đơn giản nhất, {topic} cần được hiểu như một hiện tượng có nhiều lớp: lớp khái niệm, lớp ứng dụng và lớp ảnh hưởng xã hội. Một chủ đề chỉ thật sự có ý nghĩa khi người đọc thấy nó liên quan đến đời sống, công việc hoặc cách tư duy của mình.
|
| 53 |
-
|
| 54 |
-
Vì sao chủ đề này đáng chú ý
|
| 55 |
-
|
| 56 |
-
Điểm đáng chú ý nằm ở chỗ {topic} thường không xuất hiện đơn lẻ. Nó gắn với những chuyển động rộng hơn: công nghệ, truyền thông, giáo dục, kinh tế hoặc văn hóa đại chúng. Khi một xu hướng được nhắc đến nhiều, câu hỏi quan trọng không phải là nó có “hot” hay không, mà là nó đang thay đổi điều gì.
|
| 57 |
-
|
| 58 |
-
Tác động thực tế
|
| 59 |
-
|
| 60 |
-
Với người dùng phổ thông, tác động dễ thấy nhất là cách tiếp cận thông tin và ra quyết định. Một chủ đề có giá trị khi nó giúp con người hiểu rõ hơn lựa chọn của mình, tránh bị dẫn dắt bởi thông tin rời rạc hoặc những nhận định thiếu kiểm chứng.
|
| 61 |
-
|
| 62 |
-
Những hiểu lầm thường gặp
|
| 63 |
-
|
| 64 |
-
Hiểu lầm phổ biến là xem {topic} như một câu trả lời duy nhất cho mọi vấn đề. Trên thực tế, bất kỳ xu hướng hay khái niệm nào cũng có giới hạn. Nó cần được đặt trong bối cảnh cụ thể, với dữ liệu, kinh nghiệm và đánh giá thận trọng.
|
| 65 |
-
|
| 66 |
-
Điều nên rút ra
|
| 67 |
-
|
| 68 |
-
Cách tiếp cận hợp lý là không thần tượng hóa cũng không phủ nhận cực đoan. Người đọc nên nhìn {topic} như một điểm khởi đầu để đặt câu hỏi tốt hơn: nó giúp ích ở đâu, gây rủi ro ở đâu, và con người cần chuẩn bị gì để thích nghi.
|
| 69 |
-
|
| 70 |
-
Kết lại, giá trị của {topic} không nằm ở việc nó được nhắc đến nhiều, mà ở khả năng giúp chúng ta hiểu sâu hơn về một thay đổi đang diễn ra. Khi nhìn đủ chậm và đủ kỹ, một chủ đề tưởng như quen thuộc có thể mở ra nhiều câu hỏi đáng suy nghĩ.
|
| 71 |
-
|
| 72 |
-
Nguồn tham khảo: Qwen / kiến thức tổng hợp."""
|
| 73 |
-
return txt
|
| 74 |
-
|
| 75 |
-
async def qwen_article(topic):
|
| 76 |
-
token=os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACEHUB_API_TOKEN') or ''
|
| 77 |
-
system=("Bạn là cây bút báo chí tiếng Việt. Bạn chỉ trả về BÀI VIẾT HOÀN CHỈNH, không lặp lại prompt, không liệt kê yêu cầu, không dùng vai trò chatbot. Văn phong tự nhiên, cụ thể, nhiều thông tin, có phân tích và ví dụ.")
|
| 78 |
-
user=f"""Viết một bài báo/tạp chí tiếng Việt hoàn chỉnh về chủ đề: {topic}
|
| 79 |
-
|
| 80 |
-
Quan trọng: KHÔNG trả lại các dòng hướng dẫn, KHÔNG viết dàn ý, KHÔNG bắt đầu bằng 'Bạn là...' hoặc 'Chủ đề:'. Hãy viết ngay bài đăng cho độc giả.
|
| 81 |
-
|
| 82 |
-
Định dạng mong muốn:
|
| 83 |
-
# Tiêu đề cụ thể
|
| 84 |
-
|
| 85 |
-
Sapo 2-3 câu.
|
| 86 |
-
|
| 87 |
-
## Các mục nội dung
|
| 88 |
-
Viết 4-6 mục, mỗi mục có luận điểm rõ, có bối cảnh, ví dụ và nhận định.
|
| 89 |
-
|
| 90 |
-
Kết bài bằng một nhận định đáng suy nghĩ.
|
| 91 |
-
"""
|
| 92 |
-
if token:
|
| 93 |
-
headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'}
|
| 94 |
-
for model in TEXT_MODELS:
|
| 95 |
-
try:
|
| 96 |
-
payload={'model':model,'messages':[{'role':'system','content':system},{'role':'user','content':user}], 'temperature':0.68,'top_p':0.82,'max_tokens':2600,'presence_penalty':0.45,'frequency_penalty':0.35}
|
| 97 |
-
r=requests.post('https://router.huggingface.co/v1/chat/completions',headers=headers,json=payload,timeout=140)
|
| 98 |
-
if r.status_code<300:
|
| 99 |
-
txt=(r.json().get('choices',[{}])[0].get('message',{}).get('content') or '').strip()
|
| 100 |
-
txt=sanitize_article(txt,topic)
|
| 101 |
-
if len(txt)>500:return txt
|
| 102 |
-
except Exception:pass
|
| 103 |
-
txt=await base.qwen_generate(system+'\n\n'+user,max_tokens=1800)
|
| 104 |
-
return sanitize_article(txt,topic)
|
| 105 |
-
|
| 106 |
-
def ytdlp_shorts(handle,count=24):
|
| 107 |
-
try:
|
| 108 |
-
import yt_dlp
|
| 109 |
-
opts={'quiet':True,'extract_flat':True,'skip_download':True,'playlistend':count,'ignoreerrors':True,'no_warnings':True}
|
| 110 |
-
with yt_dlp.YoutubeDL(opts) as ydl:info=ydl.extract_info(f'https://www.youtube.com/@{handle}/shorts',download=False)
|
| 111 |
-
out=[]
|
| 112 |
-
for e in (info or {}).get('entries') or []:
|
| 113 |
-
vid=e.get('id') or ''
|
| 114 |
-
if re.match(r'^[A-Za-z0-9_-]{11}$',vid):out.append({'id':vid,'title':e.get('title') or 'YouTube Short','channel':handle,'source':'yt','link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg'})
|
| 115 |
-
return out
|
| 116 |
-
except Exception:return []
|
| 117 |
-
|
| 118 |
-
def html_shorts(handle,count=24):
|
| 119 |
-
try:
|
| 120 |
-
html=requests.get(f'https://www.youtube.com/@{handle}/shorts',headers=HEADERS,timeout=15).text
|
| 121 |
-
ids=[];out=[]
|
| 122 |
-
for m in re.finditer(r'"videoId":"([A-Za-z0-9_-]{11})"',html):
|
| 123 |
-
vid=m.group(1)
|
| 124 |
-
if vid in ids:continue
|
| 125 |
-
ids.append(vid)
|
| 126 |
-
snip=html[max(0,m.start()-900):m.start()+1700]
|
| 127 |
-
mt=re.search(r'"title":\{"runs":\[\{"text":"([^"]+)"',snip) or re.search(r'"accessibilityText":"([^"]+)"',snip)
|
| 128 |
-
title=clean(mt.group(1).replace('\\n',' ')) if mt else 'YouTube Short'
|
| 129 |
-
out.append({'id':vid,'title':title,'channel':handle,'source':'yt','link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg'})
|
| 130 |
-
if len(out)>=count:break
|
| 131 |
-
return out
|
| 132 |
-
except Exception:return []
|
| 133 |
-
|
| 134 |
-
def fallback_shorts():
|
| 135 |
-
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'),('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')]
|
| 136 |
-
return [{'id':vid,'title':title,'channel':ch,'source':'yt','link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg'} for vid,title,ch in hard]
|
| 137 |
-
|
| 138 |
-
def fresh_shorts():
|
| 139 |
-
out=[];seen=set()
|
| 140 |
-
for ch in SHORT_CHANNELS:
|
| 141 |
-
for v in (ytdlp_shorts(ch) or html_shorts(ch)):
|
| 142 |
-
if v['id'] not in seen:seen.add(v['id']);out.append(v)
|
| 143 |
-
for v in fallback_shorts():
|
| 144 |
-
if v['id'] not in seen:seen.add(v['id']);out.append(v)
|
| 145 |
-
return out[:60]
|
| 146 |
-
|
| 147 |
-
app.router.routes=[r for r in app.router.routes if getattr(r,'path',None) not in {'/api/shorts','/api/topic_post','/'}]
|
| 148 |
-
|
| 149 |
-
@app.get('/api/shorts')
|
| 150 |
-
def api_shorts(refresh:int=Query(default=0)):
|
| 151 |
-
now=time.time()
|
| 152 |
-
if not refresh and CACHE['d'] and now-CACHE['t']<900:return JSONResponse(CACHE['d'])
|
| 153 |
-
data=fresh_shorts();CACHE.update({'t':now,'d':data});return JSONResponse(data)
|
| 154 |
-
|
| 155 |
-
@app.post('/api/topic_post')
|
| 156 |
-
async def topic_post(request:Request):
|
| 157 |
-
body=await request.json();topic=clean(body.get('topic',''))
|
| 158 |
-
if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
|
| 159 |
-
img=_topic_image(topic);text=await qwen_article(topic)
|
| 160 |
-
post=base.make_post(topic,text,img,'','topic_article',sources=[{'title':'Qwen text model / kiến thức tổng hợp','url':'','via':'Qwen'}]);post['images']=[img]
|
| 161 |
-
posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
|
| 162 |
-
return JSONResponse({'post':post})
|
| 163 |
-
|
| 164 |
-
PATCH=r'''
|
| 165 |
-
<style>.topic-final3,.topic-final4{display:none!important}.topic-final5{display:flex!important}.wall-live-fixed{margin:6px 4px;background:#1a1a1a;border:1px solid #2a2a2a;border-radius:8px;overflow:hidden}</style>
|
| 166 |
-
<script>
|
| 167 |
-
(function(){
|
| 168 |
-
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 169 |
-
let shorts=[];let wall=[];
|
| 170 |
-
async function loadShorts(){shorts=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);return shorts;}
|
| 171 |
-
async function loadWall(){wall=(await fetch('/api/ai_wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];renderWallFixed();return wall;}
|
| 172 |
-
function renderWallFixed(){let home=document.getElementById('view-home');if(!home)return;document.getElementById('wall-live-fixed')?.remove();if(!wall.length)return;let wrap=document.createElement('div');wrap.id='wall-live-fixed';wrap.className='wall-live-fixed';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Đã lưu</span></div><div class="slider-track">';wall.slice(0,30).forEach((p,i)=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img src="${p.img}">`:''}</div><div class="wall-title">${esc(p.title)}</div><div class="wall-text">${esc(p.text)}</div><div class="wall-actions"><button class="primary" onclick="readWallFixed(${i})">Xem</button></div></div>`});h+='</div>';wrap.innerHTML=h;let comp=document.querySelector('.ai-compose');if(comp)comp.after(wrap);else home.prepend(wrap);}
|
| 173 |
-
window.readWallFixed=function(i){let p=wall[i];if(!p)return;showView('view-article');document.getElementById('view-article').innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI</span><h1 class="article-title">${esc(p.title)}</h1>${p.img?`<img class="article-img" src="${p.img}">`:''}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p></div>`;window.scrollTo(0,0)}
|
| 174 |
-
function dedupeShortSlides(){let found=false;document.querySelectorAll('.slider-wrap').forEach(w=>{let label=w.querySelector('.slider-label')?.textContent||'';if(label.includes('Shorts')){if(found)w.remove();else{found=true;w.id='main-shorts-wrap';w.querySelectorAll('.slider-item').forEach((el,i)=>el.setAttribute('onclick',`openShortsDedupe(${i})`));}}});}
|
| 175 |
-
window.openShortsDedupe=async function(start){let data=shorts.length?shorts:await loadShorts();if(!data.length)return alert('Không tải được Shorts');let ordered=start>0?data.slice(start).concat(data.slice(0,start)):data;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Shorts</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((v,i)=>{let src='https://www.youtube.com/embed/'+v.id+'?autoplay=1&rel=0&playsinline=1';h+=`<div class="tiktok-slide"><iframe data-src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" style="width:100%;height:100%;border:0"></iframe><div class="tiktok-bottom"><span class="badge badge-fpt">YT</span><p class="tiktok-title">${esc(v.title)}</p></div><span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;initShortFeed();}
|
| 176 |
-
function initShortFeed(){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 f=sl.querySelector('iframe');if(idx===i){if(f&&!f.src)f.src=f.dataset.src}else if(f)f.src=''});cur=i}let t;feed.addEventListener('scroll',()=>{clearTimeout(t);t=setTimeout(()=>{let rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,b=0,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}});act(b)},120)});setTimeout(()=>act(0),200)}
|
| 177 |
-
let oldOpen=window.openTikTok;window.openTikTok=function(type,idx){if(type==='shorts')return openShortsDedupe(idx||0);return oldOpen?oldOpen(type,idx):null};
|
| 178 |
-
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 viết 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');wall.unshift(j.post);renderWallFixed();readWallFixed(0);if(inp)inp.value='';alert('Đã tạo bài viết bằng model text Qwen chất lượng hơn.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài kiến thức bằng Qwen'}}};
|
| 179 |
-
setTimeout(async()=>{await loadShorts();await loadWall();dedupeShortSlides();},1000);setInterval(()=>{dedupeShortSlides();if(document.getElementById('view-home')?.classList.contains('active'))renderWallFixed();},2500);
|
| 180 |
-
})();
|
| 181 |
-
</script>
|
| 182 |
-
'''
|
| 183 |
-
|
| 184 |
-
@app.get('/')
|
| 185 |
-
async def index_patched():
|
| 186 |
-
html=await restored.index_final6();text=html.body.decode('utf-8') if hasattr(html,'body') else str(html)
|
| 187 |
-
return HTMLResponse(text.replace('</body>',PATCH+'\n</body>') if '</body>' in text else text+PATCH)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|