StablecogAPI / index.html
MySafeCode's picture
Create index.html
2eeac5f verified
raw
history blame
44.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>StableCog Dashboard</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>">
<style>
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--success-color: #4CAF50;
--warning-color: #FF9800;
--danger-color: #F44336;
--light-color: #90EE90;
--dark-color: #333;
--card-bg: rgba(255, 255, 255, 0.1);
--text-light: #ffffff;
--text-dark: #333333;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
--border-radius: 15px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
min-height: 100vh;
padding: 20px;
color: var(--text-light);
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
}
.header h1 {
font-size: 2.8rem;
margin-bottom: 10px;
font-weight: 700;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
margin-bottom: 30px;
}
.card {
background: var(--card-bg);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: var(--shadow);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card-title {
font-size: 1.5rem;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title i {
font-size: 1.8rem;
}
.stat-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 20px;
}
.stat-box {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
text-align: center;
}
.stat-value {
font-size: 2.2rem;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.8;
}
.progress-container {
margin: 20px 0;
}
.progress-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.progress-bar {
height: 22px;
background: rgba(255, 255, 255, 0.15);
border-radius: 11px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
border-radius: 11px;
transition: width 0.5s ease-in-out;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.progress-text {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
font-weight: bold;
margin-top: 15px;
}
.models-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
max-height: 500px;
overflow-y: auto;
padding-right: 10px;
}
.model-card {
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
border-radius: 10px;
padding: 15px;
border-left: 4px solid var(--primary-color);
}
.model-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.model-name {
font-weight: bold;
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.model-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: bold;
}
.badge-default { background: var(--success-color); }
.badge-public { background: #2196F3; }
.badge-community { background: var(--danger-color); }
.badge-other { background: #9E9E9E; }
.model-id {
font-size: 0.8rem;
opacity: 0.7;
}
.model-description {
font-size: 0.9rem;
opacity: 0.9;
margin-bottom: 10px;
line-height: 1.4;
}
.model-meta {
display: flex;
gap: 10px;
font-size: 0.8rem;
opacity: 0.7;
}
.controls {
display: flex;
gap: 15px;
margin-top: 25px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
color: white;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.btn:active {
transform: translateY(0);
}
.api-key-input {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.api-key-input input {
flex: 1;
min-width: 300px;
padding: 12px 15px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 1rem;
}
.api-key-input input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.api-key-input input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.3);
}
.error-container {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border-radius: var(--border-radius);
padding: 25px;
margin: 20px 0;
text-align: center;
}
.error-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.error-content {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.credit-breakdown {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.credit-item {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
}
.credit-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.credit-name {
font-weight: bold;
}
.credit-amount {
font-weight: bold;
}
.credit-description {
font-size: 0.8rem;
opacity: 0.8;
margin-bottom: 8px;
}
.credit-progress {
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
}
.credit-progress-fill {
height: 100%;
border-radius: 3px;
}
.last-updated {
text-align: center;
font-size: 0.9rem;
opacity: 0.7;
margin-top: 10px;
}
.tabs {
display: flex;
gap: 5px;
margin-bottom: 25px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 5px;
}
.tab {
flex: 1;
padding: 12px 20px;
text-align: center;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.tab.active {
background: white;
color: var(--primary-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.footer {
text-align: center;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
font-size: 0.9rem;
opacity: 0.7;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
.stat-grid {
grid-template-columns: 1fr;
}
.models-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
.api-key-input input {
min-width: 100%;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎯 StableCog Dashboard</h1>
<p>Monitor your credits and explore available AI models for image generation</p>
</div>
<!-- API Key Input -->
<div class="api-key-input">
<input type="password"
id="apiKeyInput"
placeholder="Enter your StableCog API key (or leave empty for demo)"
autocomplete="off">
<button class="btn btn-primary" onclick="saveApiKey()">
<span>🔑</span> Save API Key
</button>
<button class="btn btn-secondary" onclick="clearApiKey()">
<span>🗑️</span> Clear
</button>
</div>
<!-- Tabs -->
<div class="tabs">
<div class="tab active" onclick="switchTab('credits')">💰 Credits</div>
<div class="tab" onclick="switchTab('models')">🤖 Models</div>
<div class="tab" onclick="switchTab('info')">📚 Info</div>
</div>
<!-- Credits Tab -->
<div id="creditsTab" class="tab-content active">
<div class="dashboard">
<!-- Credit Status Card -->
<div class="card">
<h2 class="card-title">🎨 Credit Status</h2>
<div class="stat-grid">
<div class="stat-box">
<div class="stat-value" id="totalCredits">0</div>
<div class="stat-label">Total Credits</div>
</div>
<div class="stat-box">
<div class="stat-value" id="remainingCredits">0</div>
<div class="stat-label">Remaining Credits</div>
</div>
<div class="stat-box">
<div class="stat-value" id="usedCredits">0</div>
<div class="stat-label">Used Credits</div>
</div>
<div class="stat-box">
<div class="stat-value" id="usagePercentage">0%</div>
<div class="stat-label">Usage</div>
</div>
</div>
<div class="progress-container">
<div class="progress-header">
<span>Overall Usage</span>
<span id="usageText">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
<div class="progress-text" id="progressText">0%</div>
</div>
</div>
<div class="status-badge" id="statusBadge">
<span id="statusEmoji">🔄</span>
<span id="statusText">Loading...</span>
</div>
<!-- Credit Breakdown -->
<div class="credit-breakdown" id="creditBreakdown">
<h3 style="margin-bottom: 15px; font-size: 18px;">📊 Credit Breakdown</h3>
<!-- Credit items will be inserted here -->
</div>
<div class="last-updated" id="creditsLastUpdated">
⏰ Last checked: Never
</div>
</div>
<!-- Recommendation Card -->
<div class="card">
<h2 class="card-title">🎯 AI Recommendation</h2>
<div id="recommendation" style="font-size: 1.1rem; line-height: 1.6; opacity: 0.9;">
Enter your API key and check credits to see personalized recommendations.
</div>
</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="checkCredits()" id="checkCreditsBtn">
<span id="creditsIcon">🔄</span>
<span id="creditsText">Check Credits</span>
</button>
<button class="btn btn-secondary" onclick="viewRawResponse('credits')">
<span>📋</span> View Raw Response
</button>
</div>
<!-- Raw Response Modal -->
<div id="creditsRawModal" style="display: none; margin-top: 20px;">
<div class="card">
<h2 class="card-title">📋 Raw API Response</h2>
<pre id="creditsRawResponse" style="
background: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 8px;
overflow: auto;
max-height: 300px;
font-family: 'Courier New', monospace;
font-size: 12px;
white-space: pre-wrap;
"></pre>
<button class="btn btn-secondary" onclick="hideRawResponse('credits')" style="margin-top: 15px;">
<span></span> Close
</button>
</div>
</div>
</div>
<!-- Models Tab -->
<div id="modelsTab" class="tab-content">
<div class="dashboard">
<div class="card">
<h2 class="card-title">🤖 Available Models</h2>
<div class="stat-grid">
<div class="stat-box">
<div class="stat-value" id="totalModels">0</div>
<div class="stat-label">Total Models</div>
</div>
<div class="stat-box">
<div class="stat-value" id="publicModels">0</div>
<div class="stat-label">Public Models</div>
</div>
<div class="stat-box">
<div class="stat-value" id="defaultModels">0</div>
<div class="stat-label">Default Models</div>
</div>
<div class="stat-box">
<div class="stat-value" id="communityModels">0</div>
<div class="stat-label">Community Models</div>
</div>
</div>
<div class="models-grid" id="modelsList">
<!-- Models will be inserted here -->
<div style="text-align: center; padding: 40px; opacity: 0.7;">
Click "Load Models" to see available models
</div>
</div>
<div class="last-updated" id="modelsLastUpdated">
⏰ Last updated: Never
</div>
</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="loadModels()" id="loadModelsBtn">
<span id="modelsIcon">🔄</span>
<span id="modelsText">Load Models</span>
</button>
<button class="btn btn-secondary" onclick="viewRawResponse('models')">
<span>📋</span> View Raw Response
</button>
</div>
<!-- Raw Response Modal -->
<div id="modelsRawModal" style="display: none; margin-top: 20px;">
<div class="card">
<h2 class="card-title">📋 Raw API Response</h2>
<pre id="modelsRawResponse" style="
background: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 8px;
overflow: auto;
max-height: 300px;
font-family: 'Courier New', monospace;
font-size: 12px;
white-space: pre-wrap;
"></pre>
<button class="btn btn-secondary" onclick="hideRawResponse('models')" style="margin-top: 15px;">
<span></span> Close
</button>
</div>
</div>
</div>
<!-- Info Tab -->
<div id="infoTab" class="tab-content">
<div class="card">
<h2 class="card-title">📚 How to Use & Setup</h2>
<div style="line-height: 1.6; opacity: 0.9;">
<h3 style="margin: 20px 0 10px 0;">Getting Started:</h3>
<ol style="margin-left: 20px; margin-bottom: 20px;">
<li>Enter your StableCog API key in the input field above</li>
<li>Click "Save API Key" to store it in your browser</li>
<li>Switch to "Credits" tab and click "Check Credits"</li>
<li>Switch to "Models" tab and click "Load Models"</li>
</ol>
<h3 style="margin: 20px 0 10px 0;">Understanding Credits:</h3>
<ul style="margin-left: 20px; margin-bottom: 20px;">
<li><strong>Total Credits:</strong> Sum of all initial credits received</li>
<li><strong>Remaining Credits:</strong> Currently available credits</li>
<li><strong>Used Credits:</strong> Credits spent on image generation</li>
<li><strong>Credit Types:</strong> Different sources (Free, Refund, etc.)</li>
</ul>
<h3 style="margin: 20px 0 10px 0;">Privacy & Security:</h3>
<ul style="margin-left: 20px; margin-bottom: 20px;">
<li>Your API key is stored only in your browser's localStorage</li>
<li>No data is sent to any server except StableCog API</li>
<li>You can clear the API key anytime using the "Clear" button</li>
<li>All API calls are made directly from your browser</li>
</ul>
<h3 style="margin: 20px 0 10px 0;">Troubleshooting:</h3>
<ul style="margin-left: 20px;">
<li>Ensure your API key is correct and has proper permissions</li>
<li>Check your internet connection</li>
<li>Verify StableCog API is available</li>
<li>Use "View Raw Response" to see detailed error messages</li>
</ul>
</div>
</div>
</div>
<!-- Error Container (hidden by default) -->
<div id="errorContainer" class="error-container" style="display: none;">
<h2 class="error-title" id="errorTitle">⚠️ API Error</h2>
<div class="error-content">
<p id="errorMessage"></p>
</div>
<button class="btn btn-secondary" onclick="hideError()">
<span></span> Dismiss
</button>
</div>
<div class="footer">
Built with ❤️ for StableCog users |
<a href="https://stablecog.com/docs/api" target="_blank" style="color: var(--light-color); text-decoration: none;">
API Documentation
</a> |
<a href="https://github.com/stability-ai/stablecog/issues" target="_blank" style="color: var(--light-color); text-decoration: none;">
Report Issues
</a>
</div>
</div>
<script>
// Configuration
const API_HOST = 'https://api.stablecog.com';
const CREDITS_ENDPOINT = '/v1/credits';
const MODELS_ENDPOINT = '/v1/image/generation/models';
// State
let apiKey = '';
let creditsData = null;
let modelsData = null;
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
loadApiKey();
// Check if API key exists, if so auto-check credits
if (apiKey) {
setTimeout(() => {
checkCredits();
loadModels();
}, 500);
}
});
// API Key Management
function loadApiKey() {
apiKey = localStorage.getItem('stablecog_api_key') || '';
document.getElementById('apiKeyInput').value = apiKey;
}
function saveApiKey() {
const input = document.getElementById('apiKeyInput');
apiKey = input.value.trim();
if (apiKey) {
localStorage.setItem('stablecog_api_key', apiKey);
showMessage('API key saved successfully!');
// Auto-check after saving
setTimeout(() => {
checkCredits();
loadModels();
}, 500);
} else {
localStorage.removeItem('stablecog_api_key');
showMessage('API key cleared. Using demo mode.');
}
}
function clearApiKey() {
document.getElementById('apiKeyInput').value = '';
saveApiKey();
}
// Tab Switching
function switchTab(tabName) {
// Update tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Activate selected tab
document.querySelector(`.tab[onclick="switchTab('${tabName}')"]`).classList.add('active');
document.getElementById(`${tabName}Tab`).classList.add('active');
}
// API Calls
async function checkCredits() {
const btn = document.getElementById('checkCreditsBtn');
const icon = document.getElementById('creditsIcon');
const text = document.getElementById('creditsText');
// Show loading state
btn.disabled = true;
icon.textContent = '';
icon.innerHTML = '<div class="loading"></div>';
text.textContent = 'Checking...';
try {
const response = await fetch(`${API_HOST}${CREDITS_ENDPOINT}`, {
headers: {
'Authorization': `Bearer ${apiKey || 'demo'}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
creditsData = data;
updateCreditsDisplay(data);
hideError();
} catch (error) {
showError('Failed to check credits', error.message);
// For demo purposes, show sample data
if (!apiKey) {
showSampleCredits();
}
} finally {
// Reset button state
btn.disabled = false;
icon.textContent = '🔄';
text.textContent = 'Check Credits';
}
}
async function loadModels() {
const btn = document.getElementById('loadModelsBtn');
const icon = document.getElementById('modelsIcon');
const text = document.getElementById('modelsText');
// Show loading state
btn.disabled = true;
icon.textContent = '';
icon.innerHTML = '<div class="loading"></div>';
text.textContent = 'Loading...';
try {
const response = await fetch(`${API_HOST}${MODELS_ENDPOINT}`, {
headers: {
'Authorization': `Bearer ${apiKey || 'demo'}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
modelsData = data;
updateModelsDisplay(data);
hideError();
} catch (error) {
showError('Failed to load models', error.message);
// For demo purposes, show sample data
if (!apiKey) {
showSampleModels();
}
} finally {
// Reset button state
btn.disabled = false;
icon.textContent = '🔄';
text.textContent = 'Load Models';
}
}
// Display Functions
function updateCreditsDisplay(data) {
// Calculate totals
const totalRemaining = data.total_remaining_credits || 0;
const creditsList = data.credits || [];
let totalInitial = 0;
let totalUsed = 0;
creditsList.forEach(credit => {
const initial = credit.type?.amount || 0;
const remaining = credit.remaining_amount || 0;
totalInitial += initial;
totalUsed += (initial - remaining);
});
const totalCredits = Math.max(totalInitial, totalRemaining + totalUsed);
const usagePercentage = totalCredits > 0 ? (totalUsed / totalCredits * 100) : 0;
// Update UI
document.getElementById('totalCredits').textContent = totalCredits;
document.getElementById('remainingCredits').textContent = totalRemaining;
document.getElementById('usedCredits').textContent = totalUsed;
document.getElementById('usagePercentage').textContent = `${usagePercentage.toFixed(1)}%`;
document.getElementById('usageText').textContent = `${usagePercentage.toFixed(1)}%`;
document.getElementById('progressText').textContent = `${usagePercentage.toFixed(1)}%`;
// Update progress bar
const progressFill = document.getElementById('progressFill');
progressFill.style.width = `${Math.min(usagePercentage, 100)}%`;
// Set progress bar color based on usage
if (usagePercentage < 50) {
progressFill.style.background = '#4CAF50';
} else if (usagePercentage < 80) {
progressFill.style.background = '#FF9800';
} else {
progressFill.style.background = '#F44336';
}
// Update status
const statusBadge = document.getElementById('statusBadge');
const statusEmoji = document.getElementById('statusEmoji');
const statusText = document.getElementById('statusText');
if (totalRemaining === 0) {
statusEmoji.textContent = '💸';
statusText.textContent = 'No Credits';
statusBadge.style.color = '#F44336';
} else if (usagePercentage >= 90) {
statusEmoji.textContent = '🔴';
statusText.textContent = 'Critically Low';
statusBadge.style.color = '#F44336';
} else if (usagePercentage >= 75) {
statusEmoji.textContent = '🟡';
statusText.textContent = 'Running Low';
statusBadge.style.color = '#FF9800';
} else if (totalRemaining < 10) {
statusEmoji.textContent = '📝';
statusText.textContent = 'Limited';
statusBadge.style.color = '#FF9800';
} else if (totalRemaining < 50) {
statusEmoji.textContent = '✨';
statusText.textContent = 'Available';
statusBadge.style.color = '#4CAF50';
} else {
statusEmoji.textContent = '🚀';
statusText.textContent = 'Plenty';
statusBadge.style.color = '#4CAF50';
}
// Update recommendation
updateRecommendation(totalRemaining, usagePercentage);
// Update credit breakdown
updateCreditBreakdown(creditsList);
// Update timestamp
const now = new Date();
document.getElementById('creditsLastUpdated').textContent =
`⏰ Last checked: ${now.toLocaleString()}`;
}
function updateModelsDisplay(data) {
const models = data.models || [];
// Update stats
document.getElementById('totalModels').textContent = models.length;
document.getElementById('publicModels').textContent =
models.filter(m => m.is_public).length;
document.getElementById('defaultModels').textContent =
models.filter(m => m.is_default).length;
document.getElementById('communityModels').textContent =
models.filter(m => m.is_community).length;
// Update models list
const modelsList = document.getElementById('modelsList');
modelsList.innerHTML = '';
models.sort((a, b) => {
// Sort: default first, then public, then by name
if (a.is_default !== b.is_default) return b.is_default - a.is_default;
if (a.is_public !== b.is_public) return b.is_public - a.is_public;
return a.name.localeCompare(b.name);
});
models.forEach((model, index) => {
const modelCard = document.createElement('div');
modelCard.className = 'model-card';
// Determine badge
let badgeClass = 'badge-other';
let badgeText = model.type?.toUpperCase() || 'UNKNOWN';
if (model.is_community) {
badgeClass = 'badge-community';
badgeText = 'COMMUNITY';
} else if (model.is_default) {
badgeClass = 'badge-default';
badgeText = 'DEFAULT';
} else if (model.is_public) {
badgeClass = 'badge-public';
badgeText = 'PUBLIC';
}
// Shorten ID for display
const shortId = model.id.length > 8 ? model.id.substring(0, 8) + '...' : model.id;
modelCard.innerHTML = `
<div class="model-header">
<div class="model-name">
#${index + 1}. ${model.name}
<span class="model-badge ${badgeClass}">${badgeText}</span>
</div>
<div class="model-id">ID: ${shortId}</div>
</div>
<div class="model-description">${model.description || 'No description available'}</div>
<div class="model-meta">
<span>📅 Created: ${model.created_at ? model.created_at.substring(0, 10) : 'Unknown'}</span>
<span>🔄 Updated: ${model.updated_at ? model.updated_at.substring(0, 10) : 'Unknown'}</span>
</div>
`;
modelsList.appendChild(modelCard);
});
// Update timestamp
const now = new Date();
document.getElementById('modelsLastUpdated').textContent =
`⏰ Last updated: ${now.toLocaleString()}`;
}
function updateRecommendation(remainingCredits, usagePercentage) {
const recommendation = document.getElementById('recommendation');
if (remainingCredits === 0) {
recommendation.innerHTML = "💸 <strong>No credits remaining.</strong> Please add credits to continue using StableCog services.";
} else if (usagePercentage >= 90) {
recommendation.innerHTML = "🛑 <strong>Critically low credits!</strong> Consider purchasing more credits before starting new projects.";
} else if (usagePercentage >= 75) {
recommendation.innerHTML = "⚠️ <strong>Credits are running low.</strong> You can still do some work, but plan ahead for larger projects.";
} else if (remainingCredits < 10) {
recommendation.innerHTML = "📝 <strong>Limited credits available.</strong> Good for small tasks, testing, or single images.";
} else if (remainingCredits < 50) {
recommendation.innerHTML = "✨ <strong>Credits available!</strong> Suitable for several medium-sized projects or batch processing.";
} else {
recommendation.innerHTML = "🚀 <strong>Plenty of credits!</strong> Ready for extensive image generation work and experimentation.";
}
}
function updateCreditBreakdown(creditsList) {
const breakdownContainer = document.getElementById('creditBreakdown');
const itemsContainer = breakdownContainer.querySelector('.credit-items') ||
document.createElement('div');
itemsContainer.className = 'credit-items';
itemsContainer.innerHTML = '';
creditsList.forEach(credit => {
const initial = credit.type?.amount || 0;
const remaining = credit.remaining_amount || 0;
const used = initial - remaining;
const percentage = initial > 0 ? (used / initial * 100) : 0;
const item = document.createElement('div');
item.className = 'credit-item';
item.innerHTML = `
<div class="credit-header">
<span class="credit-name">${credit.type?.name || 'Unknown'}</span>
<span class="credit-amount">${remaining} / ${initial} ⭐</span>
</div>
<div class="credit-description">${credit.type?.description || ''}</div>
<div class="credit-progress">
<div class="credit-progress-fill" style="width: ${Math.min(percentage, 100)}%; background: ${getProgressColor(percentage)};"></div>
</div>
`;
itemsContainer.appendChild(item);
});
if (!breakdownContainer.querySelector('.credit-items')) {
breakdownContainer.appendChild(itemsContainer);
}
}
function getProgressColor(percentage) {
if (percentage < 50) return '#4CAF50';
if (percentage < 80) return '#FF9800';
return '#F44336';
}
// Raw Response Viewer
function viewRawResponse(type) {
const modal = document.getElementById(`${type}RawModal`);
const pre = document.getElementById(`${type}RawResponse`);
const data = type === 'credits' ? creditsData : modelsData;
pre.textContent = JSON.stringify(data, null, 2);
modal.style.display = 'block';
}
function hideRawResponse(type) {
document.getElementById(`${type}RawModal`).style.display = 'none';
}
// Error Handling
function showError(title, message) {
document.getElementById('errorTitle').textContent = title;
document.getElementById('errorMessage').textContent = message;
document.getElementById('errorContainer').style.display = 'block';
}
function hideError() {
document.getElementById('errorContainer').style.display = 'none';
}
function showMessage(message) {
// Simple toast notification
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(76, 175, 80, 0.9);
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
z-index: 1000;
animation: slideIn 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Sample data for demo
function showSampleCredits() {
const sampleData = {
"total_remaining_credits": 5,
"credits": [
{
"id": "cd813ce3-b5fe-4a74-ae5c-e077ce49b984",
"remaining_amount": 0,
"expires_at": "2100-01-01T05:00:00Z",
"type": {
"id": "7ca94fd6-c201-4ca6-a9bf-4473c83e30b4",
"name": "Refund",
"amount": 0,
"description": "For generate/upscale failure refunds"
}
},
{
"id": "b8a508a2-a5dc-4329-ad25-015b50fbc51f",
"remaining_amount": 5,
"expires_at": "2100-01-01T05:00:00Z",
"type": {
"id": "3b12b23e-478b-4c18-8e34-70b3f0af1ee6",
"name": "Free",
"amount": 50,
"description": "Base free credits for user"
}
}
]
};
creditsData = sampleData;
updateCreditsDisplay(sampleData);
}
function showSampleModels() {
const sampleData = {
"models": [
{
"id": "sd-xl-1.0",
"name": "Stable Diffusion XL",
"description": "High-quality image generation model with excellent detail",
"type": "image",
"is_public": true,
"is_default": true,
"is_community": false,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-06-01T00:00:00Z"
},
{
"id": "dreamshaper-v7",
"name": "DreamShaper v7",
"description": "Artistic style model for creative image generation",
"type": "image",
"is_public": true,
"is_default": false,
"is_community": false,
"created_at": "2024-02-01T00:00:00Z",
"updated_at": "2024-05-01T00:00:00Z"
},
{
"id": "anime-diffusion",
"name": "Anime Diffusion",
"description": "Specialized model for anime-style artwork",
"type": "image",
"is_public": true,
"is_default": false,
"is_community": true,
"created_at": "2024-03-01T00:00:00Z",
"updated_at": "2024-04-01T00:00:00Z"
}
]
};
modelsData = sampleData;
updateModelsDisplay(sampleData);
}
// Add CSS animations
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
</script>
</body>
</html>