Spaces:
Paused
Paused
CrispStrobe commited on
Commit ·
225d830
1
Parent(s): 02e6e85
feat: add interactive metadata info icon with direct Hugging Face links
Browse files- scripts/providers/infomaniak.js +6 -5
- src/App.css +78 -0
- src/App.tsx +23 -1
scripts/providers/infomaniak.js
CHANGED
|
@@ -24,9 +24,9 @@ const { getText } = require('../fetch-utils');
|
|
| 24 |
|
| 25 |
const URL = 'https://www.infomaniak.com/en/hosting/ai-services/prices';
|
| 26 |
|
| 27 |
-
const
|
| 28 |
if (!text) return null;
|
| 29 |
-
if (text.trim().toLowerCase() === 'free') return 0;
|
| 30 |
const m = text.trim().match(/([\d]+\.[\d]*|[\d]+)/);
|
| 31 |
return m ? parseFloat(m[1]) : null;
|
| 32 |
};
|
|
@@ -50,6 +50,7 @@ async function fetchInfomaniak() {
|
|
| 50 |
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
| 51 |
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
| 52 |
'Accept-Language': 'en-US,en;q=0.9',
|
|
|
|
| 53 |
},
|
| 54 |
});
|
| 55 |
const $ = cheerio.load(html);
|
|
@@ -83,12 +84,12 @@ async function fetchInfomaniak() {
|
|
| 83 |
if (currText && /^[A-Z]{3}$/.test(currText)) currency = currText;
|
| 84 |
|
| 85 |
const valSpan = $(priceRow).find('[class*="IkTypography-module--h3"]').first();
|
| 86 |
-
const val =
|
| 87 |
if (val === null) return;
|
| 88 |
|
| 89 |
-
if (label.includes('incoming') || label.includes('input')) {
|
| 90 |
inputPrice = val;
|
| 91 |
-
} else if (label.includes('outgoing') || label.includes('output')) {
|
| 92 |
outputPrice = val;
|
| 93 |
} else if (label.includes('image') || label.includes('per image')) {
|
| 94 |
inputPrice = val; // image models: price per image stored as input
|
|
|
|
| 24 |
|
| 25 |
const URL = 'https://www.infomaniak.com/en/hosting/ai-services/prices';
|
| 26 |
|
| 27 |
+
const parsePrice = (text) => {
|
| 28 |
if (!text) return null;
|
| 29 |
+
if (text.trim().toLowerCase() === 'free' || text.trim().toLowerCase() === 'gratuit') return 0;
|
| 30 |
const m = text.trim().match(/([\d]+\.[\d]*|[\d]+)/);
|
| 31 |
return m ? parseFloat(m[1]) : null;
|
| 32 |
};
|
|
|
|
| 50 |
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
| 51 |
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
| 52 |
'Accept-Language': 'en-US,en;q=0.9',
|
| 53 |
+
'Cookie': 'STATIC_CURRENCY=EUR; locale=en_US',
|
| 54 |
},
|
| 55 |
});
|
| 56 |
const $ = cheerio.load(html);
|
|
|
|
| 84 |
if (currText && /^[A-Z]{3}$/.test(currText)) currency = currText;
|
| 85 |
|
| 86 |
const valSpan = $(priceRow).find('[class*="IkTypography-module--h3"]').first();
|
| 87 |
+
const val = parsePrice(valSpan.text());
|
| 88 |
if (val === null) return;
|
| 89 |
|
| 90 |
+
if (label.includes('entrant') || label.includes('incoming') || label.includes('input')) {
|
| 91 |
inputPrice = val;
|
| 92 |
+
} else if (label.includes('sortant') || label.includes('outgoing') || label.includes('output')) {
|
| 93 |
outputPrice = val;
|
| 94 |
} else if (label.includes('image') || label.includes('per image')) {
|
| 95 |
inputPrice = val; // image models: price per image stored as input
|
src/App.css
CHANGED
|
@@ -151,6 +151,84 @@ tr.group-divider {
|
|
| 151 |
color: #1e293b;
|
| 152 |
}
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
.size-cell {
|
| 155 |
font-family: 'JetBrains Mono', monospace;
|
| 156 |
font-size: 0.85rem;
|
|
|
|
| 151 |
color: #1e293b;
|
| 152 |
}
|
| 153 |
|
| 154 |
+
.model-name-wrapper {
|
| 155 |
+
display: flex;
|
| 156 |
+
align-items: center;
|
| 157 |
+
gap: 0.5rem;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.model-info-container {
|
| 161 |
+
position: relative;
|
| 162 |
+
display: inline-flex;
|
| 163 |
+
cursor: help;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.info-icon {
|
| 167 |
+
font-size: 0.85rem;
|
| 168 |
+
color: #94a3b8;
|
| 169 |
+
opacity: 0.6;
|
| 170 |
+
transition: opacity 0.2s;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.model-info-container:hover .info-icon {
|
| 174 |
+
opacity: 1;
|
| 175 |
+
color: var(--primary-color);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.model-tooltip {
|
| 179 |
+
visibility: hidden;
|
| 180 |
+
position: absolute;
|
| 181 |
+
bottom: 100%;
|
| 182 |
+
left: 50%;
|
| 183 |
+
transform: translateX(-50%) translateY(-8px);
|
| 184 |
+
background-color: #1e293b;
|
| 185 |
+
color: #f8fafc;
|
| 186 |
+
padding: 0.75rem;
|
| 187 |
+
border-radius: 0.5rem;
|
| 188 |
+
font-family: 'Inter', sans-serif;
|
| 189 |
+
font-size: 0.75rem;
|
| 190 |
+
font-weight: 400;
|
| 191 |
+
white-space: nowrap;
|
| 192 |
+
z-index: 100;
|
| 193 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 194 |
+
pointer-events: none;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.model-info-container:hover .model-tooltip {
|
| 198 |
+
visibility: visible;
|
| 199 |
+
pointer-events: auto;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
.model-tooltip::after {
|
| 203 |
+
content: "";
|
| 204 |
+
position: absolute;
|
| 205 |
+
top: 100%;
|
| 206 |
+
left: 50%;
|
| 207 |
+
margin-left: -5px;
|
| 208 |
+
border-width: 5px;
|
| 209 |
+
border-style: solid;
|
| 210 |
+
border-color: #1e293b transparent transparent transparent;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.tooltip-row {
|
| 214 |
+
margin-bottom: 0.25rem;
|
| 215 |
+
}
|
| 216 |
+
.tooltip-row:last-child {
|
| 217 |
+
margin-bottom: 0;
|
| 218 |
+
}
|
| 219 |
+
.tooltip-row strong {
|
| 220 |
+
color: #94a3b8;
|
| 221 |
+
margin-right: 0.25rem;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.hf-link {
|
| 225 |
+
color: #60a5fa;
|
| 226 |
+
text-decoration: none;
|
| 227 |
+
}
|
| 228 |
+
.hf-link:hover {
|
| 229 |
+
text-decoration: underline;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
.size-cell {
|
| 233 |
font-family: 'JetBrains Mono', monospace;
|
| 234 |
font-size: 0.85rem;
|
src/App.tsx
CHANGED
|
@@ -500,7 +500,29 @@ function App() {
|
|
| 500 |
{model.complianceStatus}
|
| 501 |
</span>
|
| 502 |
</td>
|
| 503 |
-
<td className="model-name">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
<td className="caps-cell">
|
| 505 |
{model.type === 'embedding' && (
|
| 506 |
<span className="cap-badge cap-embedding" title="embedding">{CAP_ICON.embedding}</span>
|
|
|
|
| 500 |
{model.complianceStatus}
|
| 501 |
</span>
|
| 502 |
</td>
|
| 503 |
+
<td className="model-name">
|
| 504 |
+
<div className="model-name-wrapper">
|
| 505 |
+
{model.display_name ?? model.name}
|
| 506 |
+
<div className="model-info-container">
|
| 507 |
+
<span className="info-icon">ⓘ</span>
|
| 508 |
+
<div className="model-tooltip">
|
| 509 |
+
<div className="tooltip-row"><strong>Type:</strong> {model.type}</div>
|
| 510 |
+
{model.size_b && <div className="tooltip-row"><strong>Size:</strong> {model.size_b}B</div>}
|
| 511 |
+
{model.hf_id && (
|
| 512 |
+
<div className="tooltip-row">
|
| 513 |
+
<strong>HF:</strong>
|
| 514 |
+
<a href={`https://huggingface.co/${model.hf_id}`} target="_blank" rel="noopener noreferrer" className="hf-link">
|
| 515 |
+
{model.hf_id} ↗
|
| 516 |
+
</a>
|
| 517 |
+
</div>
|
| 518 |
+
)}
|
| 519 |
+
{model.capabilities && model.capabilities.length > 0 && (
|
| 520 |
+
<div className="tooltip-row"><strong>Caps:</strong> {model.capabilities.join(', ')}</div>
|
| 521 |
+
)}
|
| 522 |
+
</div>
|
| 523 |
+
</div>
|
| 524 |
+
</div>
|
| 525 |
+
</td>
|
| 526 |
<td className="caps-cell">
|
| 527 |
{model.type === 'embedding' && (
|
| 528 |
<span className="cap-badge cap-embedding" title="embedding">{CAP_ICON.embedding}</span>
|