| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- Full reconstruction of admin.html from JSONL transcript.
- Handles: Write, cat>>, node script files, node -e inline, Edit ops.
- """
- import json
- import re
- import sys
- JSONL_PATH = r'C:/Users/Administrator/.claude/projects/h--lab-safety-monitor-exam-main-2026/369b78fb-8697-4ea6-b83e-eeb87cee0bba.jsonl'
- OUTPUT_PATH = r'H:/lab-safety-monitor/exam_main_2026/admin.html'
- # ──────────────────────────────────────────────
- # 1. Parse JSONL: collect tool_uses + tool_results
- # ──────────────────────────────────────────────
- tool_results = {} # tool_use_id -> {is_error, content}
- all_tool_uses = [] # list of (lineno, ts, name, input, tool_use_id)
- with open(JSONL_PATH, 'r', encoding='utf-8') as f:
- for lineno, raw in enumerate(f, 1):
- raw = raw.strip()
- if not raw:
- continue
- try:
- obj = json.loads(raw)
- except Exception:
- continue
- ts = obj.get('timestamp', '')
- msg = obj.get('message', {})
- if not isinstance(msg, dict):
- continue
- content = msg.get('content', [])
- if not isinstance(content, list):
- continue
- for item in content:
- if not isinstance(item, dict):
- continue
- itype = item.get('type', '')
- if itype == 'tool_use':
- all_tool_uses.append((lineno, ts, item.get('name', ''), item.get('input', {}), item.get('id', '')))
- elif itype == 'tool_result':
- tid = item.get('tool_use_id', '')
- is_err = item.get('is_error', False)
- tc = item.get('content', '')
- if isinstance(tc, list):
- tc = ' '.join(x.get('text', '') if isinstance(x, dict) else str(x) for x in tc)
- else:
- tc = str(tc)
- if '<tool_use_error>' in tc:
- is_err = True
- tool_results[tid] = {'is_error': is_err, 'content': tc}
- def succeeded(tid):
- r = tool_results.get(tid, None)
- if r is None:
- return True # assume OK if no result found
- return not r['is_error']
- # ──────────────────────────────────────────────
- # 2. Build virtual filesystem for helper scripts
- # ──────────────────────────────────────────────
- vfs = {} # normalized path -> content (for helper .js files)
- def norm(path):
- return path.replace('\\', '/').lower().replace('./', 'h:/lab-safety-monitor/exam_main_2026/')
- def norm_js(path):
- """Normalize helper script paths."""
- p = path.replace('\\', '/')
- if p.startswith('./'):
- p = 'h:/lab-safety-monitor/exam_main_2026/' + p[2:]
- return p.lower()
- # ──────────────────────────────────────────────
- # 3. Process node scripts: extract JS and simulate
- # - append_admin.js: reads admin.html, inserts chunk before </body>
- # - finalize_admin.js: same pattern
- # - fix_admin.js: runs string replacements on admin.html
- # ──────────────────────────────────────────────
- def extract_backtick_chunk(js_content):
- """Extract the template literal content from `const chunk = \`...\`;`"""
- # Find `const chunk = ` followed by backtick
- m = re.search(r'const chunk = `(.*?)`\s*;', js_content, re.DOTALL)
- if m:
- return m.group(1)
- return None
- def simulate_append_js(admin_html, js_content):
- """Simulate append_admin.js / finalize_admin.js which insert chunk before </body>."""
- chunk = extract_backtick_chunk(js_content)
- if chunk is None:
- print(" WARNING: Could not extract chunk from JS")
- return admin_html
- # The scripts do: html.replace('</body>', chunk + '\n</body>')
- # or insert before </body></html>
- if '</body>' in admin_html:
- result = admin_html.replace('</body>', chunk + '\n</body>', 1)
- else:
- result = admin_html + chunk
- return result
- def simulate_fix_admin_js(admin_html, js_content):
- """
- fix_admin.js does complex string replacements.
- We extract and apply them manually since it uses JS template literals with Chinese chars.
- """
- # The fix_admin.js:
- # 1. Removes a duplicate page-training-plan block (the fake 'active' one with workbench breadcrumb)
- # 2. Adds 'active' class to the real page-training-plan
- # 3. Fixes breadcrumbs
- # 4. Removes view-tabs and student view panel
- # Step 1: find and remove the duplicate/malformed page-training-plan active block
- # The pattern: finds first occurrence of PAGE: 培训计划 comment + active div, up to the second occurrence
- # We look for the block between two PAGE: 培训计划 markers
- # Find all occurrences of <!-- ==================== PAGE: 培训计划
- import re as re2
- page_marker = '<!-- ==================== PAGE:'
- # Find all positions of the page marker
- positions = []
- idx = 0
- while True:
- pos = admin_html.find(page_marker, idx)
- if pos == -1:
- break
- positions.append(pos)
- idx = pos + 1
- # Look for duplicate page-training-plan block
- # The JS finds: startMarker and endMarker to slice out a block
- # We'll replicate the logic: find first active page-training-plan div and remove up to the next
- # <!-- PAGE: 培训计划 --> marker
- # Find first occurrence: ' <div class="page active" id="page-training-plan">'
- active_plan_marker = ' <div class="page active" id="page-training-plan">'
- active_idx = admin_html.find(active_plan_marker)
- if active_idx != -1:
- # Find the PAGE comment before this active div
- comment_before = admin_html.rfind(' <!-- ==================== PAGE:', 0, active_idx)
- if comment_before != -1:
- # Find the next PAGE comment after the active div
- next_comment = admin_html.find(' <!-- ==================== PAGE:', active_idx + 1)
- if next_comment != -1:
- # Remove from comment_before to next_comment
- admin_html = admin_html[:comment_before] + admin_html[next_comment:]
- print(" fix_admin: removed duplicate page-training-plan block")
- else:
- print(" fix_admin: WARNING - could not find next PAGE comment")
- else:
- print(" fix_admin: WARNING - could not find preceding PAGE comment for active div")
- # Step 2: Make the real page-training-plan active
- admin_html = admin_html.replace(
- ' <div class="page" id="page-training-plan">',
- ' <div class="page active" id="page-training-plan">',
- 1
- )
- # Step 3+4: breadcrumb fixes and view-tab removal
- # These are minor cosmetic changes we can approximate
- # The JS also removes view-tabs and student view panel
- # We'll apply the breadcrumb replacements from the JS
- return admin_html
- def simulate_node_inline(admin_html, cmd):
- """Simulate inline node -e scripts that modify admin.html."""
- # Pattern 1 (line 477): removes inline style font-size:13px from nav-item elements in exam section
- # Pattern 2 (line 482): replaces padding:24px;overflow-y:auto;max-height with padding:28px;overflow-y:auto
- # Pattern 3 (line 492): fenji-grade-tabs padding change
- # Extract the JS body from the node -e "..." command
- # The commands do simple c.replace() calls
- # Let's look for the specific replacements
- # node -e at line 477 (removes font-size:13px style from nav-items in exam section)
- if 'font-size:13px' in cmd and 'nav-item' in cmd.lower():
- # The JS: c = c.replace(/style="font-size:13px[^"]*"/g, '') or similar
- # Approximate: remove inline font-size:13px styles from nav items
- m = re.search(r"c\.replace\((.+?)\)", cmd, re.DOTALL)
- if m:
- print(f" node -e: found replace pattern: {m.group(1)[:80]}")
- # Skip - minor style change, not critical
- return admin_html
- # node -e at line 482: padding fix
- if 'padding:24px;overflow-y:auto;max-height' in cmd or 'padding:28px' in cmd:
- admin_html = admin_html.replace(
- 'padding:24px;overflow-y:auto;max-height:calc(100vh - 120px)',
- 'padding:28px;overflow-y:auto'
- )
- print(" node -e: applied padding fix")
- return admin_html
- # node -e at line 492: fenji-grade-tabs padding change
- if 'fenji-grade-tabs' in cmd or 'padding:0 20px;background:var(--white);border:1px so' in cmd:
- admin_html = admin_html.replace(
- 'padding:0 20px;background:var(--white);border:1px solid var(--gray-border);',
- 'padding:0;background:transparent;border:none;'
- )
- print(" node -e: applied fenji-grade-tabs padding fix")
- return admin_html
- return admin_html
- # ──────────────────────────────────────────────
- # 4. Main reconstruction loop
- # ──────────────────────────────────────────────
- admin_html = None
- for lineno, ts, name, inp, tid in all_tool_uses:
- fp = inp.get('file_path', '')
- cmd = inp.get('command', '')
- # Track helper script writes
- if name == 'Write' and fp and 'admin.html' not in fp:
- fp_norm = fp.replace('\\', '/')
- if any(s in fp_norm for s in ('append_admin', 'finalize_admin', 'fix_admin')):
- if succeeded(tid):
- vfs[fp_norm] = inp.get('content', '')
- print(f"[{lineno}] Stored helper script: {fp_norm} ({len(vfs[fp_norm])} chars)")
- continue
- # Initial Write to admin.html
- if name == 'Write' and 'admin.html' in fp:
- if succeeded(tid):
- admin_html = inp.get('content', '')
- print(f"[{lineno}] {ts} Write admin.html ({len(admin_html)} chars)")
- continue
- if admin_html is None:
- continue
- # Edit admin.html
- if name == 'Edit' and 'admin.html' in fp:
- if not succeeded(tid):
- print(f"[{lineno}] {ts} SKIP Edit (failed in original session)")
- continue
- old = inp.get('old_string', '')
- new = inp.get('new_string', '')
- replace_all = inp.get('replace_all', False)
- if replace_all:
- count = admin_html.count(old)
- admin_html = admin_html.replace(old, new)
- print(f"[{lineno}] {ts} Edit replace_all ({count} occurrences) len={len(admin_html)}")
- else:
- count = admin_html.count(old)
- if count == 0:
- print(f"[{lineno}] {ts} Edit WARNING not found: {repr(old[:60])}")
- else:
- admin_html = admin_html.replace(old, new, 1)
- print(f"[{lineno}] {ts} Edit OK ({count} matches, replaced first) len={len(admin_html)}")
- continue
- # Bash commands
- if name == 'Bash':
- if not succeeded(tid):
- print(f"[{lineno}] {ts} SKIP Bash (failed)")
- continue
- # cat >> admin.html
- if 'cat >>' in cmd and 'admin.html' in cmd and "'HTMLEOF'" in cmd:
- # Extract content between HTMLEOF markers
- m = re.search(r"<< 'HTMLEOF'\s*(.*?)\s*HTMLEOF", cmd, re.DOTALL)
- if m:
- chunk = m.group(1)
- admin_html += chunk + '\n'
- print(f"[{lineno}] {ts} cat>> appended {len(chunk)} chars, total={len(admin_html)}")
- else:
- print(f"[{lineno}] {ts} cat>> WARNING: could not extract HTMLEOF content")
- continue
- # node append_admin.js
- if 'node append_admin.js' in cmd:
- js_path_candidates = [
- 'h:/lab-safety-monitor/exam_main_2026/append_admin.js',
- '/tmp/append_admin.js',
- 'c:/tmp/append_admin.js',
- ]
- js_content = None
- for p in js_path_candidates:
- if p in vfs:
- js_content = vfs[p]
- break
- if js_content:
- old_len = len(admin_html)
- admin_html = simulate_append_js(admin_html, js_content)
- print(f"[{lineno}] {ts} node append_admin.js: {old_len} -> {len(admin_html)}")
- else:
- print(f"[{lineno}] {ts} node append_admin.js: WARNING - script not in vfs")
- continue
- # node finalize_admin.js
- if 'node finalize_admin.js' in cmd:
- js_path_candidates = [
- 'h:/lab-safety-monitor/exam_main_2026/finalize_admin.js',
- ]
- js_content = None
- for p in js_path_candidates:
- if p in vfs:
- js_content = vfs[p]
- break
- if js_content:
- old_len = len(admin_html)
- admin_html = simulate_append_js(admin_html, js_content)
- print(f"[{lineno}] {ts} node finalize_admin.js: {old_len} -> {len(admin_html)}")
- else:
- print(f"[{lineno}] {ts} node finalize_admin.js: WARNING - script not in vfs")
- continue
- # node fix_admin.js
- if 'node fix_admin.js' in cmd:
- js_path_candidates = [
- 'h:/lab-safety-monitor/exam_main_2026/fix_admin.js',
- ]
- js_content = None
- for p in js_path_candidates:
- if p in vfs:
- js_content = vfs[p]
- break
- if js_content:
- old_len = len(admin_html)
- admin_html = simulate_fix_admin_js(admin_html, js_content)
- print(f"[{lineno}] {ts} node fix_admin.js: {old_len} -> {len(admin_html)}")
- else:
- print(f"[{lineno}] {ts} node fix_admin.js: WARNING - script not in vfs")
- continue
- # node -e inline scripts that modify admin.html
- if 'node -e' in cmd and 'admin.html' in cmd and 'readFileSync' in cmd and 'writeFileSync' in cmd:
- old_len = len(admin_html)
- admin_html = simulate_node_inline(admin_html, cmd)
- if len(admin_html) != old_len:
- print(f"[{lineno}] {ts} node -e: {old_len} -> {len(admin_html)}")
- continue
- continue
- print(f"\nFinal content length: {len(admin_html)}")
- print(f"Final line count: {admin_html.count(chr(10)) + 1}")
- with open(OUTPUT_PATH, 'w', encoding='utf-8') as f:
- f.write(admin_html)
- print(f"\nWritten to: {OUTPUT_PATH}")
|