Skip to content

Commit 1744d6a

Browse files
committed
Updated UI
1 parent 82fca4b commit 1744d6a

34 files changed

Lines changed: 1333 additions & 490 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pip-log.txt
3737
pip-delete-this-directory.txt
3838

3939
# Unit test / coverage reports
40+
node_modules/
41+
test-results/
42+
playwright-report/
4043
htmlcov/
4144
.tox/
4245
.nox/

README.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Model Meters - Azure AI Foundry Pricing Explorer
1+
# Model Meters - Microsoft Foundry Pricing Explorer
22

33
[modelmeters.com](https://modelmeters.com) provides a simple front-end to the [Azure Retail Prices API](https://docs.microsoft.com/rest/api/cost-management/retail-prices/azure-retail-prices), automatically downloading and enriching Azure AI pricing data with AI-generated summaries and insights.
44

5-
<img width="1280" height="640" alt="socialpreview" src="https://github.com/user-attachments/assets/3cb11499-770f-4e87-818a-af22979e5595" />
5+
<img width="1280" height="640" alt="Model Meters Microsoft Foundry pricing explorer" src="img/socialpreview.png" />
66

77
## Cloud Champion overview session and slides (Microsoft Partners)
88

@@ -18,7 +18,7 @@ Model Meters combines several Azure and GitHub services to create an automated p
1818

1919
1. **Daily Data Collection**: GitHub Actions automatically downloads the latest Azure pricing data using the Azure Retail Prices API
2020
2. **Data Processing**: The raw pricing data is split into segments based on the pricing `startDate` value
21-
3. **AI Enhancement**: Azure OpenAI (via Azure AI Foundry) generates intelligent summaries of the latest pricing changes, using Model Context Protocol (MCP) to include Microsoft Learn documentation
21+
3. **AI Enhancement**: Foundry models generate intelligent summaries of the latest pricing changes, using Model Context Protocol (MCP) to include Microsoft Learn documentation
2222
4. **Web Deployment**: The processed data and summaries are automatically deployed to Azure Static Web Apps using GitHub Actions
2323
5. **User Interface**: A responsive web interface allows users to explore pricing data and AI-generated insights
2424

@@ -29,14 +29,14 @@ Model Meters combines several Azure and GitHub services to create an automated p
2929
- **Documentation Integration**: Uses [Microsoft Learn MCP Server](https://github.com/microsoftdocs/mcp) to automatically include relevant documentation links
3030
- **Human-in-the-loop**: As AI summaries are stored in markdown format, this allows for quick and easy human review (and potentially AI agent review)
3131

32-
<img width="1531" height="700" alt="image" src="https://github.com/user-attachments/assets/aabbf3c8-14cb-488a-a49c-cca6440bb33d" />
32+
<img width="1531" height="700" alt="Model Meters AI Summaries with an expanded monthly pricing summary" src="img/ai-summaries.png" />
3333

3434
## Who Would Benefit From This?
3535

3636
This sample is designed to help Microsoft partners (and their customers) understand how they can:
3737

3838
- **Use GitHub and GitHub Copilot** to rapidly build a simple AI agent for data processing
39-
- **Combine GitHub Actions with Azure AI Foundry** to automate repeatable tasks at scale
39+
- **Combine GitHub Actions with Microsoft Foundry** to automate repeatable tasks at scale
4040
- **Use LLMs to enrich frequently updated structured data**, such as price lists, inventory, or sales data
4141
- **Incorporate the use of Model Context Protocol (MCP)** within their solutions for enhanced AI capabilities
4242
- **Build cost-effective monitoring solutions** for tracking Azure service pricing changes
@@ -48,7 +48,7 @@ This sample is designed to help Microsoft partners (and their customers) underst
4848
- **[GitHub Copilot](https://github.com/features/copilot)** - AI-powered code completion and development assistance
4949
- **[GitHub Actions](https://github.com/features/actions)** - CI/CD automation and workflow orchestration
5050
- **[Azure Static Web Apps](https://azure.microsoft.com/services/app-service/static/)** - Static site hosting with integrated CI/CD
51-
- **[Azure AI Foundry](https://azure.microsoft.com/products/ai-foundry/)** - AI model deployment and management platform
51+
- **[Microsoft Foundry](https://azure.microsoft.com/products/ai-foundry/)** - AI model deployment and management platform
5252
- **[Model Context Protocol (MCP)](https://modelcontextprotocol.io/)** - Protocol for AI model context sharing
5353

5454
### Development Stack
@@ -79,7 +79,7 @@ The minimal solution takes advantage of free tiers where available, and has been
7979
| Component | Minimal demo | Advanced demo | Notes |
8080
|-----------|----------------------|------------------------|-------|
8181
| **Azure Static Web Apps** | $0/month | $9/month (Standard) | Free tier includes 100GB bandwidth, 0.5GB storage |
82-
| **Azure AI Foundry - OpenAI** | ~$5-20/month | ~$50-100/month | Price varies based on token usage |
82+
| **Microsoft Foundry - OpenAI** | ~$5-20/month | ~$50-100/month | Price varies based on token usage |
8383
| **Azure Retail Prices API** | $0/month | $0/month | Free public API with rate limits |
8484
| **GitHub Actions** | $0/month | $4/month (Team plan) | 2000 minutes/month free, then $0.008/minute |
8585
| **GitHub Repository** | $0/month | $4/month (Team plan) | Public and private repos free, additional features in Team plan |
@@ -153,6 +153,23 @@ The minimal solution takes advantage of free tiers where available, and has been
153153
# Visit http://localhost:8000
154154
```
155155

156+
### UI Development and Checks
157+
158+
The pricing explorers and summary pages share [site.css](site.css). The IBM Plex Sans font and Lucide icons are served locally from [img/fonts](img/fonts) and [img/icons](img/icons), with their license files. No application build step is required.
159+
160+
With the local Python server running in another terminal, run the browser regression suite:
161+
162+
```bash
163+
cd tests
164+
npm ci
165+
npx playwright install --with-deps chromium
166+
npm test
167+
```
168+
169+
Set `MODELMETERS_URL` to test another server origin. The suite checks both pricing explorers, summary navigation, filtering, sorting, pagination, column visibility, discounts, CSV exports, shared URLs, themes, and desktop/mobile layouts. Screenshots are written to the ignored `tests/test-results/` directory. The summary pages retain their existing dependency on the marked.js CDN.
170+
171+
When changing the monthly archive layout, update [consolidate-ai-summaries.py](consolidate-ai-summaries.py) as well as [monthly/index.html](monthly/index.html) so regeneration preserves the presentation.
172+
156173
### Deployment
157174

158175
The project automatically deploys to Azure Static Web Apps via GitHub Actions when changes are pushed to the main branch.
@@ -253,7 +270,7 @@ response = client.responses.create(
253270
## Support and Resources
254271

255272
- **Azure Pricing Documentation**: https://docs.microsoft.com/azure/cost-management-billing/
256-
- **Azure AI Foundry**: https://docs.microsoft.com/azure/ai-foundry/
273+
- **Microsoft Foundry**: https://docs.microsoft.com/azure/ai-foundry/
257274
- **GitHub Actions**: https://docs.github.com/en/actions
258275
- **Azure Static Web Apps**: https://docs.microsoft.com/azure/static-web-apps/
259276
- **Microsoft Learn MCP Server**: [https://github.com/microsoftdocs/mcp](https://github.com/microsoftdocs/mcp)

agent/index.html

Lines changed: 32 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,74 +5,35 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Model Meters - AI summary</title>
77
<link rel="icon" type="image/png" href="../img/favicon.png">
8-
<style>
9-
/* Light theme (default) */
10-
:root {
11-
--bg:#f5f7fa; --bg-alt:#e9eef2; --panel:#ffffff; --panel-2:#f0f3f7; --text:#1b242b; --muted:#5d6b76; --accent:#2563eb; --border:#d0d7de; --ring:#3b82f680; --shadow:0 6px 18px rgba(0,0,0,.08);
12-
--topbar-offset: 0px; /* header not sticky on monthly page */
13-
}
14-
/* Dark theme */
15-
:root[data-theme="dark"] {
16-
--bg:#0b0c10; --bg-alt:#0e1116; --panel:#14161a; --panel-2:#1b1f24; --text:#e8eef3; --muted:#a8b3bd; --accent:#3da9fc; --border:#2a2f36; --ring:#7cc4ff80; --shadow:0 10px 24px rgba(0,0,0,.35);
17-
}
18-
html, body { min-height: 100%; }
19-
html { scroll-padding-top: calc(var(--topbar-offset) + 16px); -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
20-
body { margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Inter, Arial; background: linear-gradient(180deg,var(--bg) 0%, var(--bg-alt) 100%); color: var(--text); }
21-
.container { min-height:100dvh; padding:12px 12px 16px; box-sizing:border-box; }
22-
.card { background: var(--panel); border:1px solid var(--border); border-radius:16px; box-shadow: var(--shadow); overflow:hidden; position:relative; clip-path: inset(0 round 16px); }
23-
.card-hd { position:relative; background:var(--panel); display:flex; gap:12px; align-items:center; justify-content:space-between; padding:14px 16px; border-bottom:1px solid var(--border); flex-wrap:wrap; }
24-
.title { font-size:18px; font-weight:650; letter-spacing:.2px; }
25-
.toolbar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
26-
button { background:var(--panel-2); color:var(--text); border:1px solid var(--border); border-radius:10px; padding:8px 10px; font-size:14px; line-height:1; outline:none; cursor:pointer; }
27-
button:hover { border-color:#374151; }
28-
button:focus { box-shadow:0 0 0 4px var(--ring); border-color:var(--accent); }
29-
30-
.content { display:grid; grid-template-columns: 1fr; gap:16px; padding:16px; }
31-
.articles { min-width:0; }
32-
.article { background: var(--panel); border:1px solid var(--border); border-radius:12px; padding:0; margin-bottom:12px; box-shadow: var(--shadow); scroll-margin-top: calc(var(--topbar-offset) + 16px); }
33-
.article:target::before { content: ""; display:block; height: calc(var(--topbar-offset) + 16px); margin-top: calc(-1 * (var(--topbar-offset) + 16px)); }
34-
.month-title { cursor:pointer; display:flex; align-items:center; gap:8px; padding:14px 16px; border-bottom:1px solid var(--border); user-select:none; }
35-
.month-title h2 { margin:0; font-size:22px; line-height:1.2; font-weight:700; }
36-
.month-title .chev { transition: transform .2s; }
37-
/* When collapsed, rotate the right-pointing chevron to point down */
38-
.article.collapsed .month-title .chev { transform: rotate(90deg); }
39-
.month-details { padding:16px; }
40-
41-
/* Minimal markdown styles */
42-
.markdown-body h1, .markdown-body h2, .markdown-body h3 { border-bottom:1px solid var(--border); padding-bottom:4px; }
43-
.markdown-body pre { background: var(--panel-2); border:1px solid var(--border); padding:12px; border-radius:10px; overflow:auto; }
44-
.markdown-body code { background: var(--panel-2); border:1px solid var(--border); padding:2px 6px; border-radius:8px; }
45-
.markdown-body table { border-collapse: collapse; width: 100%; }
46-
.markdown-body th, .markdown-body td { border: 1px solid var(--border); padding: 6px 8px; }
47-
.article.collapsed .month-details { display:none; }
48-
49-
.footer { color: var(--muted); text-align:center; padding: 12px 16px; border-top:1px solid var(--border); }
50-
@media (max-width: 900px) { .content { grid-template-columns: 1fr; } }
51-
</style>
8+
<link rel="stylesheet" href="../site.css">
529
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
5310
<base target="_self">
5411
<meta name="color-scheme" content="light dark">
55-
<meta name="theme-color" content="#2563eb">
56-
<link rel="icon" href="data:,">
12+
<meta name="theme-color" content="#087f6b">
13+
5714
</head>
58-
<body>
15+
<body class="summary-page ai-summary-page">
5916
<div class="container">
6017
<div class="card">
61-
<div class="card-hd">
62-
<div class="title">Model Meters - AI summary agent ✨</div>
63-
<div style="display:flex; align-items:center; gap:12px; margin-left:auto;">
64-
<span class="title" style="font-size:16px; font-weight:500; white-space:nowrap;">Powered by <a href="https://ai.azure.com/" target="_blank" rel="noopener noreferrer" style="color:var(--accent);text-decoration:none;">Azure AI</a> + <a href="https://github.com/microsoftdocs/mcp" target="_blank" rel="noopener noreferrer" style="color:var(--accent);text-decoration:none;">Microsoft Learn MCP Server</a></span>
65-
<button id="btn-rss" title="RSS Feed">🌐 RSS</button>
66-
<button id="btn-theme" title="Toggle light/dark mode">🌗 Theme</button>
67-
<button id="btn-back" title="Back to Model Meters">↩️Model Meters</button>
68-
</div>
18+
<div class="card-hd">
19+
<div class="title"><a href="../index.html" class="brand">Model Meters</a></div>
20+
<nav class="workspace-nav" aria-label="Main navigation">
21+
<a href="../index.html">Microsoft Foundry</a>
22+
<a href="../github/index.html">GitHub Meters</a>
23+
<a href="./" aria-current="page"><span class="ui-icon icon-sparkles" aria-hidden="true"></span>AI Summaries</a>
24+
</nav>
25+
<div class="toolbar">
26+
<button id="btn-rss" title="RSS Feed"><span class="ui-icon icon-rss" aria-hidden="true"></span>RSS feed</button>
27+
<button id="btn-back" title="Back to Model Meters"><span class="ui-icon icon-back" aria-hidden="true"></span>Model Meters</button>
28+
<button id="btn-theme" class="icon-button" title="Toggle light/dark mode" aria-label="Toggle light/dark mode"><span class="ui-icon icon-moon" aria-hidden="true"></span></button>
29+
</div>
6930
</div>
7031
<div class="content">
7132
<section class="articles" id="articles">
7233
<div id="status" class="footer" style="border:none;">Loading monthly summaries…</div>
7334
</section>
7435
</div>
75-
<div class="footer">Built from Markdown files in <code>monthly/aisummary/</code> at runtime.</div>
36+
<div class="footer">Powered by <a href="https://ai.azure.com/" target="_blank" rel="noopener noreferrer">Azure AI</a> + <a href="https://github.com/microsoftdocs/mcp" target="_blank" rel="noopener noreferrer">Microsoft Learn MCP Server</a></div>
7637
</div>
7738
</div>
7839

@@ -106,9 +67,10 @@
10667
function updateBtn(){
10768
const m = currentMode();
10869
// Show target mode on the button (same UX as main page)
109-
const nextLabel = m === 'dark' ? '☀️ Light' : '🌙 Dark';
110-
btn.textContent = nextLabel;
70+
btn.innerHTML = `<span class="ui-icon icon-${m === 'dark' ? 'sun' : 'moon'}" aria-hidden="true"></span>`;
11171
btn.setAttribute('aria-label', 'Switch to ' + (m === 'dark' ? 'light' : 'dark') + ' theme');
72+
btn.title = btn.getAttribute('aria-label');
73+
btn.setAttribute('aria-pressed', m === 'dark');
11274
}
11375

11476
applyMode(mode);
@@ -192,11 +154,18 @@
192154

193155
const header = document.createElement('div');
194156
header.className = 'month-title';
195-
header.innerHTML = `<span class="chev">▶</span><h2 class="month-heading">${title}</h2>`;
157+
header.setAttribute('role', 'button');
158+
header.setAttribute('tabindex', '0');
159+
header.setAttribute('aria-expanded', String(!collapsed));
160+
header.innerHTML = `<span class="chev ui-icon icon-next" aria-hidden="true"></span><h2 class="month-heading">${title}</h2>`;
196161
header.addEventListener('click', () => {
197162
article.classList.toggle('collapsed');
163+
header.setAttribute('aria-expanded', String(!article.classList.contains('collapsed')));
198164
history.replaceState(null, '', '#' + id); // keep URL in sync
199165
});
166+
header.addEventListener('keydown', event => {
167+
if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); header.click(); }
168+
});
200169

201170
const details = document.createElement('div');
202171
details.className = 'month-details';
@@ -263,7 +232,10 @@
263232
// If there's a hash, expand and scroll
264233
if (location.hash) {
265234
const el = document.getElementById(location.hash.slice(1));
266-
if (el) el.classList.remove('collapsed');
235+
if (el) {
236+
el.classList.remove('collapsed');
237+
el.querySelector('.month-title').setAttribute('aria-expanded', 'true');
238+
}
267239
}
268240
} catch (err) {
269241
console.error(err);

0 commit comments

Comments
 (0)