Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Reverted from v. 4

...

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.

...