openclacky 1.2.16 → 1.2.18
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/lib/clacky/agent/session_serializer.rb +5 -2
- data/lib/clacky/agent/skill_manager.rb +1 -1
- data/lib/clacky/agent.rb +6 -11
- data/lib/clacky/default_skills/channel-manager/SKILL.md +4 -2
- data/lib/clacky/default_skills/media-gen/SKILL.md +1 -0
- data/lib/clacky/default_skills/skill-creator/SKILL.md +1 -0
- data/lib/clacky/media/base.rb +32 -0
- data/lib/clacky/media/dashscope.rb +243 -0
- data/lib/clacky/media/generator.rb +18 -0
- data/lib/clacky/server/channel/channel_manager.rb +115 -31
- data/lib/clacky/server/http_server.rb +25 -2
- data/lib/clacky/skill.rb +3 -1
- data/lib/clacky/telemetry.rb +20 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +305 -0
- data/lib/clacky/web/billing.js +144 -0
- data/lib/clacky/web/i18n.js +144 -1
- data/lib/clacky/web/index.html +11 -0
- data/lib/clacky/web/marked.min.js +55 -45
- data/lib/clacky/web/sessions.js +6 -1
- data/lib/clacky/web/share.js +843 -0
- data/lib/clacky/web/skills.js +5 -5
- data/lib/clacky/web/vendor/qrcode/qrcode.min.js +8 -0
- data/lib/clacky/web/ws-dispatcher.js +1 -0
- data/scripts/install.ps1 +20 -19
- metadata +5 -2
data/lib/clacky/web/billing.js
CHANGED
|
@@ -126,6 +126,9 @@ const Billing = (() => {
|
|
|
126
126
|
<div class="billing-controls">
|
|
127
127
|
<div class="billing-period-group">${periodBtns}</div>
|
|
128
128
|
<select id="billing-model-filter" class="billing-model-filter">${modelOptions}</select>
|
|
129
|
+
<button id="billing-share-btn" class="billing-share-btn" title="${I18n.t('billing.share.tooltip') || 'Share scorecard'}">
|
|
130
|
+
📤 ${I18n.t('billing.share.btn') || 'Share scorecard'}
|
|
131
|
+
</button>
|
|
129
132
|
<div class="billing-clear-container">
|
|
130
133
|
<button id="billing-clear-btn" class="billing-clear-btn" title="${I18n.t('billing.clearData') || 'Clear Data'}">
|
|
131
134
|
🗑️
|
|
@@ -169,6 +172,10 @@ const Billing = (() => {
|
|
|
169
172
|
</div>
|
|
170
173
|
</div>
|
|
171
174
|
|
|
175
|
+
<div class="billing-heatmap-row">
|
|
176
|
+
${_renderHeatmap()}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
172
179
|
<div class="billing-bottom-grid">
|
|
173
180
|
${_renderTokenBreakdown()}
|
|
174
181
|
${_renderModelBreakdown()}
|
|
@@ -201,8 +208,69 @@ const Billing = (() => {
|
|
|
201
208
|
// Bind clear button handlers
|
|
202
209
|
_bindClearHandlers();
|
|
203
210
|
|
|
211
|
+
// Bind scorecard share button
|
|
212
|
+
document.getElementById("billing-share-btn")?.addEventListener("click", _openScorecardShare);
|
|
213
|
+
|
|
204
214
|
// Bind chart tooltip handlers
|
|
205
215
|
_bindChartTooltip();
|
|
216
|
+
_bindHeatmapTooltip();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Builds the per-period scorecard numbers from a raw summary object, using
|
|
220
|
+
// the same currency / formatting conventions as the billing dashboard.
|
|
221
|
+
function _scorecardStatsFor(summary, periodKey) {
|
|
222
|
+
const prompt = summary.prompt_tokens || 0;
|
|
223
|
+
const cacheRead = summary.cache_read_tokens || 0;
|
|
224
|
+
const rate = prompt === 0 ? "0" : ((cacheRead / prompt) * 100).toFixed(1);
|
|
225
|
+
return {
|
|
226
|
+
key: periodKey,
|
|
227
|
+
period: _periodLabel(periodKey),
|
|
228
|
+
cacheHitRate: rate,
|
|
229
|
+
costStr: `${_getCurrencySymbol()}${_formatCost(_convertCost(summary.total_cost || 0))}`,
|
|
230
|
+
tokensStr: _formatCompact(summary.total_tokens || 0),
|
|
231
|
+
requests: _formatNumber(summary.record_count || 0)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Daily token totals for the heatmap (GitHub-contribution style), oldest →
|
|
236
|
+
// newest. Each entry: { date: "YYYY-MM-DD", tokens: <total> }.
|
|
237
|
+
function _heatmapDays() {
|
|
238
|
+
return (_daily || []).map((d) => ({
|
|
239
|
+
date: d.date,
|
|
240
|
+
tokens: (d.prompt_tokens || 0) + (d.completion_tokens || 0),
|
|
241
|
+
cost: d.cost || 0
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function _openScorecardShare() {
|
|
246
|
+
if (!_summary || typeof Share === "undefined" || !Share.openScorecard) return;
|
|
247
|
+
const modelParam = (_currentModel && _currentModel !== "all") ? `&model=${encodeURIComponent(_currentModel)}` : "";
|
|
248
|
+
|
|
249
|
+
// Open instantly with the period the dashboard already has, then fetch the
|
|
250
|
+
// other periods in the background and hot-swap them in (no blocking await).
|
|
251
|
+
const periods = {};
|
|
252
|
+
periods[_currentPeriod] = _scorecardStatsFor(_summary, _currentPeriod);
|
|
253
|
+
|
|
254
|
+
Share.openScorecard({
|
|
255
|
+
periods: periods,
|
|
256
|
+
defaultPeriod: _currentPeriod,
|
|
257
|
+
heatmap: _heatmapDays(),
|
|
258
|
+
period: periods[_currentPeriod].period,
|
|
259
|
+
cacheHitRate: periods[_currentPeriod].cacheHitRate,
|
|
260
|
+
costStr: periods[_currentPeriod].costStr,
|
|
261
|
+
tokensStr: periods[_currentPeriod].tokensStr,
|
|
262
|
+
requests: periods[_currentPeriod].requests
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const others = ["day", "week", "month"].filter((p) => p !== _currentPeriod);
|
|
266
|
+
others.forEach((p) => {
|
|
267
|
+
fetch(`/api/billing/summary?period=${p}${modelParam}`)
|
|
268
|
+
.then((r) => r.json())
|
|
269
|
+
.then((summary) => {
|
|
270
|
+
if (Share.addScorecardPeriod) Share.addScorecardPeriod(p, _scorecardStatsFor(summary, p));
|
|
271
|
+
})
|
|
272
|
+
.catch(() => {});
|
|
273
|
+
});
|
|
206
274
|
}
|
|
207
275
|
|
|
208
276
|
function _bindChartTooltip() {
|
|
@@ -261,6 +329,42 @@ const Billing = (() => {
|
|
|
261
329
|
});
|
|
262
330
|
}
|
|
263
331
|
|
|
332
|
+
function _bindHeatmapTooltip() {
|
|
333
|
+
const grid = document.getElementById("billing-heat-grid");
|
|
334
|
+
const tooltip = document.getElementById("billing-tooltip");
|
|
335
|
+
if (!grid || !tooltip) return;
|
|
336
|
+
|
|
337
|
+
grid.addEventListener("mousemove", (e) => {
|
|
338
|
+
const cell = e.target.closest(".billing-heat-cell");
|
|
339
|
+
if (!cell || cell.classList.contains("is-empty") || !cell.dataset.date) {
|
|
340
|
+
tooltip.style.display = "none";
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
tooltip.innerHTML = `
|
|
345
|
+
<div class="tooltip-header">
|
|
346
|
+
<span class="tooltip-date">${cell.dataset.date}</span>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="tooltip-row">
|
|
349
|
+
<span class="tooltip-dot tooltip-total"></span>
|
|
350
|
+
<span class="tooltip-label">${I18n.t("billing.totalTokens") || "Total Tokens"}</span>
|
|
351
|
+
<span class="tooltip-value">${cell.dataset.tokens}</span>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="tooltip-row">
|
|
354
|
+
<span class="tooltip-label">${I18n.t("billing.cost") || "Cost"}</span>
|
|
355
|
+
<span class="tooltip-value">${cell.dataset.cost}</span>
|
|
356
|
+
</div>
|
|
357
|
+
`;
|
|
358
|
+
tooltip.style.display = "block";
|
|
359
|
+
tooltip.style.left = `${e.clientX + 15}px`;
|
|
360
|
+
tooltip.style.top = `${e.clientY - 10}px`;
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
grid.addEventListener("mouseleave", () => {
|
|
364
|
+
tooltip.style.display = "none";
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
264
368
|
function _bindClearHandlers() {
|
|
265
369
|
const clearBtn = document.getElementById("billing-clear-btn");
|
|
266
370
|
const clearPopup = document.getElementById("billing-clear-popup");
|
|
@@ -448,6 +552,46 @@ const Billing = (() => {
|
|
|
448
552
|
`;
|
|
449
553
|
}
|
|
450
554
|
|
|
555
|
+
function _renderHeatmap() {
|
|
556
|
+
const days = _heatmapDays();
|
|
557
|
+
if (!days || days.length === 0) {
|
|
558
|
+
return `<div class="billing-chart-card billing-chart-wide"><div class="billing-chart-empty">${I18n.t("billing.noData") || "No data available"}</div></div>`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const maxTok = Math.max(...days.map(d => d.tokens), 1);
|
|
562
|
+
const firstDow = new Date(days[0].date + "T00:00:00").getDay();
|
|
563
|
+
const cells = [];
|
|
564
|
+
for (let i = 0; i < firstDow; i++) cells.push('<div class="billing-heat-cell is-empty"></div>');
|
|
565
|
+
days.forEach((d) => {
|
|
566
|
+
const ratio = d.tokens / maxTok;
|
|
567
|
+
const lvl = d.tokens === 0 ? 0 : ratio >= 0.75 ? 5 : ratio >= 0.5 ? 4 : ratio >= 0.25 ? 3 : ratio >= 0.08 ? 2 : 1;
|
|
568
|
+
const costStr = `${_getCurrencySymbol()}${_formatCost(_convertCost(d.cost))}`;
|
|
569
|
+
cells.push(`<div class="billing-heat-cell" data-level="${lvl}" data-date="${d.date}" data-tokens="${_formatCompact(d.tokens)}" data-cost="${costStr}"></div>`);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const dowLabels = (I18n.t("billing.heatmap.dow") || "S,M,T,W,T,F,S").split(",");
|
|
573
|
+
const dowHeader = dowLabels.map(l => `<span class="billing-heat-dow">${_esc(l)}</span>`).join("");
|
|
574
|
+
|
|
575
|
+
return `
|
|
576
|
+
<div class="billing-chart-card billing-chart-wide billing-heatmap-card">
|
|
577
|
+
<div class="billing-chart-header">
|
|
578
|
+
<h4>${I18n.t("billing.heatmap.title") || "Activity"}</h4>
|
|
579
|
+
<div class="billing-heat-legend">
|
|
580
|
+
<span>${I18n.t("billing.heatmap.less") || "Less"}</span>
|
|
581
|
+
<span class="billing-heat-cell" data-level="1"></span>
|
|
582
|
+
<span class="billing-heat-cell" data-level="2"></span>
|
|
583
|
+
<span class="billing-heat-cell" data-level="3"></span>
|
|
584
|
+
<span class="billing-heat-cell" data-level="4"></span>
|
|
585
|
+
<span class="billing-heat-cell" data-level="5"></span>
|
|
586
|
+
<span>${I18n.t("billing.heatmap.more") || "More"}</span>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
<div class="billing-heat-dow-row">${dowHeader}</div>
|
|
590
|
+
<div class="billing-heat-grid" id="billing-heat-grid">${cells.join("")}</div>
|
|
591
|
+
</div>
|
|
592
|
+
`;
|
|
593
|
+
}
|
|
594
|
+
|
|
451
595
|
function _renderCombinedChart() {
|
|
452
596
|
if (!_daily || _daily.length === 0) {
|
|
453
597
|
return `<div class="billing-chart-card billing-chart-wide"><div class="billing-chart-empty">${I18n.t("billing.noData") || "No data available"}</div></div>`;
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -709,6 +709,67 @@ const I18n = (() => {
|
|
|
709
709
|
"notify.tooltip.on": "Sound on task complete: ON (click to mute)",
|
|
710
710
|
"notify.tooltip.off": "Sound on task complete: OFF (click to enable)",
|
|
711
711
|
|
|
712
|
+
"share.tooltip": "Share",
|
|
713
|
+
"share.copy.1": "Been running {{brand}} with domestic models (Kimi, DeepSeek, GLM…) — first-class support, no proxy hacks. Solid.",
|
|
714
|
+
"share.copy.2": "{{brand}} is stingy with tokens in the best way — prompt caching keeps my AI bill tiny. Recommend.",
|
|
715
|
+
"share.copy.3": "Installed {{brand}} in one line and it just worked. If you build with AI, give it a try.",
|
|
716
|
+
"share.copy.4": "{{brand}}: works with domestic models, saves a ton of tokens, installs in seconds. My daily driver now.",
|
|
717
|
+
"share.copied": "Copied to clipboard",
|
|
718
|
+
"share.copyFailed": "Copy failed",
|
|
719
|
+
"share.modal.title": "Share {{brand}}",
|
|
720
|
+
"share.modal.subtitle": "Pick a poster style, tweak the text, then post it anywhere.",
|
|
721
|
+
"share.platform.weibo": "Weibo",
|
|
722
|
+
"share.platform.xhs": "Xiaohongshu",
|
|
723
|
+
"share.platform.wechat": "WeChat",
|
|
724
|
+
"share.platform.bilibili": "Bilibili",
|
|
725
|
+
"share.action.copyLink": "Copy link",
|
|
726
|
+
"share.action.copyText": "Copy text",
|
|
727
|
+
"share.action.download": "Download poster",
|
|
728
|
+
"share.action.downloadAndCopy": "Download poster + copy text",
|
|
729
|
+
"share.action.toWeibo": "Share to Weibo",
|
|
730
|
+
"share.action.shuffle": "Shuffle text",
|
|
731
|
+
"share.action.systemShare": "Share…",
|
|
732
|
+
"share.editor.label": "Edit before sharing",
|
|
733
|
+
"share.theme.label": "Poster style",
|
|
734
|
+
"share.theme.geek": "Geek dark",
|
|
735
|
+
"share.theme.light": "Clean light",
|
|
736
|
+
"share.theme.warm": "Warm vibe",
|
|
737
|
+
"share.hint.xhs": "Poster downloaded + text copied — open Xiaohongshu, create a post and pick this image.",
|
|
738
|
+
"share.hint.wechat": "Poster downloaded + text copied — open WeChat and share it to Moments.",
|
|
739
|
+
"share.hint.bilibili": "Poster downloaded + text copied — open Bilibili and post it with this image.",
|
|
740
|
+
"share.poster.tagline": "Domestic models · Token-thrifty · One-line install — {{brand}}.",
|
|
741
|
+
"share.poster.scan": "Scan to learn more",
|
|
742
|
+
"share.prompt.message": "Nice work! Loving {{brand}}? Share it with a friend.",
|
|
743
|
+
"share.prompt.action": "Share",
|
|
744
|
+
// ── Scorecard share (B-line: spend / cache-hit bragging) ──
|
|
745
|
+
"billing.share.btn": "Share scorecard",
|
|
746
|
+
"billing.share.tooltip": "Share your usage scorecard",
|
|
747
|
+
"share.scorecard.modal.title": "My {{brand}} scorecard",
|
|
748
|
+
"share.scorecard.modal.subtitle": "Pick a style, tweak the text — your numbers are filled in.",
|
|
749
|
+
"share.scorecard.poster.title": "My AI Work Scorecard",
|
|
750
|
+
"share.scorecard.poster.cacheLabel": "cache hit rate",
|
|
751
|
+
"share.scorecard.poster.costLabel": "spent",
|
|
752
|
+
"share.scorecard.poster.tokensLabel": "tokens",
|
|
753
|
+
"share.scorecard.poster.scan": "Scan to try it",
|
|
754
|
+
"share.scorecard.poster.heatmapLabel": "Daily usage this month",
|
|
755
|
+
"share.scorecard.period.day": "Today",
|
|
756
|
+
"share.scorecard.period.week": "This week",
|
|
757
|
+
"share.scorecard.period.month": "This month",
|
|
758
|
+
"share.scorecard.golden.high": "Almost free — {{cacheHitRate}}% of my context was cache hits.",
|
|
759
|
+
"share.scorecard.golden.mid": "{{tokens}} tokens, {{cost}} total. Caching pays off.",
|
|
760
|
+
"share.scorecard.golden.low": "{{requests}} tasks done for {{cost}}. Worth every cent.",
|
|
761
|
+
"share.scorecard.copy.weibo.1": "Running my AI tasks on {{brand}} — {{cacheHitRate}}% cache hit rate, only {{cost}} this period 😎 #AICoding# #SaveTokens#",
|
|
762
|
+
"share.scorecard.copy.weibo.2": "Did the math: {{requests}} tasks on {{brand}} this period for {{cost}}. Prompt caching is doing the heavy lifting 🔥 #AICoding#",
|
|
763
|
+
"share.scorecard.copy.weibo.3": "{{cacheHitRate}}% cache hits, {{tokens}} tokens, {{cost}} total. {{brand}} keeps my AI bill tiny 😎 #SaveTokens#",
|
|
764
|
+
"share.scorecard.copy.xhs.1": "Found a way to save on AI tokens‼️\nUsing {{brand}}, my cache hit rate is {{cacheHitRate}}%🤯\n{{tokens}} tokens this period for just {{cost}}💰\nIf you build with AI, try this ⬇️\n#AIGC #DevTools #SaveMoney",
|
|
765
|
+
"share.scorecard.copy.xhs.2": "AI bill check 🧾\n{{brand}} got me {{cacheHitRate}}% cache hits this period ✨\nOnly {{cost}} for {{requests}} tasks 😱\nGenuinely saving money, had to share 👇\n#AIGC #DevTools #程序员",
|
|
766
|
+
"share.scorecard.copy.xhs.3": "How is my AI cost this low 🤯\n{{cacheHitRate}}% cache hit rate on {{brand}}\n{{tokens}} tokens → {{cost}} 💰\nSave this one ⬇️\n#AIGC #效率工具 #SaveMoney",
|
|
767
|
+
"share.scorecard.copy.wechat.1": "This period my AI tasks ran {{tokens}} tokens, {{cacheHitRate}}% cache hit, cost {{cost}}. Knowing the right tools really saves money.",
|
|
768
|
+
"share.scorecard.copy.wechat.2": "Quick share: {{requests}} AI tasks done for {{cost}} this period on {{brand}}. The prompt caching genuinely cuts the bill.",
|
|
769
|
+
"share.scorecard.copy.wechat.3": "{{cacheHitRate}}% cache hit rate this period — {{brand}} brought my AI cost down to {{cost}}. Worth a look if you build with AI.",
|
|
770
|
+
"share.scorecard.copy.bilibili.1":"Sharing some numbers: {{cacheHitRate}}% cache hit rate on {{brand}} this period, token cost down to {{cost}}. Its prompt caching is no joke.",
|
|
771
|
+
"share.scorecard.copy.bilibili.2":"Ran the numbers: {{tokens}} tokens, {{requests}} tasks, {{cost}} total on {{brand}}. Caching does most of the work.",
|
|
772
|
+
"share.scorecard.copy.copylink.1":"My {{brand}} scorecard: {{cacheHitRate}}% cache hit, {{tokens}} tokens, {{cost}}.",
|
|
712
773
|
// ── Session info bar / Model switcher benchmark ──
|
|
713
774
|
"sib.bench.btn": "Benchmark",
|
|
714
775
|
"sib.bench.tooltip": "Test response latency for every configured model",
|
|
@@ -735,6 +796,10 @@ const I18n = (() => {
|
|
|
735
796
|
"billing.model": "Model",
|
|
736
797
|
"billing.cost": "Cost",
|
|
737
798
|
"billing.dailyUsage": "Usage Details",
|
|
799
|
+
"billing.heatmap.title": "Daily Activity",
|
|
800
|
+
"billing.heatmap.dow": "S,M,T,W,T,F,S",
|
|
801
|
+
"billing.heatmap.less": "Less",
|
|
802
|
+
"billing.heatmap.more": "More",
|
|
738
803
|
"billing.period.day": "Today",
|
|
739
804
|
"billing.period.week": "This Week",
|
|
740
805
|
"billing.period.month": "This Month",
|
|
@@ -1460,6 +1525,67 @@ const I18n = (() => {
|
|
|
1460
1525
|
"notify.tooltip.on": "任务完成提示音:已开启(点击关闭)",
|
|
1461
1526
|
"notify.tooltip.off": "任务完成提示音:已关闭(点击开启)",
|
|
1462
1527
|
|
|
1528
|
+
"share.tooltip": "分享",
|
|
1529
|
+
"share.copy.1": "{{brand}} 对国产模型(Kimi、DeepSeek、智谱…)适配很到位,原生支持不用折腾代理,好用。",
|
|
1530
|
+
"share.copy.2": "{{brand}} 是真省 Token —— 靠 Prompt 缓存把我的 AI 账单压得很低,推荐。",
|
|
1531
|
+
"share.copy.3": "一行命令装好 {{brand}},开箱即用。做 AI 的可以试试。",
|
|
1532
|
+
"share.copy.4": "{{brand}}:适配国产模型、省 Token、安装超简单,现在每天都在用。",
|
|
1533
|
+
"share.copied": "已复制到剪贴板",
|
|
1534
|
+
"share.copyFailed": "复制失败",
|
|
1535
|
+
"share.modal.title": "分享 {{brand}}",
|
|
1536
|
+
"share.modal.subtitle": "选个海报风格,改改文案,发到任意平台。",
|
|
1537
|
+
"share.platform.weibo": "微博",
|
|
1538
|
+
"share.platform.xhs": "小红书",
|
|
1539
|
+
"share.platform.wechat": "微信",
|
|
1540
|
+
"share.platform.bilibili": "B站",
|
|
1541
|
+
"share.action.copyLink": "复制链接",
|
|
1542
|
+
"share.action.copyText": "复制文案",
|
|
1543
|
+
"share.action.download": "下载海报",
|
|
1544
|
+
"share.action.downloadAndCopy": "下载海报 + 复制文案",
|
|
1545
|
+
"share.action.toWeibo": "一键发微博",
|
|
1546
|
+
"share.action.shuffle": "换一条",
|
|
1547
|
+
"share.action.systemShare": "分享…",
|
|
1548
|
+
"share.editor.label": "分享前可以改",
|
|
1549
|
+
"share.theme.label": "海报风格",
|
|
1550
|
+
"share.theme.geek": "极客深色",
|
|
1551
|
+
"share.theme.light": "清爽浅色",
|
|
1552
|
+
"share.theme.warm": "种草暖色",
|
|
1553
|
+
"share.hint.xhs": "海报已下载、文案已复制 —— 打开小红书发笔记时选这张图就行。",
|
|
1554
|
+
"share.hint.wechat": "海报已下载、文案已复制 —— 打开微信分享到朋友圈吧。",
|
|
1555
|
+
"share.hint.bilibili": "海报已下载、文案已复制 —— 打开 B 站发动态时配上这张图。",
|
|
1556
|
+
"share.poster.tagline": "适配国产模型 · 省 Token · 一行安装 —— {{brand}}。",
|
|
1557
|
+
"share.poster.scan": "扫码了解更多",
|
|
1558
|
+
"share.prompt.message": "干得漂亮!喜欢 {{brand}} 的话,分享给朋友吧。",
|
|
1559
|
+
"share.prompt.action": "去分享",
|
|
1560
|
+
// ── 成绩单分享(B 线:省钱 / 缓存命中炫耀)──
|
|
1561
|
+
"billing.share.btn": "晒成绩单",
|
|
1562
|
+
"billing.share.tooltip": "分享我的用量成绩单",
|
|
1563
|
+
"share.scorecard.modal.title": "我的 {{brand}} 成绩单",
|
|
1564
|
+
"share.scorecard.modal.subtitle": "选个风格、改改文案 —— 数字已经帮你填好了。",
|
|
1565
|
+
"share.scorecard.poster.title": "我的 AI 工作成绩单",
|
|
1566
|
+
"share.scorecard.poster.cacheLabel": "缓存命中率",
|
|
1567
|
+
"share.scorecard.poster.costLabel": "花费",
|
|
1568
|
+
"share.scorecard.poster.tokensLabel": "Token",
|
|
1569
|
+
"share.scorecard.poster.scan": "扫码也来试试",
|
|
1570
|
+
"share.scorecard.poster.heatmapLabel": "本月每日用量",
|
|
1571
|
+
"share.scorecard.period.day": "当天",
|
|
1572
|
+
"share.scorecard.period.week": "当周",
|
|
1573
|
+
"share.scorecard.period.month": "当月",
|
|
1574
|
+
"share.scorecard.golden.high": "几乎零成本 —— {{cacheHitRate}}% 的上下文都命中了缓存。",
|
|
1575
|
+
"share.scorecard.golden.mid": "{{tokens}} token、共花 {{cost}},缓存是真省。",
|
|
1576
|
+
"share.scorecard.golden.low": "{{requests}} 个任务只花了 {{cost}},值。",
|
|
1577
|
+
"share.scorecard.copy.weibo.1": "我用 {{brand}} 跑 AI 任务,缓存命中率 {{cacheHitRate}}%,这段时间只花了 {{cost}} 😎 #AI编程# #省token#",
|
|
1578
|
+
"share.scorecard.copy.weibo.2": "算了笔账:本期 {{requests}} 个 AI 任务在 {{brand}} 上只花了 {{cost}},prompt 缓存立大功 🔥 #AI编程#",
|
|
1579
|
+
"share.scorecard.copy.weibo.3": "缓存命中 {{cacheHitRate}}%、{{tokens}} token、共 {{cost}}。{{brand}} 把我的 AI 账单压得很低 😎 #省token#",
|
|
1580
|
+
"share.scorecard.copy.xhs.1": "姐妹们我发现个省 token 的神器‼️\n用 {{brand}} 做 AI 任务,缓存命中率居然有 {{cacheHitRate}}%🤯\n这段时间 {{tokens}} token 才花 {{cost}}💰\n做 AI 的真的可以试试⬇️\n#AIGC #程序员 #省钱攻略",
|
|
1581
|
+
"share.scorecard.copy.xhs.2": "查了下我的 AI 账单 🧾\n本期 {{brand}} 缓存命中 {{cacheHitRate}}% ✨\n{{requests}} 个任务只花 {{cost}} 😱\n是真省钱,必须分享 👇\n#AIGC #程序员 #效率工具",
|
|
1582
|
+
"share.scorecard.copy.xhs.3": "我的 AI 成本怎么这么低 🤯\n{{brand}} 缓存命中率 {{cacheHitRate}}%\n{{tokens}} token → {{cost}} 💰\n建议收藏这条 ⬇️\n#AIGC #效率工具 #省钱",
|
|
1583
|
+
"share.scorecard.copy.wechat.1": "这段时间 AI 任务跑了 {{tokens}} token,缓存命中 {{cacheHitRate}}%,花了 {{cost}}。会用工具是真省钱。",
|
|
1584
|
+
"share.scorecard.copy.wechat.2": "随手分享:本期在 {{brand}} 上做了 {{requests}} 个 AI 任务,只花 {{cost}}。prompt 缓存是真能省账单。",
|
|
1585
|
+
"share.scorecard.copy.wechat.3": "本期缓存命中率 {{cacheHitRate}}% —— {{brand}} 把我的 AI 花费压到了 {{cost}}。做 AI 的值得看看。",
|
|
1586
|
+
"share.scorecard.copy.bilibili.1":"分享个数据:本期用 {{brand}} 缓存命中率 {{cacheHitRate}}%,token 成本压到 {{cost}}。prompt cache 这块它确实做得狠。",
|
|
1587
|
+
"share.scorecard.copy.bilibili.2":"算了下账:{{tokens}} token、{{requests}} 个任务、共 {{cost}},全靠缓存扛着。",
|
|
1588
|
+
"share.scorecard.copy.copylink.1":"我的 {{brand}} 成绩单:缓存命中 {{cacheHitRate}}%,{{tokens}} token,{{cost}}。",
|
|
1463
1589
|
// ── 会话信息栏 / 模型切换器 测速 ──
|
|
1464
1590
|
"sib.bench.btn": "测速",
|
|
1465
1591
|
"sib.bench.tooltip": "测试所有已配置模型的响应延迟",
|
|
@@ -1486,6 +1612,10 @@ const I18n = (() => {
|
|
|
1486
1612
|
"billing.model": "模型",
|
|
1487
1613
|
"billing.cost": "费用",
|
|
1488
1614
|
"billing.dailyUsage": "使用详情",
|
|
1615
|
+
"billing.heatmap.title": "每日活跃度",
|
|
1616
|
+
"billing.heatmap.dow": "日,一,二,三,四,五,六",
|
|
1617
|
+
"billing.heatmap.less": "少",
|
|
1618
|
+
"billing.heatmap.more": "多",
|
|
1489
1619
|
"billing.period.day": "今日",
|
|
1490
1620
|
"billing.period.week": "本周",
|
|
1491
1621
|
"billing.period.month": "本月",
|
|
@@ -1547,6 +1677,19 @@ const I18n = (() => {
|
|
|
1547
1677
|
return str;
|
|
1548
1678
|
}
|
|
1549
1679
|
|
|
1680
|
+
// Collects numbered variant keys (`prefix.1`, `prefix.2`, …) into an array,
|
|
1681
|
+
// each interpolated with vars. Stops at the first missing index.
|
|
1682
|
+
function tList(prefix, vars = {}) {
|
|
1683
|
+
const dict = TRANSLATIONS[_lang] || TRANSLATIONS[DEFAULT_LANG];
|
|
1684
|
+
const out = [];
|
|
1685
|
+
for (let i = 1; ; i++) {
|
|
1686
|
+
const key = prefix + "." + i;
|
|
1687
|
+
if (dict[key] == null && TRANSLATIONS[DEFAULT_LANG][key] == null) break;
|
|
1688
|
+
out.push(t(key, vars));
|
|
1689
|
+
}
|
|
1690
|
+
return out;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1550
1693
|
/**
|
|
1551
1694
|
* Scan the DOM and apply translations to:
|
|
1552
1695
|
* data-i18n="key" → element.textContent
|
|
@@ -1589,7 +1732,7 @@ const I18n = (() => {
|
|
|
1589
1732
|
}
|
|
1590
1733
|
|
|
1591
1734
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
1592
|
-
return { lang, setLang, t, applyAll };
|
|
1735
|
+
return { lang, setLang, t, tList, applyAll };
|
|
1593
1736
|
})();
|
|
1594
1737
|
|
|
1595
1738
|
// ── Thinking Verbs for Progress Animation ──────────────────────────────────
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
<div id="header-right">
|
|
47
|
+
<button id="share-toggle-header" class="theme-toggle-btn" data-i18n-title="share.tooltip" title="Share">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
49
|
+
<circle cx="18" cy="5" r="3"/>
|
|
50
|
+
<circle cx="6" cy="12" r="3"/>
|
|
51
|
+
<circle cx="18" cy="19" r="3"/>
|
|
52
|
+
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
|
53
|
+
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
|
54
|
+
</svg>
|
|
55
|
+
</button>
|
|
47
56
|
<button id="notify-toggle-header" class="theme-toggle-btn" data-i18n-title="notify.tooltip.off" title="Sound on task complete"></button>
|
|
48
57
|
<button id="theme-toggle-header" class="theme-toggle-btn" title="Toggle theme">
|
|
49
58
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
@@ -1340,6 +1349,8 @@
|
|
|
1340
1349
|
<script src="/profile.js"></script>
|
|
1341
1350
|
<script src="/version.js"></script>
|
|
1342
1351
|
<script src="/sidebar.js"></script>
|
|
1352
|
+
<script src="/vendor/qrcode/qrcode.min.js"></script>
|
|
1353
|
+
<script src="/share.js"></script>
|
|
1343
1354
|
<script src="/app.js"></script>
|
|
1344
1355
|
</body>
|
|
1345
1356
|
</html>
|