Skip to content

Commit eb3e9d1

Browse files
JihaoXinclaude
andcommitted
Add restart dialog with state cleanup and pre-filled settings
- Replace simple "Start" button with restart modal that pre-fills all project settings (idea, venue, model, telegram, iterations) - User can modify any setting before restarting - Deep research checkbox: keep existing report or redo from scratch - Backend cleans state (auto_research/state/, results/, experiments/, paper generated files) while preserving venue templates and uploaded PDF - Rewrite config.yaml with updated settings before resubmitting job - Add venue_format, venue_pages, telegram_token, telegram_chat_id, has_deep_research to project detail API response - Fix subprocess.run UTF-8 decode in compile_latex (handle both bytes and str for mock compatibility) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2650399 commit eb3e9d1

File tree

3 files changed

+268
-3
lines changed

3 files changed

+268
-3
lines changed

ark/compiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ def compile_latex(self) -> bool:
201201
timeout=120,
202202
cwd=self.latex_dir,
203203
)
204-
stderr = result.stderr.decode("utf-8", errors="replace")
205-
stdout = result.stdout.decode("utf-8", errors="replace")
204+
stderr = result.stderr.decode("utf-8", errors="replace") if isinstance(result.stderr, bytes) else (result.stderr or "")
205+
stdout = result.stdout.decode("utf-8", errors="replace") if isinstance(result.stdout, bytes) else (result.stdout or "")
206206
if result.returncode != 0 and "main.tex" in cmd:
207207
self._last_compile_stderr = stderr[:1000] or stdout[-1000:]
208208
self.log(f"LaTeX compilation warning: {stderr[:500]}")

