openclaw-smartmeter 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canvas-template/app.js +62 -32
- package/canvas-template/styles.css +5 -0
- package/package.json +1 -1
package/canvas-template/app.js
CHANGED
|
@@ -111,16 +111,17 @@ function normalizeApiData(api) {
|
|
|
111
111
|
/* ─── Render All ─── */
|
|
112
112
|
function renderAll() {
|
|
113
113
|
if (!analysisData) return;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
const safe = fn => { try { fn(); } catch (e) { console.error(`[SmartMeter] ${fn.name} error:`, e); } };
|
|
115
|
+
safe(updateKPIs);
|
|
116
|
+
safe(updateMetrics);
|
|
117
|
+
safe(updateCharts);
|
|
118
|
+
safe(updateRecommendations);
|
|
119
|
+
safe(updateModelRecommendations);
|
|
120
|
+
safe(updateOtherRecommendations);
|
|
121
|
+
safe(updateBudgetControls);
|
|
122
|
+
safe(updateModelDetails);
|
|
123
|
+
safe(updateLastUpdated);
|
|
124
|
+
safe(checkCostDataNotice);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/* ─── KPIs ─── */
|
|
@@ -440,7 +441,7 @@ function updateModelRecommendations() {
|
|
|
440
441
|
${alt.isRecommended ? '<span class="model-tag best-tag">RECOMMENDED</span>' : ''}
|
|
441
442
|
</div>
|
|
442
443
|
<div class="model-alt-meta">
|
|
443
|
-
Quality: ${renderQualityDots(alt.quality_score)} · ${escHtml(alt.speed)} · Best for: ${alt.best_for.join(', ')}
|
|
444
|
+
Quality: ${renderQualityDots(alt.quality_score)} · ${escHtml(alt.speed || '')} · Best for: ${(alt.best_for || []).join(', ')}
|
|
444
445
|
</div>
|
|
445
446
|
</div>
|
|
446
447
|
<div class="model-alt-cost">
|
|
@@ -877,10 +878,13 @@ async function fetchOpenRouterUsage() {
|
|
|
877
878
|
/* ─── Config Modal ─── */
|
|
878
879
|
function openConfigModal() {
|
|
879
880
|
document.getElementById('configModal').style.display = 'flex';
|
|
880
|
-
document.getElementById('apiKeyInput')
|
|
881
|
+
const input = document.getElementById('apiKeyInput');
|
|
882
|
+
const stored = localStorage.getItem('smartmeter_openrouter_key');
|
|
883
|
+
if (stored && !input.value) input.value = stored;
|
|
884
|
+
input.focus();
|
|
881
885
|
const status = document.getElementById('configStatus');
|
|
882
886
|
status.className = 'config-status';
|
|
883
|
-
status.style.display
|
|
887
|
+
status.style.removeProperty('display');
|
|
884
888
|
}
|
|
885
889
|
function closeConfigModal() {
|
|
886
890
|
document.getElementById('configModal').style.display = 'none';
|
|
@@ -890,22 +894,28 @@ async function saveApiKey() {
|
|
|
890
894
|
const key = document.getElementById('apiKeyInput').value.trim();
|
|
891
895
|
const status = document.getElementById('configStatus');
|
|
892
896
|
|
|
897
|
+
function showStatus(msg, type) {
|
|
898
|
+
status.textContent = msg;
|
|
899
|
+
status.className = 'config-status ' + type;
|
|
900
|
+
status.style.removeProperty('display');
|
|
901
|
+
}
|
|
902
|
+
|
|
893
903
|
if (!key) {
|
|
894
|
-
|
|
895
|
-
status.className = 'config-status error';
|
|
904
|
+
showStatus('Please enter an API key.', 'error');
|
|
896
905
|
return;
|
|
897
906
|
}
|
|
898
907
|
|
|
899
908
|
if (!key.startsWith('sk-or-')) {
|
|
900
|
-
|
|
901
|
-
status.className = 'config-status error';
|
|
909
|
+
showStatus('Invalid format — key should start with sk-or-', 'error');
|
|
902
910
|
return;
|
|
903
911
|
}
|
|
904
912
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
913
|
+
showStatus('Validating…', 'validating');
|
|
914
|
+
|
|
915
|
+
let validated = false;
|
|
916
|
+
let errorMsg = '';
|
|
908
917
|
|
|
918
|
+
// Try API server first
|
|
909
919
|
try {
|
|
910
920
|
const res = await fetch(`${API_BASE_URL}/api/config/openrouter-key`, {
|
|
911
921
|
method: 'POST',
|
|
@@ -914,20 +924,40 @@ async function saveApiKey() {
|
|
|
914
924
|
});
|
|
915
925
|
const json = await res.json();
|
|
916
926
|
if (json.success) {
|
|
917
|
-
|
|
918
|
-
status.className = 'config-status success';
|
|
919
|
-
setTimeout(() => {
|
|
920
|
-
closeConfigModal();
|
|
921
|
-
fetchOpenRouterUsage();
|
|
922
|
-
navigateTo('openrouter');
|
|
923
|
-
}, 1200);
|
|
927
|
+
validated = true;
|
|
924
928
|
} else {
|
|
925
|
-
|
|
926
|
-
status.className = 'config-status error';
|
|
929
|
+
errorMsg = json.error || 'Validation failed';
|
|
927
930
|
}
|
|
928
|
-
} catch (
|
|
929
|
-
|
|
930
|
-
|
|
931
|
+
} catch (_) {
|
|
932
|
+
// API server not available — validate directly against OpenRouter
|
|
933
|
+
try {
|
|
934
|
+
const res = await fetch('https://openrouter.ai/api/v1/auth/key', {
|
|
935
|
+
headers: { 'Authorization': `Bearer ${key}` }
|
|
936
|
+
});
|
|
937
|
+
if (res.ok) {
|
|
938
|
+
const data = await res.json();
|
|
939
|
+
validated = !!(data && data.data);
|
|
940
|
+
if (!validated) errorMsg = 'Key not recognized by OpenRouter';
|
|
941
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
942
|
+
errorMsg = 'Invalid API key — authentication failed';
|
|
943
|
+
} else {
|
|
944
|
+
errorMsg = `OpenRouter returned status ${res.status}`;
|
|
945
|
+
}
|
|
946
|
+
} catch (e2) {
|
|
947
|
+
errorMsg = 'Could not reach OpenRouter to validate — check your connection';
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (validated) {
|
|
952
|
+
localStorage.setItem('smartmeter_openrouter_key', key);
|
|
953
|
+
showStatus('✅ API key saved and validated!', 'success');
|
|
954
|
+
setTimeout(() => {
|
|
955
|
+
closeConfigModal();
|
|
956
|
+
fetchOpenRouterUsage();
|
|
957
|
+
navigateTo('openrouter');
|
|
958
|
+
}, 1200);
|
|
959
|
+
} else {
|
|
960
|
+
showStatus(`❌ ${errorMsg}`, 'error');
|
|
931
961
|
}
|
|
932
962
|
}
|
|
933
963
|
|
|
@@ -860,6 +860,11 @@ a:hover { color: var(--accent-hover); }
|
|
|
860
860
|
background: var(--green-subtle);
|
|
861
861
|
color: var(--green);
|
|
862
862
|
}
|
|
863
|
+
.config-status.validating {
|
|
864
|
+
display: block;
|
|
865
|
+
background: var(--indigo-subtle, rgba(99,102,241,.08));
|
|
866
|
+
color: var(--text-secondary);
|
|
867
|
+
}
|
|
863
868
|
|
|
864
869
|
/* Code block */
|
|
865
870
|
.code-block {
|