pi-free 1.0.6 โ 1.0.7
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/package.json +2 -1
- package/widget/data.ts +113 -0
- package/widget/format.ts +26 -0
- package/widget/render.ts +117 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-free",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AIO Free AI models for Pi - Access free models from Kilo, Zen, OpenRouter, NVIDIA, Cline, Mistral, and Ollama",
|
|
6
6
|
"keywords": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"lib/**/*.ts",
|
|
35
35
|
"usage/**/*.ts",
|
|
36
36
|
"provider-failover/**/*.ts",
|
|
37
|
+
"widget/**/*.ts",
|
|
37
38
|
"config.ts",
|
|
38
39
|
"constants.ts",
|
|
39
40
|
"provider-factory.ts",
|
package/widget/data.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget data collection - aggregates metrics from all sources
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
PROVIDER_CLINE,
|
|
7
|
+
PROVIDER_KILO,
|
|
8
|
+
PROVIDER_NVIDIA,
|
|
9
|
+
PROVIDER_OPENROUTER,
|
|
10
|
+
PROVIDER_ZEN,
|
|
11
|
+
} from "../constants.ts";
|
|
12
|
+
import {
|
|
13
|
+
getCachedMetrics,
|
|
14
|
+
getDailyRequestCount,
|
|
15
|
+
getRequestCount,
|
|
16
|
+
} from "../usage/metrics.ts";
|
|
17
|
+
import { getAllCumulativeUsage } from "../usage/store.ts";
|
|
18
|
+
|
|
19
|
+
export interface ProviderRow {
|
|
20
|
+
provider: string;
|
|
21
|
+
key: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
sessionReqs: number;
|
|
24
|
+
dailyReqs: number;
|
|
25
|
+
dailyLimit?: number;
|
|
26
|
+
hourlyLimit?: number;
|
|
27
|
+
remainingToday?: number;
|
|
28
|
+
totalTokensIn: number;
|
|
29
|
+
totalTokensOut: number;
|
|
30
|
+
totalRequests: number;
|
|
31
|
+
costEquivalent: number;
|
|
32
|
+
firstUsed?: string;
|
|
33
|
+
credits?: number;
|
|
34
|
+
creditsLabel?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const KNOWN_PROVIDERS: Record<string, { icon: string; label: string }> = {
|
|
38
|
+
[PROVIDER_KILO]: { icon: "๐ฅ", label: "Kilo" },
|
|
39
|
+
[PROVIDER_OPENROUTER]: { icon: "๐", label: "OpenRouter" },
|
|
40
|
+
[PROVIDER_ZEN]: { icon: "โฆ", label: "Zen" },
|
|
41
|
+
[PROVIDER_NVIDIA]: { icon: "โก", label: "NVIDIA" },
|
|
42
|
+
[PROVIDER_CLINE]: { icon: "๐ค", label: "Cline" },
|
|
43
|
+
local: { icon: "๐ป", label: "Local" },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Session-level request tracking for non-extension providers
|
|
47
|
+
const sessionRequestCounts = new Map<string, number>();
|
|
48
|
+
|
|
49
|
+
export function recordSessionRequest(provider: string): void {
|
|
50
|
+
const current = sessionRequestCounts.get(provider) ?? 0;
|
|
51
|
+
sessionRequestCounts.set(provider, current + 1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function collectRows(): ProviderRow[] {
|
|
55
|
+
const cumulative = getAllCumulativeUsage();
|
|
56
|
+
const orMetrics = getCachedMetrics(PROVIDER_OPENROUTER);
|
|
57
|
+
const kiloMetrics = getCachedMetrics(PROVIDER_KILO);
|
|
58
|
+
|
|
59
|
+
// Discover all providers: known ones + any from cumulative store
|
|
60
|
+
const allKeys = new Set<string>(Object.keys(KNOWN_PROVIDERS));
|
|
61
|
+
for (const key of Object.keys(cumulative)) {
|
|
62
|
+
allKeys.add(key);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rows: ProviderRow[] = [];
|
|
66
|
+
|
|
67
|
+
for (const key of allKeys) {
|
|
68
|
+
const meta = KNOWN_PROVIDERS[key];
|
|
69
|
+
const c = cumulative[key];
|
|
70
|
+
const sessionReqs =
|
|
71
|
+
getRequestCount(key) || sessionRequestCounts.get(key) || 0;
|
|
72
|
+
|
|
73
|
+
const row: ProviderRow = {
|
|
74
|
+
provider: meta?.label ?? key,
|
|
75
|
+
key,
|
|
76
|
+
icon: meta?.icon ?? "๐ฆ",
|
|
77
|
+
sessionReqs,
|
|
78
|
+
dailyReqs: getDailyRequestCount(key) || 0,
|
|
79
|
+
totalTokensIn: c?.tokensIn ?? 0,
|
|
80
|
+
totalTokensOut: c?.tokensOut ?? 0,
|
|
81
|
+
totalRequests: c?.requests ?? 0,
|
|
82
|
+
costEquivalent: c?.costEquivalent ?? 0,
|
|
83
|
+
firstUsed: c?.firstUsed,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Provider-specific known limits
|
|
87
|
+
if (key === PROVIDER_OPENROUTER) {
|
|
88
|
+
row.dailyLimit = orMetrics?.rateLimit?.requestsPerDay;
|
|
89
|
+
row.remainingToday = orMetrics?.rateLimit?.remainingToday;
|
|
90
|
+
row.credits = orMetrics?.credits;
|
|
91
|
+
row.creditsLabel = "credits";
|
|
92
|
+
} else if (key === PROVIDER_KILO) {
|
|
93
|
+
row.hourlyLimit = 200;
|
|
94
|
+
row.credits = kiloMetrics?.balance;
|
|
95
|
+
row.creditsLabel = "balance";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
rows.push(row);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Sort: known providers first (in order), then unknown, then inactive
|
|
102
|
+
const order = Object.keys(KNOWN_PROVIDERS);
|
|
103
|
+
rows.sort((a, b) => {
|
|
104
|
+
const ai = order.indexOf(a.key);
|
|
105
|
+
const bi = order.indexOf(b.key);
|
|
106
|
+
const aOrder = ai >= 0 ? ai : 100;
|
|
107
|
+
const bOrder = bi >= 0 ? bi : 100;
|
|
108
|
+
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
109
|
+
return b.sessionReqs + b.totalRequests - (a.sessionReqs + a.totalRequests);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return rows;
|
|
113
|
+
}
|
package/widget/format.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function formatTokens(n: number): string {
|
|
6
|
+
if (n < 1000) return n.toString();
|
|
7
|
+
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
|
|
8
|
+
if (n < 1_000_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
9
|
+
return `${(n / 1_000_000_000).toFixed(1)}B`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatCost(n: number): string {
|
|
13
|
+
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
14
|
+
if (n < 1) return `$${n.toFixed(3)}`;
|
|
15
|
+
return `$${n.toFixed(2)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function relativeTime(isoDate: string): string {
|
|
19
|
+
const diff = Date.now() - new Date(isoDate).getTime();
|
|
20
|
+
const days = Math.floor(diff / 86_400_000);
|
|
21
|
+
if (days === 0) return "today";
|
|
22
|
+
if (days === 1) return "yesterday";
|
|
23
|
+
if (days < 30) return `${days}d ago`;
|
|
24
|
+
const months = Math.floor(days / 30);
|
|
25
|
+
return `${months}mo ago`;
|
|
26
|
+
}
|
package/widget/render.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget HTML rendering
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ProviderRow } from "./data.ts";
|
|
6
|
+
import { formatCost, formatTokens, relativeTime } from "./format.ts";
|
|
7
|
+
|
|
8
|
+
export function renderWidgetHTML(rows: ProviderRow[]): string {
|
|
9
|
+
let totalTokens = 0,
|
|
10
|
+
totalReqs = 0,
|
|
11
|
+
totalCost = 0;
|
|
12
|
+
for (const r of rows) {
|
|
13
|
+
totalTokens += r.totalTokensIn + r.totalTokensOut;
|
|
14
|
+
totalReqs += r.totalRequests;
|
|
15
|
+
totalCost += r.costEquivalent;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const summaryHTML =
|
|
19
|
+
totalReqs > 0
|
|
20
|
+
? `
|
|
21
|
+
<div style="background: rgba(255,255,255,0.04); border-radius: 8px; padding: 10px 12px; margin-bottom: 12px;">
|
|
22
|
+
<div style="display: flex; justify-content: space-between; font-size: 13px; font-weight: 500;">
|
|
23
|
+
<span>Total free value</span>
|
|
24
|
+
<span style="color: #48bb78;">${formatCost(totalCost)} saved</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div style="font-size: 11px; color: #888; margin-top: 3px;">
|
|
27
|
+
${formatTokens(totalTokens)} tokens ยท ${totalReqs} requests
|
|
28
|
+
</div>
|
|
29
|
+
</div>`
|
|
30
|
+
: "";
|
|
31
|
+
|
|
32
|
+
const providerRows = rows.map((r) => renderProviderRow(r)).join("\n");
|
|
33
|
+
|
|
34
|
+
return `<!DOCTYPE html>
|
|
35
|
+
<html><head><meta charset="UTF-8">
|
|
36
|
+
<style>
|
|
37
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
38
|
+
body {
|
|
39
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
40
|
+
background: rgba(24, 24, 30, 0.95);
|
|
41
|
+
color: #e0e0e0; padding: 14px 16px;
|
|
42
|
+
min-height: 100vh; backdrop-filter: blur(20px);
|
|
43
|
+
}
|
|
44
|
+
.header { font-size: 12px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; }
|
|
45
|
+
</style></head>
|
|
46
|
+
<body>
|
|
47
|
+
<div class="header">Free Usage</div>
|
|
48
|
+
${summaryHTML}
|
|
49
|
+
${providerRows}
|
|
50
|
+
<div style="font-size: 10px; color: #555; margin-top: 10px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,0.05);">
|
|
51
|
+
Each gateway has independent quotas โ using multiple providers multiplies capacity.
|
|
52
|
+
</div>
|
|
53
|
+
</body></html>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderProviderRow(r: ProviderRow): string {
|
|
57
|
+
const hasActivity = r.sessionReqs > 0 || r.totalRequests > 0;
|
|
58
|
+
const quotaBar = renderQuotaBar(r);
|
|
59
|
+
const infoHTML = renderInfoLine(r);
|
|
60
|
+
|
|
61
|
+
return `
|
|
62
|
+
<div style="padding: 7px 0; ${hasActivity ? "" : "opacity: 0.3;"}">
|
|
63
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
64
|
+
<span style="font-weight: 500; font-size: 13px;">${r.icon} ${r.provider}</span>
|
|
65
|
+
<span style="font-size: 10px; color: #888;">${r.sessionReqs} this session</span>
|
|
66
|
+
</div>
|
|
67
|
+
${infoHTML}
|
|
68
|
+
${quotaBar}
|
|
69
|
+
</div>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderQuotaBar(r: ProviderRow): string {
|
|
73
|
+
if (!r.dailyLimit || r.dailyLimit <= 0) return "";
|
|
74
|
+
|
|
75
|
+
const pct = Math.min(100, Math.round((r.dailyReqs / r.dailyLimit) * 100));
|
|
76
|
+
const color = pct > 80 ? "#e53e3e" : pct > 50 ? "#ecc94b" : "#48bb78";
|
|
77
|
+
|
|
78
|
+
return `
|
|
79
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-top: 3px;">
|
|
80
|
+
<div style="flex: 1; height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px; overflow: hidden;">
|
|
81
|
+
<div style="width: ${pct}%; height: 100%; background: ${color}; border-radius: 2px;"></div>
|
|
82
|
+
</div>
|
|
83
|
+
<span style="font-size: 10px; color: #666;">${r.dailyReqs}/${r.dailyLimit}</span>
|
|
84
|
+
</div>`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function renderInfoLine(r: ProviderRow): string {
|
|
88
|
+
const infoParts: string[] = [];
|
|
89
|
+
|
|
90
|
+
if (r.totalRequests > 0) {
|
|
91
|
+
infoParts.push(
|
|
92
|
+
`${formatTokens(r.totalTokensIn + r.totalTokensOut)} tok ยท ${r.totalRequests} reqs`,
|
|
93
|
+
);
|
|
94
|
+
if (r.costEquivalent > 0) {
|
|
95
|
+
infoParts.push(`โ${formatCost(r.costEquivalent)}`);
|
|
96
|
+
}
|
|
97
|
+
if (r.firstUsed) {
|
|
98
|
+
infoParts.push(`since ${relativeTime(r.firstUsed)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (r.remainingToday !== undefined) {
|
|
102
|
+
infoParts.push(`${r.remainingToday} left today`);
|
|
103
|
+
}
|
|
104
|
+
if (r.hourlyLimit) {
|
|
105
|
+
infoParts.push(`${r.hourlyLimit}/hr limit`);
|
|
106
|
+
}
|
|
107
|
+
if (r.credits !== undefined) {
|
|
108
|
+
infoParts.push(`๐ฐ ${formatCost(r.credits)}`);
|
|
109
|
+
}
|
|
110
|
+
if (r.key === "local" && r.totalRequests > 0) {
|
|
111
|
+
infoParts.push("always free");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (infoParts.length === 0) return "";
|
|
115
|
+
|
|
116
|
+
return `<div style="font-size: 10px; color: #777; margin-top: 2px;">${infoParts.join(" ยท ")}</div>`;
|
|
117
|
+
}
|