"""Render a READ-ONLY excerpt from actual ai_app page templates, styles and asset URLs.
Never imports business services, tokens, account storage or network API code.
"""
from pathlib import Path
import re,json,hashlib,base64,urllib.request,subprocess
from urllib.parse import urlsplit
R=Path(__file__).parent.resolve();repo=R.parents[1];app=repo.parent/'zhctproject/ai_app';out=R/'public-build';out.mkdir(exist_ok=True)
source=app/'pages/chat/index.vue';text=source.read_text(encoding='utf-8-sig');clean=re.sub(r'<!--[\s\S]*?-->','',text)
assert 'navigationBarTitleText": "AI运动营养师"' in (app/'pages.json').read_text()
assert '@tap="chooseImage"' in text and 'phone.svg' in text

def viewblock(marker):
 start=clean.index(marker);depth=0
 for m in re.finditer(r'<(/?)view\b[^>]*>',clean[start:]):
  if m.group(0).endswith('/>'):continue
  depth+=-1 if m.group(1) else 1
  if depth==0:return clean[start:start+m.end()]
 raise ValueError(marker)

def tags(html):
 html=re.sub(r'\s+:class="[^"]*"','',html)
 html=re.sub(r'\s+(?:v-if|v-else-if|v-model|v-html|:style)="[^"]*"','',html)
 html=html.replace(' v-else','')
 html=re.sub(r'\s+@(?:tap|click|input|confirm)="([^"]*)"',r' data-existing-action="\1"',html)
 html=re.sub(r'\s+(?:mode|cursor-spacing)="[^"]*"','',html)
 html=re.sub(r'<(/?)view\b',r'<\1div',html);html=re.sub(r'<(/?)text\b',r'<\1span',html)
 html=re.sub(r'<image\b',r'<img',html);html=html.replace('</image>','')
 html=html.replace(':src="nutritionistImage"','src="https://img.kxunpt.cn/ai/ai-img/male_default.png"')
 return html
prompt=tags(viewblock('<view class="prompt">'))
prompt=prompt.replace('<div class="prompt-content"></div>','<div class="prompt-content">我是你的运动营养师~</div>')
inputarea=viewblock('<view class="bottom-operation input-area" v-else>')
stop_tag=re.search(r'<view\b[^>]*class="send-btn bj-stop"[^>]*>',clean)
stop=viewblock(stop_tag.group(0)) if stop_tag else ''
if stop:inputarea=inputarea.replace(stop,'')
inputarea=inputarea.replace(':class="[\'send-btn\', isLoading ? \'disabled\' : \'\']"','class="send-btn"')
inputarea=tags(inputarea).replace('<input ','<input readonly aria-label="原有问题输入框（只读示意）" ')
inputarea=inputarea.replace('class="tool-icon"','class="tool-icon" role="button" tabindex="0" aria-label="原有图片入口"')
# Read the complete original SCSS plus tabbar/component styles; compile unchanged before rpx conversion.
style=re.search(r'<style lang="scss">([\s\S]*?)</style>',text).group(1)
tabfile=app/'components/custom-tabbar/custom-tabbar.vue';tabstyle=re.search(r'<style[^>]*>([\s\S]*?)</style>',tabfile.read_text()).group(1)
deepfile=app/'pages/chat/components/deepseek/depindex.vue';deepstyle=re.search(r'<style[^>]*>([\s\S]*?)</style>',deepfile.read_text()).group(1)
scss=out/'mini-source.scss';scss.write_text(style+'\n'+tabstyle+'\n'+deepstyle)
compiler=repo.parent/'zhctproject/store/public/static/node_modules/sass'
js='const fs=require("fs"),sass=require(process.argv[1]);process.stdout.write(sass.renderSync({file:process.argv[2],outputStyle:"compressed"}).css.toString())'
res=subprocess.run(['node','-e',js,str(compiler),str(scss)],capture_output=True,text=True,check=True)
css=res.stdout;css=re.sub(r'(-?\d+(?:\.\d+)?)rpx',lambda m:'calc('+m.group(1)+' * var(--rpx))',css)
css=re.sub(r'(?<![-\w])image(?=[\s,.{:#])','img',css)
asset_cache=out/'mini-assets';asset_cache.mkdir(exist_ok=True);assets=[]
def embed(url):
 if url.startswith('data:'):return url
 assert urlsplit(url).hostname=='img.kxunpt.cn',url
 f=asset_cache/(hashlib.sha256(url.encode()).hexdigest()[:16]+Path(urlsplit(url).path).suffix)
 if not f.exists():
  with urllib.request.urlopen(url,timeout=25) as response:
   assert response.status==200;f.write_bytes(response.read())
 raw=f.read_bytes()
 if raw.startswith(b'\x89PNG'):mime='image/png'
 elif raw.startswith(b'\xff\xd8'):mime='image/jpeg'
 elif b'<svg' in raw[:600]:mime='image/svg+xml'
 else:raise ValueError('Unrecognized source image payload: '+url)
 assets.append({'source':url,'sha256':hashlib.sha256(f.read_bytes()).hexdigest(),'bytes':f.stat().st_size})
 return 'data:'+mime+';base64,'+base64.b64encode(f.read_bytes()).decode()
