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
|
# Update project status
|
||||||
project.status = ProjectStatus.GRAPH_BUILDING
|
project.status = ProjectStatus.GRAPH_BUILDING
|
||||||
project.graph_build_task_id = task_id
|
project.graph_build_task_id = task_id
|
||||||
|
project.active_task_id = task_id
|
||||||
ProjectManager.save_project(project)
|
ProjectManager.save_project(project)
|
||||||
|
|
||||||
# Capture locale before spawning background thread
|
# Capture locale before spawning background thread
|
||||||
|
|
@ -592,6 +593,7 @@ def build_graph():
|
||||||
|
|
||||||
# Update project status
|
# Update project status
|
||||||
project.status = ProjectStatus.GRAPH_COMPLETED
|
project.status = ProjectStatus.GRAPH_COMPLETED
|
||||||
|
project.active_task_id = None
|
||||||
ProjectManager.save_project(project)
|
ProjectManager.save_project(project)
|
||||||
|
|
||||||
node_count = graph_data.get("node_count", 0)
|
node_count = graph_data.get("node_count", 0)
|
||||||
|
|
@ -620,6 +622,7 @@ def build_graph():
|
||||||
|
|
||||||
project.status = ProjectStatus.FAILED
|
project.status = ProjectStatus.FAILED
|
||||||
project.error = str(e)
|
project.error = str(e)
|
||||||
|
project.active_task_id = None
|
||||||
ProjectManager.save_project(project)
|
ProjectManager.save_project(project)
|
||||||
|
|
||||||
task_manager.update_task(
|
task_manager.update_task(
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ class Project:
|
||||||
# Error info
|
# Error info
|
||||||
error: Optional[str] = None
|
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]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""Convert to dictionary"""
|
"""Convert to dictionary"""
|
||||||
return {
|
return {
|
||||||
|
|
@ -69,7 +72,8 @@ class Project:
|
||||||
"simulation_requirement": self.simulation_requirement,
|
"simulation_requirement": self.simulation_requirement,
|
||||||
"chunk_size": self.chunk_size,
|
"chunk_size": self.chunk_size,
|
||||||
"chunk_overlap": self.chunk_overlap,
|
"chunk_overlap": self.chunk_overlap,
|
||||||
"error": self.error
|
"error": self.error,
|
||||||
|
"active_task_id": self.active_task_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -94,7 +98,8 @@ class Project:
|
||||||
simulation_requirement=data.get('simulation_requirement'),
|
simulation_requirement=data.get('simulation_requirement'),
|
||||||
chunk_size=data.get('chunk_size', 500),
|
chunk_size=data.get('chunk_size', 500),
|
||||||
chunk_overlap=data.get('chunk_overlap', 50),
|
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) {
|
if (res.data.status === 'ontology_generated' && !res.data.graph_id) {
|
||||||
await startBuildGraph()
|
await startBuildGraph()
|
||||||
} else if (res.data.status === 'graph_building' && res.data.graph_build_task_id) {
|
} else if (res.data.status === 'graph_building') {
|
||||||
currentPhase.value = 1
|
const taskId = res.data.active_task_id || res.data.graph_build_task_id
|
||||||
startPollingTask(res.data.graph_build_task_id)
|
if (taskId) {
|
||||||
startGraphPolling()
|
currentPhase.value = 1
|
||||||
|
addLog(t('log.reconnectingToTask', { taskId }))
|
||||||
|
startPollingTask(taskId)
|
||||||
|
startGraphPolling()
|
||||||
|
}
|
||||||
} else if (res.data.status === 'graph_completed' && res.data.graph_id) {
|
} else if (res.data.status === 'graph_completed' && res.data.graph_id) {
|
||||||
currentPhase.value = 2
|
currentPhase.value = 2
|
||||||
await loadGraph(res.data.graph_id)
|
await loadGraph(res.data.graph_id)
|
||||||
|
|
|
||||||
|
|
@ -596,7 +596,8 @@
|
||||||
"getReportInfoFailed": "Error en obtenir la informació de l'informe: {error}",
|
"getReportInfoFailed": "Error en obtenir la informació de l'informe: {error}",
|
||||||
"enterStep": "Entrant al pas {step}: {name}",
|
"enterStep": "Entrant al pas {step}: {name}",
|
||||||
"returnToStep": "Tornant 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": {
|
"report": {
|
||||||
"taskStarted": "Tasca de generació de l'informe iniciada",
|
"taskStarted": "Tasca de generació de l'informe iniciada",
|
||||||
|
|
|
||||||
|
|
@ -597,7 +597,8 @@
|
||||||
"getReportInfoFailed": "Failed to get report info: {error}",
|
"getReportInfoFailed": "Failed to get report info: {error}",
|
||||||
"enterStep": "Entering Step {step}: {name}",
|
"enterStep": "Entering Step {step}: {name}",
|
||||||
"returnToStep": "Returning to 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": {
|
"report": {
|
||||||
"taskStarted": "Report generation task started",
|
"taskStarted": "Report generation task started",
|
||||||
|
|
|
||||||
|
|
@ -596,7 +596,8 @@
|
||||||
"getReportInfoFailed": "Error al obtener información del informe: {error}",
|
"getReportInfoFailed": "Error al obtener información del informe: {error}",
|
||||||
"enterStep": "Entrando al Paso {step}: {name}",
|
"enterStep": "Entrando al Paso {step}: {name}",
|
||||||
"returnToStep": "Volviendo 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": {
|
"report": {
|
||||||
"taskStarted": "Tarea de generación de informe iniciada",
|
"taskStarted": "Tarea de generación de informe iniciada",
|
||||||
|
|
|
||||||
|
|
@ -597,7 +597,8 @@
|
||||||
"getReportInfoFailed": "获取报告信息失败: {error}",
|
"getReportInfoFailed": "获取报告信息失败: {error}",
|
||||||
"enterStep": "进入 Step {step}: {name}",
|
"enterStep": "进入 Step {step}: {name}",
|
||||||
"returnToStep": "返回 Step {step}: {name}",
|
"returnToStep": "返回 Step {step}: {name}",
|
||||||
"customSimRounds": "自定义模拟轮数: {rounds} 轮"
|
"customSimRounds": "自定义模拟轮数: {rounds} 轮",
|
||||||
|
"reconnectingToTask": "重新连接到活动任务 {taskId}…"
|
||||||
},
|
},
|
||||||
"report": {
|
"report": {
|
||||||
"taskStarted": "报告生成任务开始",
|
"taskStarted": "报告生成任务开始",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue