feat(recovery): persist active_task_id to project.json for browser-refresh reconnection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
656a3d7d5c
commit
117eabf607
|
|
@ -489,6 +489,7 @@ def build_graph():
|
|||
# Update project status
|
||||
project.status = ProjectStatus.GRAPH_BUILDING
|
||||
project.graph_build_task_id = task_id
|
||||
project.active_task_id = task_id
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
|
|
@ -592,6 +593,7 @@ def build_graph():
|
|||
|
||||
# Update project status
|
||||
project.status = ProjectStatus.GRAPH_COMPLETED
|
||||
project.active_task_id = None
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
node_count = graph_data.get("node_count", 0)
|
||||
|
|
@ -620,6 +622,7 @@ def build_graph():
|
|||
|
||||
project.status = ProjectStatus.FAILED
|
||||
project.error = str(e)
|
||||
project.active_task_id = None
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
task_manager.update_task(
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ class Project:
|
|||
# Error info
|
||||
error: Optional[str] = None
|
||||
|
||||
# Persisted so the frontend can reconnect after a page refresh
|
||||
active_task_id: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary"""
|
||||
return {
|
||||
|
|
@ -69,7 +72,8 @@ class Project:
|
|||
"simulation_requirement": self.simulation_requirement,
|
||||
"chunk_size": self.chunk_size,
|
||||
"chunk_overlap": self.chunk_overlap,
|
||||
"error": self.error
|
||||
"error": self.error,
|
||||
"active_task_id": self.active_task_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -94,7 +98,8 @@ class Project:
|
|||
simulation_requirement=data.get('simulation_requirement'),
|
||||
chunk_size=data.get('chunk_size', 500),
|
||||
chunk_overlap=data.get('chunk_overlap', 50),
|
||||
error=data.get('error')
|
||||
error=data.get('error'),
|
||||
active_task_id=data.get('active_task_id'),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
def test_project_serializes_active_task_id():
|
||||
"""active_task_id is included in Project.to_dict()."""
|
||||
from app.models.project import Project, ProjectStatus
|
||||
p = Project(
|
||||
project_id="proj-1", name="Test",
|
||||
status=ProjectStatus.GRAPH_BUILDING,
|
||||
created_at="2026-01-01", updated_at="2026-01-01",
|
||||
active_task_id="task-abc-123",
|
||||
)
|
||||
assert p.to_dict()["active_task_id"] == "task-abc-123"
|
||||
|
||||
|
||||
def test_project_deserializes_active_task_id():
|
||||
"""Project.from_dict() restores active_task_id from JSON."""
|
||||
from app.models.project import Project
|
||||
data = {
|
||||
"project_id": "proj-1", "name": "Test", "status": "graph_building",
|
||||
"created_at": "2026-01-01", "updated_at": "2026-01-01",
|
||||
"active_task_id": "task-abc-123",
|
||||
}
|
||||
assert Project.from_dict(data).active_task_id == "task-abc-123"
|
||||
|
||||
|
||||
def test_project_active_task_id_defaults_none():
|
||||
"""active_task_id defaults to None for projects without it (backward compat)."""
|
||||
from app.models.project import Project
|
||||
data = {
|
||||
"project_id": "proj-1", "name": "Test", "status": "created",
|
||||
"created_at": "2026-01-01", "updated_at": "2026-01-01",
|
||||
}
|
||||
assert Project.from_dict(data).active_task_id is None
|
||||
|
|
@ -257,10 +257,14 @@ const loadProject = async () => {
|
|||
|
||||
if (res.data.status === 'ontology_generated' && !res.data.graph_id) {
|
||||
await startBuildGraph()
|
||||
} else if (res.data.status === 'graph_building' && res.data.graph_build_task_id) {
|
||||
currentPhase.value = 1
|
||||
startPollingTask(res.data.graph_build_task_id)
|
||||
startGraphPolling()
|
||||
} else if (res.data.status === 'graph_building') {
|
||||
const taskId = res.data.active_task_id || res.data.graph_build_task_id
|
||||
if (taskId) {
|
||||
currentPhase.value = 1
|
||||
addLog(t('log.reconnectingToTask', { taskId }))
|
||||
startPollingTask(taskId)
|
||||
startGraphPolling()
|
||||
}
|
||||
} else if (res.data.status === 'graph_completed' && res.data.graph_id) {
|
||||
currentPhase.value = 2
|
||||
await loadGraph(res.data.graph_id)
|
||||
|
|
|
|||
|
|
@ -596,7 +596,8 @@
|
|||
"getReportInfoFailed": "Error en obtenir la informació de l'informe: {error}",
|
||||
"enterStep": "Entrant al pas {step}: {name}",
|
||||
"returnToStep": "Tornant al pas {step}: {name}",
|
||||
"customSimRounds": "Rondes de simulació personalitzades: {rounds} rondes"
|
||||
"customSimRounds": "Rondes de simulació personalitzades: {rounds} rondes",
|
||||
"reconnectingToTask": "Reconnectant a la tasca activa {taskId}…"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "Tasca de generació de l'informe iniciada",
|
||||
|
|
|
|||
|
|
@ -597,7 +597,8 @@
|
|||
"getReportInfoFailed": "Failed to get report info: {error}",
|
||||
"enterStep": "Entering Step {step}: {name}",
|
||||
"returnToStep": "Returning to Step {step}: {name}",
|
||||
"customSimRounds": "Custom simulation rounds: {rounds} rounds"
|
||||
"customSimRounds": "Custom simulation rounds: {rounds} rounds",
|
||||
"reconnectingToTask": "Reconnecting to active task {taskId}…"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "Report generation task started",
|
||||
|
|
|
|||
|
|
@ -596,7 +596,8 @@
|
|||
"getReportInfoFailed": "Error al obtener información del informe: {error}",
|
||||
"enterStep": "Entrando al Paso {step}: {name}",
|
||||
"returnToStep": "Volviendo al Paso {step}: {name}",
|
||||
"customSimRounds": "Rondas de simulación personalizadas: {rounds} rondas"
|
||||
"customSimRounds": "Rondas de simulación personalizadas: {rounds} rondas",
|
||||
"reconnectingToTask": "Reconectando a la tarea activa {taskId}…"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "Tarea de generación de informe iniciada",
|
||||
|
|
|
|||
|
|
@ -597,7 +597,8 @@
|
|||
"getReportInfoFailed": "获取报告信息失败: {error}",
|
||||
"enterStep": "进入 Step {step}: {name}",
|
||||
"returnToStep": "返回 Step {step}: {name}",
|
||||
"customSimRounds": "自定义模拟轮数: {rounds} 轮"
|
||||
"customSimRounds": "自定义模拟轮数: {rounds} 轮",
|
||||
"reconnectingToTask": "重新连接到活动任务 {taskId}…"
|
||||
},
|
||||
"report": {
|
||||
"taskStarted": "报告生成任务开始",
|
||||
|
|
|
|||
Loading…
Reference in New Issue