@yinxe/opencode-tui-usage 0.0.6 → 0.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/dist/quota/providers/minimax-io.d.ts +2 -0
- package/dist/quota/providers/minimax-io.d.ts.map +1 -0
- package/dist/quota/providers/minimax-io.js +1 -0
- package/dist/quota/providers/minimax.d.ts +11 -2
- package/dist/quota/providers/minimax.d.ts.map +1 -1
- package/dist/quota/providers/minimax.js +24 -8
- package/dist/quota/service.d.ts +3 -0
- package/dist/quota/service.d.ts.map +1 -1
- package/dist/quota/service.js +11 -0
- package/dist/tokens-usage.d.ts +9 -0
- package/dist/tokens-usage.d.ts.map +1 -0
- package/dist/tokens-usage.jsx +131 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.jsx +2 -0
- package/dist/usage.d.ts +3 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.jsx +60 -5
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minimax-io.d.ts","sourceRoot":"","sources":["../../../src/quota/providers/minimax-io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MiniMaxIOQuotaProvider } from "./minimax.js";
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import type { QuotaData, ProviderConfig } from "../types.js";
|
|
2
2
|
import { QuotaProvider } from "../provider.js";
|
|
3
|
-
|
|
4
|
-
readonly name
|
|
3
|
+
declare class MiniMaxQuotaProvider implements QuotaProvider {
|
|
4
|
+
readonly name: string;
|
|
5
5
|
private apiKey;
|
|
6
6
|
private baseUrl;
|
|
7
|
+
private logTag;
|
|
8
|
+
constructor(name: string, baseUrl: string);
|
|
7
9
|
init(config: ProviderConfig, _credentials: Record<string, unknown>): void;
|
|
8
10
|
fetchQuota(): Promise<QuotaData | null>;
|
|
9
11
|
resolveEnvVar(value: string | undefined): string | undefined;
|
|
10
12
|
private mapResponseToQuotaData;
|
|
11
13
|
private formatDuration;
|
|
12
14
|
}
|
|
15
|
+
export declare class MiniMaxCNQuotaProvider extends MiniMaxQuotaProvider {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
export declare class MiniMaxIOQuotaProvider extends MiniMaxQuotaProvider {
|
|
19
|
+
constructor();
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
13
22
|
//# sourceMappingURL=minimax.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minimax.d.ts","sourceRoot":"","sources":["../../../src/quota/providers/minimax.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAwB9D,
|
|
1
|
+
{"version":3,"file":"minimax.d.ts","sourceRoot":"","sources":["../../../src/quota/providers/minimax.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAwB9D,cAAM,oBAAqB,YAAW,aAAa;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAMzC,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKnE,UAAU,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA8C7C,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS;IAI5D,OAAO,CAAC,sBAAsB;IA8C9B,OAAO,CAAC,cAAc;CAMvB;AAED,qBAAa,sBAAuB,SAAQ,oBAAoB;;CAI/D;AAED,qBAAa,sBAAuB,SAAQ,oBAAoB;;CAI/D"}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { resolveEnvVar } from "../provider.js";
|
|
2
|
-
|
|
3
|
-
name
|
|
2
|
+
class MiniMaxQuotaProvider {
|
|
3
|
+
name;
|
|
4
4
|
apiKey;
|
|
5
|
-
baseUrl
|
|
5
|
+
baseUrl;
|
|
6
|
+
logTag;
|
|
7
|
+
constructor(name, baseUrl) {
|
|
8
|
+
this.name = name;
|
|
9
|
+
this.baseUrl = baseUrl;
|
|
10
|
+
this.logTag = `[${name}]`;
|
|
11
|
+
}
|
|
6
12
|
init(config, _credentials) {
|
|
7
13
|
const apiKeyRaw = config.apiKey;
|
|
8
14
|
this.apiKey = resolveEnvVar(apiKeyRaw) || resolveEnvVar(config.apiKeyEnvVar);
|
|
9
15
|
}
|
|
10
16
|
async fetchQuota() {
|
|
11
17
|
if (!this.apiKey) {
|
|
12
|
-
console.warn(
|
|
18
|
+
console.warn(`${this.logTag} Missing apiKey`);
|
|
13
19
|
return null;
|
|
14
20
|
}
|
|
15
21
|
try {
|
|
@@ -21,23 +27,23 @@ export class MiniMaxCNQuotaProvider {
|
|
|
21
27
|
},
|
|
22
28
|
});
|
|
23
29
|
if (!response.ok) {
|
|
24
|
-
console.error(
|
|
30
|
+
console.error(`${this.logTag} API error: ${response.status}`);
|
|
25
31
|
return null;
|
|
26
32
|
}
|
|
27
33
|
const data = (await response.json());
|
|
28
34
|
if (data.base_resp?.status_code !== 0) {
|
|
29
|
-
console.error(
|
|
35
|
+
console.error(`${this.logTag} API error: ${data.base_resp?.status_msg}`);
|
|
30
36
|
return null;
|
|
31
37
|
}
|
|
32
38
|
const codingPlanModels = data.model_remains.filter((m) => m.model_name.startsWith("MiniMax-M"));
|
|
33
39
|
if (codingPlanModels.length === 0) {
|
|
34
|
-
console.warn(
|
|
40
|
+
console.warn(`${this.logTag} No coding plan models found`);
|
|
35
41
|
return null;
|
|
36
42
|
}
|
|
37
43
|
return this.mapResponseToQuotaData(codingPlanModels);
|
|
38
44
|
}
|
|
39
45
|
catch (error) {
|
|
40
|
-
console.error(
|
|
46
|
+
console.error(`${this.logTag} Fetch failed:`, error);
|
|
41
47
|
return null;
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -92,3 +98,13 @@ export class MiniMaxCNQuotaProvider {
|
|
|
92
98
|
return `${Math.round(seconds / 86400)}d`;
|
|
93
99
|
}
|
|
94
100
|
}
|
|
101
|
+
export class MiniMaxCNQuotaProvider extends MiniMaxQuotaProvider {
|
|
102
|
+
constructor() {
|
|
103
|
+
super("minimax-cn-coding-plan", "https://www.minimaxi.com");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export class MiniMaxIOQuotaProvider extends MiniMaxQuotaProvider {
|
|
107
|
+
constructor() {
|
|
108
|
+
super("minimax-coding-plan", "https://api.minimax.io");
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/quota/service.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare class QuotaService {
|
|
|
10
10
|
registerProvider(provider: QuotaProvider): void;
|
|
11
11
|
setActiveProvider(providerName: string): boolean;
|
|
12
12
|
getActiveProviderName(): string | null;
|
|
13
|
+
isProviderSupported(providerName: string): boolean;
|
|
14
|
+
getRegisteredProviderNames(): string[];
|
|
15
|
+
getConfiguredProviderNames(): string[];
|
|
13
16
|
fetchQuota(): Promise<QuotaResult | null>;
|
|
14
17
|
}
|
|
15
18
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/quota/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/quota/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,gBAAgB,CAAwB;IAChD,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,YAAY,CAAK;;IAQzB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAI/C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAmBhD,qBAAqB,IAAI,MAAM,GAAG,IAAI;IAItC,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIlD,0BAA0B,IAAI,MAAM,EAAE;IAItC,0BAA0B,IAAI,MAAM,EAAE;IAIhC,UAAU,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CAahD"}
|
package/dist/quota/service.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readProviderConfig, getProviderConfig } from "./config.js";
|
|
2
2
|
import { MiniMaxCNQuotaProvider } from "./providers/minimax.js";
|
|
3
|
+
import { MiniMaxIOQuotaProvider } from "./providers/minimax-io.js";
|
|
3
4
|
import { OpenCodeGoQuotaProvider } from "./providers/opencode-go.js";
|
|
4
5
|
export class QuotaService {
|
|
5
6
|
providers = new Map();
|
|
@@ -9,6 +10,7 @@ export class QuotaService {
|
|
|
9
10
|
refreshCount = 0;
|
|
10
11
|
constructor() {
|
|
11
12
|
this.registerProvider(new MiniMaxCNQuotaProvider());
|
|
13
|
+
this.registerProvider(new MiniMaxIOQuotaProvider());
|
|
12
14
|
this.registerProvider(new OpenCodeGoQuotaProvider());
|
|
13
15
|
}
|
|
14
16
|
registerProvider(provider) {
|
|
@@ -32,6 +34,15 @@ export class QuotaService {
|
|
|
32
34
|
getActiveProviderName() {
|
|
33
35
|
return this.activeProviderName;
|
|
34
36
|
}
|
|
37
|
+
isProviderSupported(providerName) {
|
|
38
|
+
return this.providers.has(providerName);
|
|
39
|
+
}
|
|
40
|
+
getRegisteredProviderNames() {
|
|
41
|
+
return Array.from(this.providers.keys());
|
|
42
|
+
}
|
|
43
|
+
getConfiguredProviderNames() {
|
|
44
|
+
return Object.keys(this.providerRegistry);
|
|
45
|
+
}
|
|
35
46
|
async fetchQuota() {
|
|
36
47
|
if (!this.activeProvider) {
|
|
37
48
|
return null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { JSX } from "solid-js";
|
|
3
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
4
|
+
export interface TokensUsageViewProps {
|
|
5
|
+
api: TuiPluginApi;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function TokensUsageView(props: TokensUsageViewProps): JSX.Element;
|
|
9
|
+
//# sourceMappingURL=tokens-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens-usage.d.ts","sourceRoot":"","sources":["../src/tokens-usage.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAqC5D,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,YAAY,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CAsIxE"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createSignal, createEffect, Show, For } from "solid-js";
|
|
2
|
+
import { Title } from "./components.jsx";
|
|
3
|
+
function formatNumber(n) {
|
|
4
|
+
if (n >= 1000000)
|
|
5
|
+
return (n / 1000000).toFixed(1) + "M";
|
|
6
|
+
if (n >= 1000)
|
|
7
|
+
return (n / 1000).toFixed(1) + "K";
|
|
8
|
+
return n.toString();
|
|
9
|
+
}
|
|
10
|
+
function formatCost(cost) {
|
|
11
|
+
if (cost === 0)
|
|
12
|
+
return "$0";
|
|
13
|
+
const formatted = cost.toFixed(6).replace(/\.?0+$/, "");
|
|
14
|
+
return "$" + formatted;
|
|
15
|
+
}
|
|
16
|
+
function InlineMetric(props) {
|
|
17
|
+
return (<box flexDirection="row" gap={0}>
|
|
18
|
+
<text fg={props.color}>{props.label}</text>
|
|
19
|
+
<text fg="#888">:</text>
|
|
20
|
+
<text>{props.value}</text>
|
|
21
|
+
</box>);
|
|
22
|
+
}
|
|
23
|
+
export function TokensUsageView(props) {
|
|
24
|
+
const [stats, setStats] = createSignal([]);
|
|
25
|
+
const [totals, setTotals] = createSignal(null);
|
|
26
|
+
const [isLoading, setIsLoading] = createSignal(true);
|
|
27
|
+
createEffect(() => {
|
|
28
|
+
const sessionId = props.sessionId;
|
|
29
|
+
const messages = props.api.state.session.messages(sessionId);
|
|
30
|
+
if (!messages || messages.length === 0) {
|
|
31
|
+
setStats([]);
|
|
32
|
+
setTotals(null);
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const grouped = new Map();
|
|
37
|
+
let lastInput = 0;
|
|
38
|
+
messages.forEach((msg) => {
|
|
39
|
+
if (msg.role !== "assistant")
|
|
40
|
+
return;
|
|
41
|
+
const assistantMsg = msg;
|
|
42
|
+
if (!assistantMsg.tokens)
|
|
43
|
+
return;
|
|
44
|
+
const key = `${assistantMsg.providerID || "unknown"}::${assistantMsg.modelID || "unknown"}`;
|
|
45
|
+
if (!grouped.has(key)) {
|
|
46
|
+
grouped.set(key, {
|
|
47
|
+
providerID: assistantMsg.providerID || "unknown",
|
|
48
|
+
modelID: assistantMsg.modelID || "unknown",
|
|
49
|
+
totalCost: 0,
|
|
50
|
+
input: 0,
|
|
51
|
+
output: 0,
|
|
52
|
+
reasoning: 0,
|
|
53
|
+
cacheRead: 0,
|
|
54
|
+
cacheWrite: 0,
|
|
55
|
+
messageCount: 0,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const stat = grouped.get(key);
|
|
59
|
+
stat.totalCost += assistantMsg.cost || 0;
|
|
60
|
+
stat.input += assistantMsg.tokens.input || 0;
|
|
61
|
+
stat.output += assistantMsg.tokens.output || 0;
|
|
62
|
+
stat.reasoning += assistantMsg.tokens.reasoning || 0;
|
|
63
|
+
stat.cacheRead += assistantMsg.tokens.cache?.read || 0;
|
|
64
|
+
stat.cacheWrite += assistantMsg.tokens.cache?.write || 0;
|
|
65
|
+
stat.messageCount += 1;
|
|
66
|
+
lastInput = assistantMsg.tokens.input || 0;
|
|
67
|
+
});
|
|
68
|
+
let totalInput = 0, totalOutput = 0, totalReasoning = 0;
|
|
69
|
+
let totalCacheRead = 0, totalCacheWrite = 0, totalCost = 0;
|
|
70
|
+
grouped.forEach((stat) => {
|
|
71
|
+
totalInput += stat.input;
|
|
72
|
+
totalOutput += stat.output;
|
|
73
|
+
totalReasoning += stat.reasoning;
|
|
74
|
+
totalCacheRead += stat.cacheRead;
|
|
75
|
+
totalCacheWrite += stat.cacheWrite;
|
|
76
|
+
totalCost += stat.totalCost;
|
|
77
|
+
});
|
|
78
|
+
setStats(Array.from(grouped.values()));
|
|
79
|
+
setTotals({
|
|
80
|
+
input: totalInput,
|
|
81
|
+
output: totalOutput,
|
|
82
|
+
reasoning: totalReasoning,
|
|
83
|
+
cacheRead: totalCacheRead,
|
|
84
|
+
cacheWrite: totalCacheWrite,
|
|
85
|
+
cost: totalCost,
|
|
86
|
+
currentInput: lastInput,
|
|
87
|
+
});
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
});
|
|
90
|
+
return (<box flexDirection="column" gap={0}>
|
|
91
|
+
<Title text="Usage Tokens" color="#a29bfe"/>
|
|
92
|
+
|
|
93
|
+
<Show when={isLoading()}>
|
|
94
|
+
<text fg="#888">...</text>
|
|
95
|
+
</Show>
|
|
96
|
+
|
|
97
|
+
<Show when={!isLoading() && stats().length === 0}>
|
|
98
|
+
<text fg="#888">-</text>
|
|
99
|
+
</Show>
|
|
100
|
+
|
|
101
|
+
<Show when={!isLoading() && stats().length > 0}>
|
|
102
|
+
<box flexDirection="column" gap={0}>
|
|
103
|
+
<box flexDirection="row" gap={2}>
|
|
104
|
+
<InlineMetric label="In" value={formatNumber(totals()?.input ?? 0)} color="#6bcf7f"/>
|
|
105
|
+
<InlineMetric label="Out" value={formatNumber(totals()?.output ?? 0)} color="#fd79a8"/>
|
|
106
|
+
<InlineMetric label="Rea" value={formatNumber(totals()?.reasoning ?? 0)} color="#fdcb6e"/>
|
|
107
|
+
</box>
|
|
108
|
+
<box flexDirection="row" gap={2}>
|
|
109
|
+
<InlineMetric label="Cache" value={`R:${formatNumber(totals()?.cacheRead ?? 0)} W:${formatNumber(totals()?.cacheWrite ?? 0)}`} color="#00cec9"/>
|
|
110
|
+
</box>
|
|
111
|
+
<box flexDirection="row" gap={2}>
|
|
112
|
+
<InlineMetric label="Cost" value={formatCost(totals()?.cost ?? 0)} color="#ffd93d"/>
|
|
113
|
+
</box>
|
|
114
|
+
</box>
|
|
115
|
+
</Show>
|
|
116
|
+
|
|
117
|
+
<Show when={!isLoading() && stats().length > 1}>
|
|
118
|
+
<For each={stats()}>
|
|
119
|
+
{(stat) => (<box flexDirection="column" gap={0}>
|
|
120
|
+
<box flexDirection="row" gap={1}>
|
|
121
|
+
<text fg="#74b9ff">{stat.modelID || "unknown"}</text>
|
|
122
|
+
<text fg="#ffd93d">· {formatCost(stat.totalCost)}</text>
|
|
123
|
+
</box>
|
|
124
|
+
<text fg="#888">
|
|
125
|
+
I:{formatNumber(stat.input)} O:{formatNumber(stat.output)} R:{formatNumber(stat.reasoning)} C:{formatNumber(stat.cacheRead + stat.cacheWrite)}({stat.messageCount})
|
|
126
|
+
</text>
|
|
127
|
+
</box>)}
|
|
128
|
+
</For>
|
|
129
|
+
</Show>
|
|
130
|
+
</box>);
|
|
131
|
+
}
|
package/dist/tui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAAa,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAAa,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAoC1E,QAAA,MAAM,MAAM,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAG3C,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/tui.jsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UsageView } from "./usage.jsx";
|
|
2
2
|
import { SessionInfoView } from "./session-info.jsx";
|
|
3
|
+
import { TokensUsageView } from "./tokens-usage.jsx";
|
|
3
4
|
import { QuotaService } from "./quota/service.js";
|
|
4
5
|
const id = "opencode-tui-usage-plugin";
|
|
5
6
|
const tui = async (api) => {
|
|
@@ -11,6 +12,7 @@ const tui = async (api) => {
|
|
|
11
12
|
return (<box gap={0}>
|
|
12
13
|
<UsageView quotaService={quotaService} api={api} sessionId={_props.session_id}/>
|
|
13
14
|
<SessionInfoView api={api} sessionId={_props.session_id}/>
|
|
15
|
+
<TokensUsageView api={api} sessionId={_props.session_id}/>
|
|
14
16
|
</box>);
|
|
15
17
|
},
|
|
16
18
|
},
|
package/dist/usage.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export interface UsageViewProps {
|
|
|
6
6
|
quotaService: {
|
|
7
7
|
fetchQuota(): Promise<QuotaResult | null>;
|
|
8
8
|
setActiveProvider(providerName: string): boolean;
|
|
9
|
+
isProviderSupported(providerName: string): boolean;
|
|
10
|
+
getRegisteredProviderNames(): string[];
|
|
11
|
+
getConfiguredProviderNames(): string[];
|
|
9
12
|
};
|
|
10
13
|
api: TuiPluginApi;
|
|
11
14
|
sessionId: string;
|
package/dist/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE;QACZ,UAAU,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAC1C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE;QACZ,UAAU,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAC1C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QACjD,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QACnD,0BAA0B,IAAI,MAAM,EAAE,CAAC;QACvC,0BAA0B,IAAI,MAAM,EAAE,CAAC;KACxC,CAAC;IACF,GAAG,EAAE,YAAY,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoDD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA2N5D"}
|
package/dist/usage.jsx
CHANGED
|
@@ -15,24 +15,65 @@ function formatDuration(totalSeconds) {
|
|
|
15
15
|
const s = totalSeconds % 60;
|
|
16
16
|
return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
17
17
|
}
|
|
18
|
+
function EmptyState(props) {
|
|
19
|
+
if (!props.provider) {
|
|
20
|
+
return <text fg="#888">No LLM activity detected</text>;
|
|
21
|
+
}
|
|
22
|
+
if (!props.supported) {
|
|
23
|
+
return (<box flexDirection="column" gap={0}>
|
|
24
|
+
<text fg="#ffd93d">Provider not supported</text>
|
|
25
|
+
<text fg="#888">
|
|
26
|
+
Adapter for "{props.provider}" not found.
|
|
27
|
+
</text>
|
|
28
|
+
{props.registeredProviders.length > 0 ? (<text fg="#888">
|
|
29
|
+
Registered: {props.registeredProviders.join(", ")}
|
|
30
|
+
</text>) : null}
|
|
31
|
+
{props.configuredProviders.length > 0 ? (<text fg="#888">
|
|
32
|
+
Configured: {props.configuredProviders.join(", ")}
|
|
33
|
+
</text>) : (<text fg="#888">
|
|
34
|
+
Configure provider in usage.provider.json
|
|
35
|
+
</text>)}
|
|
36
|
+
</box>);
|
|
37
|
+
}
|
|
38
|
+
if (props.error) {
|
|
39
|
+
return (<box flexDirection="column" gap={0}>
|
|
40
|
+
<text fg="#ff6666">Error fetching quota</text>
|
|
41
|
+
<text fg="#888">{props.error}</text>
|
|
42
|
+
</box>);
|
|
43
|
+
}
|
|
44
|
+
return <text fg="#888">No quota data available</text>;
|
|
45
|
+
}
|
|
18
46
|
export function UsageView(props) {
|
|
19
47
|
const [result, setResult] = createSignal(null);
|
|
20
48
|
const [loading, setLoading] = createSignal(true);
|
|
21
49
|
const [currentProvider, setCurrentProvider] = createSignal(null);
|
|
22
50
|
const [currentModel, setCurrentModel] = createSignal(null);
|
|
23
51
|
const [refreshCountdown, setRefreshCountdown] = createSignal(REFRESH_INTERVAL);
|
|
52
|
+
const [providerSupported, setProviderSupported] = createSignal(true);
|
|
53
|
+
const [fetchError, setFetchError] = createSignal(null);
|
|
24
54
|
const doRefresh = () => {
|
|
25
55
|
const providerID = currentProvider();
|
|
26
56
|
if (!providerID)
|
|
27
57
|
return;
|
|
28
58
|
setLoading(true);
|
|
29
|
-
|
|
59
|
+
setFetchError(null);
|
|
60
|
+
const supported = props.quotaService.setActiveProvider(providerID);
|
|
61
|
+
setProviderSupported(supported);
|
|
62
|
+
if (!supported) {
|
|
63
|
+
setLoading(false);
|
|
64
|
+
setResult(null);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
30
67
|
props.quotaService.fetchQuota().then((data) => {
|
|
31
68
|
if (data && data.quota) {
|
|
32
69
|
setResult(data);
|
|
33
70
|
}
|
|
71
|
+
else {
|
|
72
|
+
setResult(null);
|
|
73
|
+
}
|
|
34
74
|
setLoading(false);
|
|
35
|
-
}).catch(() => {
|
|
75
|
+
}).catch((error) => {
|
|
76
|
+
setFetchError(String(error));
|
|
36
77
|
setLoading(false);
|
|
37
78
|
});
|
|
38
79
|
};
|
|
@@ -42,6 +83,8 @@ export function UsageView(props) {
|
|
|
42
83
|
if (!messages || messages.length === 0) {
|
|
43
84
|
setCurrentProvider(null);
|
|
44
85
|
setCurrentModel(null);
|
|
86
|
+
setFetchError(null);
|
|
87
|
+
setProviderSupported(false);
|
|
45
88
|
return;
|
|
46
89
|
}
|
|
47
90
|
const lastAssistantMsg = [...messages]
|
|
@@ -50,11 +93,15 @@ export function UsageView(props) {
|
|
|
50
93
|
if (!lastAssistantMsg) {
|
|
51
94
|
setCurrentProvider(null);
|
|
52
95
|
setCurrentModel(null);
|
|
96
|
+
setFetchError(null);
|
|
97
|
+
setProviderSupported(false);
|
|
53
98
|
return;
|
|
54
99
|
}
|
|
55
100
|
if (!("providerID" in lastAssistantMsg)) {
|
|
56
101
|
setCurrentProvider(null);
|
|
57
102
|
setCurrentModel(null);
|
|
103
|
+
setFetchError(null);
|
|
104
|
+
setProviderSupported(false);
|
|
58
105
|
return;
|
|
59
106
|
}
|
|
60
107
|
const providerID = lastAssistantMsg.providerID;
|
|
@@ -66,14 +113,21 @@ export function UsageView(props) {
|
|
|
66
113
|
});
|
|
67
114
|
createEffect(() => {
|
|
68
115
|
const providerID = currentProvider();
|
|
69
|
-
const modelID = currentModel();
|
|
70
116
|
if (!providerID) {
|
|
71
117
|
setResult(null);
|
|
72
118
|
setLoading(false);
|
|
119
|
+
setProviderSupported(false);
|
|
73
120
|
return;
|
|
74
121
|
}
|
|
75
122
|
setLoading(true);
|
|
76
|
-
|
|
123
|
+
setFetchError(null);
|
|
124
|
+
const supported = props.quotaService.setActiveProvider(providerID);
|
|
125
|
+
setProviderSupported(supported);
|
|
126
|
+
if (!supported) {
|
|
127
|
+
setResult(null);
|
|
128
|
+
setLoading(false);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
77
131
|
props.quotaService.fetchQuota().then((data) => {
|
|
78
132
|
if (data && data.quota) {
|
|
79
133
|
setResult(data);
|
|
@@ -84,6 +138,7 @@ export function UsageView(props) {
|
|
|
84
138
|
setLoading(false);
|
|
85
139
|
}).catch((error) => {
|
|
86
140
|
console.error("[UsageView] Failed to fetch quota:", error);
|
|
141
|
+
setFetchError(String(error));
|
|
87
142
|
setResult(null);
|
|
88
143
|
setLoading(false);
|
|
89
144
|
});
|
|
@@ -157,6 +212,6 @@ export function UsageView(props) {
|
|
|
157
212
|
<ProgressBar value={result().quota.monthly?.usage ?? 0} color="#4da6ff"/>
|
|
158
213
|
</box>
|
|
159
214
|
<text fg="#888">{formatDuration(refreshCountdown())} Refresh #{result().refreshCount}</text>
|
|
160
|
-
</>) : (<
|
|
215
|
+
</>) : (<EmptyState provider={currentProvider()} supported={providerSupported()} error={fetchError()} registeredProviders={props.quotaService.getRegisteredProviderNames()} configuredProviders={props.quotaService.getConfiguredProviderNames()}/>)}
|
|
161
216
|
</box>);
|
|
162
217
|
}
|