@osamash/floor-planner 1.2.6 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import { type AIRequestHandler } from "./aiService";
2
2
  interface AIChatPanelProps {
3
+ /** Whether the panel is the visible toolbar section. It stays mounted when
4
+ it isn't, so this drives focus and the microphone, not rendering. */
3
5
  open: boolean;
4
- onClose: () => void;
5
6
  onAIMessage?: AIRequestHandler;
6
7
  }
7
- declare const AIChatPanel: ({ open, onClose, onAIMessage }: AIChatPanelProps) => import("react/jsx-runtime").JSX.Element;
8
+ declare const AIChatPanel: ({ open, onAIMessage }: AIChatPanelProps) => import("react/jsx-runtime").JSX.Element;
8
9
  export default AIChatPanel;
@@ -38,9 +38,16 @@ export interface WallInfo {
38
38
  /** cm. */
39
39
  thickness: number | null;
40
40
  height: number | null;
41
- /** The wall's two faces. Which one looks into a given room is geometric. */
41
+ /** The wall's two faces. Which one looks into a given room is geometric, so
42
+ * `areaA`/`areaB` name the area each face looks into — null means open air,
43
+ * i.e. that face is the outside of the building. Ground surfaces such as a
44
+ * garden are not counted; a wall facing one still has an outside face. */
42
45
  textureA: string | null;
43
46
  textureB: string | null;
47
+ colorA: string | null;
48
+ colorB: string | null;
49
+ areaA: string | null;
50
+ areaB: string | null;
44
51
  }
