from pathlib import Path
import sys,json,html,os,subprocess
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/'resources'))
from course import S,E,NAVIGATION
from build_mcp_frames import build as build_mcp_frames
build_mcp_frames()
from build_research_frames import build as build_research_frames
build_research_frames()
from build_literature_frames import build as build_literature_frames
build_literature_frames()
from build_ideas_frames import build as build_ideas_frames
build_ideas_frames()
from build_grant_frames import build as build_grant_frames
build_grant_frames()
from build_meeting_frames import build as build_meeting_frames
build_meeting_frames()
from build_teaching_frames import build as build_teaching_frames
build_teaching_frames()
esc=html.escape
# Display the preserved Skill as source text, never as active instructions.
skill_source=(ROOT/'resources/evidence/arxiv-reading-SKILL.md').read_text()
skill_frame='<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>arxiv-reading / SKILL.md</title><style>*{box-sizing:border-box}html{background:#f0f0ec;color:#253130;scrollbar-gutter:stable}body{margin:0;padding:26px 32px 40px}pre{margin:0;font:28px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}</style><pre>'+esc(skill_source)+'</pre></html>'
(ROOT/'resources/evidence/arxiv-reading-skill.html').write_text(skill_frame)
def render_math(document):
 return subprocess.run([os.environ.get('NODE_BINARY','node'),str(ROOT/'scripts/render_math.cjs')],input=document,text=True,capture_output=True,check=True).stdout
# Complete visible bilingual conclusions and source context after the learning review.
fixes={
'gw-decisions':'科研经验体现在知道应该追问哪些选择。'}
for s in S:
 if s['id'] in fixes:
  import re
  body=s['body']['zh']
  if 'class="takeaway"' in body:body=re.sub(r'<p class="takeaway">.*?</p>',f'<p class="takeaway">{fixes[s["id"]]}</p>',body)
  else:body+=f'<p class="takeaway">{fixes[s["id"]]}</p>'
  s['body']['zh']=body
# Explicit line breaks are presentation choices, separate from copyable task prompts.
slide_by_id={s['id']:s for s in S}
for item in json.loads((ROOT/'resources/line_breaks.json').read_text()):
 s=slide_by_id[item['id']]
 body=s['body'][item['lang']]
 if body.count(item['before'])!=1:
  raise ValueError(f"Review stale line break: {item['id']} / {item['lang']} / {item['before']}")
 s['body'][item['lang']]=body.replace(item['before'],item['after'],1)
head='<!doctype html><html lang="zh-Hans" data-lang="zh"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>与 AI 智能体一起做科研</title><link rel="stylesheet" href="resources/vendor/katex/katex.min.css"><link rel="stylesheet" href="resources/training.css"><body><main id="stage">'
controls='''<nav id="controls" data-ui="nav" aria-label="幻灯片导航"><button id="menu" data-ui="toc" aria-expanded="false"><span class="menu-glyph" aria-hidden="true">☰</span></button><button id="notes-toggle" type="button" data-ui="notes" aria-controls="notes-panel" aria-expanded="false" aria-pressed="false"><svg class="notes-glyph" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="3" width="15" height="18" rx="2"/><path d="M8 3v18M3 8h4M3 16h4M11 8h6M11 12h6M11 16h4"/></svg></button><button id="prev" data-ui="prev">←</button><input id="page" data-ui="page" inputmode="numeric" aria-label="页码"><span id="total"></span><button id="next" data-ui="next">→</button><button data-choose-lang="zh" aria-pressed="true">中文</button><button data-choose-lang="en" aria-pressed="false">EN</button><button id="full" data-ui="full">⛶</button></nav>'''
toc='<nav id="toc">'+''.join(f'<a href="#{id}" data-zh="{esc(label["zh"])}" data-en="{esc(label["en"])}">{esc(label["zh"])}</a>' for id,label in NAVIGATION)+'</nav>'
# One original-language note per slide, populated only when the user requests it.
notes_entries=[]
for i,s in enumerate(S):
 text=s['notes']
 note_body=f'<p class="note-body">{esc(text)}</p>' if text else ''
 notes_entries.append(f'<article class="note-entry" data-slide-id="{s["id"]}"><a class="note-link" href="#{s["id"]}"><span class="note-number">{i+1}</span><span>{esc(s["title"]["zh"])}</span></a>{note_body}</article>')
