sonuprasad23 commited on
Commit
df7d15c
·
1 Parent(s): 9e1485f
Files changed (2) hide show
  1. index.html +101 -0
  2. server.js +33 -12
index.html ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Hillside Dashboard API - Status</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg-color: #111827;
13
+ --card-color: #1F2937;
14
+ --text-color: #D1D5DB;
15
+ --header-color: #FFFFFF;
16
+ --accent-color: #22C55E;
17
+ --subtle-text: #6B7281;
18
+ }
19
+ body {
20
+ margin: 0;
21
+ font-family: 'Inter', sans-serif;
22
+ background-color: var(--bg-color);
23
+ color: var(--text-color);
24
+ display: flex;
25
+ justify-content: center;
26
+ align-items: center;
27
+ min-height: 100vh;
28
+ padding: 2rem;
29
+ box-sizing: border-box;
30
+ }
31
+ .container {
32
+ background-color: var(--card-color);
33
+ border-radius: 1rem;
34
+ padding: 3rem;
35
+ text-align: center;
36
+ border: 1px solid rgba(255, 255, 255, 0.1);
37
+ max-width: 500px;
38
+ width: 100%;
39
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
40
+ }
41
+ .logo {
42
+ width: 48px;
43
+ height: 48px;
44
+ margin: 0 auto 1.5rem auto;
45
+ stroke: #3B82F6;
46
+ }
47
+ h1 {
48
+ color: var(--header-color);
49
+ font-weight: 700;
50
+ font-size: 1.5rem;
51
+ margin: 0 0 0.5rem 0;
52
+ }
53
+ .status-indicator {
54
+ display: flex;
55
+ justify-content: center;
56
+ align-items: center;
57
+ gap: 0.75rem;
58
+ margin-top: 1.5rem;
59
+ margin-bottom: 1.5rem;
60
+ background-color: rgba(34, 197, 94, 0.1);
61
+ border: 1px solid rgba(34, 197, 94, 0.2);
62
+ border-radius: 9999px;
63
+ padding: 0.5rem 1rem;
64
+ display: inline-flex;
65
+ }
66
+ .status-dot {
67
+ width: 10px;
68
+ height: 10px;
69
+ background-color: var(--accent-color);
70
+ border-radius: 50%;
71
+ animation: pulse 2s infinite;
72
+ }
73
+ .status-text {
74
+ color: var(--accent-color);
75
+ font-weight: 600;
76
+ }
77
+ p {
78
+ line-height: 1.6;
79
+ color: var(--subtle-text);
80
+ }
81
+ @keyframes pulse {
82
+ 0%, 100% { opacity: 1; }
83
+ 50% { opacity: 0.5; }
84
+ }
85
+ </style>
86
+ </head>
87
+ <body>
88
+ <div class="container">
89
+ <svg class="logo" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h3m8-12h3a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3M8 7v10m8-10v10"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14 12-4-2m4 4-4 2"/>
91
+ </svg>
92
+ <h1>Hillside Dashboard Backend</h1>
93
+ <p>The Application Programming Interface (API) for the Hillside Dashboard.</p>
94
+ <div class="status-indicator">
95
+ <div class="status-dot"></div>
96
+ <span class="status-text">Service is Running</span>
97
+ </div>
98
+ <p>This service is for API requests only. Your frontend application should connect to this URL to fetch data.</p>
99
+ </div>
100
+ </body>
101
+ </html>
server.js CHANGED
@@ -2,6 +2,7 @@ const express = require('express');
2
  const { google } = require('googleapis');
3
  const { GoogleGenerativeAI } = require('@google/generative-ai');
4
  const cors = require('cors');
 
5
  require('dotenv').config();
6
 
7
  const app = express();
