...
Rendering is performed by a vendored cytoscape.js inside a WebView2 (WPF) or OpenSilver (HTML5) host — no external network dependency at runtime.
...
| HTML |
|---|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UNS Visual Report — LocalAI KnowledgeGraph Demo</title>
<script src="C:/Program Files/Tatsoft/FrameworX/fx-10/HTML5/Libraries/cytoscape/dist/cytoscape.min.js"></script>
<style>
:root {
--bg: #fafafa;
--fg: #222222;
--border: #d0d0d0;
--panel-bg: #f0f0f0;
--toolbar-bg: #ececec;
--muted-fg: #6a6a6a;
--input-bg: #ffffff;
--button-bg: #e0e0e0;
--info-td-fg: #6a6a6a;
--info-td-border: #e0e0e0;
--code-bg: #f5f5f5;
--text-bg-edge: #ffffff;
--node-class-fill: #4a90d9;
--node-class-border: #2c5f8f;
--node-folder-fill: #b8b886;
--node-folder-border: #807f54;
--node-tag-fill: #5a9f5a;
--node-tag-border: #3a6f3a;
--edge-reference: #e67e22;
--edge-composition: #9b59b6;
--edge-inheritance: #888888;
--edge-containment: #999999;
--selection: #ff9800;
--node-label: #ffffff;
--edge-label: #444444;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1e1e1e;
--fg: #dddddd;
--border: #3a3a3a;
--panel-bg: #252526;
--toolbar-bg: #2d2d30;
--muted-fg: #999999;
--input-bg: #1e1e1e;
--button-bg: #3a3a3a;
--info-td-fg: #888888;
--info-td-border: #333333;
--code-bg: #1e1e1e;
--text-bg-edge: #1e1e1e;
--node-class-fill: #4a90d9;
--node-class-border: #2c5f8f;
--node-folder-fill: #7a7a4a;
--node-folder-border: #555533;
--node-tag-fill: #5a9f5a;
--node-tag-border: #3a6f3a;
--edge-reference: #e67e22;
--edge-composition: #9b59b6;
--edge-inheritance: #c0c0c0;
--edge-containment: #777777;
--selection: #ff9800;
--node-label: #ffffff;
--edge-label: #cccccc;
}
}
:root {
--bg: #343536;
--fg: #DADADA;
--border: #595959;
--panel-bg: #202123;
--toolbar-bg: #202123;
--input-bg: #343536;
--code-bg: #343536;
--text-bg-edge: #343536;
--node-class-fill: #5064B4;
--node-folder-fill: #484848;
--node-tag-fill: #008C3C;
--edge-reference: #FFF546;
--edge-composition: #D1D1D1;
--edge-inheritance: #595959;
--edge-containment: #595959;
--selection: #FFC107;
}
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; background: var(--bg); color: var(--fg); }
#header { padding: 12px 16px; background: var(--panel-bg); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 16px; }
#header h1 { margin: 0; font-size: 16px; font-weight: 500; }
#header .stats { font-size: 12px; color: var(--muted-fg); }
#toolbar { padding: 8px 16px; background: var(--toolbar-bg); border-bottom: 1px solid var(--border); display: flex; gap: 12px; align-items: center; font-size: 12px; }
#toolbar label { cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: 5px; }
#toolbar input[type=text] { flex: 0 0 220px; padding: 4px 8px; background: var(--input-bg); color: var(--fg); border: 1px solid var(--border); border-radius: 3px; }
#toolbar button { background: var(--button-bg); color: var(--fg); border: none; padding: 4px 10px; border-radius: 3px; cursor: pointer; }
.sw { display: inline-block; vertical-align: middle; flex-shrink: 0; }
.sw-node { width: 10px; height: 10px; border-radius: 2px; border: 1px solid rgba(0,0,0,0.4); }
.sw-edge { width: 18px; height: 3px; }
.sw-class { background: var(--node-class-fill); border-color: var(--node-class-border); }
.sw-folder { background: var(--node-folder-fill); border-color: var(--node-folder-border); }
.sw-tag { background: var(--node-tag-fill); border-color: var(--node-tag-border); border-radius: 50%; }
.sw-reference { background: transparent; height: 0; border-top: 2px dashed var(--edge-reference); }
.sw-composition { background: var(--edge-composition); }
.sw-inheritance { background: var(--edge-inheritance); }
.sw-containment { background: var(--edge-containment); }
#container { display: flex; height: calc(100vh - 95px); }
#cy { flex: 1; background: var(--bg); }
#info { width: 320px; padding: 12px 16px; background: var(--panel-bg); border-left: 1px solid var(--border); overflow-y: auto; font-size: 13px; }
#info h3 { margin: 0 0 8px 0; font-size: 14px; }
#info .kind { font-size: 11px; color: var(--muted-fg); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
#info table { width: 100%; border-collapse: collapse; margin-top: 8px; }
#info td { padding: 3px 6px; border-bottom: 1px solid var(--info-td-border); font-size: 12px; vertical-align: top; }
#info td:first-child { color: var(--info-td-fg); width: 90px; }
#info code { font-family: Consolas, Menlo, monospace; background: var(--code-bg); padding: 1px 4px; border-radius: 2px; }
</style>
</head>
<body>
<div id="header">
<h1>UNS Visual Report — LocalAI KnowledgeGraph Demo</h1>
<span class="stats">18 UDTs · 18 tags · 13 folders · 38 relations</span>
</div>
<div id="toolbar">
<input id="search" type="text" placeholder="Search nodes…" />
<label><input type="checkbox" class="filter" data-kind="class" checked><span class="sw sw-node sw-class"></span>UDTs</label>
<label><input type="checkbox" class="filter" data-kind="folder" checked><span class="sw sw-node sw-folder"></span>Folders</label>
<label><input type="checkbox" class="filter" data-kind="tag" checked><span class="sw sw-node sw-tag"></span>Tags</label>
<span style="color: var(--muted-fg);">|</span>
<label><input type="checkbox" class="edgefilter" data-kind="reference" checked><span class="sw sw-edge sw-reference"></span>Reference</label>
<label><input type="checkbox" class="edgefilter" data-kind="composition" checked><span class="sw sw-edge sw-composition"></span>Composition</label>
<label><input type="checkbox" class="edgefilter" data-kind="inheritance" checked><span class="sw sw-edge sw-inheritance"></span>Inheritance</label>
<label><input type="checkbox" class="edgefilter" data-kind="containment" checked><span class="sw sw-edge sw-containment"></span>Containment</label>
<span style="flex: 1;"></span>
<button id="downloadLayout" title="Save current node positions as a JSON file">Download Layout</button>
<button id="loadLayout" title="Load node positions from a JSON file">Load Layout</button>
<input id="loadLayoutFile" type="file" accept="application/json,.json" style="display:none;" />
<button onclick="cy.fit()">Fit</button>
</div>
<div id="container">
<div id="cy"></div>
<div id="info"><h3>UNS</h3><div class="kind">Click a node for details</div></div>
</div>
<script>const ELEMENTS = [{"data":{"id":"ut_MixerType","label":"MixerType","name":"MixerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Mixer","labels":"High-Shear Mixer; Granulator","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"StirrerSpeed","t":"Double"},{"n":"JacketTemp","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_ReactorType"}]}},{"data":{"id":"e1","source":"ut_MixerType","target":"ut_ReactorType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e2","source":"ut_MixerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_ReactorType","label":"ReactorType","name":"ReactorType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Reactor","labels":"Reaction Vessel; Agitated Reactor","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"Level","t":"Double"},{"n":"StirrerSpeed","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CoaterType"},{"n":"ReceivesFrom","t":"Reference_MixerType"}]}},{"data":{"id":"e3","source":"ut_ReactorType","target":"ut_CoaterType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e4","source":"ut_ReactorType","target":"ut_MixerType","label":"ReceivesFrom","kind":"reference"}},{"data":{"id":"e5","source":"ut_ReactorType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_CoaterType","label":"CoaterType","name":"CoaterType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Coater","labels":"Coating Drum; Tablet Coater","members":[{"n":"Temperature","t":"Double"},{"n":"SprayRate","t":"Double"},{"n":"DrumSpeed","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_DryerType"},{"n":"ReceivesFromMain","t":"Reference_ReactorType"},{"n":"ReceivesFromAdditive","t":"Reference_CrystallizerType"}]}},{"data":{"id":"e6","source":"ut_CoaterType","target":"ut_DryerType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e7","source":"ut_CoaterType","target":"ut_ReactorType","label":"ReceivesFromMain","kind":"reference"}},{"data":{"id":"e8","source":"ut_CoaterType","target":"ut_CrystallizerType","label":"ReceivesFromAdditive","kind":"reference"}},{"data":{"id":"e9","source":"ut_CoaterType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_DryerType","label":"DryerType","name":"DryerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Dryer","labels":"Fluid-Bed Dryer","members":[{"n":"Temperature","t":"Double"},{"n":"Humidity","t":"Double"},{"n":"AirFlow","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"}]}},{"data":{"id":"e10","source":"ut_DryerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_BlenderType","label":"BlenderType","name":"BlenderType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Blender","labels":"Powder Blender","members":[{"n":"Speed","t":"Double"},{"n":"FillLevel","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CrystallizerType"}]}},{"data":{"id":"e11","source":"ut_BlenderType","target":"ut_CrystallizerType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e12","source":"ut_BlenderType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_CrystallizerType","label":"CrystallizerType","name":"CrystallizerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Crystallizer","labels":"Crystallization Vessel","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CoaterType"},{"n":"ReceivesFrom","t":"Reference_BlenderType"}]}},{"data":{"id":"e13","source":"ut_CrystallizerType","target":"ut_CoaterType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e14","source":"ut_CrystallizerType","target":"ut_BlenderType","label":"ReceivesFrom","kind":"reference"}},{"data":{"id":"e15","source":"ut_CrystallizerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_Attr_Site","label":"Attr_Site","name":"Attr_Site","kind":"class","description":"","sourceIri":"","labels":"","members":[{"n":"EmergencyContact","t":"Text"},{"n":"OperatingHours","t":"Integer"},{"n":"FDA_RegistrationNumber","t":"Integer"},{"n":"TimeZone","t":"Text"},{"n":"PlantCode","t":"Integer"},{"n":"Address","t":"Text"},{"n":"Longitude","t":"Integer"},{"n":"Latitude","t":"Integer"}]}},{"data":{"id":"ut_Attr_Enterprise","label":"Attr_Enterprise","name":"Attr_Enterprise","kind":"class","description":"","sourceIri":"","labels":"","members":[{"n":"LogoUrl","t":"Text"},{"n":"CEO","t":"Text"},{"n":"Website","t":"Text"},{"n":"NDC_LabelerCode","t":"Text"},{"n":"DUNS_Number","t":"Text"},{"n":"Headquarters","t":"Text"},{"n":"FoundedYear","t":"Integer"},{"n":"CompanyName","t":"Text"}]}},{"data":{"id":"ut_ChatState","label":"ChatState","name":"ChatState","kind":"class","description":"Page-state for the Home AI chat panel — single flat container holding Query (operator input) and Reply (LLM envelope, JSON). Server domain so all connected clients share one conversation.","sourceIri":"","labels":"","members":[{"n":"Query","t":"Text"},{"n":"Reply","t":"Json"}]}},{"data":{"id":"ut_S88_Unit","label":"S88_Unit","name":"S88_Unit","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Unit","labels":"Vessel; Reactor","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"},{"n":"Temperature_C","t":"Double"},{"n":"Pressure_bar","t":"Double"},{"n":"Speed_rpm","t":"Double"},{"n":"Setpoint","t":"Double"},{"n":"Running","t":"Digital"},{"n":"AnomalyScore","t":"Double"},{"n":"Capacity_L","t":"Double"}]}},{"data":{"id":"ut_S88_ProcessCell","label":"S88_ProcessCell","name":"S88_ProcessCell","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/ProcessCell","labels":"Production Line; Cell","members":[{"n":"Name","t":"Text"},{"n":"ActiveBatch","t":"Text"},{"n":"BatchProgress","t":"Double"}]}},{"data":{"id":"ut_S88_Operation","label":"S88_Operation","name":"S88_Operation","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Operation","labels":"Procedure Step; Phase","members":[{"n":"OpID","t":"Text"},{"n":"Description","t":"Text"},{"n":"Status","t":"Text"},{"n":"PrecedingOperation","t":"Reference_S88_Operation"},{"n":"FollowingOperation","t":"Reference_S88_Operation"},{"n":"RequiredState","t":"Text"},{"n":"HoldConditions","t":"Text"}]}},{"data":{"id":"e16","source":"ut_S88_Operation","target":"ut_S88_Operation","label":"PrecedingOperation","kind":"reference"}},{"data":{"id":"e17","source":"ut_S88_Operation","target":"ut_S88_Operation","label":"FollowingOperation","kind":"reference"}},{"data":{"id":"ut_S88_EquipmentModule","label":"S88_EquipmentModule","name":"S88_EquipmentModule","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/EquipmentModule","labels":"Module; Subassembly","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"},{"n":"Speed_rpm","t":"Double"},{"n":"Running","t":"Digital"},{"n":"Output","t":"Double"},{"n":"Setpoint","t":"Double"},{"n":"Current_A","t":"Double"}]}},{"data":{"id":"ut_S88_Enterprise","label":"S88_Enterprise","name":"S88_Enterprise","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Enterprise","labels":"Company; Corporation","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"}]}},{"data":{"id":"ut_S88_Batch","label":"S88_Batch","name":"S88_Batch","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Batch","labels":"Production Run; Lot","members":[{"n":"BatchID","t":"Text"},{"n":"ProductCode","t":"Text"},{"n":"Status","t":"Text"},{"n":"StartTime","t":"DateTime"}]}},{"data":{"id":"ut_S88_Area","label":"S88_Area","name":"S88_Area","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Area","labels":"Department","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"}]}},{"data":{"id":"ut_S88_Site","label":"S88_Site","name":"S88_Site","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Site","labels":"Plant","members":[{"n":"Name","t":"Text"},{"n":"Location","t":"Text"},{"n":"TotalThroughput","t":"Double"},{"n":"OEE","t":"Double"}]}},{"data":{"id":"ut_Attr_Batch","label":"Attr_Batch","name":"Attr_Batch","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88ext/BatchProfile","labels":"Batch Outcome","members":[{"n":"CompletionTime","t":"DateTime"},{"n":"DurationMinutes","t":"Integer"},{"n":"MaxTemperature_C","t":"Double"},{"n":"AnomaliesFired","t":"Integer"},{"n":"QualityVerdict","t":"Text"},{"n":"BatchDescription","t":"Text"},{"n":"OutcomeNotes","t":"Text"}]}},{"data":{"id":"e18","source":"ut_Attr_Batch","target":"ut_S88_Batch","label":"extends","kind":"inheritance"}},{"data":{"id":"f_PharmaCo","label":"PharmaCo","path":"PharmaCo","kind":"folder"}},{"data":{"id":"f_PharmaCo_Houston","label":"Houston","path":"PharmaCo/Houston","kind":"folder"}},{"data":{"id":"e19","source":"f_PharmaCo","target":"f_PharmaCo_Houston","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Granulation","label":"Granulation","path":"PharmaCo/Houston/Granulation","kind":"folder"}},{"data":{"id":"e20","source":"f_PharmaCo_Houston","target":"f_PharmaCo_Houston_Granulation","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Granulation_MainLine","label":"MainLine","path":"PharmaCo/Houston/Granulation/MainLine","kind":"folder"}},{"data":{"id":"e21","source":"f_PharmaCo_Houston_Granulation","target":"f_PharmaCo_Houston_Granulation_MainLine","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Additives","label":"Additives","path":"PharmaCo/Houston/Additives","kind":"folder"}},{"data":{"id":"e22","source":"f_PharmaCo_Houston","target":"f_PharmaCo_Houston_Additives","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Additives_SideLine","label":"SideLine","path":"PharmaCo/Houston/Additives/SideLine","kind":"folder"}},{"data":{"id":"e23","source":"f_PharmaCo_Houston_Additives","target":"f_PharmaCo_Houston_Additives_SideLine","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant","label":"NewBostonPlant","path":"PharmaCo/NewBostonPlant","kind":"folder"}},{"data":{"id":"e24","source":"f_PharmaCo","target":"f_PharmaCo_NewBostonPlant","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea","label":"GranulationArea","path":"PharmaCo/NewBostonPlant/GranulationArea","kind":"folder"}},{"data":{"id":"e25","source":"f_PharmaCo_NewBostonPlant","target":"f_PharmaCo_NewBostonPlant_GranulationArea","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","label":"CellA","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA","kind":"folder"}},{"data":{"id":"e26","source":"f_PharmaCo_NewBostonPlant_GranulationArea","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","label":"Mixer_M101","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101","kind":"folder"}},{"data":{"id":"e27","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","label":"Reactor_R101","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101","kind":"folder"}},{"data":{"id":"e28","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","kind":"containment"}},{"data":{"id":"f_Operations","label":"Operations","path":"Operations","kind":"folder"}},{"data":{"id":"f_Chat","label":"Chat","path":"Chat","kind":"folder"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","label":"Mixer M-201 : MixerType","path":"PharmaCo/Houston/Granulation/MainLine/Mixer_M101","userType":"MixerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Houston_Mixer_M201","kind":"tag"}},{"data":{"id":"e29","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","kind":"containment"}},{"data":{"id":"e30","source":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","target":"ut_MixerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e31","source":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","target":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","label":"Reactor R-201 : ReactorType","path":"PharmaCo/Houston/Granulation/MainLine/Reactor_R101","userType":"ReactorType","sourceIri":"https://example.tatsoft.com/ontology/demo/Houston_Reactor_R201","kind":"tag"}},{"data":{"id":"e32","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","kind":"containment"}},{"data":{"id":"e33","source":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","target":"ut_ReactorType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e34","source":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"Coater C-201 : CoaterType","path":"PharmaCo/Houston/Granulation/MainLine/Coater_C201","userType":"CoaterType","sourceIri":"https://example.tatsoft.com/ontology/demo/Coater_C201","kind":"tag"}},{"data":{"id":"e35","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","kind":"containment"}},{"data":{"id":"e36","source":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","target":"ut_CoaterType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e37","source":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","target":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","label":"Dryer D-301 : DryerType","path":"PharmaCo/Houston/Granulation/MainLine/Dryer_D301","userType":"DryerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Dryer_D301","kind":"tag"}},{"data":{"id":"e38","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","kind":"containment"}},{"data":{"id":"e39","source":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","target":"ut_DryerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","label":"Blender B-101 : BlenderType","path":"PharmaCo/Houston/Additives/SideLine/Blender_B101","userType":"BlenderType","sourceIri":"https://example.tatsoft.com/ontology/demo/Blender_B101","kind":"tag"}},{"data":{"id":"e40","source":"f_PharmaCo_Houston_Additives_SideLine","target":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","kind":"containment"}},{"data":{"id":"e41","source":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","target":"ut_BlenderType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e42","source":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","target":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","label":"Crystallizer X-101 : CrystallizerType","path":"PharmaCo/Houston/Additives/SideLine/Crystallizer_X101","userType":"CrystallizerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Crystallizer_X101","kind":"tag"}},{"data":{"id":"e43","source":"f_PharmaCo_Houston_Additives_SideLine","target":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","kind":"containment"}},{"data":{"id":"e44","source":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","target":"ut_CrystallizerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e45","source":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_Attr","label":"New Boston Plant : S88_Site","path":"PharmaCo/NewBostonPlant/Attr","userType":"S88_Site","sourceIri":"https://example.tatsoft.com/ontology/demo/NewBoston_Plant","kind":"tag"}},{"data":{"id":"e46","source":"f_PharmaCo_NewBostonPlant","target":"t_PharmaCo_NewBostonPlant_Attr","kind":"containment"}},{"data":{"id":"e47","source":"t_PharmaCo_NewBostonPlant_Attr","target":"ut_S88_Site","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","label":"Granulation Cell A : S88_ProcessCell","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Attr","userType":"S88_ProcessCell","sourceIri":"https://example.tatsoft.com/ontology/demo/Cell_A","kind":"tag"}},{"data":{"id":"e48","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","kind":"containment"}},{"data":{"id":"e49","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","target":"ut_S88_ProcessCell","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","label":"High-Shear Mixer M-101 : S88_Unit","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Attr","userType":"S88_Unit","sourceIri":"https://example.tatsoft.com/ontology/demo/Mixer_M101","kind":"tag"}},{"data":{"id":"e50","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","kind":"containment"}},{"data":{"id":"e51","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","target":"ut_S88_Unit","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","label":"Mixer M-101 Heater Jacket : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Heater_H101","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Heater_H101","kind":"tag"}},{"data":{"id":"e52","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","kind":"containment"}},{"data":{"id":"e53","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","label":"Mixer M-101 Stirrer : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Stirrer_S101","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Stirrer_S101","kind":"tag"}},{"data":{"id":"e54","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","kind":"containment"}},{"data":{"id":"e55","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","label":"Reaction Vessel R-101 : S88_Unit","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101/Attr","userType":"S88_Unit","sourceIri":"https://example.tatsoft.com/ontology/demo/Reactor_R101","kind":"tag"}},{"data":{"id":"e56","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","kind":"containment"}},{"data":{"id":"e57","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","target":"ut_S88_Unit","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","label":"Reactor R-101 Stirrer : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101/Stirrer_S102","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Stirrer_S102","kind":"tag"}},{"data":{"id":"e58","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","kind":"containment"}},{"data":{"id":"e59","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_Op_Charge_001","label":"Charge API Powder : S88_Operation","path":"Operations/Op_Charge_001","userType":"S88_Operation","sourceIri":"https://example.tatsoft.com/ontology/demo/Op_Charge_001","kind":"tag"}},{"data":{"id":"e60","source":"f_Operations","target":"t_Operations_Op_Charge_001","kind":"containment"}},{"data":{"id":"e61","source":"t_Operations_Op_Charge_001","target":"ut_S88_Operation","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_Op_Mix_001","label":"Wet Granulation Mix : S88_Operation","path":"Operations/Op_Mix_001","userType":"S88_Operation","sourceIri":"https://example.tatsoft.com/ontology/demo/Op_Mix_001","kind":"tag"}},{"data":{"id":"e62","source":"f_Operations","target":"t_Operations_Op_Mix_001","kind":"containment"}},{"data":{"id":"e63","source":"t_Operations_Op_Mix_001","target":"ut_S88_Operation","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_AnomalyNarrative","label":"AnomalyNarrative : Text","path":"Operations/AnomalyNarrative","userType":"Text","sourceIri":"","kind":"tag"}},{"data":{"id":"e64","source":"f_Operations","target":"t_Operations_AnomalyNarrative","kind":"containment"}},{"data":{"id":"t_Chat_Query","label":"Query : Text","path":"Chat/Query","userType":"Text","sourceIri":"","kind":"tag"}},{"data":{"id":"e65","source":"f_Chat","target":"t_Chat_Query","kind":"containment"}},{"data":{"id":"t_Chat_Reply","label":"Reply : Json","path":"Chat/Reply","userType":"Json","sourceIri":"","kind":"tag"}},{"data":{"id":"e66","source":"f_Chat","target":"t_Chat_Reply","kind":"containment"}}];
const SOLUTION_NAME = "LocalAI KnowledgeGraph Demo";</script>
<script>
// Wait for the cytoscape global before init. The <script src='Libraries/...'>
// above can 404 when the output folder is not co-located with a vendored
// cytoscape (e.g. HTML hand-copied away from the install-tree sibling). There
// is intentionally NO CDN fallback (offline plant-floor safety), so the only
// way the global appears is a successful local load. This poll caps at 60
// retries (3s) and then surfaces a clear console error instead of polling
// forever, naming the local path that needs to be reachable.
(function initWhenReady(retries) {
if (typeof cytoscape === 'undefined') {
if (retries > 60) { console.error('cytoscape.js failed to load after 3s. Graph cannot render. Check that Libraries/cytoscape/dist/cytoscape.min.js is present next to this report (vendored with the install tree).'); return; }
setTimeout(function() { initWhenReady(retries + 1); }, 50);
return;
}
// Resolve theme colors from the CSS custom-properties declared in <style>.
// The same vars drive #header / #toolbar / #info chrome, so the cytoscape
// graph stays in lockstep with the HTML chrome under any of three palette
// sources: (1) Light base defaults, (2) prefers-color-scheme dark override,
// (3) explicit Designer-captured palette injected after the media query.
// Resolved ONCE at init — the standalone HTML is a baked artifact, not a
// live-switching surface.
const css = getComputedStyle(document.documentElement);
const v = (name, fallback) => ((css.getPropertyValue(name) || '').trim() || fallback);
const NODE_CLASS_FILL = v('--node-class-fill', '#4a90d9');
const NODE_CLASS_BORDER = v('--node-class-border', '#2c5f8f');
const NODE_FOLDER_FILL = v('--node-folder-fill', '#7a7a4a');
const NODE_FOLDER_BORDER= v('--node-folder-border','#555533');
const NODE_TAG_FILL = v('--node-tag-fill', '#5a9f5a');
const NODE_TAG_BORDER = v('--node-tag-border', '#3a6f3a');
const EDGE_REFERENCE = v('--edge-reference', '#e67e22');
const EDGE_COMPOSITION = v('--edge-composition', '#9b59b6');
const EDGE_INHERITANCE = v('--edge-inheritance', '#c0c0c0');
const EDGE_CONTAINMENT = v('--edge-containment', '#777777');
const SELECTION = v('--selection', '#ff9800');
// Node label color tracks --fg (captured TextForeground) so node text matches the
// theme like the embedded TKnowledgeGraph control: dark on a light page, light on a
// dark page. Was pinned to --node-label (#ffffff) — which left unreadable white text
// on the light-theme soft node fills. (Luis 2026-06-02 light-theme parity.)
const NODE_LABEL = v('--fg', '#dddddd');
const EDGE_LABEL = v('--edge-label', '#cccccc');
const TEXT_BG_EDGE = v('--text-bg-edge', '#1e1e1e');
const cy = cytoscape({
container: document.getElementById('cy'),
elements: ELEMENTS,
style: [
{ selector: 'node[kind = "class"]', style: {
'background-color': NODE_CLASS_FILL, 'shape': 'round-rectangle', 'label': 'data(label)',
'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
'font-size': 12, 'width': 'label', 'height': 28, 'padding': '8px',
'border-color': NODE_CLASS_BORDER, 'border-width': 1 } },
{ selector: 'node[kind = "folder"]', style: {
'background-color': NODE_FOLDER_FILL, 'shape': 'round-rectangle', 'label': 'data(label)',
'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
'font-size': 11, 'width': 'label', 'height': 24, 'padding': '6px',
'border-color': NODE_FOLDER_BORDER, 'border-width': 1 } },
{ selector: 'node[kind = "tag"]', style: {
'background-color': NODE_TAG_FILL, 'shape': 'ellipse', 'label': 'data(label)',
'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
'font-size': 11, 'width': 'label', 'height': 24, 'padding': '6px',
'border-color': NODE_TAG_BORDER, 'border-width': 1 } },
{ selector: 'edge[kind = "reference"]', style: {
'line-color': EDGE_REFERENCE, 'line-style': 'dashed', 'width': 1.5,
'target-arrow-color': EDGE_REFERENCE, 'target-arrow-shape': 'triangle',
'curve-style': 'bezier', 'label': 'data(label)', 'font-size': 10,
'color': EDGE_LABEL, 'text-background-color': TEXT_BG_EDGE,
'text-background-opacity': 0.8, 'text-background-padding': 2 } },
{ selector: 'edge[kind = "composition"]', style: {
'line-color': EDGE_COMPOSITION, 'width': 2,
'target-arrow-color': EDGE_COMPOSITION, 'target-arrow-shape': 'diamond',
'curve-style': 'bezier', 'label': 'data(label)', 'font-size': 10,
'color': EDGE_LABEL, 'text-background-color': TEXT_BG_EDGE,
'text-background-opacity': 0.8, 'text-background-padding': 2 } },
{ selector: 'edge[kind = "inheritance"]', style: {
'line-color': EDGE_INHERITANCE, 'width': 2,
'target-arrow-color': EDGE_INHERITANCE, 'target-arrow-shape': 'triangle-tee',
'curve-style': 'bezier' } },
{ selector: 'edge[kind = "containment"]', style: {
'line-color': EDGE_CONTAINMENT, 'width': 1, 'target-arrow-color': EDGE_CONTAINMENT,
'target-arrow-shape': 'triangle', 'curve-style': 'bezier' } },
{ selector: 'node:selected', style: { 'border-color': SELECTION, 'border-width': 3 } },
{ selector: '.hidden', style: { 'display': 'none' } },
{ selector: '.dimmed', style: { 'opacity': 0.15 } }
],
layout: { name: 'cose', animate: false, padding: 30, nodeRepulsion: 8000, idealEdgeLength: 80 }
});
// Click ? detail panel
const info = document.getElementById('info');
cy.on('tap', 'node', evt => {
const d = evt.target.data();
let html = '<h3>' + esc(d.label || d.name || d.id) + '</h3>';
html += '<div class="kind">' + (d.kind || '') + '</div><table>';
const add = (k, v) => { if (v) html += '<tr><td>' + esc(k) + '</td><td>' + esc(v) + '</td></tr>'; };
add('Name', d.name);
add('Path', d.path);
add('UserType', d.userType);
add('Description', d.description);
add('Labels', d.labels);
add('Source IRI', d.sourceIri);
html += '</table>';
if (d.members && d.members.length) {
html += '<h3 style="margin-top:16px;">Members</h3><table>';
for (const m of d.members)
html += '<tr><td><code>' + esc(m.n) + '</code></td><td>' + esc(m.t) + '</td></tr>';
html += '</table>';
}
info.innerHTML = html;
});
cy.on('tap', evt => { if (evt.target === cy) info.innerHTML = '<h3>UNS</h3><div class="kind">Click a node for details</div>'; });
// Node-kind filter toggles
document.querySelectorAll('.filter').forEach(cb => cb.addEventListener('change', () => {
document.querySelectorAll('.filter').forEach(c => {
const kind = c.getAttribute('data-kind');
cy.nodes('[kind = "' + kind + '"]').toggleClass('hidden', !c.checked);
});
}));
// Edge-kind filter toggles
document.querySelectorAll('.edgefilter').forEach(cb => cb.addEventListener('change', () => {
document.querySelectorAll('.edgefilter').forEach(c => {
const kind = c.getAttribute('data-kind');
cy.edges('[kind = "' + kind + '"]').toggleClass('hidden', !c.checked);
});
}));
// Live search — highlights matches, dims non-matches
document.getElementById('search').addEventListener('input', e => {
const q = e.target.value.trim().toLowerCase();
if (!q) { cy.nodes().removeClass('dimmed'); return; }
cy.nodes().forEach(n => {
const d = n.data();
const hay = (d.label + ' ' + (d.name || '') + ' ' + (d.path || '')).toLowerCase();
n.toggleClass('dimmed', !hay.includes(q));
});
});
// Download current node positions as JSON. Uses cytoscape's per-node position()
// rather than the full cy.json() blob — keeps the file tiny and forward-compatible.
// Round-trip works against both this position-only payload and a raw cy.json() dump.
document.getElementById('downloadLayout').addEventListener('click', () => {
const positions = {};
cy.nodes().forEach(n => {
const p = n.position();
positions[n.id()] = { x: p.x, y: p.y };
});
const payload = {
version: 1,
kind: 'uns-visual-layout',
solution: SOLUTION_NAME,
generated: new Date().toISOString(),
nodeCount: Object.keys(positions).length,
positions: positions
};
const safe = (SOLUTION_NAME || 'uns').replace(/[^A-Za-z0-9_\-]+/g, '_');
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = safe + '-layout.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
});
// Load layout from JSON file — accepts either the position-only payload above
// or a raw cy.json() dump (elements.nodes[].data.id + .position). Missing nodes
// in the graph are skipped silently; nodes in the graph not in the file keep
// their current/auto-layout position.
document.getElementById('loadLayout').addEventListener('click', () => {
document.getElementById('loadLayoutFile').click();
});
document.getElementById('loadLayoutFile').addEventListener('change', e => {
const f = e.target.files && e.target.files[0];
if (!f) return;
const reader = new FileReader();
reader.onload = ev => {
try {
const data = JSON.parse(ev.target.result);
let positions = null;
if (data && data.positions && typeof data.positions === 'object') {
positions = data.positions;
} else if (data && data.elements && Array.isArray(data.elements.nodes)) {
positions = {};
for (const n of data.elements.nodes) {
if (n && n.data && n.data.id && n.position) positions[n.data.id] = n.position;
}
}
if (!positions) { alert('Layout file format not recognized.'); return; }
let applied = 0, missing = 0;
for (const id of Object.keys(positions)) {
const node = cy.getElementById(id);
if (node && node.length > 0) {
const p = positions[id];
if (typeof p.x === 'number' && typeof p.y === 'number') {
node.position({ x: p.x, y: p.y });
applied++;
}
} else {
missing++;
}
}
console.log('UNS layout loaded: ' + applied + ' node(s) repositioned, ' + missing + ' id(s) not in graph.');
} catch (err) {
alert('Failed to load layout: ' + err.message);
} finally {
e.target.value = '';
}
};
reader.readAsText(f);
});
function esc(s) { return (s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
})(0);
</script>
</body>
</html>
|
Requirements
This component is Portable: it runs both on Windows WPF displays and on Web Pages on any platform. WPF deployments use WebView2 (Microsoft Edge runtime, bundled with FrameworX). HTML5 deployments render through the OpenSilver-hosted cytoscape.js, served from the Display contents.
...