feat(simulation): read-only Conversation view for completed runs

Step 3 (Step3Simulation.vue) called doStartSimulation({force:true}) on every
onMounted, so opening a finished simulation force-restarted it — wiping the run
and re-spending tokens. There was also no way to revisit a completed run's
timeline from history.

- Step3Simulation: add initSimulation() that checks run-status first. If the
  simulation already ran (completed/stopped/running, or has actions), load the
  existing timeline read-only (resume polling only if still running) instead of
  force-starting. Genuinely fresh simulations start as before.
- HistoryDatabase: add a "Step3 · Conversation" button to the history modal
  that routes to the SimulationRun view for the selected simulation.
- locales: add history.step3Button (en/zh) and update replayHint to reflect
  that completed runs are now viewable read-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
FeelRatchanons 2026-06-13 14:29:27 +07:00
parent 96096ea0ff
commit 667eb55641
4 changed files with 62 additions and 7 deletions

View File

@ -161,16 +161,25 @@
<span class="btn-icon"></span>
<span class="btn-text">{{ $t('history.step1Button') }}</span>
</button>
<button
class="modal-btn btn-simulation"
<button
class="modal-btn btn-simulation"
@click="goToSimulation"
>
<span class="btn-step">Step2</span>
<span class="btn-icon"></span>
<span class="btn-text">{{ $t('history.step2Button') }}</span>
</button>
<button
class="modal-btn btn-report"
<button
class="modal-btn btn-simulation"
@click="goToSimulationRun"
:disabled="!selectedProject.simulation_id"
>
<span class="btn-step">Step3</span>
<span class="btn-icon"></span>
<span class="btn-text">{{ $t('history.step3Button') }}</span>
</button>
<button
class="modal-btn btn-report"
@click="goToReport"
:disabled="!selectedProject.report_id"
>
@ -425,6 +434,17 @@ const goToSimulation = () => {
}
}
// /Step3
const goToSimulationRun = () => {
if (selectedProject.value?.simulation_id) {
router.push({
name: 'SimulationRun',
params: { simulationId: selectedProject.value.simulation_id }
})
closeModal()
}
}
// Report
const goToReport = () => {
if (selectedProject.value?.report_id) {

View File

@ -687,10 +687,43 @@ watch(() => props.systemLogs?.length, () => {
})
})
// onMounted force-restart
const initSimulation = async () => {
if (!props.simulationId) return
try {
const res = await getRunStatus(props.simulationId)
const data = res && res.data
const alreadyRan = data && (
data.runner_status === 'completed' ||
data.runner_status === 'stopped' ||
data.runner_status === 'running' ||
(data.total_actions_count || 0) > 0
)
if (alreadyRan) {
addLog('Loading existing run (view mode — no restart)')
runStatus.value = data
await fetchRunStatusDetail() //
if (data.runner_status === 'running') {
phase.value = 1
startStatusPolling()
startDetailPolling()
} else {
phase.value = 2
emit('update-status', 'completed')
}
return
}
} catch (err) {
console.warn('初始化状态检查失败,将按新模拟启动:', err)
}
//
doStartSimulation()
}
onMounted(() => {
addLog(t('log.step3Init'))
if (props.simulationId) {
doStartSimulation()
initSimulation()
}
})

View File

@ -311,8 +311,9 @@
"replayTitle": "Simulation Replay",
"step1Button": "Graph Build",
"step2Button": "Env Setup",
"step3Button": "Conversation",
"step4Button": "Analysis Report",
"replayHint": "Step 3 'Run Simulation' and Step 5 'Deep Interaction' must be started during runtime and do not support history replay",
"replayHint": "Step 5 'Deep Interaction' must be started during runtime; completed runs can be viewed read-only via 'Conversation'",
"notStarted": "Not started",
"roundsProgress": "{current}/{total} rounds",
"untitledSimulation": "Untitled simulation",

View File

@ -311,8 +311,9 @@
"replayTitle": "推演回放",
"step1Button": "图谱构建",
"step2Button": "环境搭建",
"step3Button": "模拟对话",
"step4Button": "分析报告",
"replayHint": "Step3「开始模拟」与 Step5「深度互动」需在运行中启动不支持历史回放",
"replayHint": "Step5「深度互动」需在运行中启动已完成的模拟可通过「模拟对话」只读查看",
"notStarted": "未开始",
"roundsProgress": "{current}/{total} 轮",
"untitledSimulation": "未命名模拟",