notes_panel='<aside id="notes-panel" aria-labelledby="notes-title" hidden><header class="notes-header"><h2 id="notes-title">备注</h2><button id="notes-close" type="button" data-ui="closeNotes">×</button></header><div id="notes-list" tabindex="0" role="region" aria-labelledby="notes-title" data-interactive>'+''.join(notes_entries)+'</div></aside>'
sections=[]
for s in S:
 def heading(l):
  text=esc(s['title'][l])
  if s['id']!='start':return text
  term='AI 智能体' if l=='zh' else 'AI Agents'
  before,match,after=text.partition(term)
  if not match:return text
  punctuation='，。；：！？、,.!?;:'
  left=' ' if before and before[-1] not in punctuation else ''
  right=' ' if after and after.lstrip()[0] not in punctuation else ''
  return before.rstrip()+left+'<span class="subject">'+match+'</span>'+right+after.lstrip()
 def location(l):
  sub=s.get('subsection')
  if not sub:return ''
  label=sub['part_label'][l]
  if s['id']!=sub['start']:label+=f" / {sub['number']}. {sub['title'][l]}"
  return f'<p class="section-location">{esc(label)}</p>'
 def view(l):
  if s['kind']=='full-image':
   return f'<div class="lang-view" data-lang="{l}">{s["body"][l]}</div>'
  if s['id']=='start':
   return f'<div class="lang-view" data-lang="{l}"><header class="cover-heading"><h1>{heading(l)}</h1>{s["body"][l]}</header></div>'
  if 'section-outline' in s['kind']:
   number, title=s['title'][l].split(' · ',1)
   intro, links=s['body'][l].split('<nav class="subsection-list">',1)
   return f'<div class="lang-view" data-lang="{l}"><div class="chapter-layout"><header class="chapter-intro"><h1><span class="chapter-number">{esc(number)} · </span><span class="chapter-title">{esc(title)}</span></h1>{intro}</header><nav class="subsection-list">{links}</div></div>'
  return f'<div class="lang-view" data-lang="{l}">{location(l)}<h1>{heading(l)}</h1><div class="content">{s["body"][l]}</div></div>'
 views=''.join(view(l) for l in ['zh','en'])
 sources='<footer class="sources">'+''.join(f'<a href="{esc(url)}" target="_blank" rel="noopener">{esc(label)}</a>' for label,url in s['sources'])+'</footer>'
 subsection_class=" has-subsection" if s.get("subsection") else ""
 sections.append(f'<section class="slide {s["kind"]}{subsection_class}" id="{s["id"]}" aria-hidden="true">{views}{sources}</section>')