45
52
  export interface HoleInfo {
46
53
  id: string;
@@ -155,7 +162,39 @@ export type RoomSide = "left" | "right" | "above" | "below";
155
162
  * `ACTION_SPECS` (what the model may say), and a branch in
156
163
  * `applyPlannerActions` (what actually happens).
157
164
  */
158
- export type PlannerAction = AddRoomAction | SetRoomPropertiesAction | AddHoleAction | AddItemAction | SetWallPropertiesAction | SetItemPropertiesAction | AddSurfaceAction | DeleteAction | SetSurfacePropertiesAction;
165
+ export type PlannerAction = GenerateLayoutAction | AddRoomAction | SetRoomPropertiesAction | AddHoleAction | AddItemAction | SetWallPropertiesAction | SetItemPropertiesAction | AddSurfaceAction | DeleteAction | SetSurfacePropertiesAction;
166
+ /**
167
+ * A whole floor plan as a program: which rooms, of what type, roughly how big.
168
+ * No geometry — the packer works out every rectangle. Applying this REPLACES
169
+ * whatever is on the canvas.
170
+ */
171
+ export type GenerateLayoutAction = {
172
+ type: "generate-layout";
173
+ rooms: {
174
+ name: string;
175
+ roomType: string;
176
+ size?: "small" | "medium" | "large";
177
+ }[];
178
+ /** A roomCategory value; the room types must belong to it. Backend defaults
179
+ * this to "house", so it always arrives populated. */
180
+ buildingType: string;
181
+ /** Ground outside the building — garden, pool, patio. Not rooms: no walls,
182
+ * so no doors, windows or furniture. The packer picks which outside wall
183
+ * each one lies against. */
184
+ outdoor?: {
185
+ name: string;
186
+ texture?: string;
187
+ color?: string;
188
+ depth?: number;
189
+ }[];
190
+ /** m², if the user gave one. Absent means the packer sizes the plan itself. */
191
+ totalArea?: number;
192
+ /** Only the building OUTLINE varies — every room stays a rectangle. Absent
193
+ * means the packer picks from the seed. */
194
+ footprint?: "rectangle" | "L" | "U";
195
+ /** Always populated backend-side, so the same action replays identically. */
196
+ seed: number;
197
+ };
159
198
  export type AddRoomAction = {
160
199
  type: "add-room";
161
200
  /**
@@ -201,6 +240,9 @@ export type AddHoleAction = {
201
240
  * and land as two doors. Mutually exclusive with room/side.
202
241
  */
203
242
  between?: [string, string];
243
+ /** Where along the wall, 1-8. Absent means centred, and the frontend then
244
+ * spaces multiple openings on one wall evenly. */
245
+ at?: number;
204
246
  /** A particular wall — the only way to reach an exterior one. */
205
247
  room?: string;
206
248
  side?: RoomSide;
@@ -222,10 +264,13 @@ export type SetWallPropertiesAction = {
222
264
  /** A room name, or "selected". All four of its walls unless `side` is given. */
223
265
  room: string;
224
266
  side?: RoomSide;
225
- /** Applied to the wall face looking into `room`. */
267
+ /** Which of the wall's two faces to paint. "outside" only works on a wall
268
+ * with open air behind it; a wall shared with another room is skipped. */
269
+ face?: "inside" | "outside";
226
270
  color?: string;
227
271
  texture?: string;
228
- /** cm. Whole-wall, so a shared wall changes for both rooms. */
272
+ /** cm. Whole-wall rather than per-face, so a shared wall changes for both
273
+ * rooms and neither can be combined with `face: "outside"`. */
229
274
  thickness?: number;
230
275
  height?: number;
231
276
  };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Furnishing a generated plan.
3
+ *
4
+ * This lives in the frontend rather than the packer because it needs the
5
+ * catalog: what furniture exists, what it is tagged as, and how big it renders.
6
+ *
7
+ * A note on sizes. The catalog ships almost every item as a 100x100 placeholder
8
+ * — a double bed, a bedside cabinet and a TV have the same footprint — and
9
+ * neither renderer honours a declared width/depth, only a single uniform
10
+ * `scale` (viewer2d/Item.tsx measures the drawn geometry; viewer3d sets
11
+ * pivot.scale to one value on all three axes). So a role's size is expressed as
12
+ * a scale, and clearances are measured against the footprint that produces.
13
+ * Furniture is therefore square-ish but honestly sized.
14
+ */
15
+ type Rect = {
16
+ x0: number;
17
+ y0: number;
18
+ x1: number;
19
+ y1: number;
20
+ };
21
+ export type Furnishing = {
22
+ itemType: string;
23
+ x: number;
24
+ y: number;
25
+ rotation: number;
26
+ scale: number;
27
+ };
28
+ /**
29
+ * Furniture for one room: what goes in it, where, and facing which way.
30
+ *
31
+ * Roles are placed in order of importance, each reserving its own footprint AND
32
+ * the walking space in front of it, so later items cannot land in the space the
33
+ * bed needs to be got into.
34
+ */
35
+ export declare function furnishRoom(planner: any, layerID: string, roomType: string, room: Rect, walls: Record<string, string[]>, seed: number): Furnishing[];
36
+ export {};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Turns a room program — which rooms, of what type, roughly how big — into
3
+ * rectangles. The agent never sends coordinates, so this is where a plan
4
+ * actually acquires geometry.
5
+ *
6
+ * Rooms stay axis-aligned rectangles; the BUILDING outline is what varies,
7
+ * built by packing those rectangles into one, two or three wings.
8
+ */
9
+ export type Rect = {
10
+ x0: number;
11
+ y0: number;
12
+ x1: number;
13
+ y1: number;
14
+ };
15
+ export type ProgramRoom = {
16
+ name: string;
17
+ roomType: string;
18
+ size?: "small" | "medium" | "large";
19
+ /** Other rooms in the same program this one should sit beside, or not. The
20
+ * user's own wishes; the RELATIONS table below covers the architecture. */
21
+ near?: string[];
22
+ away?: string[];
23
+ };
24
+ export type PackedRoom = ProgramRoom & {
25
+ rect: Rect;
26
+ };
27
+ export type PackOptions = {
28
+ totalArea?: number;
29
+ footprint?: "rectangle" | "L" | "U";
30
+ seed: number;
31
+ /** Room types valid for this building type — a hallway is only added if the
32
+ * catalog has somewhere to put it. */
33
+ validTypes: Set<string>;
34
+ /** Scene centre, in cm. The footprint is centred on it. */
35
+ centre: {
36
+ x: number;
37
+ y: number;
38
+ };
39
+ /** Gardens, pools and patios to lay against the outside of the building. */
40
+ outdoor?: OutdoorRequest[];
41
+ };
42
+ /** Narrower than this and a room stops being usable — the caller reports these
43
+ * rather than dropping them silently, since the user asked for them. */
44
+ export declare const MIN_SIDE = 150;
45
+ /** A corridor is legitimately narrower than a room. Callers must use this
46
+ * rather than MIN_SIDE directly, or they drop the hallway and leave the two
47
+ * halves of the plan with no way between them. */
48
+ export declare const minSideFor: (roomType: string) => 150 | 130;
49
+ export type Side = "left" | "right" | "above" | "below";
50
+ /**
51
+ * An opening the plan needs. Expressed in the same terms `add-hole` already
52
+ * uses — two room names, or one room and a side — so the frontend resolves it
53
+ * with the wall-finding it already has.
54
+ */
55
+ export type Opening = {
56
+ kind: "interior";
57
+ a: string;
58
+ b: string;
59
+ holeType: string;
60
+ } | {
61
+ kind: "entrance";
62
+ room: string;
63
+ side: Side;
64
+ holeType: string;
65
+ at: number;
66
+ } | {
67
+ kind: "window";
68
+ room: string;
69
+ side: Side;
70
+ holeType: string;
71
+ at: number;
72
+ width: number;
73
+ height: number;
74
+ altitude: number;
75
+ };
76
+ /**
77
+ * Every opening the plan needs: one front door, and a spanning tree of interior
78
+ * doors reaching every room from it.
79
+ *
80
+ * A spanning tree rather than a door on every shared wall — a 9-room house has
81
+ * around twenty adjacencies and should have about ten doors. Edges are taken
82
+ * cheapest-first, and a corridor edge always costs zero, so rooms are reached
83
+ * off the hall rather than through each other whenever the hall touches them.
84
+ */
85
+ export declare function planDoors(packed: PackedRoom[], seed?: number): Opening[];
86
+ export type OutdoorRequest = {
87
+ name: string;
88
+ texture?: string;
89
+ color?: string;
90
+ depth?: number;
91
+ };
92
+ export type OutdoorSurface = OutdoorRequest & {
93
+ /** The room whose wall it lies against, and which side. */
94
+ of: string;
95
+ side: Side;
96
+ depth: number;
97
+ };
98
+ /**
99
+ * Where the garden, the pool and the patio go.
100
+ *
101
+ * A surface runs the FULL width of the wall it lies against, so a side that is
102
+ * only partly exterior is no good — the surface would be laid straight over the
103
+ * neighbouring room. Longest wall first, never the entrance elevation (a pool
104
+ * across the front door is nobody's idea of a villa), and never overlapping a
105
+ * surface already placed.
106
+ */
107
+ export declare function planOutdoor(packed: PackedRoom[], wanted: OutdoorRequest[], openings: Opening[]): OutdoorSurface[];
108
+ export declare function packLayout(program: ProgramRoom[], opts: PackOptions): {
109
+ rooms: PackedRoom[];
110
+ openings: Opening[];
111
+ surfaces: OutdoorSurface[];
112
+ };
113
+ /** Rooms sharing enough wall for a door. Anything less is a corner touch. */
114
+ export declare function adjacencyOf(packed: PackedRoom[]): Map<string, PackedRoom[]>;
@@ -1,20 +1,5 @@
1
1
  import type { PlanSummary, PlannerAction } from "./aiService";
2
2
  export declare function summarizePlan(planner: any): PlanSummary;
3
- /**
4
- * Applies a batch of actions from the agent to the planner.
5
- *
6
- * The agent never sends coordinates. It says how big a room is, what to call
7
- * it, and which existing room it sits next to — every number describing a
8
- * POSITION is worked out here, where it can be exact. A room with no `of` /
9
- * `side` is centered in the scene, which is only ever right for the first room.
10
- *
11
- * Rooms created earlier in this same batch are valid anchors: each one is
12
- * looked up by name from live state after it is created, so a five-room plan
13
- * arrives as five actions that chain off each other.
14
- *
15
- * @returns `skipped` — human-readable reasons anything was not applied, shown
16
- * to the user by the caller.
17
- */
18
3
  export declare function applyPlannerActions(actions: PlannerAction[]): {
19
4
  skipped: string[];
20
5
  };
@@ -1,7 +1,9 @@
1
1
  import type Catalog from "../catalog/catalog";
2
+ import type { AIRequestHandler } from "../ai-chat/aiService";
2
3
  interface ToolbarProps {
3
4
  catalog: Catalog;
4
5
  has3dView?: boolean;
6
+ onAIMessage?: AIRequestHandler;
5
7
  }
6
- declare function Toolbar({ catalog, has3dView }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function Toolbar({ catalog, has3dView, onAIMessage }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
7
9
  export default Toolbar;
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- export type RailSection = "project" | "build" | "info" | "objects" | "styleboards" | "finishes" | "help";
2
+ export type RailSection = "project" | "build" | "info" | "objects" | "ai" | "styleboards" | "finishes" | "help";
3
3
  export interface RailItem {
4
4
  key: RailSection;
5
5
  label: string;
@@ -11,6 +11,12 @@ export interface SubPanelOption {
11
11
  }
12
12
  interface ToolbarSubPanelProps {
13
13
  title: string;
14
+ /** Fills the panel below the header — used by the chat, which manages its
15
+ own scrolling and needs the full height rather than a list of options. */
16
+ children?: ReactNode;
17
+ /** Kept mounted but out of sight, so a panel holding state (the chat's
18
+ conversation) survives a trip to another section. */
19
+ hidden?: boolean;
14
20
  options?: SubPanelOption[];
15
21
  cta?: string;
16
22
  emptyState?: ReactNode;
@@ -19,5 +25,5 @@ interface ToolbarSubPanelProps {
19
25
  onBack?: () => void;
20
26
  headerExtras?: ReactNode;
21
27
  }
22
- declare const ToolbarSubPanel: ({ title, options, cta, emptyState, catalog, catalogPage, onBack, headerExtras, }: ToolbarSubPanelProps) => import("react/jsx-runtime").JSX.Element;
28
+ declare const ToolbarSubPanel: ({ title, children, hidden, options, cta, emptyState, catalog, catalogPage, onBack, headerExtras, }: ToolbarSubPanelProps) => import("react/jsx-runtime").JSX.Element;
23
29
  export default ToolbarSubPanel;
@@ -1,5 +1,5 @@
1
1
  interface ZoomControlsProps {
2
- subPanelOpen?: boolean;
2
+ subPanel?: "none" | "standard" | "ai";
3
3
  }
4
- declare const ZoomControls: ({ subPanelOpen }: ZoomControlsProps) => import("react/jsx-runtime").JSX.Element;
4
+ declare const ZoomControls: ({ subPanel }: ZoomControlsProps) => import("react/jsx-runtime").JSX.Element;
5
5
  export default ZoomControls;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  "dist"
13
13
  ],
14
- "version": "1.2.6",
14
+ "version": "1.2.7",
15
15
  "type": "module",
16
16
  "scripts": {
17
17
  "dev": "vite",
@@ -1,6 +0,0 @@
1
- import type { AIRequestHandler } from "./aiService";
2
- interface AIChatBubbleProps {
3
- onAIMessage?: AIRequestHandler;
4
- }
5
- declare const AIChatBubble: ({ onAIMessage }: AIChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
6
- export default AIChatBubble;