css=re.sub(r'url\(["\']?(https://img\.kxunpt\.cn/[^)"\']+)["\']?\)',lambda m:'url("'+embed(m.group(1))+'")',css)
shortcuts='''<div class="functional-area image-preview-area"><div class="function-scroll"><div class="function-scroll-inner"><div class="history-shortcut-button"><img class="history-shortcut-icon" src="https://img.kxunpt.cn/ai/ai-img/history.svg"><span class="history-shortcut-text">历史记录</span></div><div class="history-shortcut-button" style="width:calc(160 * var(--rpx))"><img class="history-shortcut-icon" src="https://img.kxunpt.cn/ai/ai-img/new_chat1.svg"><span class="history-shortcut-text">新聊天</span></div><div class="deep-thinking-button"><img class="check-icon" src="https://img.kxunpt.cn/ai/ai-tips.png"><span>深度思考</span></div><div class="deep-thinking-button"><img class="check-icon" src="https://img.kxunpt.cn/ai/wdsc.svg"><span>文档阅读</span></div></div></div></div>'''
# Default buildTabbarList() from custom-tabbar. No guessed account-specific falgModel.
nav=[('首页','static/tabbar/home.svg','pages/index/index'),('订餐','restaurant/shoppingCart.svg','pages/ordering/index'),('','static/tabbar/AI.png','pages/chat/index'),('订单','dd.svg','pages/order/index'),('我的','static/tabbar/mine.svg','pages/user/index')]
tab='<nav class="container" aria-label="源码默认底部导航">'+''.join(f'<div class="tabbar-item {"center-item" if i==2 else ""}" style="width:20%" data-route="{route}"><div class="item-top"><img class="item-img" src="https://img.kxunpt.cn/ai/{asset}"></div><div class="item-bottom">{name}</div></div>' for i,(name,asset,route) in enumerate(nav))+'</nav>'
body='<div class="source-status"><span>09:41</span><span>▮▮▮ ◉ ▰</span></div><header class="custom-navbar"><span class="source-home">⌂</span><strong>AI运动营养师</strong><span class="source-capsule">••• │ ◉</span></header><div class="chat-container"><div class="chat-list"><div class="message-list-container">'+prompt+'</div></div>'+shortcuts+inputarea+'</div>'+tab
body=re.sub(r'src="(https://img\.kxunpt\.cn/[^"]+)"',lambda m:'src="'+embed(m.group(1))+'"',body)
# These adapter rules only bridge native viewport/tag semantics and the system navigation capsule.
adapter='''html,body{margin:0;padding:0;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;background:#AFDCFF;color:#333}*{box-sizing:border-box}body{max-width:430px;margin:auto;position:relative}img{object-fit:contain}input{font-family:inherit;border:0;outline:0;background:transparent;min-width:0}.source-status{height:25px;display:flex;justify-content:space-between;padding:8px 22px 0;font-size:11px}.custom-navbar{height:48px;background:#AFDCFF;display:flex;align-items:center;justify-content:center;position:relative}.custom-navbar strong{font-size:17px}.source-home{position:absolute;left:19px;font-size:24px}.source-capsule{position:absolute;right:12px;border:1px solid #7299ba60;border-radius:18px;padding:2px 8px;font-size:12px}.chat-container{height:calc(100vh - 73px - 135 * var(--rpx))}.chat-list{height:100%;overflow:hidden}.input-area,.image-preview-area,.container{max-width:430px;left:50%;right:auto;transform:translateX(-50%);width:var(--page-width)}.question-list{width:calc(var(--page-width) - 50 * var(--rpx))}.function-scroll{overflow-x:auto;scrollbar-width:none}.function-scroll::-webkit-scrollbar{display:none}.function-scroll-inner>div{flex-shrink:0}.deep-thinking-button{min-width:calc(180 * var(--rpx))}.check-icon{width:calc(34 * var(--rpx));height:calc(34 * var(--rpx))}.tool-icon{cursor:pointer}.tool-icon:focus-visible,.tool-icon.source-highlight{outline:3px solid #e66831;outline-offset:5px;border-radius:4px}.source-feedback{position:fixed;bottom:160px;left:50%;transform:translateX(-50%);width:86%;max-width:370px;padding:10px 14px;border-radius:10px;color:white;background:#253e5e;font-size:12px;z-index:2000}.source-feedback[hidden]{display:none}'''
script='''function size(){var w=Math.min(innerWidth,430);document.documentElement.style.setProperty('--rpx',(w/750)+'px');document.documentElement.style.setProperty('--page-width',w+'px')}size();addEventListener('resize',size);function locate(){var icon=document.querySelector('.tool-icon');icon.classList.add('source-highlight');var msg=document.querySelector('.source-feedback');msg.textContent='代码入口：输入栏右侧图片图标 → chooseImage()。这里只定位原功能，不上传或发送。';msg.hidden=false;setTimeout(()=>{msg.hidden=true;icon.classList.remove('source-highlight')},6000)}document.querySelector('.tool-icon').onclick=locate;document.querySelector('.tool-icon').onkeydown=e=>{if(e.key==='Enter'||e.key===' ')locate()};window.addEventListener('message',e=>{if(e.source===parent&&e.origin===location.origin&&e.data?.type==='dwm-locate-image')locate()});'''
page='<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="mini-program-source" content="ai_app/pages/chat/index.vue"><title>AI运动营养师 · 源码页面示意</title><style>'+css+adapter+'</style><script>'+script.split('function locate')[0]+'</script></head><body>'+body+'<div class="source-feedback" hidden role="status"></div><script>function locate'+script.split('function locate',1)[1]+'</script></body></html>'
assert 'data-existing-action="stopResponse"' not in page
assert '健康数据问答' not in page and '你的个人健康助理' not in page
assert 'phone.svg' in text and 'data-existing-action="chooseImage"' in page
(out/'mini-program-review.html').write_text(page)
(R/'mini-program-code-evidence.json').write_text(json.dumps({'source_files':{str(p.relative_to(app)):hashlib.sha256(p.read_bytes()).hexdigest() for p in [source,tabfile,deepfile,app/'pages/chat/services/imageService.js',app/'pages.json']},'default_state':'No personalInfo or targetInfo, default male_default avatar; default buildTabbarList configuration. Account-specific nutrition mode is not guessed.','entry':'pages/chat/index → bottom-operation.input-area → image.tool-icon @tap=chooseImage','render_method':'Extract original prompt and input template blocks; compile original SCSS and tabbar style; adapt native rpx/view/image tags; only annotation interaction, no business services.','not_live_capture':True,'assets':list({a['source']:a for a in assets}.values())},ensure_ascii=False,indent=2)+'\n')
print('Code-based mini-program:',len(page.encode()),'bytes;',len(set(a['source'] for a in assets)),'original assets')
