+
+
+
+
+
+
+
+
+
+
+
+ Please upload a file and select a service to view details.
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+ - Upload data and select a service to see recurring issues.
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
-
-
+
+
-
-
-
-
+
+
-
+
diff --git a/service_insights.js b/service_insights.js
index 1442cb4..ce1f928 100644
--- a/service_insights.js
+++ b/service_insights.js
@@ -57,7 +57,31 @@ function extractIncidentData(filteredData) {
return filteredData.map(d => d["Incident Data"]);
}
- //Generic function to fetch info from LLM
+const detailSection = document.getElementById("detail-section");
+
+// Whenever the user selects a service
+dropdown.addEventListener("change", (e) => {
+ const service = e.target.value;
+ if (service) {
+ // Show the detail boxes
+ detailSection.classList.remove("d-none");
+ updateContent(service); // Your existing logic
+ } else {
+ // Hide the detail boxes if no service is selected
+ detailSection.classList.add("d-none");
+ }
+});
+
+
+
+function delay(ms){
+ return new Promise((resolve)=>{
+ setTimeout(resolve,ms);
+ });
+
+}
+
+// Generic function to fetch info from the LLM
async function askLLMQuestion(filteredData, questionPrompt, useFullData = false) {
const selectedService = filteredData[0]?.Service || "Unknown Service";
const systemMessage = `You are a financial analyst for incident management.
@@ -84,7 +108,7 @@ ${JSON.stringify(dataToSend)}`;
{
method: "POST",
headers: { "Content-Type": "application/json" },
- credentials: "include",
+ credentials: "include",
body: JSON.stringify({
model: "gemini-1.5-pro-latest",
stream: true,
@@ -123,6 +147,7 @@ function updateUI(upstream, downstream, mainProblem, recurringPatterns) {
// Problem Description
problemText.textContent = mainProblem || "No problem description found.";
recurringList.innerHTML = ""; // clear recurring patterns
+
if (recurringPatterns && recurringPatterns.recurringPatterns) {
const rp = recurringPatterns.recurringPatterns;
const items = [
@@ -152,7 +177,7 @@ function updateUI(upstream, downstream, mainProblem, recurringPatterns) {
drawNetwork(upstream, downstream);
}
-//Called when user selects a service
+// Called when user selects a service
async function updateContent(service) {
if (!service) return;
@@ -180,6 +205,9 @@ async function updateContent(service) {
let upstream = upstreamResponse.upstream || [];
upstream = upstream.slice(0, 8);
+ await delay(1000);
+
+
// Downstream
const downstreamResponse = await askLLMQuestion(
filteredData,
@@ -189,6 +217,8 @@ async function updateContent(service) {
let downstream = downstreamResponse.downstream || [];
downstream = downstream.slice(0, 8);
+ await delay(1000);
+
// Main problem
const mainProblemResponse = await askLLMQuestion(
filteredData,
@@ -197,6 +227,9 @@ async function updateContent(service) {
);
const mainProblem = mainProblemResponse.mainProblem || "No problem identified.";
+ await delay(1000);
+
+
// Recurring patterns
const recurringPatternsResponse = await askLLMQuestion(
filteredData,
@@ -215,7 +248,7 @@ async function updateContent(service) {
}
}
-//Draw (or update) the network diagram
+// Draw (or update) the network diagram
function drawNetwork(upstream, downstream, numIncidents = 0) {
// If no CSV loaded or no data yet, just clear the network
if (!upstream.length && !downstream.length && !numIncidents) {
@@ -278,7 +311,7 @@ function drawNetwork(upstream, downstream, numIncidents = 0) {
shape: "box",
borderWidth: 2,
color: { border: "orange", background: "#fff" },
- font: { color: "black", face: "arial", size: 16 },
+ font: { color: "black", face: "arial", size: 20 },
x: startX_down + i * spacing,
y: 100,
fixed: false
@@ -322,6 +355,3 @@ function drawNetwork(upstream, downstream, numIncidents = 0) {
if (networkInstance) networkInstance.destroy();
networkInstance = new vis.Network(container, dataVis, options);
}
-
-// Listen for dropdown changes
-dropdown.addEventListener("change", (e) => updateContent(e.target.value));
diff --git a/services.html b/services.html
deleted file mode 100644
index c205275..0000000
--- a/services.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
Services Chainflow
-
-
-
-
-
-
-
-
-
-
-
Service Chainflow
-
What are the main relations between services?
-
-
-
This application is designed for incident analysis, allowing users to upload CSV files containing Service Node data and Service Links data.
-
It visualizes the relationships between different Services and their impacts using Network Flow Diagram.
-
You can use sample data from this folder if you have access.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/services.js b/services.js
deleted file mode 100644
index 801e46b..0000000
--- a/services.js
+++ /dev/null
@@ -1,200 +0,0 @@
-class SupplyChainViz {
- constructor() {
- this.nodes = [];
- this.links = [];
- this.hoveredNode = null;
-
- this.STAGES = ["Node 1", "Node 2", "Node 3", "Node 4", "Node 5"];
- this.STAGE_COLORS = {
- 0: "#4299E1",
- 1: "#ED8936",
- 2: "#48BB78",
- 3: "#E53E3E",
- 4: "#805AD5",
- };
-
- this.STAGE_SPACING = 200;
- this.NODE_SPACING = 70;
- this.MARGIN = 60;
- this.MAX_NODE_SIZE = 20;
- this.MIN_NODE_SIZE = 8;
-
- this.svg = document.getElementById("chart");
- this.setupEventListeners();
- }
-
- setupEventListeners() {
- document.getElementById("nodesFile").addEventListener("change", (e) => this.handleNodesFile(e));
- document.getElementById("linksFile").addEventListener("change", (e) => this.handleLinksFile(e));
- }
-
- async handleNodesFile(event) {
- const file = event.target.files[0];
- if (file) {
- Papa.parse(file, {
- header: true,
- dynamicTyping: true,
- complete: (results) => {
- this.nodes = results.data.filter((node) => node.id);
- if (this.links.length > 0) {
- this.render();
- }
- },
- });
- }
- }
-
- async handleLinksFile(event) {
- const file = event.target.files[0];
- if (file) {
- Papa.parse(file, {
- header: true,
- dynamicTyping: true,
- complete: (results) => {
- this.links = results.data.filter((link) => link.source && link.target);
- if (this.nodes.length > 0) {
- this.render();
- }
- },
- });
- }
- }
-
- getNodePosition(stage, position) {
- return {
- x: this.MARGIN + stage * this.STAGE_SPACING,
- y: this.MARGIN + position * this.NODE_SPACING,
- };
- }
-
- getNodeSize(value) {
- if (!value) return this.MIN_NODE_SIZE;
- const maxValue = Math.max(...this.nodes.map((n) => n.value || 0));
- const minValue = Math.min(...this.nodes.map((n) => n.value || 0));
- const scale = (value - minValue) / (maxValue - minValue);
- return this.MIN_NODE_SIZE + scale * (this.MAX_NODE_SIZE - this.MIN_NODE_SIZE);
- }
-
- createPath(sourceNode, targetNode) {
- const source = this.getNodePosition(sourceNode.stage, sourceNode.position);
- const target = this.getNodePosition(targetNode.stage, targetNode.position);
- const midX = (source.x + target.x) / 2;
-
- return `M ${source.x} ${source.y}
- C ${midX} ${source.y},
- ${midX} ${target.y},
- ${target.x} ${target.y}`;
- }
-
- getLinkWidth(value) {
- const maxValue = Math.max(...this.links.map((l) => l.value || 0));
- const minValue = Math.min(...this.links.map((l) => l.value || 0));
- const scale = (value - minValue) / (maxValue - minValue);
- return 2 + scale * 6;
- }
-
- render() {
- // Clear previous content
- this.svg.innerHTML = "";
-
- // Add stage labels
- this.STAGES.forEach((stage, index) => {
- const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
- text.setAttribute("x", this.MARGIN + index * this.STAGE_SPACING);
- text.setAttribute("y", 20);
- text.setAttribute("text-anchor", "middle");
- text.classList.add("node-label");
- text.textContent = stage;
- this.svg.appendChild(text);
- });
-
- // Add links
- this.links.forEach((link) => {
- const sourceNode = this.nodes.find((n) => n.id === link.source);
- const targetNode = this.nodes.find((n) => n.id === link.target);
- if (!sourceNode || !targetNode) return;
-
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
- path.setAttribute("d", this.createPath(sourceNode, targetNode));
- path.setAttribute("fill", "none");
- path.setAttribute("stroke", this.STAGE_COLORS[sourceNode.stage]);
- path.setAttribute("stroke-width", this.getLinkWidth(link.value));
- path.setAttribute("opacity", "0.6");
- path.classList.add("link");
- path.dataset.source = link.source;
- path.dataset.target = link.target;
- path.dataset.value = link.value;
- this.svg.appendChild(path);
- });
-
- // Add nodes
- this.nodes.forEach((node) => {
- const pos = this.getNodePosition(node.stage, node.position);
- const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
- group.setAttribute("transform", `translate(${pos.x},${pos.y})`);
- group.classList.add("node");
-
- const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
- circle.setAttribute("r", this.getNodeSize(node.value));
- circle.setAttribute("fill", this.STAGE_COLORS[node.stage]);
- circle.setAttribute("opacity", "0.8");
-
- const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
- label.setAttribute("y", -this.getNodeSize(node.value) - 5);
- label.setAttribute("text-anchor", "middle");
- label.classList.add("node-label");
- label.textContent = node.id;
-
- group.appendChild(circle);
- group.appendChild(label);
-
- // Add hover events
- group.addEventListener("mouseenter", () => this.handleNodeHover(node.id));
- group.addEventListener("mouseleave", () => this.handleNodeHover(null));
-
- this.svg.appendChild(group);
- });
- }
-
- handleNodeHover(nodeId) {
- this.hoveredNode = nodeId;
-
- // Update links visibility
- const links = this.svg.querySelectorAll(".link");
- links.forEach((link) => {
- if (!nodeId) {
- link.setAttribute("opacity", "0.6");
- } else if (link.dataset.source === nodeId || link.dataset.target === nodeId) {
- link.setAttribute("opacity", "0.8");
- } else {
- link.setAttribute("opacity", "0.01");
- }
- });
-
- // Update value labels
- const valueLabels = this.svg.querySelectorAll(".value-label");
- valueLabels.forEach((label) => label.remove());
-
- if (nodeId) {
- const relevantLinks = this.links.filter((link) => link.source === nodeId || link.target === nodeId);
-
- relevantLinks.forEach((link) => {
- const sourceNode = this.nodes.find((n) => n.id === link.source);
- const targetNode = this.nodes.find((n) => n.id === link.target);
- const source = this.getNodePosition(sourceNode.stage, sourceNode.position);
- const target = this.getNodePosition(targetNode.stage, targetNode.position);
-
- const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
- label.setAttribute("x", (source.x + target.x) / 2);
- label.setAttribute("y", (source.y + target.y) / 2 - 10);
- label.setAttribute("text-anchor", "middle");
- label.classList.add("value-label");
- label.textContent = `${link.value.toLocaleString()}`;
- this.svg.appendChild(label);
- });
- }
- }
-}
-
-// Initialize visualization
-const viz = new SupplyChainViz();