deepseek-v4-flash-free · api
api
- config
- deepseek-v4-flash-free--zen-free--dev
- batch
- 2026-07-19--unified
- transport
- opencode-zen-free-tier
- protocol
- api · plan
- served model
- deepseek-v4-flash-free
- 采集于
- 2026年7月18日 11:54 · N=2
variant
live
slot 0 · P-q · sandboxed iframe

截图是 similarity 通道测量时的原始像素。运行 live 会在 sandboxed iframe 中重新执行原始输出;渲染结果取决于你的浏览器和字体,不保证与测量像素一致。
similarity 邻居
最近
claude-opus-4-6 @high · kiro0.69claude-opus-4-6 @max · kiro0.67claude-opus-4-6 @low · kiro0.66deepseek-v4-flash · opencode0.66minimax-m2.5 · kiro0.66最远
qwen3.5-plus · go0.42minimax-m2.5 · go0.42gpt-5.4-mini @medium · codex-oauth0.42minimax-m2.1 · kiro0.44deepseek-v4-pro @high · api0.44限于 session 2026-07-19--unified
similarity 衡量本 session 内的趋同程度 —— 不是质量。
Fidelity —— 卡片展示的数据对不对?
- namematch
- conditionmatch
- datematch
- max tempnot-foundno-temperature-candidate
- min tempnot-foundno-temperature-candidate
- hourlynot-foundcoverage · 0 命中 · 0 不符 · 24 未展示
- request sidematch0 处违规
extractor fidelity-extractor-v1+Berlin-2026-07-15
源码 —— 原始模型输出,一字节未改
下载 card.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280">
<title>Weather Card</title>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif;
padding: 40px;
background: #0b1120;
transition: background 0.8s ease;
}
#app {
width: 720px;
max-width: 100%;
position: relative;
}
/* --- Card --- */
.card {
background: rgba(255, 255, 255, 0.045);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border-radius: 28px;
border: 1px solid rgba(255, 255, 255, 0.07);
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
padding: 40px 44px 36px;
color: #fff;
transition: all 0.4s ease;
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 100%;
height: 100%;
background: radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 60%);
pointer-events: none;
}
/* --- Header --- */
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 18px;
}
.location {
display: flex;
align-items: center;
gap: 12px;
}
.location-icon {
font-size: 30px;
line-height: 1;
}
.location-name {
font-size: 24px;
font-weight: 600;
letter-spacing: -0.01em;
color: #fff;
}
.date {
font-size: 14px;
font-weight: 400;
color: rgba(255, 255, 255, 0.55);
letter-spacing: 0.02em;
padding-top: 4px;
white-space: nowrap;
}
/* --- Divider --- */
.divider {
height: 1px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%);
margin: 0 0 24px 0;
}
/* --- Condition --- */
.condition-section {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
}
.condition-emoji {
font-size: 36px;
line-height: 1;
}
.condition-text {
font-size: 18px;
font-weight: 450;
color: rgba(255, 255, 255, 0.85);
letter-spacing: 0.01em;
}
/* --- Temperatures --- */
.temps-section {
display: flex;
align-items: center;
gap: 32px;
margin-bottom: 28px;
padding: 8px 0;
}
.temp-block {
display: flex;
flex-direction: column;
align-items: center;
}
.temp-value {
font-size: 48px;
font-weight: 600;
letter-spacing: -0.02em;
line-height: 1;
color: #fff;
}
.temp-value .deg {
font-size: 28px;
font-weight: 400;
color: rgba(255, 255, 255, 0.5);
vertical-align: super;
margin-left: 2px;
}
.temp-label {
font-size: 12px;
font-weight: 500;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-top: 6px;
}
.temp-separator {
font-size: 32px;
font-weight: 200;
color: rgba(255, 255, 255, 0.2);
align-self: center;
padding-bottom: 14px;
}
.temp-range-bar {
flex: 1;
min-width: 80px;
height: 4px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.08);
position: relative;
align-self: center;
margin-bottom: 14px;
overflow: hidden;
}
.temp-range-fill {
height: 100%;
border-radius: 4px;
background: linear-gradient(90deg, #42a5f5, #ffa726);
transition: width 0.6s ease;
position: relative;
}
.temp-range-dots {
display: flex;
justify-content: space-between;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
/* --- Chart --- */
.chart-section {
background: rgba(0, 0, 0, 0.2);
border-radius: 16px;
padding: 20px 20px 14px;
border: 1px solid rgba(255, 255, 255, 0.04);
margin-bottom: 6px;
}
.chart-title {
font-size: 13px;
font-weight: 500;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.chart-title span {
font-size: 16px;
}
.chart-wrapper {
position: relative;
width: 100%;
}
#hourlyChart {
display: block;
width: 100%;
height: auto;
aspect-ratio: 580/200;
border-radius: 8px;
background: rgba(0, 0, 0, 0.15);
}
.chart-x-labels {
display: flex;
justify-content: space-between;
padding: 6px 6px 0 6px;
font-size: 11px;
font-weight: 450;
color: rgba(255, 255, 255, 0.35);
letter-spacing: 0.03em;
user-select: none;
}
/* --- Footer --- */
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 18px;
font-size: 12px;
color: rgba(255, 255, 255, 0.25);
letter-spacing: 0.02em;
}
.card-footer .source {
display: flex;
align-items: center;
gap: 6px;
}
.card-footer .source svg {
width: 14px;
height: 14px;
opacity: 0.4;
}
.card-footer .update-time {
color: rgba(255, 255, 255, 0.2);
}
/* --- Error --- */
.error {
display: none;
background: rgba(255, 255, 255, 0.045);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 28px;
border: 1px solid rgba(255, 100, 100, 0.15);
padding: 48px 44px;
text-align: center;
color: #fff;
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
}
.error.show {
display: block;
}
.error .error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error .error-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 8px;
}
.error .error-detail {
font-size: 15px;
color: rgba(255, 255, 255, 0.55);
line-height: 1.5;
}
.error .error-retry {
margin-top: 20px;
padding: 10px 28px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.06);
color: #fff;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.error .error-retry:hover {
background: rgba(255, 255, 255, 0.12);
}
/* --- Loading --- */
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 0;
color: rgba(255, 255, 255, 0.5);
gap: 20px;
}
.loading .spinner {
width: 36px;
height: 36px;
border: 3px solid rgba(255, 255, 255, 0.06);
border-top-color: rgba(255, 255, 255, 0.3);
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading .loading-text {
font-size: 15px;
font-weight: 400;
letter-spacing: 0.02em;
}
/* --- Hidden / Show --- */
.hidden {
display: none !important;
}
/* --- Responsive within 1280x800 --- */
@media (max-height: 780px) {
.card {
padding: 28px 36px 24px;
}
.temp-value {
font-size: 38px;
}
.temps-section {
margin-bottom: 18px;
gap: 20px;
}
.chart-section {
padding: 14px 16px 10px;
}
.condition-emoji {
font-size: 28px;
}
.condition-text {
font-size: 16px;
}
.location-name {
font-size: 20px;
}
.card-header {
margin-bottom: 12px;
}
.divider {
margin-bottom: 16px;
}
.condition-section {
margin-bottom: 12px;
}
.temp-value .deg {
font-size: 22px;
}
.temp-range-bar {
height: 3px;
}
}
@media (max-width: 800px) {
body {
padding: 20px;
}
.card {
padding: 28px 24px 24px;
border-radius: 20px;
}
.temp-value {
font-size: 36px;
}
.temps-section {
gap: 16px;
}
.location-name {
font-size: 20px;
}
}
</style>
</head>
<body>
<div id="app">
<!-- Card -->
<div id="card" class="card hidden">
<div class="card-header">
<div class="location">
<span class="location-icon" id="locIcon">🌍</span>
<span class="location-name" id="locName">Berlin</span>
</div>
<div class="date" id="dateDisplay">June 15, 2024</div>
</div>
<div class="divider"></div>
<div class="condition-section">
<span class="condition-emoji" id="condEmoji">⛅</span>
<span class="condition-text" id="condText">Partly Cloudy</span>
</div>
<div class="temps-section">
<div class="temp-block">
<div class="temp-value" id="tempMax">24<span class="deg">°C</span></div>
<span class="temp-label">Maximum</span>
</div>
<div class="temp-separator">/</div>
<div class="temp-block">
<div class="temp-value" id="tempMin">15<span class="deg">°C</span></div>
<span class="temp-label">Minimum</span>
</div>
<div style="flex:1;min-width:60px;align-self:center;margin-bottom:14px;padding:0 4px;">
<div class="temp-range-bar">
<div class="temp-range-fill" id="tempRangeFill" style="width:50%;"></div>
</div>
</div>
</div>
<div class="chart-section">
<div class="chart-title"><span>🌡</span> Hourly Temperature</div>
<div class="chart-wrapper">
<canvas id="hourlyChart" width="580" height="200"></canvas>
<div class="chart-x-labels" id="chartLabels">
<span>00</span><span>03</span><span>06</span><span>09</span>
<span>12</span><span>15</span><span>18</span><span>21</span>
</div>
</div>
</div>
<div class="card-footer">
<span class="source">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a10 10 0 1 0 10 10h-10V2z"/>
<path d="M12 12 20 12"/>
<path d="M12 4v8"/>
</svg>
Open-Meteo
</span>
<span class="update-time" id="updateTime"></span>
</div>
</div>
<!-- Loading -->
<div id="loading" class="loading">
<div class="spinner"></div>
<span class="loading-text">Loading weather …</span>
</div>
<!-- Error -->
<div id="error" class="error">
<div class="error-icon">⚠️</div>
<div class="error-title">Unable to load weather data</div>
<div class="error-detail" id="errorDetail">Please check your connection and try again.</div>
<button class="error-retry" onclick="location.reload()">Retry</button>
</div>
</div>
<script>
(function() {
'use strict';
// ─── Weather Condition Map ────────────────────────────────────────
const WEATHER = {
0: { emoji: '☀️', label: 'Clear Sky' },
1: { emoji: '🌤️', label: 'Mainly Clear' },
2: { emoji: '⛅', label: 'Partly Cloudy' },
3: { emoji: '☁️', label: 'Overcast' },
45: { emoji: '🌫️', label: 'Foggy' },
48: { emoji: '🌫️', label: 'Depositing Rime Fog' },
51: { emoji: '🌦️', label: 'Light Drizzle' },
53: { emoji: '🌦️', label: 'Moderate Drizzle' },
55: { emoji: '🌦️', label: 'Dense Drizzle' },
56: { emoji: '🌧️', label: 'Freezing Light Drizzle' },
57: { emoji: '🌧️', label: 'Freezing Dense Drizzle' },
61: { emoji: '🌧️', label: 'Slight Rain' },
63: { emoji: '🌧️', label: 'Moderate Rain' },
65: { emoji: '🌧️', label: 'Heavy Rain' },
66: { emoji: '🌧️', label: 'Freezing Light Rain' },
67: { emoji: '🌧️', label: 'Freezing Heavy Rain' },
71: { emoji: '❄️', label: 'Slight Snow' },
73: { emoji: '❄️', label: 'Moderate Snow' },
75: { emoji: '❄️', label: 'Heavy Snow' },
77: { emoji: '❄️', label: 'Snow Grains' },
80: { emoji: '🌦️', label: 'Slight Rain Showers' },
81: { emoji: '🌦️', label: 'Moderate Rain Showers' },
82: { emoji: '🌦️', label: 'Violent Rain Showers' },
85: { emoji: '❄️', label: 'Slight Snow Showers' },
86: { emoji: '❄️', label: 'Heavy Snow Showers' },
95: { emoji: '⛈️', label: 'Thunderstorm' },
96: { emoji: '⛈️', label: 'Thunderstorm with Slight Hail' },
99: { emoji: '⛈️', label: 'Thunderstorm with Heavy Hail' }
};
// ─── Theme Map (weather code → colours) ─────────────────────────
function getTheme(code) {
if (code === 0 || code === 1) return {
bg: 'linear-gradient(145deg, #0b0e1a 0%, #161b2e 40%, #1a1f3a 100%)',
accent: '#ffd54f',
chartLine: 'rgba(255,213,79,0.9)',
chartFill: 'rgba(255,213,79,0.18)',
chartFillTo: 'rgba(255,213,79,0.00)'
};
if (code === 2) return {
bg: 'linear-gradient(145deg, #0b111e 0%, #141e30 40%, #1a2a40 100%)',
accent: '#81d4fa',
chartLine: 'rgba(129,212,250,0.9)',
chartFill: 'rgba(129,212,250,0.18)',
chartFillTo: 'rgba(129,212,250,0.00)'
};
if (code === 3) return {
bg: 'linear-gradient(145deg, #0a0f1a 0%, #121e2a 40%, #1a2a3a 100%)',
accent: '#b0bec5',
chartLine: 'rgba(176,190,197,0.9)',
chartFill: 'rgba(176,190,197,0.15)',
chartFillTo: 'rgba(176,190,197,0.00)'
};
if (code >= 95) return {
bg: 'linear-gradient(145deg, #0a0818 0%, #18102a 40%, #2a1040 100%)',
accent: '#ce93d8',
chartLine: 'rgba(206,147,216,0.9)',
chartFill: 'rgba(206,147,216,0.18)',
chartFillTo: 'rgba(206,147,216,0.00)'
};
if (code >= 71 && code <= 86) return {
bg: 'linear-gradient(145deg, #0c1420 0%, #182838 40%, #203848 100%)',
accent: '#e0e0e0',
chartLine: 'rgba(224,224,224,0.85)',
chartFill: 'rgba(224,224,224,0.15)',
chartFillTo: 'rgba(224,224,224,0.00)'
};
// rain, drizzle, fog → cool blue
return {
bg: 'linear-gradient(145deg, #0a1220 0%, #0f1e30 40%, #142840 100%)',
accent: '#64b5f6',
chartLine: 'rgba(100,181,246,0.9)',
chartFill: 'rgba(100,181,246,0.18)',
chartFillTo: 'rgba(100,181,246,0.00)'
};
}
// ─── DOM refs ────────────────────────────────────────────────────
const $ = id => document.getElementById(id);
const cardEl = $('card');
const loadingEl = $('loading');
const errorEl = $('error');
const errorDetail = $('errorDetail');
const locName = $('locName');
const locIcon = $('locIcon');
const dateDisplay = $('dateDisplay');
const condEmoji = $('condEmoji');
const condText = $('condText');
const tempMax = $('tempMax');
const tempMin = $('tempMin');
const tempRangeFill = $('tempRangeFill');
const canvas = $('hourlyChart');
const chartLabels = $('chartLabels');
const updateTime = $('updateTime');
// ─── Helpers ─────────────────────────────────────────────────────
function pad(n) { return String(n).padStart(2, '0'); }
function formatDate(iso) {
const [y, m, d] = iso.split('-').map(Number);
const dt = new Date(Date.UTC(y, m - 1, d));
return dt.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
}
function todayUTC() {
const d = new Date();
return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate());
}
function parseParams() {
const url = new URL(window.location.href);
const lat = parseFloat(url.searchParams.get('lat')) || 52.52;
const lon = parseFloat(url.searchParams.get('lon')) || 13.405;
const name = url.searchParams.get('name') || 'Berlin';
const date = url.searchParams.get('date') || todayUTC();
return { lat, lon, name, date };
}
function isPastDate(iso) {
return iso < todayUTC();
}
function getWeatherInfo(code) {
return WEATHER[code] || { emoji: '❓', label: 'Unknown' };
}
// ─── API ─────────────────────────────────────────────────────────
async function fetchWeather(lat, lon, date) {
const past = isPastDate(date);
const base = past ? '/api/om/archive' : '/api/om/forecast';
const params = new URLSearchParams({
latitude: lat,
longitude: lon,
daily: 'temperature_2m_max,temperature_2m_min,weather_code',
hourly: 'temperature_2m',
timezone: 'UTC',
temperature_unit: 'celsius',
start_date: date,
end_date: date
});
const url = base + '?' + params.toString();
const resp = await fetch(url);
if (!resp.ok) {
throw new Error('API responded with status ' + resp.status);
}
return resp.json();
}
// ─── Chart ───────────────────────────────────────────────────────
function drawChart(canvasEl, hourlyTemps, theme) {
const ctx = canvasEl.getContext('2d');
const rect = canvasEl.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const w = canvasEl.width;
const h = canvasEl.height;
ctx.clearRect(0, 0, w, h);
const temps = hourlyTemps;
if (!temps || temps.length < 2) {
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Insufficient data', w / 2, h / 2 + 5);
return;
}
const n = temps.length;
const minT = Math.min(...temps);
const maxT = Math.max(...temps);
const range = maxT - minT || 1;
// padding
const padL = 38;
const padR = 16;
const padT = 16;
const padB = 8;
const chartW = w - padL - padR;
const chartH = h - padT - padB;
const xScale = i => padL + (i / (n - 1)) * chartW;
const yScale = v => padT + chartH - ((v - minT) / range) * chartH;
// ── grid & Y labels ──
const gridSteps = 4;
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let i = 0; i <= gridSteps; i++) {
const frac = i / gridSteps;
const y = padT + frac * chartH;
const val = maxT - frac * range;
ctx.beginPath();
ctx.moveTo(padL, y);
ctx.lineTo(w - padR, y);
ctx.strokeStyle = 'rgba(255,255,255,0.045)';
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif';
ctx.fillText(Math.round(val) + '°', padL - 8, y);
}
// ── area fill ──
ctx.beginPath();
ctx.moveTo(xScale(0), yScale(temps[0]));
for (let i = 1; i < n; i++) {
ctx.lineTo(xScale(i), yScale(temps[i]));
}
ctx.lineTo(xScale(n - 1), h - padB);
ctx.lineTo(xScale(0), h - padB);
ctx.closePath();
const grad = ctx.createLinearGradient(0, padT, 0, h - padB);
grad.addColorStop(0, theme.chartFill);
grad.addColorStop(1, theme.chartFillTo || 'rgba(100,181,246,0)');
ctx.fillStyle = grad;
ctx.fill();
// ── line ──
ctx.beginPath();
for (let i = 0; i < n; i++) {
const x = xScale(i);
const y = yScale(temps[i]);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
const lineGrad = ctx.createLinearGradient(0, 0, w, 0);
lineGrad.addColorStop(0, theme.chartLine);
lineGrad.addColorStop(0.5, theme.chartLine);
lineGrad.addColorStop(1, theme.chartLine);
ctx.strokeStyle = lineGrad;
ctx.lineWidth = 2.2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.stroke();
// ── dots ──
for (let i = 0; i < n; i++) {
const x = xScale(i);
const y = yScale(temps[i]);
ctx.beginPath();
ctx.arc(x, y, 3.2, 0, 2 * Math.PI);
ctx.fillStyle = 'rgba(255,255,255,0.85)';
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, 1.6, 0, 2 * Math.PI);
ctx.fillStyle = theme.chartLine;
ctx.fill();
}
// ── X labels ──
const labelIndices = [];
for (let i = 0; i < n; i++) {
if (i === 0 || i === n - 1 || i % 3 === 0) labelIndices.push(i);
}
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif';
for (const i of labelIndices) {
const x = xScale(i);
ctx.fillText(pad(i), x, h - padB + 4);
}
}
// ─── Render ──────────────────────────────────────────────────────
function render(data, params) {
const { daily, hourly } = data;
if (!daily || !daily.time || daily.time.length === 0) {
throw new Error('No daily data available for this date.');
}
const idx = 0;
const maxTemp = daily.temperature_2m_max[idx];
const minTemp = daily.temperature_2m_min[idx];
const weatherCode = daily.weather_code[idx];
const weather = getWeatherInfo(weatherCode);
const theme = getTheme(weatherCode);
// Location
locName.textContent = params.name;
locIcon.textContent = weather.emoji;
// Date
dateDisplay.textContent = formatDate(params.date);
// Condition
condEmoji.textContent = weather.emoji;
condText.textContent = weather.label;
// Temperatures
tempMax.innerHTML = Math.round(maxTemp) + '<span class="deg">°C</span>';
tempMin.innerHTML = Math.round(minTemp) + '<span class="deg">°C</span>';
// Range bar
const allTemps = [];
if (hourly && hourly.temperature_2m) {
for (const t of hourly.temperature_2m) allTemps.push(t);
}
allTemps.push(maxTemp, minTemp);
const globalMin = Math.min(...allTemps);
const globalMax = Math.max(...allTemps);
const globalRange = globalMax - globalMin || 1;
const mid = (minTemp + maxTemp) / 2;
// Position the fill from min to max as a fraction of the range
const fromFrac = (minTemp - globalMin) / globalRange;
const toFrac = (maxTemp - globalMin) / globalRange;
const leftPct = fromFrac * 100;
const widthPct = (toFrac - fromFrac) * 100;
tempRangeFill.style.marginLeft = leftPct + '%';
tempRangeFill.style.width = Math.max(widthPct, 4) + '%';
tempRangeFill.style.background = 'linear-gradient(90deg, #42a5f5, #ffa726)';
// Theme background
document.body.style.background = theme.bg;
// Chart
if (hourly && hourly.temperature_2m && hourly.temperature_2m.length > 0) {
const filtered = [];
const times = hourly.time || [];
// Filter only the entries for the requested date
const datePrefix = params.date;
for (let i = 0; i < times.length; i++) {
if (times[i].startsWith(datePrefix)) {
filtered.push(hourly.temperature_2m[i]);
}
}
// If filtering didn't work (API might already filter), use all
const chartData = filtered.length > 0 ? filtered : hourly.temperature_2m;
drawChart(canvas, chartData, theme);
// Update X labels every 3 hours
const n = chartData.length;
const steps = [0];
for (let i = 3; i < n - 1; i += 3) steps.push(i);
if (n > 1 && steps[steps.length - 1] !== n - 1) steps.push(n - 1);
const labelEls = chartLabels.querySelectorAll('span');
for (let i = 0; i < labelEls.length; i++) {
if (i < steps.length) {
labelEls[i].textContent = pad(steps[i]);
labelEls[i].style.display = 'inline';
} else {
labelEls[i].style.display = 'none';
}
}
} else {
// No hourly data – show empty chart
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.font = '13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Hourly data unavailable', canvas.width / 2, canvas.height / 2 + 4);
}
// Update time
const now = new Date();
const timeStr = now.toUTCString().split(' ')[4] || '';
updateTime.textContent = timeStr ? 'Updated ' + timeStr + ' UTC' : '';
// Show card
loadingEl.classList.add('hidden');
cardEl.classList.remove('hidden');
errorEl.classList.remove('show');
errorEl.classList.add('hidden');
}
// ─── Error ───────────────────────────────────────────────────────
function showError(message) {
loadingEl.classList.add('hidden');
cardEl.classList.add('hidden');
errorEl.classList.add('show');
errorEl.classList.remove('hidden');
errorDetail.textContent = message || 'An unexpected error occurred. Please try again later.';
}
// ─── Main ────────────────────────────────────────────────────────
async function main() {
const params = parseParams();
// Validate date format
if (!/^\d{4}-\d{2}-\d{2}$/.test(params.date)) {
showError('Invalid date format. Please use YYYY-MM-DD.');
return;
}
try {
const data = await fetchWeather(params.lat, params.lon, params.date);
render(data, params);
} catch (err) {
console.error(err);
let msg = err.message || 'Could not load weather data.';
if (msg.includes('fetch')) msg = 'Network error. Please check your connection.';
if (msg.includes('status 4')) msg = 'Data not available for the requested location or date.';
showError(msg);
}
}
// ─── Start ───────────────────────────────────────────────────────
// Show loading first
loadingEl.classList.remove('hidden');
cardEl.classList.add('hidden');
errorEl.classList.add('hidden');
errorEl.classList.remove('show');
// If canvas context fails for some reason
if (!canvas.getContext) {
showError('Your browser does not support canvas rendering.');
} else {
main();
}
// Handle resize to redraw chart (debounced)
let resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
if (!cardEl.classList.contains('hidden')) {
// Re-draw chart at new size – canvas resolution stays same, but we re-apply DPR
const hourlyData = window._lastHourlyData;
const theme = window._lastTheme;
if (hourlyData && theme) {
drawChart(canvas, hourlyData, theme);
}
}
}, 200);
});
// Store last data for redraw (chart only)
const origRender = render;
render = function(data, params) {
const { daily, hourly } = data;
const weatherCode = daily.weather_code[0];
const theme = getTheme(weatherCode);
window._lastTheme = theme;
if (hourly && hourly.temperature_2m) {
const filtered = [];
const times = hourly.time || [];
const datePrefix = params.date;
for (let i = 0; i < times.length; i++) {
if (times[i].startsWith(datePrefix)) {
filtered.push(hourly.temperature_2m[i]);
}
}
window._lastHourlyData = filtered.length > 0 ? filtered : hourly.temperature_2m;
} else {
window._lastHourlyData = null;
}
origRender(data, params);
};
// Expose for potential debugging
window.__weatherApp = { params: parseParams() };
})();
</script>
</body>
</html>L1 —— 廉价的确定性描述量
| bytes 总计 | 36,573 | html / css / js | 3,450 / 10,769 / 22,354 |
|---|---|---|---|
| dom 节点 | 61 | dom 深度 | 8 |
| css 规则 | 65 | 含 js | 是 |
| brightness | 32.5 | contrast | 12.9 |
| colorfulness | 14.4 | 留白 | 91.9% |
该 slot 的 telemetry
| prompt tokens | 400 | completion tokens | 22,827 |
|---|---|---|---|
| total tokens | 23,227 | wall | 147.3 s |
| cost | — | request id | — |
config.json577 B
{
"N": 2,
"auth": {
"credential_ref": "WCB_ZEN_KEY",
"method": "api-key"
},
"billing": "plan",
"config_id": "deepseek-v4-flash-free--zen-free--dev",
"effort": null,
"family": "deepseek",
"m": {
"min": 2,
"q": 2
},
"model_id": "deepseek-v4-flash-free",
"protocol": "api",
"served_model": "deepseek-v4-flash-free",
"telemetry": {
"completion_tokens": 59595,
"cost_usd": null,
"prompt_tokens": 1584,
"total_tokens": 61179,
"wall_ms": 405287
},
"transport": "opencode-zen-free-tier",
"vendor_sanction_ref": null
}send-log2.5 KB
{
"N": 2,
"batch_id": "2026-07-18--dev-zen-free",
"config_id": "deepseek-v4-flash-free--zen-free--dev",
"positions": [
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-07-18T00:47:55.879185+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-18T00:47:11.107389+00:00"
}
],
"block_index": 0,
"model_reaching_attempt_index": 0,
"slot_index": 0,
"terminal_state": "valid",
"variant": "P-min"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-07-18T00:50:26.087032+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-18T00:47:58.815750+00:00"
}
],
"block_index": 0,
"model_reaching_attempt_index": 0,
"slot_index": 0,
"terminal_state": "valid",
"variant": "P-q"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-07-18T00:52:14.610575+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-18T00:50:30.127961+00:00"
}
],
"block_index": 1,
"model_reaching_attempt_index": 0,
"slot_index": 1,
"terminal_state": "valid",
"variant": "P-q"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-07-18T00:54:08.198731+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-18T00:52:19.435910+00:00"
}
],
"block_index": 1,
"model_reaching_attempt_index": 0,
"slot_index": 1,
"terminal_state": "valid",
"variant": "P-min"
}
],
"retry_policy": {
"max_unreachable_retries": 5,
"rate_limit_backoff": {
"max_attempts": 4,
"max_total_ms": 60000
}
}
}