(ROOT/'ai_agents_research_training.html').write_text(render_math(head+controls+toc+''.join(sections)+notes_panel+'<div id="toast" role="status"></div></main><script src="resources/training.js"></script></body></html>'))
# Companion handout: same prompts, more room for practical troubleshooting.
style='''@font-face{font-family:NotoSC;src:url(resources/fonts/NotoSansSC-VariableFont_wght.ttf)}*{box-sizing:border-box}body{font:19px/1.75 NotoSC,sans-serif;background:#fcfcfa;color:#253130;margin:0 auto;max-width:1040px;padding:50px 40px}h1{font-size:42px;line-height:1.3}h2{font-size:28px;color:#cd5d0d}p{margin:12px 0}article{scroll-margin-top:110px;padding:25px 0 40px;border-bottom:1px solid #ccd0cb}blockquote{margin:22px 0;background:#f0f0ec;padding:24px 30px;border-left:5px solid #cd5d0d}nav{position:sticky;top:0;z-index:2;background:#fcfcfa;padding:15px 0;display:flex;gap:22px}a{color:#63706e}button{font:inherit;border:1px solid #c8cecb;background:#fcfcfa;padding:4px 14px;cursor:pointer}button[aria-pressed=true]{color:#cd5d0d} [data-language]{display:none}html[data-lang=zh] [data-language=zh],html[data-lang=en] [data-language=en]{display:block}html[data-lang=zh] span[data-language=zh],html[data-lang=en] span[data-language=en]{display:inline}code{background:#f0f0ec;padding:2px 6px}'''
hand='<!doctype html><html lang="zh-Hans" data-lang="zh"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AI 科研练习册 / Research exercises</title><link rel="stylesheet" href="resources/vendor/katex/katex.min.css"><style>'+style+'</style><body><nav><button data-l="zh" aria-pressed="true">中文</button><button data-l="en" aria-pressed="false">EN</button><a href="ai_agents_research_training.html#practice">↗ <span data-language="zh">幻灯片</span><span data-language="en">Slides</span></a></nav>'
for l in ['zh','en']:
 hand+=f'<div data-language="{l}"><h1>'+('选做任务' if l=='zh' else 'Optional tasks')+'</h1><p>'+('先选与你的工作接近、容易判断结果的一项。下面的文字可直接作为起始任务，再换成你自己的材料。' if l=='zh' else 'Choose a familiar task whose result you can assess. Use these prompts as starting points and substitute your own material.')+'</p>'
 for e in E:
  labs=('准备','任务','完成后','卡住时') if l=='zh' else ('Prepare','Task','Finish with','If you get stuck')
  hand+=f'<article id="{e["id"]}-{l}"><h2>{esc(e["title"][l])}</h2><p><b>{labs[0]}：</b>{esc(e["setup"][l])}</p><blockquote>{esc(e["prompt"][l])}</blockquote><p><b>{labs[2]}：</b>{esc(e["result"][l])}</p><p><b>{labs[3]}：</b>{esc(e["stuck"][l])}</p>'
  answers={'ex-web':('在 \\(m,k>0\\)、\\(k_c\\ge 0\\) 时，同相模频率为 \\(\\sqrt{\\frac{k}{m}}\\)，反相模频率为 \\(\\sqrt{\\frac{k+2k_c}{m}}\\)。\\(k_c\\to 0\\) 时两者相同。','For \\(m,k>0\\) and \\(k_c\\ge 0\\), the in-phase frequency is \\(\\sqrt{\\frac{k}{m}}\\), and the out-of-phase frequency is \\(\\sqrt{\\frac{k+2k_c}{m}}\\). They coincide as \\(k_c\\to 0\\).'),'ex-wolfram':('解析结果为 \\(\\sqrt{\\frac{\\pi}{a}}\\exp\\!\\left(-\\frac{b^2}{4a}\\right)\\)。\\(a=2\\)、\\(b=3\\) 时约为 0.4068915。','The result is \\(\\sqrt{\\frac{\\pi}{a}}\\exp\\!\\left(-\\frac{b^2}{4a}\\right)\\). At \\(a=2\\), \\(b=3\\) it is about 0.4068915.'),'ex-mathgr':('\\(\\delta\\) 的收缩应返回相应分量；对称张量交换两个相同类型的指标后，两式之差应为 0。','Delta contraction returns the corresponding component; swapping two matching indices of a symmetric tensor should leave a zero difference.')}
  if e['id'] in answers:hand+='<details><summary>'+('做完后核对' if l=='zh' else 'Check after you finish')+'</summary><p>'+answers[e['id']][0 if l=='zh' else 1]+'</p></details>'
  if e['id']=='ex-wolfram':hand+='<p><a href="https://reference.wolfram.com/language/Wolfram/AgentTools/ref/InstallMCPServer.html">Wolfram AgentTools · InstallMCPServer</a></p>'
  if e['id']=='ex-gw':hand+='<p><a href="resources/data/gw150914.json">GW150914 JSON</a> · <a href="resources/gw_explorer.html">'+('交互图' if l=='zh' else 'Interactive plot')+'</a></p>'
  hand+=' '.join(f'<a href="{url}">{label}</a>' for label,url in e['source'])+'</article>'
 hand+='</div>'
hand+='''<script>document.querySelectorAll('[data-l]').forEach(b=>b.onclick=()=>{document.documentElement.dataset.lang=b.dataset.l;document.documentElement.lang=b.dataset.l==='zh'?'zh-Hans':'en';document.querySelectorAll('[data-l]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));});</script></body></html>'''
(ROOT/'hands_on.html').write_text(render_math(hand))
(ROOT/'resources/slide_manifest.json').write_text(json.dumps([dict(number=i+1,id=s['id'],title=s['title'],notes=s['notes'],sources=s['sources'],subsection=s.get('subsection')) for i,s in enumerate(S)],ensure_ascii=False,indent=2))
print(f'Built {len(S)} slides and {len(E)} exercises.')