ark/webapp/routes.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,57 @@ def _write_config_yaml(project_dir: Path, project: Project, model: str = "claude
175175
config_path.write_text(yaml.dump(config, default_flow_style=False, allow_unicode=True))
176176

177177

178+
def _clean_project_state(project_dir: Path, keep_deep_research: bool = True):
179+
"""Remove all generated state/results for a fresh restart.
180+
181+
Preserves: config.yaml, uploaded.pdf, venue template files (.cls/.sty/.bst),
182+
agent prompts, and optionally deep_research.md/pdf.
183+
"""
184+
# Clean auto_research/state/
185+
state_dir = project_dir / "auto_research" / "state"
186+
if state_dir.exists():
187+
for f in state_dir.iterdir():
188+
if keep_deep_research and f.name.startswith("deep_research"):
189+
continue
190+
if f.is_file():
191+
f.unlink()
192+
193+
# Clean auto_research/logs/
194+
logs_dir = project_dir / "auto_research" / "logs"
195+
if logs_dir.exists():
196+
shutil.rmtree(logs_dir, ignore_errors=True)
197+
logs_dir.mkdir(exist_ok=True)
198+
199+
# Clean results/ and experiments/
200+
for dirname in ("results", "experiments"):
201+
d = project_dir / dirname
202+
if d.exists():
203+
shutil.rmtree(d, ignore_errors=True)
204+
d.mkdir(exist_ok=True)
205+
206+
# Clean paper/ — keep venue template files, remove generated content
207+
paper_dir = project_dir / "paper"
208+
if paper_dir.exists():
209+
keep_exts = {".cls", ".sty", ".bst"}
210+
for f in paper_dir.iterdir():
211+
if f.is_dir():
212+
if f.name == "figures":
213+
shutil.rmtree(f, ignore_errors=True)
214+
f.mkdir(exist_ok=True)
215+
elif f.suffix not in keep_exts:
216+
f.unlink()
217+
218+
# Clean scripts/ (generated figure scripts)
219+
scripts_dir = project_dir / "scripts"
220+
if scripts_dir.exists():
221+
shutil.rmtree(scripts_dir, ignore_errors=True)
222+
223+
# Remove .git (will be re-initialized)
224+
git_dir = project_dir / ".git"
225+
if git_dir.exists():
226+
shutil.rmtree(git_dir, ignore_errors=True)
227+
228+
178229
def _write_user_instructions(project_dir: Path, message: str, source: str = "webapp_create"):
179230
"""Write a persistent instruction to user_instructions.yaml."""
180231
state_dir = project_dir / "auto_research" / "state"
@@ -703,6 +754,8 @@ async def api_get_project(project_id: str, request: Request):
703754
"title": project.title,
704755
"idea": project.idea,
705756
"venue": project.venue,
757+
"venue_format": project.venue_format,
758+
"venue_pages": project.venue_pages,
706759
"mode": project.mode,
707760
"status": project.status,
708761
"score": score,
@@ -715,6 +768,9 @@ async def api_get_project(project_id: str, request: Request):
715768
"queue_position": _queue_position(project_id, session),
716769
"user_email": owner.email if owner else "",
717770
"model": _read_project_model(pdir),
771+
"telegram_token": project.telegram_token,
772+
"telegram_chat_id": project.telegram_chat_id,
773+
"has_deep_research": (pdir / "auto_research" / "state" / "deep_research.md").exists(),
718774
"environment": "ROCS Testbed" if project.slurm_job_id and project.slurm_job_id != "local" else "Local",
719775
"created_at": project.created_at.isoformat(),
720776
"updated_at": project.updated_at.isoformat(),
@@ -745,6 +801,13 @@ async def api_restart_project(project_id: str, request: Request):
745801
user = _require_user(request)
746802
_check_webapp_enabled()
747803
settings = get_settings()
804+
805+
# Parse JSON body (new restart dialog sends settings)
806+
try:
807+
body = await request.json()
808+
except Exception:
809+
body = {}
810+
748811
with get_session(settings.db_path) as session:
749812
project = get_project(session, project_id)
750813
if not project or not _can_access_project(user, project):
@@ -756,6 +819,41 @@ async def api_restart_project(project_id: str, request: Request):
756819
if active:
757820
raise HTTPException(400, "You already have an active project.")
758821
pdir = _project_dir(settings, project.user_id, project_id)
822+
823+
# Update project fields from request body
824+
if body.get("idea"):
825+
project.idea = body["idea"]
826+
if body.get("venue"):
827+
project.venue = body["venue"]
828+
if body.get("venue_format"):
829+
project.venue_format = body["venue_format"]
830+
if "venue_pages" in body:
831+
project.venue_pages = int(body["venue_pages"])
832+
if "max_iterations" in body:
833+
project.max_iterations = max(1, min(3, int(body["max_iterations"])))
834+
if "telegram_token" in body:
835+
project.telegram_token = body["telegram_token"]
836+
if "telegram_chat_id" in body:
837+
project.telegram_chat_id = body["telegram_chat_id"]
838+
project.score = 0.0
839+
840+
# Clean up project state for fresh restart
841+
redo_deep_research = body.get("redo_deep_research", False)
842+
_clean_project_state(pdir, keep_deep_research=not redo_deep_research)
843+
844+
# Rewrite config.yaml with updated settings
845+
model = body.get("model", "claude-sonnet-4-6")
846+
_write_config_yaml(pdir, project, model=model)
847+
848+
# Write instructions if provided
849+
comment = body.get("comment", "").strip()
850+
if comment:
851+
_write_user_instructions(pdir, comment, source="webapp_restart")
852+
853+
session.add(project)
854+
session.commit()
855+
session.refresh(project)
856+
759857
final_status = _try_submit_or_pending(project, pdir, session, settings)
760858
send_telegram_notify(
761859
f"🔄 <b>{project.name}</b> restarted ({final_status})",

ark/webapp/static/app.html

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,72 @@ <h3 data-i18n="continue_project">Continue Project</h3>
907907
</div>
908908
</div>
909909

910+
<!-- RESTART MODAL -->
911+
<div class="modal-backdrop" id="modal-restart">
912+
<div class="modal" style="max-width:620px">
913+
<h3>Restart Project</h3>
914+
<div class="form-group">
915+
<label>Research Idea</label>
916+
<textarea id="restart-idea" rows="4" style="width:100%"></textarea>
917+
</div>
918+
<div class="form-group">
919+
<label>Instructions <span style="font-weight:400;color:var(--text-muted)">(optional)</span></label>
920+
<textarea id="restart-comment" rows="3" style="width:100%"
921+
placeholder="e.g. Focus on a different baseline, change experiment setup..."></textarea>
922+
</div>
923+
<div class="form-row">
924+
<div class="form-group" style="flex:2">
925+
<label>Venue</label>
926+
<select id="restart-venue" onchange="restartVenueChanged(this)"></select>
927+
</div>
928+
<div class="form-group" style="flex:1">
929+
<label>Max iterations</label>
930+
<input type="number" id="restart-max-iter" value="3" min="1" max="3" style="width:80px" />
931+
</div>
932+
</div>
933+
<input type="hidden" id="restart-venue-format" />
934+
<input type="hidden" id="restart-venue-pages" />
935+
<div class="form-group">
936+
<label>Model</label>
937+
<div style="display:flex;gap:8px;flex-wrap:wrap" id="restart-model-options">
938+
<label class="model-option"><input type="radio" name="restart-model" value="claude-sonnet-4-6" checked /><span class="model-chip">Sonnet 4.6</span></label>
939+
<label class="model-option restart-model-admin" style="display:none"><input type="radio" name="restart-model" value="claude-opus-4-6" /><span class="model-chip">Opus 4.6</span></label>
940+
<label class="model-option restart-model-admin" style="display:none"><input type="radio" name="restart-model" value="claude-haiku-4-5" /><span class="model-chip">Haiku 4.5</span></label>
941+
<label class="model-option restart-model-admin" style="display:none"><input type="radio" name="restart-model" value="gemini" /><span class="model-chip">Gemini</span></label>
942+
</div>
943+
</div>
944+
<div class="tg-section" style="margin-top:12px">
945+
<div class="tg-section-title">
946+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" style="flex-shrink:0">
947+
<circle cx="12" cy="12" r="12" fill="#29B6F6"/>
948+
<path d="M5.5 11.5l10-4.5-1.5 9-3-2.5-2 2v-3l5-4.5-6.5 3.5-2-1z" fill="white"/>
949+
</svg>
950+
<span>Telegram Notifications</span>
951+
</div>
952+
<div class="form-row" style="margin-top:8px">
953+
<div class="form-group" style="margin-bottom:0">
954+
<label>Bot Token</label>
955+
<input type="text" id="restart-tg-token" placeholder="123456789:ABCdef..." autocomplete="off" />
956+
</div>
957+
<div class="form-group" style="margin-bottom:0">
958+
<label>Chat ID</label>
959+
<input type="text" id="restart-tg-chatid" placeholder="1234567890" />
960+
</div>
961+
</div>
962+
</div>
963+
<div id="restart-deep-research-section" style="margin-top:16px;display:none">
964+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
965+
<input type="checkbox" id="restart-keep-dr" checked />
966+
<span>Keep existing Deep Research report <span style="color:var(--text-muted);font-weight:400">(uncheck to redo)</span></span>
967+
</label>
968+
</div>
969+
<div class="modal-footer">
970+
<button type="button" class="btn-secondary" onclick="closeModal('modal-restart')">Cancel</button>
971+
<button class="btn-primary" id="restart-btn" onclick="submitRestart()">Restart</button>
972+
</div>
973+
</div>
974+
</div>
975+
910976
<!-- ADMIN EDIT MODAL -->
911977
<div class="modal-backdrop" id="modal-admin-edit">
912978
<div class="modal" style="max-width:620px">
@@ -1903,7 +1969,7 @@ <h3 data-i18n="edit_project">Edit Project</h3>
19031969
html += `<button class="btn-secondary" onclick="stopProject('${p.id}')">${t('stop')}</button>`;
19041970
}
19051971
if (p.status === 'stopped' || p.status === 'failed') {
1906-
html += `<button class="btn-primary" onclick="startProject('${p.id}')">${t('start')}</button>`;
1972+
html += `<button class="btn-primary" onclick="openRestartModal('${p.id}')">Restart</button>`;
19071973
}
19081974
if (p.status === 'done') {
19091975
html += `<button class="btn-primary" onclick="openContinueModal('${p.id}')">${t('continue_action')}</button>`;
@@ -2259,6 +2325,107 @@ <h3 data-i18n="edit_project">Edit Project</h3>
22592325
}
22602326
}
22612327