@@ -54,6 +55,7 @@ async function updateSheetCache() {
54
  const headers = rows[0];
55
  const dataRows = rows.slice(1);
56
  let currentLocation = '';
 
57
  const allStaff = [];
58
 
59
  dataRows.forEach((row, rowIndex) => {
@@ -62,23 +64,30 @@ async function updateSheetCache() {
62
 
63
  if (isValidName(rowData.Location)) {
64
  currentLocation = rowData.Location;
 
 
 
 
 
 
65
  }
66
  rowData.Location = currentLocation;
67
-
 
68
  const baseId = `r${rowIndex}`;
69
 
70
  if (isValidName(rowData.Provider)) {
71
  allStaff.push({ ...rowData, id: `${baseId}-p`, role: 'Provider' });
72
  }
73
- if (isValidName(rowData['Team Leads/Manager'])) {
74
- allStaff.push({ ...rowData, id: `${baseId}-tl`, role: 'Team Lead / Manager', Extension: rowData['Ext/Phone#'] });
75
- }
76
  if (isValidName(rowData.MA)) {
77
  allStaff.push({ ...rowData, id: `${baseId}-ma`, role: 'Medical Assistant', Extension: rowData.Extension });
78
  }
79
  if (isValidName(rowData.VA)) {
80
  allStaff.push({ ...rowData, id: `${baseId}-va`, role: 'Virtual Assistant', Extension: rowData.Ext });
81
  }
 
 
 
82
  if (isValidName(rowData['Ext/Phone#']) && /^[a-zA-Z]+-\s*\d+$/.test(rowData['Ext/Phone#'])) {
83
  const [name, ext] = rowData['Ext/Phone#'].split('-');
84
  allStaff.push({ ...rowData, id: `${baseId}-other`, role: 'Other Staff', OtherStaffName: name.trim(), Extension: ext.trim() });
@@ -86,7 +95,7 @@ async function updateSheetCache() {
86
  });
87
 
88
  processedStaffCache = allStaff;
89
- console.log('Cache updated. Processed records:', processedStaffCache.length, 'Raw rows:', rawSheetCache.length);
90
  } catch (error) {
91
  console.error('Error refreshing spreadsheet cache:', error.message);
92
  }
@@ -102,7 +111,6 @@ async function generateWithRetry(model, prompt, maxRetries = 3) {
102
  } catch (error) {
103
  if (error.status === 503 && attempt < maxRetries - 1) {
104
  const delayTime = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
105
- console.log(`Attempt ${attempt + 1} failed. Retrying in ${Math.round(delayTime / 1000)}s...`);
106
  await delay(delayTime);
107
  attempt++;
108
  } else {
@@ -112,6 +120,10 @@ async function generateWithRetry(model, prompt, maxRetries = 3) {
112
  }
113
  }
114
 
 
 
 
 
115
  app.get('/api/data', (req, res) => {
116
  if (!processedStaffCache) {
117
  return res.status(503).json({ message: 'Data is being fetched.' });
@@ -138,11 +150,17 @@ app.get('/api/ask-luis', async (req, res) => {
138
  const prompt = `
139
  You are "Luis", an AI data analyst for the Hillside Medical Group staff directory.
140
  Your primary function is to answer questions about staff based ONLY on the JSON data provided.
141
- **Core Instructions:**
142
- 1. **Data Schema:** Each object in the JSON array represents ONE person. Their role is in the 'role' key.
143
- 2. **Name Keys:** Use the correct key for the person's name based on their role: 'Provider' for Providers, 'Team Leads/Manager' for Managers, 'MA' for Medical Assistants, 'VA' for Virtual Assistants, and 'OtherStaffName' for Other Staff.
144
- 3. **Structured Output:** For lists of people, ALWAYS use a Markdown table.
145
- 4. **Ambiguity:** If a question is unclear, ask for the specific clinic location.
 
 
 
 
 
 
146
  **Directory Data:**
147
  ${JSON.stringify(processedStaffCache)}
148
  **User's Question:**
@@ -156,7 +174,10 @@ app.get('/api/ask-luis', async (req, res) => {
156
  }
157
  }
158
  } catch (error) {
159
- const errorMessage = error.status === 503 ? "The service is busy. Please try again." : "I'm having trouble connecting.";
 
 
 
160
  res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`);
161
  } finally {
162
  res.write('data: [DONE]\n\n');
 
2
  const { google } = require('googleapis');
3
  const { GoogleGenerativeAI } = require('@google/generative-ai');
4
  const cors = require('cors');
5
+ const path = require('path');
6
  require('dotenv').config();
7
 
8
  const app = express();
 
55
  const headers = rows[0];
56
  const dataRows = rows.slice(1);
57
  let currentLocation = '';
58
+ let currentOfficeExt = 'N/A';
59
  const allStaff = [];
60
 
61
  dataRows.forEach((row, rowIndex) => {
 
64
 
65
  if (isValidName(rowData.Location)) {
66
  currentLocation = rowData.Location;
67
+ if (currentLocation.toLowerCase().includes('liveoak')) {
68
+ currentOfficeExt = 'Main Branch';
69
+ } else {
70
+ const extMatch = currentLocation.match(/Ext\s*(\d+)/i);
71
+ currentOfficeExt = extMatch ? extMatch[1] : 'N/A';
72
+ }
73
  }
74
  rowData.Location = currentLocation;
75
+ rowData.OfficeExtension = currentOfficeExt;
76
+
77
  const baseId = `r${rowIndex}`;
78
 
79
  if (isValidName(rowData.Provider)) {
80
  allStaff.push({ ...rowData, id: `${baseId}-p`, role: 'Provider' });
81
  }
 
 
 
82
  if (isValidName(rowData.MA)) {
83
  allStaff.push({ ...rowData, id: `${baseId}-ma`, role: 'Medical Assistant', Extension: rowData.Extension });
84
  }
85
  if (isValidName(rowData.VA)) {
86
  allStaff.push({ ...rowData, id: `${baseId}-va`, role: 'Virtual Assistant', Extension: rowData.Ext });
87
  }
88
+ if (isValidName(rowData['Team Leads/Manager'])) {
89
+ allStaff.push({ ...rowData, id: `${baseId}-tl`, role: 'Team Lead / Manager', Extension: rowData['Ext/Phone#'] });
90
+ }
91
  if (isValidName(rowData['Ext/Phone#']) && /^[a-zA-Z]+-\s*\d+$/.test(rowData['Ext/Phone#'])) {
92
  const [name, ext] = rowData['Ext/Phone#'].split('-');
93
  allStaff.push({ ...rowData, id: `${baseId}-other`, role: 'Other Staff', OtherStaffName: name.trim(), Extension: ext.trim() });
 
95
  });
96
 
97
  processedStaffCache = allStaff;
98
+ console.log('Cache updated. Processed records:', processedStaffCache.length);
99
  } catch (error) {
100
  console.error('Error refreshing spreadsheet cache:', error.message);
101
  }
 
111
  } catch (error) {
112
  if (error.status === 503 && attempt < maxRetries - 1) {
113
  const delayTime = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
 
114
  await delay(delayTime);
115
  attempt++;
116
  } else {
 
120
  }
121
  }
122
 
123
+ app.get('/', (req, res) => {
124
+ res.sendFile(path.join(__dirname, 'index.html'));
125
+ });
126
+
127
  app.get('/api/data', (req, res) => {
128
  if (!processedStaffCache) {
129
  return res.status(503).json({ message: 'Data is being fetched.' });
 
150
  const prompt = `
151
  You are "Luis", an AI data analyst for the Hillside Medical Group staff directory.
152
  Your primary function is to answer questions about staff based ONLY on the JSON data provided.
153
+ **Your Core Instructions:**
154
+ 1. **Data Relationships:**
155
+ - The 'Location' key groups all staff at a specific clinic.
156
+ - 'MA' (Medical Assistant) is directly associated with the 'Provider' in the same row.
157
+ - 'VA' (Virtual Assistant) is associated with the 'Location' of its row. Their extension is in the 'Ext' column.
158
+ - The 'Team Leads/Manager' column lists supervisors for a 'Location'. Their extension is in 'Ext/Phone#'.
159
+ 2. **Structured Output:** When asked to list multiple people (e.g., "list the VAs for LiveOak"), you MUST format the response as a clean Markdown table. Include relevant columns like Name and Extension.
160
+ 3. **Highlighting:** To make important data like names, locations, or extensions stand out in your text or tables, enclose them in single backticks. For example: "The main office for \`LiveOak\` is the \`Main Branch\`."
161
+ 4. **Aggregations (Counts):** If asked for a count ("how many MAs?", "VA count"), calculate the total from the data and give a precise number.
162
+ 5. **Handling Ambiguity:** If a question is ambiguous (e.g., "give me the list of MAs" without a location), you MUST ask a clarifying question (e.g., "Of which location would you like the list of MAs? You can also ask for all of them.").
163
+
164
  **Directory Data:**
165
  ${JSON.stringify(processedStaffCache)}
166
  **User's Question:**
 
174
  }
175
  }
176
  } catch (error) {
177
+ const errorMessage = error.status === 503
178
+ ? "The directory service is currently busy. Please try again in a few moments."
179
+ : "I'm having a bit of trouble connecting right now.";
180
+
181
  res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`);
182
  } finally {
183
  res.write('data: [DONE]\n\n');