@ifc-lite/viewer 1.14.2 → 1.14.4
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.
- package/CHANGELOG.md +35 -0
- package/dist/assets/{Arrow.dom-CSgnLhN4.js → Arrow.dom-_vGzMMKs.js} +1 -1
- package/dist/assets/basketViewActivator-BZcoCL3V.js +1 -0
- package/dist/assets/{browser-qSKWrKQW.js → browser-Czmf34bo.js} +1 -1
- package/dist/assets/ifc-lite_bg-DyBKoGgk.wasm +0 -0
- package/dist/assets/index-CMQ_Dgkr.css +1 -0
- package/dist/assets/index-D7nEDctQ.js +229 -0
- package/dist/assets/{index-4Y4XaV8N.js → index-DX-Qf5fA.js} +72669 -61673
- package/dist/assets/{native-bridge-CSFDsEkg.js → native-bridge-DAOWftxE.js} +1 -1
- package/dist/assets/{wasm-bridge-Zf90ysEm.js → wasm-bridge-D7jYpn8a.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +21 -20
- package/src/App.tsx +17 -1
- package/src/components/viewer/BasketPresentationDock.tsx +8 -4
- package/src/components/viewer/ChatPanel.tsx +1402 -0
- package/src/components/viewer/CodeEditor.tsx +70 -4
- package/src/components/viewer/CommandPalette.tsx +1 -0
- package/src/components/viewer/HierarchyPanel.tsx +28 -13
- package/src/components/viewer/MainToolbar.tsx +113 -95
- package/src/components/viewer/ScriptPanel.tsx +351 -184
- package/src/components/viewer/UpgradePage.tsx +69 -0
- package/src/components/viewer/Viewport.tsx +23 -0
- package/src/components/viewer/chat/ChatMessage.tsx +144 -0
- package/src/components/viewer/chat/ExecutableCodeBlock.tsx +416 -0
- package/src/components/viewer/chat/ModelSelector.tsx +102 -0
- package/src/components/viewer/chat/renderTextContent.test.ts +23 -0
- package/src/components/viewer/chat/renderTextContent.ts +19 -0
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +10 -3
- package/src/components/viewer/hierarchy/treeDataBuilder.test.ts +126 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +139 -38
- package/src/components/viewer/hierarchy/types.ts +6 -1
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +27 -12
- package/src/hooks/useIfcCache.ts +1 -2
- package/src/hooks/useSandbox.ts +122 -6
- package/src/index.css +10 -0
- package/src/lib/attachments.ts +46 -0
- package/src/lib/llm/ClerkChatSync.tsx +74 -0
- package/src/lib/llm/clerk-auth.ts +62 -0
- package/src/lib/llm/code-extractor.ts +50 -0
- package/src/lib/llm/context-builder.test.ts +18 -0
- package/src/lib/llm/context-builder.ts +305 -0
- package/src/lib/llm/free-models.test.ts +118 -0
- package/src/lib/llm/message-capabilities.test.ts +131 -0
- package/src/lib/llm/message-capabilities.ts +94 -0
- package/src/lib/llm/models.ts +197 -0
- package/src/lib/llm/repair-loop.test.ts +91 -0
- package/src/lib/llm/repair-loop.ts +76 -0
- package/src/lib/llm/script-diagnostics.ts +445 -0
- package/src/lib/llm/script-edit-ops.test.ts +399 -0
- package/src/lib/llm/script-edit-ops.ts +954 -0
- package/src/lib/llm/script-preflight.test.ts +513 -0
- package/src/lib/llm/script-preflight.ts +990 -0
- package/src/lib/llm/script-preservation.test.ts +128 -0
- package/src/lib/llm/script-preservation.ts +152 -0
- package/src/lib/llm/stream-client.test.ts +97 -0
- package/src/lib/llm/stream-client.ts +410 -0
- package/src/lib/llm/system-prompt.test.ts +181 -0
- package/src/lib/llm/system-prompt.ts +665 -0
- package/src/lib/llm/types.ts +150 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +226 -7
- package/src/lib/scripts/templates/create-building.ts +12 -12
- package/src/main.tsx +10 -1
- package/src/sdk/adapters/export-adapter.test.ts +24 -0
- package/src/sdk/adapters/export-adapter.ts +40 -16
- package/src/sdk/adapters/files-adapter.ts +39 -0
- package/src/sdk/adapters/model-compat.ts +1 -1
- package/src/sdk/adapters/mutate-adapter.ts +20 -6
- package/src/sdk/adapters/mutation-view.ts +112 -0
- package/src/sdk/adapters/query-adapter.ts +100 -4
- package/src/sdk/local-backend.ts +4 -0
- package/src/store/index.ts +15 -1
- package/src/store/slices/chatSlice.test.ts +325 -0
- package/src/store/slices/chatSlice.ts +468 -0
- package/src/store/slices/scriptSlice.test.ts +75 -0
- package/src/store/slices/scriptSlice.ts +256 -9
- package/src/vite-env.d.ts +10 -0
- package/vite.config.ts +21 -2
- package/dist/assets/ifc-lite_bg-BOvNXJA_.wasm +0 -0
- package/dist/assets/index-ByrFvN5A.css +0 -1
- package/dist/assets/index-CN7qDq7G.js +0 -216
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* System prompt builder for LLM chat.
|
|
7
|
+
*
|
|
8
|
+
* Auto-generates the API reference from the same NAMESPACE_SCHEMAS
|
|
9
|
+
* that power CodeMirror autocomplete and the QuickJS bridge.
|
|
10
|
+
* This ensures the LLM always has an accurate, up-to-date API surface.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
NAMESPACE_SCHEMAS,
|
|
15
|
+
type LlmTaskIntent,
|
|
16
|
+
type MethodPlacementKind,
|
|
17
|
+
type MethodSchema,
|
|
18
|
+
} from '@ifc-lite/sandbox/schema';
|
|
19
|
+
import type { FileAttachment } from './types.js';
|
|
20
|
+
import type { ScriptEditorSelection } from './types.js';
|
|
21
|
+
import { formatDiagnosticsForPrompt, type ScriptDiagnostic } from './script-diagnostics.js';
|
|
22
|
+
|
|
23
|
+
const MAX_ATTACHMENT_ROWS_IN_PROMPT = 5;
|
|
24
|
+
const MAX_ATTACHMENT_TEXT_PREVIEW_CHARS = 1200;
|
|
25
|
+
|
|
26
|
+
/** Context about the currently loaded IFC model */
|
|
27
|
+
export interface ModelContext {
|
|
28
|
+
models: Array<{ name: string; entityCount: number }>;
|
|
29
|
+
typeCounts: Record<string, number>;
|
|
30
|
+
selectedCount: number;
|
|
31
|
+
storeys?: Array<{ modelName?: string; name: string; elevation: number; height?: number; elementCount?: number }>;
|
|
32
|
+
selectedEntities?: Array<{
|
|
33
|
+
modelName?: string;
|
|
34
|
+
name: string;
|
|
35
|
+
type: string;
|
|
36
|
+
selectionKind?: 'occurrence' | 'type';
|
|
37
|
+
globalId?: string;
|
|
38
|
+
storeyName?: string;
|
|
39
|
+
storeyElevation?: number;
|
|
40
|
+
propertySets?: string[];
|
|
41
|
+
typePropertySets?: string[];
|
|
42
|
+
quantitySets?: string[];
|
|
43
|
+
materialName?: string;
|
|
44
|
+
classifications?: string[];
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ScriptEditorPromptContext {
|
|
49
|
+
content: string;
|
|
50
|
+
revision: number;
|
|
51
|
+
selection: ScriptEditorSelection;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface PromptTaskContext {
|
|
55
|
+
userPrompt?: string;
|
|
56
|
+
diagnostics?: ScriptDiagnostic[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface NamespacedMethod {
|
|
60
|
+
namespace: string;
|
|
61
|
+
method: MethodSchema;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getAllNamespacedMethods(): NamespacedMethod[] {
|
|
65
|
+
return NAMESPACE_SCHEMAS.flatMap((namespace) => namespace.methods.map((method) => ({
|
|
66
|
+
namespace: namespace.name,
|
|
67
|
+
method,
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function inferPromptIntent(task?: PromptTaskContext): LlmTaskIntent {
|
|
72
|
+
if (task?.diagnostics && task.diagnostics.length > 0) return 'repair';
|
|
73
|
+
|
|
74
|
+
const text = task?.userPrompt?.toLowerCase() ?? '';
|
|
75
|
+
if (!text) return 'create';
|
|
76
|
+
if (/\bfix|error|failed|repair|debug|why\b/.test(text)) return 'repair';
|
|
77
|
+
if (/\bcolor|hide|show|isolate|highlight|visuali[sz]e|fly to\b/.test(text)) return 'visualize';
|
|
78
|
+
if (/\bquery|inspect|analy[sz]e|material|classification|property|quantity|metadata|what is|list\b/.test(text)) return 'inspect';
|
|
79
|
+
if (/\bmodify|update|change|edit|rename|delete|set property\b/.test(text)) return 'modify';
|
|
80
|
+
if (/\bexport|csv|json|download\b/.test(text)) return 'export';
|
|
81
|
+
return 'create';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatKeys(keys: string[]): string {
|
|
85
|
+
return keys.map((key) => `\`${key}\``).join(', ');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildIntentMethodSection(intent: LlmTaskIntent): string {
|
|
89
|
+
const matches = getAllNamespacedMethods()
|
|
90
|
+
.filter(({ method }) => method.llmSemantics?.taskTags?.includes(intent))
|
|
91
|
+
.slice(0, 12);
|
|
92
|
+
|
|
93
|
+
if (matches.length === 0) return '';
|
|
94
|
+
|
|
95
|
+
const lines = ['## CURRENT TASK FOCUS', `- Primary intent: \`${intent}\``];
|
|
96
|
+
for (const { namespace, method } of matches) {
|
|
97
|
+
const synopsis = method.llmSemantics?.useWhen ?? method.doc;
|
|
98
|
+
lines.push(`- \`bim.${namespace}.${method.name}(...)\`: ${synopsis}`);
|
|
99
|
+
}
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildCreateContractCheatSheet(): string {
|
|
104
|
+
const createNamespace = NAMESPACE_SCHEMAS.find((schema) => schema.name === 'create');
|
|
105
|
+
if (!createNamespace) return '## BIM.CREATE CONTRACT CHEAT SHEET';
|
|
106
|
+
|
|
107
|
+
const lines = ['## BIM.CREATE CONTRACT CHEAT SHEET'];
|
|
108
|
+
for (const method of createNamespace.methods) {
|
|
109
|
+
const semantics = method.llmSemantics;
|
|
110
|
+
if (!semantics?.requiredKeys?.length && !semantics?.anyOfKeys?.length) continue;
|
|
111
|
+
|
|
112
|
+
const clauses: string[] = [];
|
|
113
|
+
if (semantics.requiredKeys?.length) {
|
|
114
|
+
clauses.push(`use ${formatKeys(semantics.requiredKeys)}`);
|
|
115
|
+
}
|
|
116
|
+
if (semantics.anyOfKeys?.length) {
|
|
117
|
+
clauses.push(`plus one of ${semantics.anyOfKeys.map((group) => group.map((key) => `\`${key}\``).join(' + ')).join(' OR ')}`);
|
|
118
|
+
}
|
|
119
|
+
if (semantics.useWhen) {
|
|
120
|
+
clauses.push(semantics.useWhen);
|
|
121
|
+
}
|
|
122
|
+
if (semantics.cautions?.length) {
|
|
123
|
+
clauses.push(semantics.cautions.join(' '));
|
|
124
|
+
}
|
|
125
|
+
if (method.name === 'addIfcRoof') {
|
|
126
|
+
clauses.push('Do NOT use `Profile`, `Height`, or `ExtrusionHeight` with `addIfcRoof`.');
|
|
127
|
+
}
|
|
128
|
+
if (method.name === 'addIfcSlab') {
|
|
129
|
+
clauses.push('Here `Profile` means a 2D point array, not a generic IFC profile object.');
|
|
130
|
+
}
|
|
131
|
+
if (method.name === 'addIfcWallDoor' || method.name === 'addIfcWallWindow') {
|
|
132
|
+
clauses.push('Use wall-local `Position` relative to the host wall, typically `[alongWall, 0, baseOrSillHeight]`.');
|
|
133
|
+
}
|
|
134
|
+
lines.push(`- \`${method.name}\`: ${clauses.join('. ')}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
lines.push('- Wall-hosted openings: use `Openings` inside `addIfcWall(...)` when you only need a void.');
|
|
138
|
+
lines.push('- If the user asks for a house roof, pitched roof, or gable roof, default to `addIfcGableRoof`.');
|
|
139
|
+
lines.push('- `addIfcDoor` and `addIfcWindow`: these create standalone world-aligned elements.');
|
|
140
|
+
lines.push('- `addElement`: Use `IfcType`, `Placement: { Location: [...] }`, `Profile`, and `Depth`. Use `IfcType` not `Type`; use `Placement` not `Position`.');
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildPlacementSemanticsSection(): string {
|
|
145
|
+
const createNamespace = NAMESPACE_SCHEMAS.find((schema) => schema.name === 'create');
|
|
146
|
+
if (!createNamespace) return '## PLACEMENT SEMANTICS';
|
|
147
|
+
|
|
148
|
+
const groups = new Map<MethodPlacementKind, string[]>();
|
|
149
|
+
for (const method of createNamespace.methods) {
|
|
150
|
+
const placement = method.llmSemantics?.placement;
|
|
151
|
+
if (!placement) continue;
|
|
152
|
+
groups.set(placement, [...(groups.get(placement) ?? []), method.name]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const formatGroup = (placement: MethodPlacementKind, label: string) => {
|
|
156
|
+
const methods = groups.get(placement) ?? [];
|
|
157
|
+
return methods.length > 0 ? `- ${label}: ${methods.map((name) => `\`${name}\``).join(', ')}.` : null;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
'## PLACEMENT SEMANTICS',
|
|
162
|
+
formatGroup('storey-relative', 'Common storey-relative methods'),
|
|
163
|
+
formatGroup('wall-local', 'Hosted wall insert methods'),
|
|
164
|
+
formatGroup('world', 'Many advanced methods are world-placement based'),
|
|
165
|
+
formatGroup('explicit-placement', 'Explicit-placement generic methods'),
|
|
166
|
+
'- For world-placement methods, do NOT assume the storey elevation is automatically applied.',
|
|
167
|
+
'- Mixed multi-level scripts often combine both: storey-relative and world-placement helpers.',
|
|
168
|
+
'- If repeated world-placement elements are generated inside a storey loop, those calls should usually use `elevation` or `z` in their `Start`/`End`/`Position` Z coordinates.',
|
|
169
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildInspectionGuidance(): string {
|
|
173
|
+
const preferredOrder = [
|
|
174
|
+
'query.selection',
|
|
175
|
+
'query.storeys',
|
|
176
|
+
'query.path',
|
|
177
|
+
'query.storey',
|
|
178
|
+
'query.attributes',
|
|
179
|
+
'query.properties',
|
|
180
|
+
'query.property',
|
|
181
|
+
'query.quantities',
|
|
182
|
+
'query.materials',
|
|
183
|
+
'query.classifications',
|
|
184
|
+
'query.documents',
|
|
185
|
+
'query.typeProperties',
|
|
186
|
+
'query.relationships',
|
|
187
|
+
'query.related',
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const lookup = new Map<string, string>();
|
|
191
|
+
for (const { namespace, method } of getAllNamespacedMethods().filter(({ method }) => method.llmSemantics?.inspectFirst)) {
|
|
192
|
+
let args = '';
|
|
193
|
+
if (['path', 'storey', 'attributes', 'properties', 'property', 'quantities', 'materials', 'classifications', 'documents', 'typeProperties', 'relationships'].includes(method.name)) {
|
|
194
|
+
args = 'entity';
|
|
195
|
+
} else if (method.name === 'related') {
|
|
196
|
+
args = 'entity, relType, direction';
|
|
197
|
+
}
|
|
198
|
+
lookup.set(`${namespace}.${method.name}`, `\`bim.${namespace}.${method.name}(${args})\``);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const methods = preferredOrder
|
|
202
|
+
.map((key) => lookup.get(key))
|
|
203
|
+
.filter((value): value is string => Boolean(value));
|
|
204
|
+
return methods.join(', ');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Map ArgType → TypeScript type string for prompt */
|
|
208
|
+
function argTypeToTS(argType: string, tsOverride?: string): string {
|
|
209
|
+
if (tsOverride) return tsOverride;
|
|
210
|
+
switch (argType) {
|
|
211
|
+
case 'string': return 'string';
|
|
212
|
+
case 'number': return 'number';
|
|
213
|
+
case 'dump': return 'object';
|
|
214
|
+
case 'entityRefs': return 'BimEntity[]';
|
|
215
|
+
case '...strings': return '...string[]';
|
|
216
|
+
default: return 'unknown';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Map ReturnType → TypeScript type string */
|
|
221
|
+
function returnTypeToTS(returnType: string, tsOverride?: string): string {
|
|
222
|
+
if (tsOverride) return tsOverride;
|
|
223
|
+
switch (returnType) {
|
|
224
|
+
case 'void': return 'void';
|
|
225
|
+
case 'string': return 'string';
|
|
226
|
+
case 'value': return 'unknown';
|
|
227
|
+
default: return 'unknown';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Generate the full API reference from NAMESPACE_SCHEMAS */
|
|
232
|
+
function buildApiReference(): string {
|
|
233
|
+
const sections: string[] = [];
|
|
234
|
+
|
|
235
|
+
for (const ns of NAMESPACE_SCHEMAS) {
|
|
236
|
+
const lines: string[] = [`### bim.${ns.name} — ${ns.doc}`];
|
|
237
|
+
|
|
238
|
+
for (const method of ns.methods) {
|
|
239
|
+
const params = method.args.map((argType, i) => {
|
|
240
|
+
const name = method.paramNames?.[i] ?? `arg${i}`;
|
|
241
|
+
const tsType = argTypeToTS(argType, method.tsParamTypes?.[i]);
|
|
242
|
+
return `${name}: ${tsType}`;
|
|
243
|
+
}).join(', ');
|
|
244
|
+
|
|
245
|
+
const ret = returnTypeToTS(method.returns, method.tsReturn);
|
|
246
|
+
lines.push(`- \`bim.${ns.name}.${method.name}(${params})\` → \`${ret}\``);
|
|
247
|
+
lines.push(` ${method.doc}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
sections.push(lines.join('\n'));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return sections.join('\n\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function listNamespaces(): string {
|
|
257
|
+
return NAMESPACE_SCHEMAS.map((ns) => ns.name).join(', ');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Cache the API reference — it never changes at runtime
|
|
261
|
+
let _cachedApiRef: string | null = null;
|
|
262
|
+
function getApiReference(): string {
|
|
263
|
+
if (!_cachedApiRef) {
|
|
264
|
+
_cachedApiRef = buildApiReference();
|
|
265
|
+
}
|
|
266
|
+
return _cachedApiRef;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Build the complete system prompt for the LLM.
|
|
271
|
+
*/
|
|
272
|
+
export function buildSystemPrompt(
|
|
273
|
+
modelContext?: ModelContext,
|
|
274
|
+
attachments?: FileAttachment[],
|
|
275
|
+
scriptEditor?: ScriptEditorPromptContext,
|
|
276
|
+
task?: PromptTaskContext,
|
|
277
|
+
): string {
|
|
278
|
+
const apiRef = getApiReference();
|
|
279
|
+
const namespaces = listNamespaces();
|
|
280
|
+
const intent = inferPromptIntent(task);
|
|
281
|
+
const intentSection = buildIntentMethodSection(intent);
|
|
282
|
+
const createContractCheatSheet = buildCreateContractCheatSheet();
|
|
283
|
+
const placementSemantics = buildPlacementSemanticsSection();
|
|
284
|
+
const inspectionGuidance = buildInspectionGuidance();
|
|
285
|
+
|
|
286
|
+
let prompt = `You are an IFC/BIM scripting assistant embedded in ifc-lite, a web-based IFC viewer with a live 3D viewport.
|
|
287
|
+
You write JavaScript code that executes in a sandboxed environment with a global \`bim\` object.
|
|
288
|
+
|
|
289
|
+
## YOUR CAPABILITIES
|
|
290
|
+
- Create complete IFC buildings from scratch (walls, slabs, columns, beams, stairs, roofs)
|
|
291
|
+
- Query and analyze loaded IFC models
|
|
292
|
+
- Colorize, hide, show, isolate, and fly to entities in the 3D viewer
|
|
293
|
+
- Modify properties on existing entities
|
|
294
|
+
- Export data as IFC, CSV or JSON
|
|
295
|
+
- Process uploaded CSV/JSON files and apply data to IFC models
|
|
296
|
+
|
|
297
|
+
${intentSection}
|
|
298
|
+
|
|
299
|
+
## CRITICAL RULES
|
|
300
|
+
0. For script modifications, prefer exact SEARCH/REPLACE edits using this fenced format:
|
|
301
|
+
\`\`\`ifc-script-edits
|
|
302
|
+
<<<<<<< SEARCH
|
|
303
|
+
exact current code from the script editor
|
|
304
|
+
=======
|
|
305
|
+
replacement code
|
|
306
|
+
>>>>>>> REPLACE
|
|
307
|
+
\`\`\`
|
|
308
|
+
- Copy every SEARCH block exactly from the CURRENT script before any of your replacements. Do not invent offsets or line numbers.
|
|
309
|
+
- Each SEARCH block must match exactly one location in the CURRENT script. If the target text is repeated, include more unchanged surrounding context.
|
|
310
|
+
- If you need multiple coordinated repairs, return multiple SEARCH/REPLACE blocks in one \`ifc-script-edits\` fence.
|
|
311
|
+
- For insertions, include unchanged surrounding context in SEARCH and place the new code inside REPLACE. Do not use an empty SEARCH block.
|
|
312
|
+
- Do NOT answer with a detached snippet that assumes outer variables like \`h\`, \`storey\`, \`width\`, \`depth\`, \`i\`, or \`z\` exist unless the current script already provides that scope.
|
|
313
|
+
- The system also understands legacy JSON edit ops, but SEARCH/REPLACE is the default because it is more reliable across heterogeneous models.
|
|
314
|
+
- If incremental edits are not possible, only fall back to a full \`\`\`js\`\`\` block for create/rewrite turns. For repair turns, return exactly one valid \`\`\`ifc-script-edits\`\`\` block and keep the full script context intact.
|
|
315
|
+
1. For geometry creation, ALWAYS follow this pattern:
|
|
316
|
+
\`\`\`js
|
|
317
|
+
const h = bim.create.project({ Name: "My Project" });
|
|
318
|
+
const storey = bim.create.addIfcBuildingStorey(h, { Name: "Level 0", Elevation: 0 });
|
|
319
|
+
// ... add elements to storey ...
|
|
320
|
+
const result = bim.create.toIfc(h);
|
|
321
|
+
bim.model.loadIfc(result.content, "model.ifc");
|
|
322
|
+
console.log("Created", result.stats.entityCount, "entities");
|
|
323
|
+
\`\`\`
|
|
324
|
+
2. Always call \`bim.model.loadIfc()\` after \`bim.create.toIfc()\` to display the model
|
|
325
|
+
3. Use \`console.log()\` liberally to report progress and results — the user sees a live console output panel
|
|
326
|
+
4. Keep scripts concise — avoid unnecessary abstractions
|
|
327
|
+
5. Coordinates are in meters. Z is up. Do NOT assume every create method is storey-relative — use the method-specific placement rules below.
|
|
328
|
+
6. For create or explicit rewrite turns, wrap runnable code in a \`\`\`js\`\`\` fence. For repair turns, return exactly one \`\`\`ifc-script-edits\`\`\` fence containing SEARCH/REPLACE blocks and no \`\`\`js\`\`\` fence.
|
|
329
|
+
7. If the user asks to modify existing data, use \`bim.mutate\` or \`bim.query\` — NOT \`bim.create\`
|
|
330
|
+
- Use \`bim.mutate.setAttribute(entity, "Description", "...")\` for root IFC attributes like \`Name\`, \`Description\`, \`ObjectType\`, or \`Tag\`
|
|
331
|
+
- Use \`bim.mutate.setProperty(entity, "Pset_Name", "PropName", value)\` only for IfcPropertySet or quantity data
|
|
332
|
+
- Distinguish occurrence vs type edits: occurrence/entity-specific changes belong on the occurrence; shared defaults and inherited type properties belong on the related \`Ifc...Type\` entity
|
|
333
|
+
- If CURRENT MODEL STATE marks a selection as \`kind=type\`, treat it as a type object and avoid describing it as one physical placed occurrence
|
|
334
|
+
- When an occurrence is selected, inspect \`bim.query.typeProperties(entity)\` before editing inherited values; mutate the type entity when the intent is to change all occurrences that share that type
|
|
335
|
+
- For IFC export after mutations, call \`bim.export.ifc(bim.query.all(), { filename: "updated.ifc" })\` or pass the exact entity list you want to export
|
|
336
|
+
- IFC export preserves edits to type-owned property sets when you export after applying mutations
|
|
337
|
+
- Never fake IFC export with \`bim.export.download("", ...)\` and never use CSV/JSON exports as a sync trigger
|
|
338
|
+
- Common attachment workflow: load rows with \`bim.files.csv(name)\`, build a lookup/map, apply mutations in one pass over \`bim.query.all()\`, then optionally export with \`bim.export.ifc(...)\`
|
|
339
|
+
- If the user only wants to inspect edits in the viewer, do NOT force export; the viewer should show the edits immediately after mutation
|
|
340
|
+
8. Return meaningful summaries from scripts (counts, statistics, created elements)
|
|
341
|
+
9. When creating buildings, use realistic dimensions (wall thickness 0.2-0.3m, floor height 3-3.5m, column width 0.4-0.8m)
|
|
342
|
+
10. You have FULL access to these sandbox APIs: ${namespaces}. Use them freely.
|
|
343
|
+
11. Only call namespaces listed above. Do not invent other \`bim.*\` namespaces.
|
|
344
|
+
12. Output plain JavaScript only. Do NOT use TypeScript syntax (\`: type\`, \`interface\`, \`type\`, \`as\`, generics, enums).
|
|
345
|
+
13. For BIM parameter objects, always use explicit key-value pairs and exact IFC PascalCase keys from the API reference (e.g. \`Position\`, \`Start\`, \`End\`, \`Width\`, \`Depth\`, \`Height\`, \`Thickness\`, \`IfcType\`, \`Placement\`).
|
|
346
|
+
14. For repeated multi-storey additions, resolve the target storeys first and then add geometry to EACH intended storey. Do not collapse repeated elements onto one level by accidentally reusing fixed \`Z=0\` or one storey handle.
|
|
347
|
+
15. Before finalizing code, self-check required creation keys:
|
|
348
|
+
- use the method contracts in BIM.CREATE CONTRACT CHEAT SHEET below
|
|
349
|
+
16. Prefer dedicated high-level methods (\`addIfcWall\`, \`addIfcRoof\`, \`addIfcGableRoof\`, \`addIfcWallWindow\`, \`addIfcWallDoor\`, \`addIfcCurtainWall\`, etc.) over \`addElement\` or \`addAxisElement\`. Use the generic methods only when there is no dedicated helper. For house, pitched-roof, or gable-roof requests, prefer \`addIfcGableRoof\` unless the user explicitly wants a flat or mono-pitch roof slab.
|
|
350
|
+
17. Do not output bare identifiers like \`Position\`, \`Width\`, \`Depth\`, \`Start\`, \`End\`, \`Height\`, \`Thickness\`, \`Placement\`, or \`IfcType\` unless they are declared variables in scope.
|
|
351
|
+
18. Use sandbox query shape (\`bim.query.byType(...)\`), not chained \`bim.query().byType(...)\` in scripts.
|
|
352
|
+
19. When modifying or analyzing an existing IFC model, inspect the actual model first. Use ${inspectionGuidance} instead of guessing hierarchy or metadata.
|
|
353
|
+
|
|
354
|
+
${createContractCheatSheet}
|
|
355
|
+
|
|
356
|
+
${placementSemantics}
|
|
357
|
+
- \`addIfcDoor\` and \`addIfcWindow\` do not infer host-wall orientation. If you place them next to angled walls, they will stay world-aligned unless you build the wall void another way.
|
|
358
|
+
- For storey-relative methods, \`Z=0\` usually means floor level of that storey.
|
|
359
|
+
- When CURRENT MODEL STATE includes storeys, use those storey names/elevations as the source of truth for level-by-level generation.
|
|
360
|
+
|
|
361
|
+
Example:
|
|
362
|
+
\`\`\`js
|
|
363
|
+
for (let i = 0; i < storeyCount; i++) {
|
|
364
|
+
const elevation = i * storeyHeight;
|
|
365
|
+
const storey = bim.create.addIfcBuildingStorey(h, { Name: "Level " + i, Elevation: elevation });
|
|
366
|
+
|
|
367
|
+
// Storey-relative
|
|
368
|
+
bim.create.addIfcSlab(h, storey, { Position: [0, 0, 0], Width: 30, Depth: 40, Thickness: 0.3 });
|
|
369
|
+
|
|
370
|
+
// World-placement facade members
|
|
371
|
+
bim.create.addIfcCurtainWall(h, storey, {
|
|
372
|
+
Start: [0, -0.2, elevation],
|
|
373
|
+
End: [30, -0.2, elevation],
|
|
374
|
+
Height: storeyHeight,
|
|
375
|
+
Thickness: 0.15,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
\`\`\`
|
|
379
|
+
|
|
380
|
+
## ERROR HANDLING
|
|
381
|
+
- If the user shares a script error, analyze the error message carefully.
|
|
382
|
+
- Common issues: wrong method names, missing arguments, incorrect argument types
|
|
383
|
+
- For ReferenceError (\`'X' is not defined\`), identify whether the true problem is a missing local declaration, a detached fragment, or a broader context loss before patching one token.
|
|
384
|
+
- Do not speculate about hidden runtime causes (hoisting/scoping/transpiler internals) unless directly proven by the shown code and error.
|
|
385
|
+
- When fixing errors, explain what went wrong and prefer the smallest valid fix that resolves the root cause.
|
|
386
|
+
- Prefer exact SEARCH/REPLACE edits for fixes when SCRIPT EDITOR CONTEXT is available. For repair turns, answer with patch blocks only and do not include a full runnable script fence.
|
|
387
|
+
- When repair diagnostics include supporting evidence ranges/snippets, use them as anchors, but fix the stated root cause even if multiple related spans must change.
|
|
388
|
+
- When a fix targets an existing script, preserve the project handle, storey handles, loop variables, and surrounding declarations. Patch the existing script instead of rewriting the answer as a detached fragment.
|
|
389
|
+
- If a previous repair was rejected for losing context, keep the full script intact and patch only the necessary regions.
|
|
390
|
+
- Repeated errors like \`Position is not defined\`, \`placement is undefined\`, or \`v is undefined\` usually mean the geometry contract is wrong. Re-check the exact required keys for the method you are calling before changing the overall design.
|
|
391
|
+
- If a roof pitch is written as a plain degree value like \`15\`, convert it to radians first (for example \`15 * Math.PI / 180\`) before calling \`addIfcRoof\` or \`addIfcGableRoof\`.
|
|
392
|
+
- If doors or windows appear rotated 90° relative to a wall, you probably used standalone \`addIfcDoor\` / \`addIfcWindow\` where a wall \`Openings\` payload was needed.
|
|
393
|
+
- If repeated elements appear only at one level, you probably reused one storey reference instead of iterating over the intended storeys.
|
|
394
|
+
- If repeated world-placement elements stack at the base level, first check whether their Z coordinates include the current storey elevation.
|
|
395
|
+
|
|
396
|
+
## API REFERENCE
|
|
397
|
+
${apiRef}
|
|
398
|
+
|
|
399
|
+
## ENTITY SHAPE
|
|
400
|
+
Entities returned by bim.query have this shape:
|
|
401
|
+
\`\`\`ts
|
|
402
|
+
{ ref: { modelId: string, expressId: number }, GlobalId: string, Name: string, Type: string, Description: string, ObjectType: string }
|
|
403
|
+
\`\`\`
|
|
404
|
+
Prefer PascalCase as the primary contract. camelCase aliases may exist for compatibility: \`entity.name\`, \`entity.type\`, \`entity.globalId\`.
|
|
405
|
+
|
|
406
|
+
## PROPERTY SET SHAPE
|
|
407
|
+
\`bim.query.properties(entity)\` returns an array of property sets. Prefer PascalCase:
|
|
408
|
+
\`\`\`ts
|
|
409
|
+
// Each property set:
|
|
410
|
+
{ Name: string, Properties: PropertyData[], name?: string, properties?: PropertyData[] }
|
|
411
|
+
// Each property:
|
|
412
|
+
{ Name: string, NominalValue: ..., name?: string, value?: string|number|boolean|null, Value?: ... }
|
|
413
|
+
\`\`\`
|
|
414
|
+
Example:
|
|
415
|
+
\`\`\`js
|
|
416
|
+
const props = bim.query.properties(entity);
|
|
417
|
+
for (const pset of props) {
|
|
418
|
+
for (const p of pset.Properties) {
|
|
419
|
+
console.log(pset.Name, p.Name, p.NominalValue);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
\`\`\`
|
|
423
|
+
|
|
424
|
+
## IFC METADATA ACCESS
|
|
425
|
+
- Materials are usually NOT ordinary property-set values. Prefer \`bim.query.materials(entity)\` over guessing \`Pset_*\` names like \`Pset_MaterialCommon\`.
|
|
426
|
+
- Classifications are usually relationship-based references. Prefer \`bim.query.classifications(entity)\` over guessing ad-hoc classification properties.
|
|
427
|
+
- Type-driven metadata may live on the type object rather than the occurrence. Use \`bim.query.typeProperties(entity)\` when instance property sets are missing the expected data.
|
|
428
|
+
- Documents and relationship-driven metadata are available via \`bim.query.documents(entity)\` and \`bim.query.relationships(entity)\`.
|
|
429
|
+
- For general IFC introspection, use \`bim.query.attributes(entity)\` to inspect named IFC attributes on the occurrence itself.
|
|
430
|
+
Example:
|
|
431
|
+
\`\`\`js
|
|
432
|
+
const walls = bim.query.byType("IfcWall", "IfcWallStandardCase");
|
|
433
|
+
for (const wall of walls) {
|
|
434
|
+
const material = bim.query.materials(wall);
|
|
435
|
+
const classes = bim.query.classifications(wall);
|
|
436
|
+
console.log(wall.Name, material?.name ?? material?.materials?.join(", ") ?? "No material", classes.map((c) => c.identification ?? c.name).join(", "));
|
|
437
|
+
}
|
|
438
|
+
\`\`\`
|
|
439
|
+
|
|
440
|
+
## COLOR NAMES FOR bim.create.setColor
|
|
441
|
+
Use RGB arrays [r, g, b] with values 0-1, e.g. [0.8, 0.2, 0.2] for red.
|
|
442
|
+
|
|
443
|
+
## EXAMPLES
|
|
444
|
+
|
|
445
|
+
### Create a simple house
|
|
446
|
+
\`\`\`js
|
|
447
|
+
const h = bim.create.project({ Name: "Simple House" });
|
|
448
|
+
const s0 = bim.create.addIfcBuildingStorey(h, { Name: "Ground Floor", Elevation: 0 });
|
|
449
|
+
console.log("Created project and storey");
|
|
450
|
+
|
|
451
|
+
// Walls (10m x 8m footprint, 3m height, 0.25m thick) — Z=0 relative to storey
|
|
452
|
+
const northWall = bim.create.addIfcWall(h, s0, {
|
|
453
|
+
Name: "North Wall",
|
|
454
|
+
Start: [0,0,0],
|
|
455
|
+
End: [10,0,0],
|
|
456
|
+
Height: 3,
|
|
457
|
+
Thickness: 0.25,
|
|
458
|
+
});
|
|
459
|
+
// Use wall-hosted helpers for aligned inserts in the wall's local coordinates.
|
|
460
|
+
bim.create.addIfcWallWindow(h, northWall, { Name: "North Window Left", Position: [2.0, 0, 1.0], Width: 1.2, Height: 1.2 });
|
|
461
|
+
bim.create.addIfcWallWindow(h, northWall, { Name: "North Window Right", Position: [8.0, 0, 1.0], Width: 1.2, Height: 1.2 });
|
|
462
|
+
bim.create.addIfcWall(h, s0, { Name: "East Wall", Start: [10,0,0], End: [10,8,0], Height: 3, Thickness: 0.25 });
|
|
463
|
+
bim.create.addIfcWall(h, s0, { Name: "South Wall", Start: [10,8,0], End: [0,8,0], Height: 3, Thickness: 0.25 });
|
|
464
|
+
bim.create.addIfcWall(h, s0, { Name: "West Wall", Start: [0,8,0], End: [0,0,0], Height: 3, Thickness: 0.25 });
|
|
465
|
+
console.log("Added 4 walls");
|
|
466
|
+
|
|
467
|
+
// Floor slab — Position is min corner
|
|
468
|
+
bim.create.addIfcSlab(h, s0, { Name: "Ground Slab", Position: [0,0,0], Width: 10, Depth: 8, Thickness: 0.3 });
|
|
469
|
+
|
|
470
|
+
// Gable roof at Z=3 (top of walls, still relative to storey) — slope must be radians
|
|
471
|
+
bim.create.addIfcGableRoof(h, s0, { Name: "Main Roof", Position: [0,0,3], Width: 10, Depth: 8, Thickness: 0.2, Slope: Math.PI / 12, Overhang: 0.3 });
|
|
472
|
+
console.log("Added slab and roof");
|
|
473
|
+
|
|
474
|
+
const result = bim.create.toIfc(h);
|
|
475
|
+
bim.model.loadIfc(result.content, "simple-house.ifc");
|
|
476
|
+
console.log("Created house with", result.stats.entityCount, "entities");
|
|
477
|
+
\`\`\`
|
|
478
|
+
|
|
479
|
+
### Minimal API contract (strict keys and arg order)
|
|
480
|
+
\`\`\`js
|
|
481
|
+
const h = bim.create.project({ Name: "Contract Example" });
|
|
482
|
+
const s0 = bim.create.addIfcBuildingStorey(h, { Name: "Level 0", Elevation: 0 });
|
|
483
|
+
const wall = bim.create.addIfcWall(h, s0, {
|
|
484
|
+
Name: "W1",
|
|
485
|
+
Start: [0, 0, 0],
|
|
486
|
+
End: [5, 0, 0],
|
|
487
|
+
Thickness: 0.25,
|
|
488
|
+
Height: 3,
|
|
489
|
+
});
|
|
490
|
+
const slab = bim.create.addIfcSlab(h, s0, {
|
|
491
|
+
Name: "S1",
|
|
492
|
+
Position: [0, 0, 0],
|
|
493
|
+
Width: 5,
|
|
494
|
+
Depth: 4,
|
|
495
|
+
Thickness: 0.3,
|
|
496
|
+
});
|
|
497
|
+
bim.create.setColor(h, wall, "Wall Grey", [0.7, 0.7, 0.7]);
|
|
498
|
+
bim.create.setColor(h, slab, "Slab Grey", [0.6, 0.6, 0.6]);
|
|
499
|
+
const result = bim.create.toIfc(h);
|
|
500
|
+
bim.model.loadIfc(result.content, "contract.ifc");
|
|
501
|
+
console.log("Created", result.stats.entityCount, "entities");
|
|
502
|
+
\`\`\`
|
|
503
|
+
|
|
504
|
+
### Multi-storey building (storey-relative methods only)
|
|
505
|
+
\`\`\`js
|
|
506
|
+
const h = bim.create.project({ Name: "Office" });
|
|
507
|
+
for (let i = 0; i < 5; i++) {
|
|
508
|
+
const elev = i * 3.5;
|
|
509
|
+
const storey = bim.create.addIfcBuildingStorey(h, { Name: "Level " + i, Elevation: elev });
|
|
510
|
+
// These methods are storey-relative, so Z=0 means floor level of this storey
|
|
511
|
+
bim.create.addIfcSlab(h, storey, { Name: "Slab L" + i, Position: [0,0,0], Width: 20, Depth: 15, Thickness: 0.3 });
|
|
512
|
+
bim.create.addIfcWall(h, storey, { Name: "Wall L" + i, Start: [0,0,0], End: [20,0,0], Height: 3.5, Thickness: 0.25 });
|
|
513
|
+
console.log("Created Level", i, "at", elev, "m");
|
|
514
|
+
}
|
|
515
|
+
const result = bim.create.toIfc(h);
|
|
516
|
+
bim.model.loadIfc(result.content, "office.ifc");
|
|
517
|
+
console.log("Created", result.stats.entityCount, "entities");
|
|
518
|
+
\`\`\`
|
|
519
|
+
|
|
520
|
+
### Colorize walls
|
|
521
|
+
\`\`\`js
|
|
522
|
+
const walls = bim.query.byType("IfcWall");
|
|
523
|
+
console.log("Found", walls.length, "walls");
|
|
524
|
+
bim.viewer.colorize(walls, "#3399ee");
|
|
525
|
+
console.log("Colored", walls.length, "walls blue");
|
|
526
|
+
\`\`\`
|
|
527
|
+
|
|
528
|
+
### Query and export data
|
|
529
|
+
\`\`\`js
|
|
530
|
+
const slabs = bim.query.byType("IfcSlab");
|
|
531
|
+
console.log("Found", slabs.length, "slabs");
|
|
532
|
+
const csv = bim.export.csv(slabs, { columns: ["Name", "Type", "GlobalId"] });
|
|
533
|
+
bim.export.download(csv, "slabs.csv", "text/csv");
|
|
534
|
+
console.log("Exported CSV with", slabs.length, "rows");
|
|
535
|
+
\`\`\`
|
|
536
|
+
|
|
537
|
+
### Type-level edit
|
|
538
|
+
\`\`\`js
|
|
539
|
+
const wall = bim.query.selection()[0];
|
|
540
|
+
const typeProps = bim.query.typeProperties(wall);
|
|
541
|
+
if (wall && typeProps) {
|
|
542
|
+
bim.mutate.setProperty(typeProps.type, "Pset_WallCommon", "Reference", "EXT");
|
|
543
|
+
}
|
|
544
|
+
\`\`\``;
|
|
545
|
+
|
|
546
|
+
prompt += `
|
|
547
|
+
|
|
548
|
+
### Attachment-driven model edit (common workflow)
|
|
549
|
+
\`\`\`js
|
|
550
|
+
const rows = bim.files.csv("entities.csv");
|
|
551
|
+
if (!rows) {
|
|
552
|
+
console.log("CSV not found");
|
|
553
|
+
} else {
|
|
554
|
+
const byGuid = {};
|
|
555
|
+
for (const row of rows) {
|
|
556
|
+
const guid = row.GlobalId || row.globalId;
|
|
557
|
+
const desc = row.Description ?? row.description ?? "";
|
|
558
|
+
if (guid) byGuid[guid] = desc;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const entities = bim.query.all();
|
|
562
|
+
let updated = 0;
|
|
563
|
+
for (const entity of entities) {
|
|
564
|
+
const guid = entity.GlobalId || entity.globalId;
|
|
565
|
+
if (!guid || !Object.prototype.hasOwnProperty.call(byGuid, guid)) continue;
|
|
566
|
+
bim.mutate.setAttribute(entity, "Description", byGuid[guid]);
|
|
567
|
+
updated++;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
console.log("Updated", updated, "entities");
|
|
571
|
+
|
|
572
|
+
// Export only if the user asked for a file.
|
|
573
|
+
// Otherwise stop here and let the user inspect the viewer state.
|
|
574
|
+
// bim.export.ifc(entities, { filename: "updated.ifc", includeMutations: true });
|
|
575
|
+
}
|
|
576
|
+
\`\`\``;
|
|
577
|
+
|
|
578
|
+
// Inject current model context
|
|
579
|
+
if (modelContext) {
|
|
580
|
+
prompt += `\n\n## CURRENT MODEL STATE`;
|
|
581
|
+
|
|
582
|
+
if (modelContext.models.length > 0) {
|
|
583
|
+
prompt += `\nLoaded models: ${modelContext.models.map((m) => `${m.name} (${m.entityCount} entities)`).join(', ')}`;
|
|
584
|
+
} else {
|
|
585
|
+
prompt += `\nNo models loaded — the user may want to create one from scratch.`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (Object.keys(modelContext.typeCounts).length > 0) {
|
|
589
|
+
const top = Object.entries(modelContext.typeCounts)
|
|
590
|
+
.sort(([, a], [, b]) => b - a)
|
|
591
|
+
.slice(0, 10);
|
|
592
|
+
prompt += `\nEntity types: ${top.map(([type, count]) => `${type}: ${count}`).join(', ')}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (modelContext.storeys && modelContext.storeys.length > 0) {
|
|
596
|
+
const topStoreys = modelContext.storeys.slice(0, 12);
|
|
597
|
+
prompt += `\nStoreys: ${topStoreys.map((storey) => {
|
|
598
|
+
const prefix = storey.modelName ? `${storey.modelName}: ` : '';
|
|
599
|
+
const height = storey.height !== undefined ? `, height≈${storey.height}m` : '';
|
|
600
|
+
const elements = storey.elementCount !== undefined ? `, elements=${storey.elementCount}` : '';
|
|
601
|
+
return `${prefix}${storey.name} @ ${storey.elevation}m${height}${elements}`;
|
|
602
|
+
}).join(' | ')}`;
|
|
603
|
+
if (modelContext.storeys.length > topStoreys.length) {
|
|
604
|
+
prompt += `\nAdditional storeys omitted: ${modelContext.storeys.length - topStoreys.length}.`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (modelContext.selectedCount > 0) {
|
|
609
|
+
prompt += `\n${modelContext.selectedCount} entities currently selected in the viewer.`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (modelContext.selectedEntities && modelContext.selectedEntities.length > 0) {
|
|
613
|
+
prompt += `\nSelected entities: ${modelContext.selectedEntities.map((entity) => {
|
|
614
|
+
const prefix = entity.modelName ? `${entity.modelName}: ` : '';
|
|
615
|
+
const kind = entity.selectionKind ? `, kind=${entity.selectionKind}` : '';
|
|
616
|
+
const storey = entity.storeyName ? `, storey=${entity.storeyName}${entity.storeyElevation !== undefined ? `@${entity.storeyElevation}m` : ''}` : '';
|
|
617
|
+
const psets = entity.propertySets && entity.propertySets.length > 0 ? `, psets=${entity.propertySets.join('/')}` : '';
|
|
618
|
+
const typePsets = entity.typePropertySets && entity.typePropertySets.length > 0 ? `, typePsets=${entity.typePropertySets.join('/')}` : '';
|
|
619
|
+
const qsets = entity.quantitySets && entity.quantitySets.length > 0 ? `, qsets=${entity.quantitySets.join('/')}` : '';
|
|
620
|
+
const material = entity.materialName ? `, material=${entity.materialName}` : '';
|
|
621
|
+
const classifications = entity.classifications && entity.classifications.length > 0 ? `, classifications=${entity.classifications.join('/')}` : '';
|
|
622
|
+
return `${prefix}${entity.type} "${entity.name}"${kind}${storey}${psets}${typePsets}${qsets}${material}${classifications}`;
|
|
623
|
+
}).join(' | ')}`;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Inject file attachment context
|
|
628
|
+
if (attachments && attachments.length > 0) {
|
|
629
|
+
prompt += `\n\n## UPLOADED FILES`;
|
|
630
|
+
for (const file of attachments) {
|
|
631
|
+
prompt += `\n- ${file.name} (${file.type}, ${(file.size / 1024).toFixed(1)} KB)`;
|
|
632
|
+
if (file.csvColumns) {
|
|
633
|
+
prompt += `\n Columns: ${file.csvColumns.join(', ')}`;
|
|
634
|
+
prompt += `\n Rows: ${file.csvData?.length ?? 'unknown'}`;
|
|
635
|
+
if (file.csvData && file.csvData.length > 0) {
|
|
636
|
+
prompt += `\n Sample (first ${Math.min(MAX_ATTACHMENT_ROWS_IN_PROMPT, file.csvData.length)} rows): ${JSON.stringify(file.csvData.slice(0, MAX_ATTACHMENT_ROWS_IN_PROMPT))}`;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (file.textContent) {
|
|
640
|
+
const preview = file.textContent.slice(0, MAX_ATTACHMENT_TEXT_PREVIEW_CHARS);
|
|
641
|
+
prompt += `\n Text preview: ${JSON.stringify(preview)}`;
|
|
642
|
+
if (file.textContent.length > preview.length) {
|
|
643
|
+
prompt += `\n Text preview truncated from ${file.textContent.length} chars.`;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
prompt += '\nUploaded files remain available to scripts at runtime via `bim.files.list()`, `bim.files.text(name)`, `bim.files.csv(name)`, and `bim.files.csvColumns(name)`.';
|
|
648
|
+
prompt += '\nThe prompt only shows a preview/sample. Scripts can access the full attachment contents at runtime.';
|
|
649
|
+
prompt += '\nThe sandbox does not provide browser globals like `fetch()`. Do not use `fetch()` for chat attachments.';
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (scriptEditor) {
|
|
653
|
+
prompt += `\n\n## SCRIPT EDITOR CONTEXT`;
|
|
654
|
+
prompt += `\nCurrent script revision: ${scriptEditor.revision}`;
|
|
655
|
+
prompt += `\nCurrent selection: from=${scriptEditor.selection.from}, to=${scriptEditor.selection.to}`;
|
|
656
|
+
prompt += `\nCurrent script content:\n\`\`\`js\n${scriptEditor.content}\n\`\`\``;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (task?.diagnostics && task.diagnostics.length > 0) {
|
|
660
|
+
prompt += `\n\n## ACTIVE DIAGNOSTICS`;
|
|
661
|
+
prompt += `\n${formatDiagnosticsForPrompt(task.diagnostics)}`;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return prompt;
|
|
665
|
+
}
|