2328+
// ══════════════════════════════════════════════════════
2329+
// Restart Modal
2330+
// ══════════════════════════════════════════════════════
2331+
let restartProjectId = null;
2332+
2333+
async function openRestartModal(id) {
2334+
restartProjectId = id;
2335+
// Fetch project detail to pre-fill fields
2336+
const res = await fetch(`/api/projects/${id}`);
2337+
if (!res.ok) { alert('Failed to load project details'); return; }
2338+
const p = await res.json();
2339+
2340+
document.getElementById('restart-idea').value = p.idea || '';
2341+
document.getElementById('restart-comment').value = '';
2342+
document.getElementById('restart-max-iter').value = p.max_iterations || 3;
2343+
document.getElementById('restart-tg-token').value = p.telegram_token || '';
2344+
document.getElementById('restart-tg-chatid').value = p.telegram_chat_id || '';
2345+
2346+
// Populate venue select (reuse venues from hero form)
2347+
const venueSelect = document.getElementById('restart-venue');
2348+
const heroSelect = document.getElementById('hero-venue-select');
2349+
venueSelect.innerHTML = heroSelect.innerHTML;
2350+
// Set current venue
2351+
if (p.venue) {
2352+
for (const opt of venueSelect.options) {
2353+
if (opt.value === p.venue || opt.textContent === p.venue) {
2354+
opt.selected = true;
2355+
break;
2356+
}
2357+
}
2358+
}
2359+
// Set hidden venue_format and venue_pages from current project
2360+
document.getElementById('restart-venue-format').value = p.venue_format || 'neurips';
2361+
document.getElementById('restart-venue-pages').value = p.venue_pages || 9;
2362+
2363+
// Set model radio
2364+
const modelMap = { 'claude-sonnet-4-6': 'claude-sonnet-4-6', 'claude-opus-4-6': 'claude-opus-4-6',
2365+
'claude-haiku-4-5': 'claude-haiku-4-5', 'gemini': 'gemini', 'sonnet': 'claude-sonnet-4-6',
2366+
'opus': 'claude-opus-4-6', 'haiku': 'claude-haiku-4-5' };
2367+
const modelVal = modelMap[p.model] || 'claude-sonnet-4-6';
2368+
document.querySelectorAll('input[name="restart-model"]').forEach(r => {
2369+
r.checked = (r.value === modelVal);
2370+
});
2371+
// Show admin model options if user is admin
2372+
if (currentUser && currentUser.is_admin) {
2373+
document.querySelectorAll('.restart-model-admin').forEach(el => el.style.display = '');
2374+
}
2375+
2376+
// Deep research checkbox
2377+
const drSection = document.getElementById('restart-deep-research-section');
2378+
if (p.has_deep_research) {
2379+
drSection.style.display = '';
2380+
document.getElementById('restart-keep-dr').checked = true;
2381+
} else {
2382+
drSection.style.display = 'none';
2383+
}
2384+
2385+
document.getElementById('modal-restart').classList.add('open');
2386+
}
2387+
2388+
function restartVenueChanged(sel) {
2389+
const opt = sel.options[sel.selectedIndex];
2390+
if (opt && opt.dataset.format) {
2391+
document.getElementById('restart-venue-format').value = opt.dataset.format;
2392+
document.getElementById('restart-venue-pages').value = opt.dataset.pages || 9;
2393+
}
2394+
}
2395+
2396+
async function submitRestart() {
2397+
const btn = document.getElementById('restart-btn');
2398+
btn.disabled = true;
2399+
try {
2400+
const body = {
2401+
idea: document.getElementById('restart-idea').value.trim(),
2402+
venue: document.getElementById('restart-venue').value,
2403+
venue_format: document.getElementById('restart-venue-format').value,
2404+
venue_pages: parseInt(document.getElementById('restart-venue-pages').value) || 9,
2405+
max_iterations: Math.max(1, Math.min(3, parseInt(document.getElementById('restart-max-iter').value) || 3)),
2406+
model: ([...document.querySelectorAll('input[name="restart-model"]')].find(r => r.checked) || {}).value || 'claude-sonnet-4-6',
2407+
telegram_token: document.getElementById('restart-tg-token').value.trim(),
2408+
telegram_chat_id: document.getElementById('restart-tg-chatid').value.trim(),
2409+
comment: document.getElementById('restart-comment').value.trim(),
2410+
redo_deep_research: !document.getElementById('restart-keep-dr').checked,
2411+
};
2412+
const res = await fetch(`/api/projects/${restartProjectId}/restart`, {
2413+
method: 'POST',
2414+
headers: { 'Content-Type': 'application/json' },
2415+
body: JSON.stringify(body),
2416+
});
2417+
if (!res.ok) {
2418+
const e = await res.json().catch(() => ({}));
2419+
alert(e.detail || 'Failed to restart project.');
2420+
return;
2421+
}
2422+
closeModal('modal-restart');
2423+
loadDetail(restartProjectId);
2424+
} finally {
2425+
btn.disabled = false;
2426+
}
2427+
}
2428+
22622429
// ══════════════════════════════════════════════════════
22632430
// Utils
22642431
// ══════════════════════════════════════════════════════

0 commit comments

Comments
 (0)