@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.
@@ -0,0 +1,2 @@
1
+ export { MiniMaxIOQuotaProvider } from "./minimax.js";
2
+ //# sourceMappingURL=minimax-io.d.ts.map
@@ -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
- export declare class MiniMaxCNQuotaProvider implements QuotaProvider {
4
- readonly name = "minimax-cn-coding-plan";
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,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,QAAQ,CAAC,IAAI,4BAA4B;IAEzC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAA8B;IAE7C,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"}
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
- export class MiniMaxCNQuotaProvider {
3
- name = "minimax-cn-coding-plan";
2
+ class MiniMaxQuotaProvider {
3
+ name;
4
4
  apiKey;
5
- baseUrl = "https://www.minimaxi.com";
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("[MiniMaxCNQuotaProvider] Missing apiKey");
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(`[MiniMaxCNQuotaProvider] API error: ${response.status}`);
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(`[MiniMaxCNQuotaProvider] API error: ${data.base_resp?.status_msg}`);
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("[MiniMaxCNQuotaProvider] No coding plan models found");
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("[MiniMaxCNQuotaProvider] Fetch failed:", 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
+ }
@@ -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;AAKnD,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;;IAOzB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAI/C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAmBhD,qBAAqB,IAAI,MAAM,GAAG,IAAI;IAIhC,UAAU,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CAahD"}
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"}
@@ -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;AA+B1E,QAAA,MAAM,MAAM,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAG3C,CAAC;AAEF,eAAe,MAAM,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;
@@ -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;KAClD,CAAC;IACF,GAAG,EAAE,YAAY,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA0L5D"}
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
- props.quotaService.setActiveProvider(providerID);
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
- props.quotaService.setActiveProvider(providerID);
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
- </>) : (<text>No data</text>)}
215
+ </>) : (<EmptyState provider={currentProvider()} supported={providerSupported()} error={fetchError()} registeredProviders={props.quotaService.getRegisteredProviderNames()} configuredProviders={props.quotaService.getConfiguredProviderNames()}/>)}
161
216
  </box>);
162
217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yinxe/opencode-tui-usage",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "OpenCode TUI 额度显示插件 - 在侧边栏显示用量和额度信息",
5
5
  "repository": "github:Yinxe/opencode-tui-usage",
6
6
  "type": "module",