"""Shared exploration operations; no separate Web execution path.""" from contextlib import contextmanager from fastapi import APIRouter, HTTPException, Query from artemis.config.exploration import ExplorationConfig from artemis.exploration.models import ( CreateExploration, ExplorationMessage, ExplorationRequest, SaveCaseRequest, ValidateCaseRequest, DemonstrationRequest, ExtendDemonstration, GenerateCaseRequest, CaseBatchRequest, ) from artemis.exploration.store import ExplorationConflict from apps.admin_console.services.exploration_service import exploration_service as service router = APIRouter(prefix="/api/explorations", tags=["exploration"]) @router.get("/{exploration_id}/deletion-preview") def preview_deletion(exploration_id: str): from apps.admin_console.services.task_deletion import exploration_deletion_preview with domain_errors(): return exploration_deletion_preview(exploration_id) @router.delete("/{exploration_id}") def delete_conversation(exploration_id: str): from apps.admin_console.services.exploration_deletion import delete_exploration with domain_errors(): deleted = delete_exploration(service.repository, exploration_id) return {"status": "success", "deleted_session_ids": deleted} @contextmanager def domain_errors(): try: yield except KeyError as exc: raise HTTPException(404, str(exc).strip("'")) from exc except ExplorationConflict as exc: raise HTTPException(409, str(exc)) from exc except ValueError as exc: # Pydantic may echo sensitive input. Invalid nested fields get a plain error. from pydantic import ValidationError raise HTTPException( 400, "探索参数不合法" if isinstance(exc, ValidationError) else str(exc) ) from exc @router.get("/settings") def settings(): return service.settings() @router.put("/settings") def save_settings(value: ExplorationConfig): return service.save_settings(value) @router.get("") def list_explorations(limit: int = Query(100, ge=1, le=500)): return service.repository.list(limit) @router.post("") def create(value: CreateExploration, source: str = Query("web", pattern="^(web|mcp)$")): with domain_errors(): return service.create(value, source=source) @router.get("/{session_id}") def detail(session_id: str, after: int = Query(0, ge=0)): with domain_errors(): return service.detail(session_id, after=after) @router.post("/{session_id}/start") async def start( session_id: str, value: ExplorationRequest, source: str = Query("web", pattern="^(web|mcp)$") ): with domain_errors(): return await service.start(session_id, value.request_id, source=source) @router.post("/{session_id}/messages") async def message( session_id: str, value: ExplorationMessage, source: str = Query("web", pattern="^(web|mcp)$") ): with domain_errors(): result = service.message(session_id, value.text, value.request_id, source=source) if not result.get("run_id"): from apps.admin_console.services.automation_service import spawn from artemis.exploration.editorial import discuss spawn(discuss(service.repository, session_id, result)) return result @router.post("/{session_id}/pause") def pause(session_id: str): with domain_errors(): return service.pause(session_id) @router.post("/{session_id}/stop") async def stop(session_id: str): with domain_errors(): return service.stop(session_id) @router.post("/{session_id}/continue") async def resume( session_id: str, value: ExplorationRequest, source: str = Query("web", pattern="^(web|mcp)$") ): with domain_errors(): return await service.resume(session_id, value.request_id, source=source) @router.get("/{session_id}/case") def case(session_id: str, version: int | None = Query(None, ge=1)): with domain_errors(): return service.repository.case(session_id, version) @router.post("/{session_id}/case") def save_case(session_id: str, value: SaveCaseRequest): with domain_errors(): return service.save_case(session_id, value) @router.post("/{session_id}/validate") async def validate( session_id: str, value: ValidateCaseRequest, source: str = Query("web", pattern="^(web|mcp)$") ): with domain_errors(): return await service.validate(session_id, value, source=source) @router.post("/{session_id}/demonstrations") def demonstrate(session_id: str, value: DemonstrationRequest): with domain_errors(): return service.demonstrate(session_id, value.request_id, value.source) @router.post("/{session_id}/demonstrations/{demo_id}/extend") def extend_demonstration(session_id: str, demo_id: str, value: ExtendDemonstration): with domain_errors(): return service.demonstration_command( session_id, demo_id, "extend", value.request_id, seconds=value.seconds ) @router.post("/{session_id}/demonstrations/{demo_id}/{command}") def demonstration_command(session_id: str, demo_id: str, command: str, value: ExplorationRequest): if command not in {"finish", "cancel"}: raise HTTPException(404, "示范操作不存在") with domain_errors(): return service.demonstration_command(session_id, demo_id, command, value.request_id) @router.post("/{session_id}/generate") async def generate_case(session_id: str, value: GenerateCaseRequest): with domain_errors(): return await service.generate_case(session_id, value) @router.post("/{session_id}/batch") def case_batch(session_id: str, value: CaseBatchRequest): with domain_errors(): return service.create_case_batch(session_id, value)