feat: ship end-to-end interior design MVP
This commit is contained in:
@@ -61,6 +61,27 @@ class PlanIssue(BaseModel):
|
||||
resolved: bool = False
|
||||
|
||||
|
||||
class RoomProfile(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
kind: str
|
||||
area_sqm: float | None = None
|
||||
confidence: float = Field(default=0.5, ge=0, le=1)
|
||||
notes: str = ""
|
||||
|
||||
|
||||
class StructureAnalysis(BaseModel):
|
||||
status: str = "pending"
|
||||
summary: str = ""
|
||||
rooms: list[RoomProfile] = Field(default_factory=list)
|
||||
circulation: str = ""
|
||||
daylight: str = ""
|
||||
layout_opportunities: list[str] = Field(default_factory=list)
|
||||
risks: list[str] = Field(default_factory=list)
|
||||
model_name: str = ""
|
||||
degraded: bool = False
|
||||
|
||||
|
||||
class PlanState(BaseModel):
|
||||
source_name: str
|
||||
source_kind: str
|
||||
@@ -81,6 +102,8 @@ class PlanState(BaseModel):
|
||||
issues: list[PlanIssue] = Field(default_factory=list)
|
||||
scale_mm_per_unit: float | None = None
|
||||
ceiling_height_mm: int = 2800
|
||||
gross_area_sqm: float | None = None
|
||||
structure: StructureAnalysis = Field(default_factory=StructureAnalysis)
|
||||
|
||||
|
||||
class CameraView(BaseModel):
|
||||
@@ -113,6 +136,52 @@ class StyleState(BaseModel):
|
||||
density: str = "适度留白"
|
||||
avoid: list[str] = Field(default_factory=list)
|
||||
locked_decisions: list[str] = Field(default_factory=list)
|
||||
selected_direction_id: str | None = None
|
||||
|
||||
|
||||
class DesignBrief(BaseModel):
|
||||
residents: str = ""
|
||||
lifestyle: list[str] = Field(default_factory=list)
|
||||
focus_rooms: list[str] = Field(default_factory=list)
|
||||
preferred_styles: list[str] = Field(default_factory=list)
|
||||
preferred_colors: list[str] = Field(default_factory=list)
|
||||
disliked_elements: list[str] = Field(default_factory=list)
|
||||
must_keep: list[str] = Field(default_factory=list)
|
||||
budget_level: str = "适中"
|
||||
additional_notes: str = ""
|
||||
completed: bool = False
|
||||
|
||||
|
||||
class StyleDirection(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
thesis: str
|
||||
keywords: list[str] = Field(default_factory=list)
|
||||
palette: list[ColorToken] = Field(default_factory=list)
|
||||
materials: list[str] = Field(default_factory=list)
|
||||
lighting: str = ""
|
||||
prompt: str = ""
|
||||
model_name: str = ""
|
||||
|
||||
|
||||
class RenderAsset(BaseModel):
|
||||
id: str
|
||||
direction_id: str
|
||||
room: str
|
||||
view: str
|
||||
object_key: str
|
||||
prompt: str
|
||||
model_name: str
|
||||
parent_id: str | None = None
|
||||
edit_instruction: str = ""
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class ProjectEvent(BaseModel):
|
||||
id: str
|
||||
kind: str
|
||||
message: str
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class ProjectSnapshot(BaseModel):
|
||||
@@ -123,6 +192,10 @@ class ProjectSnapshot(BaseModel):
|
||||
plan: PlanState
|
||||
scene: SceneState = Field(default_factory=SceneState)
|
||||
style: StyleState = Field(default_factory=StyleState)
|
||||
brief: DesignBrief = Field(default_factory=DesignBrief)
|
||||
directions: list[StyleDirection] = Field(default_factory=list)
|
||||
renders: list[RenderAsset] = Field(default_factory=list)
|
||||
events: list[ProjectEvent] = Field(default_factory=list)
|
||||
available_commands: list[WorkflowCommand] = Field(default_factory=list)
|
||||
last_stable_stage: WorkflowStage | None = None
|
||||
failure_reason: str | None = None
|
||||
|
||||
@@ -2,8 +2,11 @@ from copy import deepcopy
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.domain.models import (
|
||||
CameraView,
|
||||
CommandRequest,
|
||||
DesignBrief,
|
||||
ProjectSnapshot,
|
||||
RoomProfile,
|
||||
WorkflowCommand,
|
||||
WorkflowDefinition,
|
||||
WorkflowStage,
|
||||
@@ -91,13 +94,51 @@ def apply_command(project: ProjectSnapshot, request: CommandRequest) -> ProjectS
|
||||
updated.plan.ceiling_height_mm = int(
|
||||
request.payload.get("ceiling_height_mm", updated.plan.ceiling_height_mm)
|
||||
)
|
||||
if request.payload.get("gross_area_sqm") is not None:
|
||||
updated.plan.gross_area_sqm = float(request.payload["gross_area_sqm"])
|
||||
if request.payload.get("room_names"):
|
||||
existing = {room.name: room for room in updated.plan.structure.rooms}
|
||||
updated.plan.structure.rooms = [
|
||||
existing.get(name)
|
||||
or RoomProfile(
|
||||
id=f"room-{index + 1}",
|
||||
name=name,
|
||||
kind="other",
|
||||
confidence=1,
|
||||
notes="用户确认",
|
||||
)
|
||||
for index, raw_name in enumerate(request.payload["room_names"])
|
||||
if (name := str(raw_name).strip())
|
||||
]
|
||||
for issue in updated.plan.issues:
|
||||
if issue.kind in {"scale", "ocr"}:
|
||||
issue.resolved = True
|
||||
elif request.command == WorkflowCommand.BUILD_BLOCKOUT:
|
||||
room_names = [room.name for room in updated.plan.structure.rooms]
|
||||
focus = room_names[:3] or ["客餐厅", "主卧", "入口"]
|
||||
updated.scene.cameras = [
|
||||
CameraView(id=f"camera-{index + 1}", label=f"{room}主视角", room=room)
|
||||
for index, room in enumerate(focus)
|
||||
]
|
||||
elif request.command == WorkflowCommand.SUBMIT_STYLE_BRIEF:
|
||||
updated.brief = DesignBrief.model_validate({**updated.brief.model_dump(), **request.payload, "completed": True})
|
||||
for field in ("concept", "lighting", "forms", "density"):
|
||||
if field in request.payload:
|
||||
setattr(updated.style, field, request.payload[field])
|
||||
for field in ("keywords", "materials", "avoid", "locked_decisions"):
|
||||
if field in request.payload:
|
||||
setattr(updated.style, field, list(request.payload[field]))
|
||||
elif request.command == WorkflowCommand.SELECT_DIRECTION:
|
||||
direction_id = str(request.payload.get("direction_id", ""))
|
||||
direction = next((item for item in updated.directions if item.id == direction_id), None)
|
||||
if direction is None:
|
||||
raise InvalidTransitionError(f"Direction '{direction_id}' does not exist in this project.")
|
||||
updated.style.selected_direction_id = direction_id
|
||||
updated.style.concept = direction.thesis
|
||||
updated.style.keywords = direction.keywords
|
||||
updated.style.palette = direction.palette
|
||||
updated.style.materials = direction.materials
|
||||
updated.style.lighting = direction.lighting
|
||||
|
||||
updated.available_commands = available_commands(updated.stage)
|
||||
return updated
|
||||
|
||||
Reference in New Issue
Block a user