devagent-ignition 0.1.0__py3-none-any.whl
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.
- devagent_hmi.py +471 -0
- devagent_ignition-0.1.0.dist-info/METADATA +204 -0
- devagent_ignition-0.1.0.dist-info/RECORD +133 -0
- devagent_ignition-0.1.0.dist-info/WHEEL +4 -0
- devagent_ignition-0.1.0.dist-info/entry_points.txt +2 -0
- devagent_spatial/__init__.py +1 -0
- devagent_spatial/_resources/README.md +187 -0
- devagent_spatial/_resources/RELEASE_MANIFEST.json +312 -0
- devagent_spatial/_resources/SHA256SUMS.txt +270 -0
- devagent_spatial/_resources/SOURCE_PROVENANCE.json +186 -0
- devagent_spatial/_resources/assets/agent.js +102 -0
- devagent_spatial/_resources/assets/creator.css +247 -0
- devagent_spatial/_resources/assets/creator_2d.mjs +16 -0
- devagent_spatial/_resources/assets/creator_original_r9.mjs +58 -0
- devagent_spatial/_resources/assets/creator_original_v8.mjs +120 -0
- devagent_spatial/_resources/assets/creator_r9_diagnostics.mjs +53 -0
- devagent_spatial/_resources/assets/creator_scene.mjs +128 -0
- devagent_spatial/_resources/assets/creator_ui.mjs +60 -0
- devagent_spatial/_resources/assets/customer_documents.js +110 -0
- devagent_spatial/_resources/assets/equipment_controls.mjs +27 -0
- devagent_spatial/_resources/assets/facility2d.css +24 -0
- devagent_spatial/_resources/assets/facility2d_app.mjs +45 -0
- devagent_spatial/_resources/assets/facility2d_core.mjs +114 -0
- devagent_spatial/_resources/assets/facility2d_simulation.mjs +167 -0
- devagent_spatial/_resources/assets/facility2d_viewer.html +7 -0
- devagent_spatial/_resources/assets/fallback_scene.js +17 -0
- devagent_spatial/_resources/assets/flow_geometry.mjs +218 -0
- devagent_spatial/_resources/assets/harness.js +78 -0
- devagent_spatial/_resources/assets/live_design_review.js +129 -0
- devagent_spatial/_resources/assets/operations_ui.js +305 -0
- devagent_spatial/_resources/assets/operator_viewer.html +64 -0
- devagent_spatial/_resources/assets/project_package.js +52 -0
- devagent_spatial/_resources/assets/r9_transport.mjs +70 -0
- devagent_spatial/_resources/assets/runtime_feed.js +30 -0
- devagent_spatial/_resources/assets/studio.html +139 -0
- devagent_spatial/_resources/assets/style.css +191 -0
- devagent_spatial/_resources/assets/test_workspace.html +195 -0
- devagent_spatial/_resources/assets/three_scene.js +442 -0
- devagent_spatial/_resources/assets/unified_engineer.js +101 -0
- devagent_spatial/_resources/assets/v076_fat_ui.js +40 -0
- devagent_spatial/_resources/assets/v078_command_ui.js +44 -0
- devagent_spatial/_resources/assets/viewer.html +45 -0
- devagent_spatial/_resources/assets/viewer.js +112 -0
- devagent_spatial/_resources/design-system.json +130 -0
- devagent_spatial/_resources/examples/CV101-command-contract-PENDING.json +30 -0
- devagent_spatial/_resources/examples/cv101_guided.json +272 -0
- devagent_spatial/_resources/examples/make_examples.py +36 -0
- devagent_spatial/_resources/examples/single_floor.json +444 -0
- devagent_spatial/_resources/examples/v0771_training_wizard.json +11 -0
- devagent_spatial/_resources/examples/warehouse-3floor-standard-2d.json +708 -0
- devagent_spatial/_resources/examples/warehouse-operations-synthetic.json +1186 -0
- devagent_spatial/_resources/examples/warehouse-v07-lab-bindings-PENDING.json +372 -0
- devagent_spatial/_resources/examples/warehouse.json +708 -0
- devagent_spatial/_resources/examples/water.json +288 -0
- devagent_spatial/_resources/reference/r9-golden/REFERENCE_SHA256.json +13 -0
- devagent_spatial/_resources/reference/r9-golden/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-golden/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-golden/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-golden/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-golden/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-golden/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/README_R9.md +27 -0
- devagent_spatial/_resources/reference/r9-original/SHA256SUMS.txt +63 -0
- devagent_spatial/_resources/reference/r9-original/SNAPSHOT_SHA256SUMS.txt +16 -0
- devagent_spatial/_resources/reference/r9-original/flow_routes.json +527 -0
- devagent_spatial/_resources/reference/r9-original/flow_sim.py +117 -0
- devagent_spatial/_resources/reference/r9-original/lab_control.py +73 -0
- devagent_spatial/_resources/reference/r9-original/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-original/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-original/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-original/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-original/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-original/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/LICENSE-three.txt +21 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/SHA256SUMS.txt +4 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/addons/controls/OrbitControls.js +1860 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.core.js +58773 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.module.js +18251 -0
- devagent_spatial/_resources/reference/v8-original/README.md +36 -0
- devagent_spatial/_resources/reference/v8-original/SHA256SUMS.txt +12 -0
- devagent_spatial/_resources/reference/v8-original/model.py +124 -0
- devagent_spatial/_resources/reference/v8-original/run_demo.sh +4 -0
- devagent_spatial/_resources/reference/v8-original/server.py +100 -0
- devagent_spatial/_resources/reference/v8-original/tests/browser_smoke.py +50 -0
- devagent_spatial/_resources/reference/v8-original/tests/test_v8.py +144 -0
- devagent_spatial/_resources/reference/v8-original/topology.json +24 -0
- devagent_spatial/_resources/reference/v8-original/web/app.js +114 -0
- devagent_spatial/_resources/reference/v8-original/web/index.html +22 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.html +1 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.js +6 -0
- devagent_spatial/_resources/reference/v8-original/web/style.css +16 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Equipment_Register.csv +4 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Flow_Requirements.txt +5 -0
- devagent_spatial/_resources/sample_customer_inputs/Raw_PLC_Tag_Export.csv +4 -0
- devagent_spatial/_resources/templates/warehouse_connections.csv +12 -0
- devagent_spatial/_resources/templates/warehouse_equipment.csv +21 -0
- devagent_spatial/_resources/templates/warehouse_signals.csv +34 -0
- devagent_spatial/_resources/templates/water_connections.csv +7 -0
- devagent_spatial/_resources/templates/water_equipment.csv +10 -0
- devagent_spatial/_resources/templates/water_signals.csv +12 -0
- devagent_spatial/agent_harness.py +299 -0
- devagent_spatial/agent_runtime.py +314 -0
- devagent_spatial/contract_opc_fat.py +216 -0
- devagent_spatial/contract_opc_source.py +265 -0
- devagent_spatial/control_intents.py +38 -0
- devagent_spatial/customer_intake.py +235 -0
- devagent_spatial/engineering_expertise.py +114 -0
- devagent_spatial/generator.py +201 -0
- devagent_spatial/ignition_native.py +234 -0
- devagent_spatial/ignition_project.py +144 -0
- devagent_spatial/live_lab_binding.py +128 -0
- devagent_spatial/model.py +297 -0
- devagent_spatial/native_reference.py +69 -0
- devagent_spatial/opc_snapshot.py +87 -0
- devagent_spatial/operations.py +284 -0
- devagent_spatial/operations_capture.py +47 -0
- devagent_spatial/operations_lab.py +64 -0
- devagent_spatial/original_r9.py +169 -0
- devagent_spatial/original_v8.py +81 -0
- devagent_spatial/preflight.py +140 -0
- devagent_spatial/preview_simulation.py +189 -0
- devagent_spatial/project_package.py +101 -0
- devagent_spatial/qualification.py +152 -0
- devagent_spatial/strict_json.py +29 -0
- devagent_spatial/studio.py +894 -0
- devagent_spatial/studio_workflow.py +277 -0
- devagent_spatial/test_reporting.py +87 -0
- devagent_spatial/v076_opc_fat.py +180 -0
- devagent_spatial/v078_commands.py +277 -0
- devagent_spatial/v078_native_commands.py +161 -0
- devagent_spatial/v07_fat.py +110 -0
- devagent_spatial/v07_native.py +318 -0
- devagent_spatial/v083_native.py +349 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* V0.8.2: original R9 snapshot, isolated simulation and no native Ignition claim. */
|
|
2
|
+
const $=id=>document.getElementById(id);
|
|
3
|
+
const PANEL=$('original-r9-panel'),FRAME=$('original-r9-frame'),BUTTON=$('demo-original-r9');
|
|
4
|
+
function clearR9(){
|
|
5
|
+
BUTTON?.classList.remove('selected');
|
|
6
|
+
BUTTON?.setAttribute('aria-pressed','false');
|
|
7
|
+
PANEL?.classList.add('hidden');
|
|
8
|
+
FRAME?.removeAttribute('src');
|
|
9
|
+
}
|
|
10
|
+
document.addEventListener('devagent:project-reset',clearR9);
|
|
11
|
+
// The existing V0.8.1 reset event invalidates every approval and draft.
|
|
12
|
+
// R9 lab is in-memory; it is reinitialized only when a new R9 demo is opened.
|
|
13
|
+
async function loadR9(){
|
|
14
|
+
const api=window.DevAgentProjectReset;
|
|
15
|
+
if(!api){$('result').textContent='HOLD: Project reset handler unavailable';return;}
|
|
16
|
+
if(api.hasCurrentWork()&&!window.confirm('Loading R9 replaces current unsaved Creator inputs and preview, not saved projects. Continue?'))return;
|
|
17
|
+
if(!api.reset('replace-with-original-r9',false))return;
|
|
18
|
+
const generation=api.generation;
|
|
19
|
+
try{
|
|
20
|
+
const response=await fetch('/api/original-r9/info',{cache:'no-store'});
|
|
21
|
+
if(!response.ok)throw Error('R9_SOURCE_UNAVAILABLE');
|
|
22
|
+
const info=await response.json();
|
|
23
|
+
if(generation!==api.generation)return;
|
|
24
|
+
const expected=['R9_SPIRAL_01','R9_SPIRAL_02','R9_SPIRAL_03','R9_SPIRAL_04'];
|
|
25
|
+
if(info.status!=='SOURCE_BACKED_CONCEPT_ONLY'||info.floors.join(',')!=='1,2,3'||
|
|
26
|
+
info.spiral_ids.join(',')!==expected.join(',')||info.native_ignition_verified!==false)
|
|
27
|
+
throw Error('R9_SOURCE_EVIDENCE_MISMATCH');
|
|
28
|
+
// No unknown mappings or plant parameters are inserted into the customer
|
|
29
|
+
// wizard. The source-backed R9 viewer is a separate virtual demo only.
|
|
30
|
+
const r=await fetch('/api/original-r9/reset',{method:'POST',
|
|
31
|
+
headers:{'Content-Type':'application/json','X-DevAgent-Request':'original-r9-simulator-v1'},
|
|
32
|
+
body:JSON.stringify({confirm:'RESET_SYNTHETIC_R9_ONLY'})});
|
|
33
|
+
if(generation!==api.generation)return;
|
|
34
|
+
if(!r.ok)throw Error('R9_ISOLATED_SIMULATOR_RESET_FAILED');
|
|
35
|
+
document.body.dataset.activeDemo='original-r9';
|
|
36
|
+
BUTTON.classList.add('selected');BUTTON.setAttribute('aria-pressed','true');
|
|
37
|
+
FRAME.src='/original-r9/index.html';PANEL.classList.remove('hidden');
|
|
38
|
+
$('stage-eyebrow').textContent='R9 SOURCE SNAPSHOT / SPATIAL OPERATIONS';
|
|
39
|
+
$('stage-heading').textContent='3D Flow · R9 Four Spirals';
|
|
40
|
+
$('stage-mode').textContent='SYNTHETIC';
|
|
41
|
+
$('stage-title').textContent='3 floors · 4 spiral declines · isolated simulator';
|
|
42
|
+
$('stage-details').textContent='Select 3D equipment to inspect virtual PE, motor and fault status. No PLC or native Ignition source is connected.';
|
|
43
|
+
$('result').className='status';
|
|
44
|
+
$('result').textContent='R9 source-backed WEB CONCEPT loaded. 4 spiral IDs / 3 floors. 0 verified PLC mappings. Native Ignition project identity NOT VERIFIED; production HOLD.';
|
|
45
|
+
document.dispatchEvent(new CustomEvent('devagent:original-r9-loaded',{detail:info}));
|
|
46
|
+
PANEL.scrollIntoView({behavior:'smooth',block:'nearest'});
|
|
47
|
+
}catch(error){
|
|
48
|
+
if(generation!==api.generation)return;
|
|
49
|
+
api.reset('original-r9-load-failed',false);
|
|
50
|
+
$('result').className='status hold';
|
|
51
|
+
$('result').textContent='HOLD: '+String(error?.message||error).slice(0,120);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
BUTTON?.addEventListener('click',loadR9);
|
|
55
|
+
$('r9-back-to-creator')?.addEventListener('click',()=>{
|
|
56
|
+
window.DevAgentProjectReset?.reset('Return to Creator',false);
|
|
57
|
+
document.querySelector('.creator-start')?.scrollIntoView({behavior:'smooth',block:'start'});
|
|
58
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* V0.8.1 Stage 1: source-backed Original V8 demo plus in-browser project reset. */
|
|
2
|
+
const $=id=>document.getElementById(id);
|
|
3
|
+
const PROJECT_FIELDS=['name','industry','layout','palette','description','equipment_csv','connections_csv','signals_csv','requirements'];
|
|
4
|
+
let workspaceGeneration=0;
|
|
5
|
+
|
|
6
|
+
function quote(value){
|
|
7
|
+
const text=String(value??'');
|
|
8
|
+
return /[\n",]/.test(text)?'"'+text.replaceAll('"','""')+'"':text;
|
|
9
|
+
}
|
|
10
|
+
function csv(rows,keys){return [keys.join(','),...rows.map(row=>keys.map(key=>quote(row[key])).join(','))].join('\n');}
|
|
11
|
+
function hasCurrentWork(){
|
|
12
|
+
return PROJECT_FIELDS.some(id=>$(id)?.value.trim())||Boolean($('ai-request')?.value.trim())||
|
|
13
|
+
[...document.querySelectorAll('input[type="file"]')].some(input=>input.files?.length)||
|
|
14
|
+
!$('success')?.classList.contains('hidden')||!$('live-hmi-panel')?.classList.contains('hidden')||
|
|
15
|
+
!$('original-v8-panel')?.classList.contains('hidden')||
|
|
16
|
+
!$('original-r9-panel')?.classList.contains('hidden');
|
|
17
|
+
}
|
|
18
|
+
function confirmed(action){
|
|
19
|
+
if(!hasCurrentWork())return true;
|
|
20
|
+
return window.confirm(action+' will clear the current unsaved Creator form, uploads, AI review receipt, and preview. Previously generated project folders will remain. Continue?');
|
|
21
|
+
}
|
|
22
|
+
function hidePreview(){
|
|
23
|
+
$('demo-original-v8')?.classList.remove('selected');
|
|
24
|
+
$('demo-original-v8')?.setAttribute('aria-pressed','false');
|
|
25
|
+
document.body.removeAttribute('data-active-demo');
|
|
26
|
+
const original=$('original-v8-panel'),originalFrame=$('original-v8-frame');
|
|
27
|
+
original?.classList.add('hidden');
|
|
28
|
+
if(originalFrame)originalFrame.removeAttribute('src');
|
|
29
|
+
const generated=$('live-hmi-panel'),generatedFrame=$('live-hmi-frame');
|
|
30
|
+
generated?.classList.add('hidden');
|
|
31
|
+
if(generatedFrame)generatedFrame.removeAttribute('src');
|
|
32
|
+
$('design-review')?.classList.add('hidden');
|
|
33
|
+
$('success')?.classList.add('hidden');
|
|
34
|
+
for(const id of ['preview-link','studio-test-link','full-review-new-tab'])$(id)?.removeAttribute('href');
|
|
35
|
+
$('stage-eyebrow').textContent='INTERACTIVE WORKSPACE / LAYOUT CONCEPT';
|
|
36
|
+
$('stage-heading').innerHTML='Facility preview <span>✦</span>';
|
|
37
|
+
$('stage-mode').textContent='SCHEMATIC';
|
|
38
|
+
$('stage-empty').hidden=false;$('stage-title').textContent='Waiting for equipment';
|
|
39
|
+
$('stage-count').textContent='0 assets · 0 links';
|
|
40
|
+
$('stage-details').textContent='Select equipment to inspect the concept.';$('stage-error').textContent='';
|
|
41
|
+
}
|
|
42
|
+
function resetWorkspace(action,ask=true){
|
|
43
|
+
if(ask&&!confirmed(action))return false;
|
|
44
|
+
workspaceGeneration++;
|
|
45
|
+
// Listeners revoke closure-held proposals, server receipts, generated-project
|
|
46
|
+
// handles, and document/package drafts before form values are discarded.
|
|
47
|
+
document.dispatchEvent(new CustomEvent('devagent:project-reset',{detail:{action}}));
|
|
48
|
+
for(const id of PROJECT_FIELDS){
|
|
49
|
+
const field=$(id);if(!field)continue;field.value='';
|
|
50
|
+
field.dispatchEvent(new Event('input',{bubbles:true}));
|
|
51
|
+
field.dispatchEvent(new Event('change',{bubbles:true}));
|
|
52
|
+
}
|
|
53
|
+
const request=$('ai-request');if(request){request.value='';request.dispatchEvent(new Event('input',{bubbles:true}));}
|
|
54
|
+
for(const input of document.querySelectorAll('input[type="file"]'))input.value='';
|
|
55
|
+
for(const id of ['customer-doc-consent','engineer-confirm'])if($(id))$(id).checked=false;
|
|
56
|
+
hidePreview();
|
|
57
|
+
$('result').className='status';$('result').textContent='No validation has run yet.';
|
|
58
|
+
$('validate').disabled=false;$('build').disabled=true;
|
|
59
|
+
$('engineer-results').classList.add('hidden');
|
|
60
|
+
$('engineer-approval').classList.add('hidden');
|
|
61
|
+
$('engineer-stage-badge').textContent='WAITING FOR INPUTS';
|
|
62
|
+
$('engineer-validation-state').textContent='NOT RUN';
|
|
63
|
+
$('engineer-equipment-state').textContent='0 supplied';
|
|
64
|
+
$('step-0')?.scrollIntoView({block:'nearest'});
|
|
65
|
+
document.querySelector('[data-step="0"]')?.click();
|
|
66
|
+
document.dispatchEvent(new CustomEvent('devagent:project-reset-complete',{detail:{action}}));
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function resetV8Simulator(){
|
|
71
|
+
try{
|
|
72
|
+
await fetch('/api/original-v8/reset',{method:'POST',headers:{'Content-Type':'application/json','X-DevAgent-Request':'original-v8-simulator-v1'},body:JSON.stringify({confirm:'RESET_SYNTHETIC_DEMO_ONLY'})});
|
|
73
|
+
}catch(_error){/* The operator iframe will surface API unavailability without inventing state. */}
|
|
74
|
+
}
|
|
75
|
+
async function loadOriginalV8(){
|
|
76
|
+
if(!confirmed('Loading the Original V8 demo'))return;
|
|
77
|
+
resetWorkspace('replace-with-original-v8',false);
|
|
78
|
+
const generation=workspaceGeneration;
|
|
79
|
+
$('result').textContent='Loading source-pinned Original V8 topology…';
|
|
80
|
+
try{
|
|
81
|
+
const [response]=await Promise.all([fetch('/topology.json',{cache:'no-store'}),resetV8Simulator()]);
|
|
82
|
+
if(!response.ok)throw Error('ORIGINAL_V8_TOPOLOGY_UNAVAILABLE');
|
|
83
|
+
const topology=await response.json();
|
|
84
|
+
if(generation!==workspaceGeneration)return;
|
|
85
|
+
if(!Array.isArray(topology.sections)||topology.sections.length!==13)throw Error('ORIGINAL_V8_TOPOLOGY_INVALID');
|
|
86
|
+
const ids=topology.sections.map(section=>section.id);
|
|
87
|
+
if(ids.join(',')!=='CV101,CV102,CV103,CV104,CV105,CV106,CV107,CV108,CV109,CV110,CV111,CV112,CV113')throw Error('ORIGINAL_V8_IDENTIFIERS_CHANGED');
|
|
88
|
+
const links=topology.sections.reduce((count,section)=>count+section.downstream.length,0);
|
|
89
|
+
const values={
|
|
90
|
+
name:topology.facility,
|
|
91
|
+
industry:'Warehouse / Distribution',
|
|
92
|
+
layout:'standard_2d_flow',
|
|
93
|
+
palette:'r9_cyan',
|
|
94
|
+
description:'Source-backed DevAgent Distribution Operations HMI V8. Two receiving sources feed induction and gapping, a shared scan/merge, the primary sorter, and outbound destinations. All displayed operating values are synthetic; no PLC connection.',
|
|
95
|
+
equipment_csv:csv(topology.sections.map(section=>({id:section.id,name:section.name,kind:'conveyor',floor:1,zone:section.area})),['id','name','kind','floor','zone']),
|
|
96
|
+
connections_csv:csv(topology.sections.flatMap(section=>section.downstream.map(target=>({from:section.id,to:target}))),['from','to']),
|
|
97
|
+
signals_csv:'id,asset_id,role,datatype,namespace_uri,node_id,unit,polarity',
|
|
98
|
+
requirements:'Preserve the source-backed receiving → induction → sortation → outbound material flow.\nKeep the operator HMI read-only and label every operating value as synthetic / no PLC connection.'
|
|
99
|
+
};
|
|
100
|
+
for(const [id,value] of Object.entries(values)){$(id).value=value;$(id).dispatchEvent(new Event('input',{bubbles:true}));}
|
|
101
|
+
document.body.dataset.activeDemo='original-v8';
|
|
102
|
+
$('demo-original-v8').classList.add('selected');$('demo-original-v8').setAttribute('aria-pressed','true');
|
|
103
|
+
$('original-v8-frame').src='/original-v8/index.html';$('original-v8-panel').classList.remove('hidden');
|
|
104
|
+
$('stage-eyebrow').textContent='ORIGINAL SOURCE / V8 DISTRIBUTION OPERATIONS';
|
|
105
|
+
$('stage-heading').textContent='2D Flow · Distribution Operations';$('stage-mode').textContent='SYNTHETIC';
|
|
106
|
+
$('result').textContent='Original V8 demo loaded from pinned source: 13 conveyors / '+links+' declared links / 0 PLC mappings. Use Scenario controls for the isolated synthetic state engine. Production HOLD.';
|
|
107
|
+
document.dispatchEvent(new CustomEvent('devagent:original-v8-loaded',{detail:{assets:13,links}}));
|
|
108
|
+
$('original-v8-panel').scrollIntoView({behavior:'smooth',block:'nearest'});
|
|
109
|
+
}catch(error){
|
|
110
|
+
if(generation!==workspaceGeneration)return;
|
|
111
|
+
resetWorkspace('original-v8-load-failed',false);
|
|
112
|
+
$('result').className='status hold';$('result').textContent='HOLD: '+String(error?.message||error).slice(0,120)+'. No demo data retained.';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
$('demo-original-v8')?.addEventListener('click',loadOriginalV8);
|
|
117
|
+
for(const id of ['new-project','new-project-card'])$(id)?.addEventListener('click',()=>resetWorkspace('New Project'));
|
|
118
|
+
$('clear-existing')?.addEventListener('click',()=>resetWorkspace('Clear Existing'));
|
|
119
|
+
window.DevAgentProjectReset={reset:resetWorkspace,hasCurrentWork,
|
|
120
|
+
get generation(){return workspaceGeneration;}};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* Creator-only diagnostic overlay for the immutable Original R9 snapshot.
|
|
2
|
+
Values from the local lab are explicitly synthetic. Physical PLC/VFD fields
|
|
3
|
+
remain NOT MAPPED; this module has no write path. */
|
|
4
|
+
const $=id=>document.getElementById(id);
|
|
5
|
+
const stats=document.querySelector('.selected-asset > .asset-stats');
|
|
6
|
+
|
|
7
|
+
function addDiagnostic(label,id){
|
|
8
|
+
const row=document.createElement('div');
|
|
9
|
+
const name=document.createElement('span');
|
|
10
|
+
const value=document.createElement('strong');
|
|
11
|
+
name.textContent=label;value.id=id;value.textContent='NOT MAPPED';
|
|
12
|
+
row.append(name,value);stats?.append(row);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addDiagnostic('MOTOR COMMAND','asset-command');
|
|
16
|
+
addDiagnostic('VFD STATUS','asset-vfd-status');
|
|
17
|
+
addDiagnostic('VFD PARAMETERS','asset-vfd-parameters');
|
|
18
|
+
|
|
19
|
+
function setText(id,value){const node=$(id);if(node&&node.textContent!==value)node.textContent=value;}
|
|
20
|
+
function syncDiagnostics(){
|
|
21
|
+
const concept=$('asset-location')?.textContent.includes('CONCEPT ASSET');
|
|
22
|
+
if(!concept){
|
|
23
|
+
setText('asset-command','NOT MAPPED');
|
|
24
|
+
setText('asset-vfd-status','NOT MAPPED');
|
|
25
|
+
setText('asset-vfd-parameters','NOT MAPPED');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const state=window.devagentLabState;
|
|
29
|
+
const id=$('asset-name')?.textContent||'';
|
|
30
|
+
if(!state?.enabled){
|
|
31
|
+
for(const field of ['asset-command','asset-run','asset-speed','asset-fault','asset-vfd-status','asset-vfd-parameters'])setText(field,'NOT MAPPED');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const running=state.run_state==='RUNNING';
|
|
35
|
+
const selectedFault=Boolean(state.fault&&state.equipment_id===id);
|
|
36
|
+
setText('asset-command',(running?'RUN':'STOP')+' · SYNTHETIC');
|
|
37
|
+
setText('asset-run',(running?'TRUE':'FALSE')+' · SYNTHETIC');
|
|
38
|
+
setText('asset-speed','NOT MAPPED');
|
|
39
|
+
setText('asset-fault',(selectedFault?'ACTIVE':'NONE')+' · SYNTHETIC');
|
|
40
|
+
setText('asset-vfd-status','NOT MAPPED');
|
|
41
|
+
setText('asset-vfd-parameters','NOT MAPPED');
|
|
42
|
+
const badge=$('asset-badge');
|
|
43
|
+
if(badge){
|
|
44
|
+
const label=selectedFault?'FAULT · SYNTHETIC':(running?'RUNNING · SYNTHETIC':'STOPPED · SYNTHETIC');
|
|
45
|
+
if(badge.textContent!==label)badge.textContent=label;
|
|
46
|
+
badge.className='run-badge'+(selectedFault?' fault':running?'':' unknown');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
window.addEventListener('devagent:lab-state',syncDiagnostics);
|
|
51
|
+
const selectionObserver=new MutationObserver(syncDiagnostics);
|
|
52
|
+
for(const id of ['asset-name','asset-location'])if($(id))selectionObserver.observe($(id),{childList:true,subtree:true,characterData:true});
|
|
53
|
+
syncDiagnostics();
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* Local, read-only plant-layout preview. No status colors, no network sources, no OPC/PLC writes. */
|
|
2
|
+
import {planVisualAssembly,sorterBranchPoints} from './flow_geometry.mjs';
|
|
3
|
+
import {buildR9Transport} from './r9_transport.mjs';
|
|
4
|
+
let THREE=null;
|
|
5
|
+
try{THREE=await import('./creator-vendor/three.module.js');if(THREE.REVISION!=='180')THREE=null;}catch(_){THREE=null;}
|
|
6
|
+
const KIND_COLORS={conveyor:'#51afc8',spiral:'#6bb3cc',sorter:'#528fb5',vfd:'#739db7',photoeye:'#91ded4',scanner:'#e8c886',pump:'#6a9bbb',tank:'#6db7ba',valve:'#c8b074',robot:'#a7bdce',motor:'#839caf',other:'#789db0'};
|
|
7
|
+
const colorFor=k=>KIND_COLORS[k]||KIND_COLORS.other;
|
|
8
|
+
export function available3D(){return Boolean(THREE);}
|
|
9
|
+
export function createScene(host,onPick){
|
|
10
|
+
let current=null,renderer=null,scene=null,camera=null,observer=null,raf=0,objects=[],lights=[];
|
|
11
|
+
let view='iso',pointerDown=null,selected='', az=.7,el=.58,dist=28,target=null,active=true;
|
|
12
|
+
const T=THREE;
|
|
13
|
+
function clear(){cancelAnimationFrame(raf);if(observer){observer.disconnect();observer=null;}if(renderer){renderer.dispose();renderer.domElement.remove();renderer=null;}for(const c of host.querySelectorAll('svg'))c.remove();objects=[];scene=null;camera=null;}
|
|
14
|
+
function paint(data){current=data;clear();if(!data?.assets?.length)return;if(!T){svg(data);return;}init(data);}
|
|
15
|
+
function svg(data){
|
|
16
|
+
for(const prior of host.querySelectorAll('svg'))prior.remove();
|
|
17
|
+
const ns='http://www.w3.org/2000/svg',S=(name,atts={},parent=null)=>{const e=document.createElementNS(ns,name);for(const [k,v] of Object.entries(atts))e.setAttribute(k,String(v));(parent||host).append(e);return e;};
|
|
18
|
+
const svg=S('svg',{viewBox:'0 0 740 350','aria-label':'Conceptual equipment layout, schematic fallback',role:'img'});
|
|
19
|
+
const defs=S('defs',{},svg),filter=S('filter',{id:'shadow-soft',x:'-35%',y:'-50%',width:'190%',height:'220%'},defs);S('feGaussianBlur',{stdDeviation:'9'},filter);
|
|
20
|
+
const xs=data.assets.map(a=>a.position.x),zs=data.assets.map(a=>a.position.z);
|
|
21
|
+
const lo=Math.min(...xs),hi=Math.max(...xs),zmin=Math.min(...zs),zmax=Math.max(...zs);
|
|
22
|
+
const dx=Math.max(1,hi-lo),dz=Math.max(1,zmax-zmin);
|
|
23
|
+
const point=a=>{const x=(a.position.x-lo)/dx,z=(a.position.z-zmin)/dz;return view==='top'?[100+x*530,80+z*170]:[98+x*525-z*43,164-z*102-x*42];};
|
|
24
|
+
const find=new Map(data.assets.map(a=>[a.id,a]));
|
|
25
|
+
for(const edge of data.connections){const a=find.get(edge.from),b=find.get(edge.to);if(!a||!b)continue;const p=point(a),q=point(b);S('path',{d:`M${p[0]},${p[1]} L${q[0]},${q[1]}`,stroke:'#68b6c7','stroke-width':3,'stroke-dasharray':'6 6',opacity:.72,fill:'none'},svg);}
|
|
26
|
+
for(const asset of data.assets){const [x,y]=point(asset),main=colorFor(asset.kind),g=S('g',{tabindex:'0',role:'button','aria-label':`Select ${asset.id}`,style:'cursor:pointer'},svg);
|
|
27
|
+
S('ellipse',{cx:x+2,cy:y+30,rx:52,ry:16,fill:'#020a11',opacity:.55,filter:'url(#shadow-soft)'},g);
|
|
28
|
+
if(['conveyor','sorter','spiral'].includes(asset.kind)){
|
|
29
|
+
S('path',{d:`M${x-44} ${y+9}l28 -18l74 0l-28 18Z`,fill:main,stroke:'#afd7e4','stroke-width':1},g);
|
|
30
|
+
S('path',{d:`M${x-44} ${y+9}l0 14l74 0l0 -14Z`,fill:'#24485a',stroke:'#638ba0','stroke-width':1},g);
|
|
31
|
+
for(let i=0;i<6;i++)S('path',{d:`M${x-32+i*12} ${y+7}l28 -17`,stroke:'#0e3b4d','stroke-width':2},g);
|
|
32
|
+
}else if(asset.kind==='photoeye'){
|
|
33
|
+
S('circle',{cx:x,cy:y,r:11,fill:main,stroke:'#d4fff7','stroke-width':2},g);
|
|
34
|
+
S('path',{d:`M${x} ${y+11}v24`,stroke:'#7a99ae','stroke-width':4},g);
|
|
35
|
+
}else{
|
|
36
|
+
S('path',{d:`M${x-20} ${y-13}l22 -12l36 0l-22 12Z`,fill:'#86aab9',stroke:'#bfe5ec','stroke-width':1},g);
|
|
37
|
+
S('path',{d:`M${x-20} ${y-13}l0 35l36 0l0 -35Z`,fill:main,stroke:'#76a6b6','stroke-width':1},g);
|
|
38
|
+
S('path',{d:`M${x+16} ${y-13}l22 -12l0 36l-22 11Z`,fill:'#225064',stroke:'#6c9cae','stroke-width':1},g);
|
|
39
|
+
}
|
|
40
|
+
const tag=S('g',{},g),label=asset.id.slice(0,20),width=Math.max(57,label.length*8+18);S('rect',{x:x-width/2,y:y+38,width,height:25,rx:6,fill:selected===asset.id?'#275f76':'#071c2b',stroke:selected===asset.id?'#9af5ec':'#48778c'},tag);const tx=S('text',{x,y:y+55,fill:'#e6f6fe','font-size':12,'font-family':'system-ui','font-weight':750,'text-anchor':'middle'},tag);tx.textContent=label;
|
|
41
|
+
g.addEventListener('click',()=>{selected=asset.id;onPick(asset);paint(current);});g.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();g.dispatchEvent(new Event('click'));}});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function init(data){
|
|
45
|
+
const palette=0x58c6d9;scene=new T.Scene();scene.fog=new T.FogExp2(0x0b2435,.009);
|
|
46
|
+
camera=new T.PerspectiveCamera(44,1,.1,1500);target=new T.Vector3();
|
|
47
|
+
renderer=new T.WebGLRenderer({antialias:true,alpha:true,powerPreference:'default'});renderer.setPixelRatio(Math.min(devicePixelRatio||1,1.6));renderer.setClearColor(0x000000,0);renderer.outputColorSpace=T.SRGBColorSpace;renderer.toneMapping=T.ACESFilmicToneMapping;renderer.toneMappingExposure=1.32;
|
|
48
|
+
host.append(renderer.domElement);renderer.domElement.setAttribute('aria-label','Interactive conceptual industrial layout. Drag to orbit, scroll to zoom, click to inspect equipment.');renderer.domElement.setAttribute('tabindex','0');renderer.domElement.style.cursor='grab';
|
|
49
|
+
scene.add(new T.HemisphereLight(0xd0ecff,0x18304a,2.4));const sun=new T.DirectionalLight(0xf0faff,3.0);sun.position.set(-20,36,18);scene.add(sun);const rim=new T.DirectionalLight(0x59bbd7,1.55);rim.position.set(14,12,-18);scene.add(rim);
|
|
50
|
+
const base=new T.MeshStandardMaterial({color:0x386b83,metalness:.5,roughness:.45}),trim=new T.MeshStandardMaterial({color:0x99b7c7,metalness:.8,roughness:.29}),belt=new T.MeshStandardMaterial({color:0x193949,metalness:.33,roughness:.65}),roller=new T.MeshStandardMaterial({color:0x638e9e,metalness:.72,roughness:.3});
|
|
51
|
+
const byId=new Map();const group=new T.Group();scene.add(group);const boxGeometry=new T.BoxGeometry(1,1,1);
|
|
52
|
+
// Shared R9 deterministic assembly. Only explicitly declared material edges
|
|
53
|
+
// become conveyor-transfer proposals; every gap stays CONCEPTUAL, not as-built.
|
|
54
|
+
const assembly=planVisualAssembly({equipment:data.assets,connections:data.connections});
|
|
55
|
+
const transportMaterials={frame:base,belt,rail:trim,roller};
|
|
56
|
+
function conveyorCurve(points){return new T.CatmullRomCurve3(points.map(p=>new T.Vector3(p.x,p.y,p.z)),false,'centripetal',.34);}
|
|
57
|
+
|
|
58
|
+
function box(g,mat,x,y,z,sx,sy,sz){const o=new T.Mesh(boxGeometry,mat);o.scale.set(Math.max(.04,sx),Math.max(.04,sy),Math.max(.04,sz));o.position.set(x,y,z);g.add(o);return o;}
|
|
59
|
+
function label(id){const c=document.createElement('canvas');c.width=300;c.height=70;const ctx=c.getContext('2d');ctx.fillStyle='#091c2b';ctx.fillRect(2,2,296,65);ctx.strokeStyle='#6399ae';ctx.lineWidth=2;ctx.strokeRect(2,2,296,65);ctx.fillStyle='#e9f9ff';ctx.font='bold 30px system-ui';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(id.slice(0,16),150,34);const texture=new T.CanvasTexture(c);texture.colorSpace=T.SRGBColorSpace;const sprite=new T.Sprite(new T.SpriteMaterial({map:texture,transparent:true,depthWrite:false}));sprite.scale.set(2.7,.65,1);return sprite;}
|
|
60
|
+
for(const a of data.assets){const root=new T.Group();root.position.set(a.position.x,a.position.y,a.position.z);group.add(root);byId.set(a.id,root);const station=assembly.stations[a.id];if(station?.direction)root.rotation.y=-Math.atan2(station.direction.z,station.direction.x);const mat=new T.MeshStandardMaterial({color:new T.Color(colorFor(a.kind)),roughness:.44,metalness:.54}),d=a.dimensions;
|
|
61
|
+
if(['conveyor','sorter','spiral'].includes(a.kind)){
|
|
62
|
+
// Same true centerline/ribbon as generated R9 Operator HMI. Station
|
|
63
|
+
// ports are exact input/output points for every planned transfer.
|
|
64
|
+
if(a.kind==='spiral'&&station?.helix){
|
|
65
|
+
const helix=station.helix.map(q=>new T.Vector3(q.x-a.position.x,q.y-a.position.y,q.z-a.position.z));
|
|
66
|
+
buildR9Transport(T,root,new T.CatmullRomCurve3(helix,false,'centripetal',.34),
|
|
67
|
+
{width:.84,materials:transportMaterials,conceptual:true,rollers:false});
|
|
68
|
+
}else if(station?.direction){
|
|
69
|
+
const L=station.length,W=Math.max(.55,Math.min(2,d.z*.79));
|
|
70
|
+
const local=new T.LineCurve3(new T.Vector3(-L/2,1.1,0),new T.Vector3(L/2,1.1,0));
|
|
71
|
+
buildR9Transport(T,root,local,{width:W,materials:transportMaterials,conceptual:true});
|
|
72
|
+
for(const x of [-L*.37,L*.37])for(const z of [-W*.38,W*.38])box(root,base,x,.53,z,.10,1.04,.10);
|
|
73
|
+
}
|
|
74
|
+
}else if(a.kind==='photoeye'){
|
|
75
|
+
box(root,trim,0,.6,0,.10,1.2,.12);box(root,mat,0,1.26,0,.28,.24,.38);box(root,new T.MeshStandardMaterial({color:0xaaf9e8,emissive:0x1c6c67,emissiveIntensity:.4}),.17,1.26,0,.06,.12,.17);
|
|
76
|
+
}else if(a.kind==='scanner'){
|
|
77
|
+
box(root,trim,0,.7,0,.17,1.4,.17);box(root,mat,0,1.4,0,.6,.38,.38);box(root,trim,.24,1.4,0,.08,.23,.24);
|
|
78
|
+
}else if(a.kind==='vfd'||a.kind==='motor'){
|
|
79
|
+
box(root,mat,0,.7,0,.83,1.35,.64);box(root,trim,0,1.0,.335,.52,.26,.04);box(root,belt,0,.43,.34,.52,.2,.05);
|
|
80
|
+
}else if(a.kind==='tank'){
|
|
81
|
+
const o=new T.Mesh(new T.CylinderGeometry(.75,.75,1.85,24),mat);o.position.y=.93;root.add(o);box(root,trim,0,1.93,0,1.4,.06,1.4);
|
|
82
|
+
}else{
|
|
83
|
+
box(root,mat,0,.62,0,1.15,1.2,.9);box(root,trim,0,1.25,0,.95,.08,.7);
|
|
84
|
+
}
|
|
85
|
+
root.children.filter(c=>c.isMesh).forEach(c=>{c.userData.asset=a;objects.push(c);});const text=label(a.id);text.position.set(0,Math.max(2.1,d.y+1.1),0);root.add(text);
|
|
86
|
+
}
|
|
87
|
+
// Draw continuous, material-handling transition surfaces between exact
|
|
88
|
+
// ports. Logical docks and unverified non-material links remain dashed.
|
|
89
|
+
for(const [id,st] of Object.entries(assembly.stations)){
|
|
90
|
+
if(st.kind!=='sorter')continue;
|
|
91
|
+
for(const branch of st.branches||[]){
|
|
92
|
+
const pts=sorterBranchPoints(st,branch.to);if(!pts)continue;
|
|
93
|
+
const shape=buildR9Transport(T,scene,conveyorCurve(pts),
|
|
94
|
+
{width:.70,materials:transportMaterials,conceptual:true});
|
|
95
|
+
shape.userData.from=id;shape.userData.to=branch.to;shape.userData.visualOnly=true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for(const route of assembly.routes){
|
|
99
|
+
const a=byId.get(route.from),b=byId.get(route.to);if(!a||!b)continue;
|
|
100
|
+
if(route.kind==='conceptual_conveyor_transition'||route.kind==='conceptual_spiral_transfer'){
|
|
101
|
+
const pts=route.points;
|
|
102
|
+
if(pts.length<2)continue;
|
|
103
|
+
const curve=conveyorCurve(pts);
|
|
104
|
+
if(curve.getLength()>.12){const shape=buildR9Transport(T,scene,curve,{width:.84,materials:transportMaterials,conceptual:true});
|
|
105
|
+
shape.userData.from=route.from;shape.userData.to=route.to;
|
|
106
|
+
shape.userData.geometry_status='CONCEPTUAL_CONNECTION_NEEDS_APPROVAL';}
|
|
107
|
+
}else{
|
|
108
|
+
const points=route.points.map(q=>new T.Vector3(q.x,q.y,q.z));
|
|
109
|
+
const line=new T.Line(new T.BufferGeometry().setFromPoints(points),new T.LineDashedMaterial({color:palette,dashSize:.27,gapSize:.16,opacity:.75,transparent:true}));
|
|
110
|
+
line.computeLineDistances();scene.add(line);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const bounds=new T.Box3();for(const a of data.assets){const p=byId.get(a.id).position;bounds.expandByPoint(p.clone().add(new T.Vector3(1.5,2,1.5)));bounds.expandByPoint(p.clone().add(new T.Vector3(-1.5,0,-1.5)));}
|
|
114
|
+
const sz=bounds.getSize(new T.Vector3());target.copy(bounds.getCenter(new T.Vector3()));dist=Math.max(12,Math.max(sz.x,sz.z)*1.8+7);dist=Math.min(dist,180);
|
|
115
|
+
const grid=new T.GridHelper(Math.max(28,Math.max(sz.x,sz.z)+16),Math.min(38,Math.max(12,Math.floor(Math.max(sz.x,sz.z)+12))),0x3e6577,0x204458);grid.position.set(target.x,-.11,target.z);scene.add(grid);
|
|
116
|
+
function cameraPosition(){camera.position.set(target.x+dist*Math.cos(el)*Math.sin(az),target.y+dist*Math.sin(el),target.z+dist*Math.cos(el)*Math.cos(az));camera.lookAt(target);}
|
|
117
|
+
function resize(){if(!renderer)return;const r=host.getBoundingClientRect();renderer.setSize(Math.max(1,r.width),Math.max(1,r.height),false);camera.aspect=Math.max(1,r.width)/Math.max(1,r.height);camera.updateProjectionMatrix();cameraPosition();}
|
|
118
|
+
observer=new ResizeObserver(resize);observer.observe(host);resize();
|
|
119
|
+
const ray=new T.Raycaster(),mouse=new T.Vector2();renderer.domElement.addEventListener('pointerdown',ev=>{if(ev.button!==0)return;pointerDown={x:ev.clientX,y:ev.clientY,px:ev.clientX,py:ev.clientY};renderer.domElement.setPointerCapture(ev.pointerId);});
|
|
120
|
+
renderer.domElement.addEventListener('pointermove',ev=>{if(!pointerDown)return;const dx=ev.clientX-pointerDown.px,dy=ev.clientY-pointerDown.py;pointerDown.px=ev.clientX;pointerDown.py=ev.clientY;if(Math.hypot(ev.clientX-pointerDown.x,ev.clientY-pointerDown.y)>5){az-=dx*.007;el=Math.max(.12,Math.min(1.5,el+dy*.007));cameraPosition();}});
|
|
121
|
+
renderer.domElement.addEventListener('pointerup',ev=>{if(!pointerDown)return;const dragged=Math.hypot(ev.clientX-pointerDown.x,ev.clientY-pointerDown.y)>5;pointerDown=null;if(dragged)return;const rect=renderer.domElement.getBoundingClientRect();mouse.set(((ev.clientX-rect.left)/rect.width)*2-1,-((ev.clientY-rect.top)/rect.height)*2+1);ray.setFromCamera(mouse,camera);const hit=ray.intersectObjects(objects,false).find(x=>x.object.userData.asset);if(hit){selected=hit.object.userData.asset.id;onPick(hit.object.userData.asset);}});
|
|
122
|
+
renderer.domElement.addEventListener('pointercancel',()=>pointerDown=null);
|
|
123
|
+
renderer.domElement.addEventListener('wheel',ev=>{ev.preventDefault();dist=Math.max(5,Math.min(280,dist*(ev.deltaY>0?1.1:.9)));cameraPosition();},{passive:false});
|
|
124
|
+
function frame(){if(!renderer)return;renderer.render(scene,camera);raf=requestAnimationFrame(frame);}frame();
|
|
125
|
+
}
|
|
126
|
+
function setView(mode){view=mode;if(!current)return;if(!T){svg(current);return;}if(!camera)return;if(mode==='top'){az=0;el=1.53;}else if(mode==='side'){az=Math.PI/2;el=.22;}else{az=.7;el=.58;}const coords=current.assets.map(a=>a.position);const xs=coords.map(p=>p.x),zs=coords.map(p=>p.z);dist=Math.max(12,Math.max(Math.max(...xs)-Math.min(...xs),Math.max(...zs)-Math.min(...zs))*1.8+7);camera.position.set(target.x+dist*Math.cos(el)*Math.sin(az),target.y+dist*Math.sin(el),target.z+dist*Math.cos(el)*Math.cos(az));camera.lookAt(target);}
|
|
127
|
+
return {paint,setView,dispose:()=>{active=false;clear();},is3d:Boolean(T)};
|
|
128
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {createScene,available3D} from './creator_scene.mjs';
|
|
2
|
+
import {makeCreator2D} from './creator_2d.mjs';
|
|
3
|
+
const $=id=>document.getElementById(id);
|
|
4
|
+
const stage=$('preview-stage');
|
|
5
|
+
const stageEmpty=$('stage-empty');
|
|
6
|
+
let view=null,sequence=0,sceneData=null,timer=null;
|
|
7
|
+
const stageCanvas=document.createElement('div');stageCanvas.id='creator-geometry';stageCanvas.style.cssText='position:absolute;inset:0;z-index:1;overflow:hidden';
|
|
8
|
+
stage.insertBefore(stageCanvas,stageEmpty);
|
|
9
|
+
view=createScene(stageCanvas,asset=>{
|
|
10
|
+
$('stage-title').textContent=asset.id+' · '+asset.name;
|
|
11
|
+
$('stage-details').textContent='Declared equipment: '+asset.kind.toUpperCase()+' · Floor '+asset.floor+' · Zone '+(asset.zone||'Unspecified')+'. Source: equipment CSV. Geometry: '+(asset.geometry_status==='CONCEPTUAL_AUTO_LAYOUT'?'AUTO-LAYOUT — NOT AS BUILT':'CUSTOMER-SUPPLIED — NOT VERIFIED')+'. Live status: NOT CONNECTED.';
|
|
12
|
+
});
|
|
13
|
+
const flow2d=makeCreator2D(stage,asset=>{
|
|
14
|
+
$('stage-title').textContent=asset.id+' · '+asset.name;
|
|
15
|
+
$('stage-details').textContent='Declared '+asset.kind+' · Floor '+(asset.floor||1)+' · Zone '+(asset.zone||'Unspecified')+'. Source: customer equipment. Source quality: NOT VERIFIED.';
|
|
16
|
+
});
|
|
17
|
+
function is2D(){return $('layout').value==='standard_2d_flow';}
|
|
18
|
+
let lastIs2D=null;
|
|
19
|
+
function displayMode(){
|
|
20
|
+
const two=is2D();flow2d.activate(two);stageCanvas.style.display=two?'none':'block';
|
|
21
|
+
for(const id of ['stage-iso','stage-top','stage-side'])$(id).style.display=two?'none':'';
|
|
22
|
+
$('stage-mode').textContent=two?'STANDARD 2D':'';
|
|
23
|
+
$('stage-mode').style.display=two?'inline-flex':'none';
|
|
24
|
+
if(lastIs2D===two)return;lastIs2D=two;
|
|
25
|
+
if(sceneData){if(two){view.paint(null);flow2d.paint(sceneData);}else{flow2d.paint(null);view.paint(sceneData);}}
|
|
26
|
+
}
|
|
27
|
+
displayMode();
|
|
28
|
+
$('stage-iso').addEventListener('click',()=>view.setView('iso'));
|
|
29
|
+
$('stage-top').addEventListener('click',()=>view.setView('top'));
|
|
30
|
+
$('stage-side').addEventListener('click',()=>view.setView('side'));
|
|
31
|
+
function setReadiness(){
|
|
32
|
+
const eq=$('equipment_csv').value.trim().split(/\r?\n/).slice(1).filter(Boolean).length;
|
|
33
|
+
const sig=$('signals_csv').value.trim().split(/\r?\n/).slice(1).filter(Boolean).length;
|
|
34
|
+
const req=$('requirements').value.split(/\r?\n/).filter(l=>l.trim()).length;
|
|
35
|
+
$('ready-equipment').textContent=eq;
|
|
36
|
+
$('ready-signal').textContent=sig;
|
|
37
|
+
$('ready-check').textContent='HOLD';
|
|
38
|
+
$('ready-message').textContent=eq?(sig?`Equipment: ${eq} · signal rows: ${sig} · requirements: ${req}. All OPC mappings remain proposed until source verification.`:`Equipment identified (${eq}). Ready for a conceptual layout; signal-dependent status will remain UNKNOWN / NOT MAPPED.`):'Enter equipment IDs or import customer files. DevAgent does not invent equipment or signal sources.';
|
|
39
|
+
}
|
|
40
|
+
async function updateScene(){
|
|
41
|
+
const seq=++sequence;
|
|
42
|
+
setReadiness();
|
|
43
|
+
if(!$('equipment_csv').value.trim()&&!$('name').value.trim()){
|
|
44
|
+
stageEmpty.hidden=false;view.paint(null);flow2d.paint(null);$('stage-title').textContent='Waiting for equipment';$('stage-count').textContent='0 assets · 0 links';$('stage-error').textContent='';sceneData=null;return;
|
|
45
|
+
}
|
|
46
|
+
try{
|
|
47
|
+
const response=await fetch('/api/creator-scene',{method:'POST',headers:{'Content-Type':'application/json','X-DevAgent-Request':'concept-scene-v1'},body:JSON.stringify({name:$('name').value,equipment_csv:$('equipment_csv').value,connections_csv:$('connections_csv').value})});
|
|
48
|
+
const body=await response.json();if(seq!==sequence)return;
|
|
49
|
+
if(!response.ok){stageEmpty.hidden=false;view.paint(null);flow2d.paint(null);sceneData=null;$('stage-title').textContent='Preview needs valid equipment';$('stage-count').textContent='0 preview assets';$('stage-error').textContent=(body.reason||'Please check equipment CSV').replaceAll('_',' ');return;}
|
|
50
|
+
sceneData=body;stageEmpty.hidden=true;if(is2D()){view.paint(null);flow2d.paint(body);}else{flow2d.paint(null);view.paint(body);}
|
|
51
|
+
$('stage-title').textContent='Equipment geometry · conceptual';$('stage-count').textContent=body.assets.length+' assets · '+body.connections.length+' links';
|
|
52
|
+
$('stage-details').textContent='Click a device to inspect its declaration. These 3D positions are conceptual until a customer drawing confirms dimensions and physical locations.';
|
|
53
|
+
$('stage-error').textContent='';
|
|
54
|
+
}catch(err){if(seq!==sequence)return;stageEmpty.hidden=false;view.paint(null);$('stage-error').textContent='Preview unavailable: '+String(err?.message||err).slice(0,100);}
|
|
55
|
+
}
|
|
56
|
+
function schedule(){setReadiness();clearTimeout(timer);timer=setTimeout(updateScene,220);}
|
|
57
|
+
document.addEventListener('devagent:changed',()=>{displayMode();schedule();});
|
|
58
|
+
for(const item of document.querySelectorAll('[data-load]'))item.addEventListener('change',()=>setTimeout(schedule,70));
|
|
59
|
+
window.addEventListener('pagehide',()=>{view.dispose();flow2d.dispose();},{once:true});
|
|
60
|
+
setReadiness();schedule();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* V0.7.7.2: customer document intake. Nothing is saved server-side or auto-approved. */
|
|
2
|
+
(()=>{'use strict';
|
|
3
|
+
const $=id=>document.getElementById(id);
|
|
4
|
+
const files=$('customer-source-files'),list=$('customer-source-list'),status=$('customer-source-status');
|
|
5
|
+
const preview=$('customer-source-preview'),draftBox=$('customer-doc-draft');
|
|
6
|
+
let results=[], aiDraft=null, sequence=0;
|
|
7
|
+
function docRow(r){
|
|
8
|
+
const div=document.createElement('div');div.className='customer-source-item';
|
|
9
|
+
const name=document.createElement('strong');name.textContent=r.name+' · '+r.sha256.slice(0,10)+'…';
|
|
10
|
+
const note=document.createElement('div');note.className='source-state';
|
|
11
|
+
note.textContent=(r.status||'HOLD')+(r.candidate_field?' · recognized '+r.candidate_field:'');
|
|
12
|
+
div.append(name,note);return div;
|
|
13
|
+
}
|
|
14
|
+
function update(){
|
|
15
|
+
list.replaceChildren(...results.map(docRow));
|
|
16
|
+
const valid=results.filter(x=>x.status==='EXTRACTED_FOR_REVIEW');
|
|
17
|
+
preview.textContent=valid.map(x=>`SOURCE: ${x.name}\nSHA-256: ${x.sha256}\n${x.text.slice(0,2400)}`).join('\n\n')||'No machine-readable source text. Visual/CAD review may be required.';
|
|
18
|
+
$('customer-doc-structured').disabled=!results.some(x=>x.candidate_field&&x.candidate_csv);
|
|
19
|
+
$('customer-doc-ai').disabled=!valid.length;
|
|
20
|
+
}
|
|
21
|
+
files.addEventListener('change',async()=>{
|
|
22
|
+
const selected=[...(files.files||[])];
|
|
23
|
+
if(selected.length===1&&selected[0].name.toLowerCase().endsWith('.zip')){
|
|
24
|
+
status.textContent='ZIP detected. Opening Project Package importer…';
|
|
25
|
+
document.dispatchEvent(new CustomEvent('devagent:package-selected',{detail:{file:selected[0]}}));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const mine=++sequence;results=[];aiDraft=null;draftBox.classList.add('hidden');$('customer-doc-apply').hidden=true;
|
|
29
|
+
const selection=[...(files.files||[])];
|
|
30
|
+
if(selection.length>8){status.textContent='HOLD: Maximum 8 source documents per review batch.';return;}
|
|
31
|
+
if(!selection.length){status.textContent='No documents loaded.';update();return;}
|
|
32
|
+
status.textContent='Reading selected files locally. No AI request is sent yet…';update();
|
|
33
|
+
for(const file of selection){
|
|
34
|
+
if(mine!==sequence)return;
|
|
35
|
+
if(file.size>5_000_000||!file.size){results.push({name:file.name,status:'HOLD_FILE_SIZE',sha256:'0000000000'});continue;}
|
|
36
|
+
try{
|
|
37
|
+
const reply=await fetch('/api/customer-documents/inspect',{method:'POST',
|
|
38
|
+
headers:{'Content-Type':'application/octet-stream','X-DevAgent-Request':'customer-doc-intake-v1',
|
|
39
|
+
'X-DevAgent-Filename':encodeURIComponent(file.name)},body:file});
|
|
40
|
+
const parsed=await reply.json();
|
|
41
|
+
if(mine!==sequence)return;
|
|
42
|
+
results.push(reply.ok?parsed:{name:file.name,status:'HOLD '+(parsed.reason||reply.status),sha256:'0000000000'});
|
|
43
|
+
}catch(e){results.push({name:file.name,status:'HOLD_NETWORK_OR_PARSER',sha256:'0000000000'});}
|
|
44
|
+
update();
|
|
45
|
+
}
|
|
46
|
+
status.textContent=`${results.length} uploaded · ${results.filter(x=>x.status==='EXTRACTED_FOR_REVIEW').length} text sources · ${results.filter(x=>x.candidate_field).length} recognized structured tables. All require review.`;
|
|
47
|
+
});
|
|
48
|
+
document.addEventListener('devagent:project-reset',()=>{
|
|
49
|
+
sequence++;results=[];aiDraft=null;files.value='';list.replaceChildren();status.textContent='No files selected';
|
|
50
|
+
preview.textContent='No extracted text';draftBox.textContent='';draftBox.classList.add('hidden');
|
|
51
|
+
$('customer-doc-apply').hidden=true;$('customer-doc-structured').disabled=true;$('customer-doc-ai').disabled=true;
|
|
52
|
+
});
|
|
53
|
+
function applyToEmpty(data){
|
|
54
|
+
let written=[],blocked=[];
|
|
55
|
+
for(const [field,value] of Object.entries(data)){
|
|
56
|
+
if(!value||!$(field))continue;
|
|
57
|
+
if($(field).value.trim()){blocked.push(field+' (existing data preserved)');continue;}
|
|
58
|
+
$(field).value=value;$(field).dispatchEvent(new Event('input',{bubbles:true}));written.push(field);
|
|
59
|
+
}
|
|
60
|
+
return {written,blocked};
|
|
61
|
+
}
|
|
62
|
+
$('customer-doc-structured').addEventListener('click',()=>{
|
|
63
|
+
const accepted={};let conflicts=[];
|
|
64
|
+
for(const r of results){if(!r.candidate_field||!r.candidate_csv)continue;
|
|
65
|
+
if(accepted[r.candidate_field]){conflicts.push(r.name);continue;}
|
|
66
|
+
accepted[r.candidate_field]=r.candidate_csv;
|
|
67
|
+
}
|
|
68
|
+
const r=applyToEmpty(accepted);
|
|
69
|
+
status.textContent='Exact-schema source tables reviewed for import. Applied: '+(r.written.join(', ')||'none')+'. Not overwritten: '+r.blocked.join(', ')+(conflicts.length?'. Multiple tables need manual merge: '+conflicts.join(', '):'')+'. AI + Engineering review still REQUIRED.';
|
|
70
|
+
});
|
|
71
|
+
$('customer-doc-ai').addEventListener('click',async()=>{
|
|
72
|
+
if(!window.DevAgentAIFlow?.requireConnection())return;
|
|
73
|
+
if(!$('customer-doc-consent').checked){status.textContent='HOLD: Tick the explicit source-text AI consent checkbox first.';return;}
|
|
74
|
+
const docs=results.filter(x=>x.status==='EXTRACTED_FOR_REVIEW').map(x=>({name:x.name,sha256:x.sha256,text:x.text.slice(0,6000)}));
|
|
75
|
+
if(!docs.length)return;
|
|
76
|
+
aiDraft=null;$('customer-doc-apply').hidden=true;draftBox.classList.remove('hidden');
|
|
77
|
+
draftBox.textContent='AI is reading source excerpts. Draft will require your review and independent validation…';
|
|
78
|
+
const button=$('customer-doc-ai');button.disabled=true;
|
|
79
|
+
const ctl=new AbortController();const timer=setTimeout(()=>ctl.abort(),115000);
|
|
80
|
+
try{
|
|
81
|
+
const connection={provider:$('ai-provider').value,model:$('ai-model').value.trim(),
|
|
82
|
+
api_key:$('ai-key').value,endpoint:$('ai-endpoint').value.trim()};
|
|
83
|
+
const response=await fetch('/api/customer-documents/suggest',{method:'POST',
|
|
84
|
+
headers:{'Content-Type':'application/json','X-DevAgent-Request':'customer-doc-intake-v1'},
|
|
85
|
+
body:JSON.stringify({connection,documents:docs,confirm:'CUSTOMER_REQUESTED_AI_DOCUMENT_EXTRACTION'}),signal:ctl.signal});
|
|
86
|
+
const result=await response.json();
|
|
87
|
+
if(!response.ok){draftBox.textContent='HOLD: '+(result.reason||response.status)+'\nNothing was changed.';return;}
|
|
88
|
+
aiDraft=result;
|
|
89
|
+
draftBox.textContent=`Source-grounded proposed draft (UNVERIFIED): ${result.assets} equipment, ${result.links} explicit connections.\n`+
|
|
90
|
+
`Signals: NOT PROPOSED (requires source verification).\nQuestions:\n`+(result.questions||[]).map(x=>'• '+x).join('\n')+
|
|
91
|
+
'\n\nReview source excerpts above before applying. Existing fields will NOT be overwritten.';
|
|
92
|
+
$('customer-doc-apply').hidden=false;
|
|
93
|
+
}catch(e){draftBox.textContent='HOLD: AI timeout/network failure. No source data changed.';}
|
|
94
|
+
finally{clearTimeout(timer);button.disabled=false;}
|
|
95
|
+
});
|
|
96
|
+
$('customer-doc-apply').addEventListener('click',()=>{
|
|
97
|
+
if(!aiDraft)return;
|
|
98
|
+
const r=applyToEmpty({equipment_csv:aiDraft.equipment_csv,connections_csv:aiDraft.connections_csv,requirements:aiDraft.requirements});
|
|
99
|
+
// Optional AI context passes only on the next explicit user click of the engineering button.
|
|
100
|
+
const aiRequest=$('ai-request');
|
|
101
|
+
if(aiRequest&&!aiRequest.value.trim()){
|
|
102
|
+
const context=results.filter(x=>x.status==='EXTRACTED_FOR_REVIEW').map(x=>`File ${x.name} (${x.sha256.slice(0,12)}):\n${x.text.slice(0,900)}`).join('\n\n');
|
|
103
|
+
aiRequest.value=('Analyze the customer document excerpts below as untrusted sources. Do not invent PLC/OPC mappings or material connections. Source completeness and physical signal verification remain HOLD.\n'+context).slice(0,5700);
|
|
104
|
+
}
|
|
105
|
+
draftBox.textContent+='\n\nDraft applied to: '+(r.written.join(', ')||'none')+'. Existing fields preserved: '+(r.blocked.join(', ')||'none')+'.\nNEXT: Review the four Creator steps, then run AI Engineer → Design → Verify; no production approval.';
|
|
106
|
+
$('customer-doc-apply').hidden=true;
|
|
107
|
+
$('customer-doc-structured').disabled=false;
|
|
108
|
+
});
|
|
109
|
+
update();
|
|
110
|
+
})();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Generated command INTENTS are NOT wired commands. No local or PLC writes. */
|
|
2
|
+
export function showDeviceControls(plan, asset, mount) {
|
|
3
|
+
mount.replaceChildren();
|
|
4
|
+
const entry=plan?.devices?.find(item=>item.asset_id===asset?.id);
|
|
5
|
+
if(!entry || !Array.isArray(entry.intents) || entry.enabled!==false ||
|
|
6
|
+
plan.runtime_command_authority!=='NONE' || plan.plc_opcua_writes!=='DISABLED') {
|
|
7
|
+
const error=document.createElement('p');error.className='control-readiness';
|
|
8
|
+
error.textContent='HOLD · Control authorization contract invalid';mount.append(error);return;
|
|
9
|
+
}
|
|
10
|
+
const title=document.createElement('div');title.className='control-readiness';
|
|
11
|
+
title.textContent=entry.intents.length?'DEVICE CONTROL · LOCKED':'MONITORING DEVICE · NO COMMANDS';
|
|
12
|
+
mount.append(title);
|
|
13
|
+
const bar=document.createElement('div');bar.className='device-command-grid';
|
|
14
|
+
for(const intent of entry.intents){
|
|
15
|
+
const button=document.createElement('button');button.type='button';button.textContent=intent;
|
|
16
|
+
button.className='device-command-intent';button.disabled=true;button.setAttribute('aria-disabled','true');
|
|
17
|
+
button.title='Command unavailable: no verified mapping, RBAC, interlock, acknowledgement or FAT evidence';
|
|
18
|
+
button.setAttribute('aria-label',`${asset.id} ${intent} unavailable — command authority not verified`);
|
|
19
|
+
bar.append(button);
|
|
20
|
+
}
|
|
21
|
+
mount.append(bar);
|
|
22
|
+
const note=document.createElement('p');note.className='control-lock-note';
|
|
23
|
+
note.textContent=entry.intents.length
|
|
24
|
+
?'Read-only design. Commands require approved device mapping, role access, interlocks, feedback and FAT. STOP is not an emergency stop.'
|
|
25
|
+
:'Sensor/asset is read-only in this design. No control action is inferred.';
|
|
26
|
+
mount.append(note);
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
:root{font-family:Inter,'Segoe UI',Arial,sans-serif;color:#e4f1f8;background:#091723;--border:#234356;--teal:#2bc7b1}*{box-sizing:border-box}body{margin:0;background:#091723;color:#eaf6fa}button,select{font:inherit;cursor:pointer}button:focus-visible,select:focus-visible{outline:2px solid #56dcd0;outline-offset:3px}.app{min-height:100vh;display:grid;grid-template-columns:70px minmax(0,1fr)}.rail{background:#091521;border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:22px 10px;gap:25px}.brand{font-size:22px;font-weight:900;letter-spacing:-2px}.rail span{color:#668da3;font-size:17px}.content{min-width:0}.top{min-height:74px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;padding:12px 28px;gap:20px}.kicker{color:#61b4be;font-size:10px;font-weight:850;letter-spacing:1.8px}.site{font-size:16px;font-weight:850;margin-top:5px}.status{font-size:10px;font-weight:800;color:#81cabc;border:1px solid #246a63;border-radius:6px;padding:10px 12px;background:#0b2c33}.body{max-width:1650px;margin:0 auto;padding:26px 28px}.breadcrumb{font-size:10px;color:#7296ac;letter-spacing:1.5px;text-transform:uppercase}.heading{display:flex;align-items:end;justify-content:space-between;gap:20px;margin-bottom:23px}.heading h1{font-size:28px;letter-spacing:-.05em;margin:12px 0 5px}.sub{color:#8ca9bb;font-size:12px}.tag{font-size:10px;color:#93b5c5;letter-spacing:1px}.metrics{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin-bottom:15px}.metric{border:1px solid var(--border);border-radius:7px;background:#122432;padding:17px;min-height:112px}.metric small{font-size:9px;letter-spacing:1.6px;color:#87adc2;font-weight:800}.metric strong{display:block;font-size:26px;letter-spacing:-1px;margin:13px 0 5px}.metric .unavailable{font-size:24px;color:#d4ad71}.metric p{font-size:10px;color:#809caf;margin:0;line-height:1.5}.layout{display:grid;grid-template-columns:minmax(0,1fr) 275px;gap:13px}.panel{border:1px solid var(--border);background:#102230;border-radius:8px;overflow:hidden}.panel-title{display:flex;justify-content:space-between;align-items:center;padding:17px 18px;border-bottom:1px solid var(--border);gap:8px}.panel-title h2{margin:6px 0 0;font-size:17px}.controlbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 16px;background:#0d2030;border-bottom:1px solid var(--border);flex-wrap:wrap}.controlbar .group{display:flex;align-items:center;gap:7px}.controlbar select,.controlbar button{border:1px solid #35596d;border-radius:5px;background:#18364a;color:#d6eff8;padding:9px 12px;font-size:11px;font-weight:750}.controlbar label{font-size:10px;color:#a5bac7}.map-canvas{height:min(57vh,570px);min-height:425px;overflow:hidden;background:#0b1f2d;position:relative}.map-inner{width:100%;height:100%;transform-origin:50% 50%;transition:transform .2s}.map-inner svg{width:100%;height:100%}.map-caption{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:10px;color:#6f9eb0;padding:12px 15px;border-top:1px solid var(--border)}.map-note{color:#87a7b8;font-size:10px;padding:12px 15px;background:#102a37;border-top:1px solid var(--border)}.inspector{padding:17px}.inspector .panel-kicker{font-size:10px;color:#6ca9bc;letter-spacing:1.5px;font-weight:800}.inspector h3{font-size:19px;margin:10px 0}.inspector .muted{font-size:11px;color:#83a3b5;line-height:1.5}.divider{height:1px;background:#295064;margin:18px 0}.signal-row{font-size:10px;display:flex;justify-content:space-between;padding:10px 0;gap:5px;border-bottom:1px solid #224052;color:#bed2de}.signal-row b{color:#d6aa71;font-weight:750;white-space:nowrap}.safety{color:#d0a975;font-size:11px;line-height:1.6;background:#282a2b;border:1px solid #685033;padding:12px;border-radius:6px;margin-top:20px}.footer{font-size:10px;color:#577a8f;padding:19px 2px;letter-spacing:1px}.pill{border:1px solid #2e5669;color:#8bb4c5;padding:5px 9px;border-radius:5px;font-size:10px}@media(max-width:1050px){.metrics{grid-template-columns:repeat(3,minmax(0,1fr))}.layout{grid-template-columns:minmax(0,1fr)}.inspector{min-height:220px}}@media(max-width:650px){.app{grid-template-columns:1fr}.rail{display:none}.top{padding:12px 15px}.body{padding:15px}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.heading{display:block}.map-canvas{min-height:300px;height:360px}.panel-title{flex-wrap:wrap}.status{max-width:180px}}
|
|
2
|
+
|
|
3
|
+
/* V0.7.7.3 device drilldown and fail-closed command intents */
|
|
4
|
+
.control-readiness{font-size:10px;letter-spacing:.06em;color:#adc9d9;font-weight:800;margin:10px 0}
|
|
5
|
+
.device-command-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}
|
|
6
|
+
.device-command-intent{background:#1d3b4d;color:#b4c8d4;border:1px solid #48697a;border-radius:5px;padding:10px;font-size:10px;font-weight:800}
|
|
7
|
+
.device-command-intent:disabled{opacity:.7;cursor:not-allowed}
|
|
8
|
+
.control-lock-note{font-size:10px;line-height:1.6;color:#d0b187;margin:10px 0}
|
|
9
|
+
|
|
10
|
+
/* V0.7.9.2: 2D floor-isolated operator navigation */
|
|
11
|
+
.floor-tabs{display:flex;gap:5px;flex-wrap:wrap}.floor-tabs button{border:1px solid #386175;border-radius:5px;padding:9px 12px;background:#143246;color:#b4d2df;font-size:11px;font-weight:800}.floor-tabs button.active{background:#106f76;color:#fff;border-color:#32d0bb}.floor-transfers{display:flex;gap:7px;flex-wrap:wrap;padding:10px 16px;background:#0d2836;border-top:1px solid #244759}.floor-transfers[hidden]{display:none}.floor-transfer-link{border:1px solid #568596;background:#144154;color:#c8f2fa;border-radius:5px;font-size:11px;font-weight:700;padding:9px 12px}.floor-transfer-link:hover{background:#1a5b70}
|
|
12
|
+
|
|
13
|
+
/* Precise 2D-only operator control strip. Colors are command identification,
|
|
14
|
+
never evidence of authority. Disabled remains enforced in markup. */
|
|
15
|
+
.facility-control-strip{display:flex;align-items:center;gap:12px;flex-wrap:wrap;justify-content:space-between;padding:9px 12px;margin:0 0 8px;border:1px solid #2b5268;border-radius:6px;background:#102839;min-height:54px}
|
|
16
|
+
.facility-control-label strong{font-size:12px;font-weight:750;color:#e6f4fa}
|
|
17
|
+
.facility-control-buttons{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-left:auto}
|
|
18
|
+
.facility-command{min-width:78px;min-height:32px;padding:8px 10px;border:1px solid transparent;border-radius:4px;font-size:10px;font-weight:800;letter-spacing:.04em;opacity:.72;cursor:not-allowed}
|
|
19
|
+
.facility-command.fc-start{background:#0b806e;border-color:#2cae93;color:#eefdf8}
|
|
20
|
+
.facility-command.fc-stop{background:#ab334c;border-color:#d36275;color:#fff3f5}
|
|
21
|
+
.facility-command.fc-reset{background:#266e9c;border-color:#4b9ac4;color:#f0f8ff}
|
|
22
|
+
.facility-command.fc-ack{background:#96702c;border-color:#c49b4b;color:#fff7e5}
|
|
23
|
+
.facility-control-locked{font-size:10px;font-weight:800;color:#f0ca77;white-space:nowrap;border:1px solid #72592b;padding:8px 9px;border-radius:4px}
|
|
24
|
+
@media(max-width:840px){.facility-control-buttons{margin-left:0}.facility-control-strip{justify-content:flex-start}}
|