diff --git a/src/components/LLMChatViewer.tsx b/src/components/LLMChatViewer.tsx index 351de82..566c2ba 100644 --- a/src/components/LLMChatViewer.tsx +++ b/src/components/LLMChatViewer.tsx @@ -170,6 +170,22 @@ export function LLMChatViewer({ conversationId }: LLMChatViewerProps) { const [loadingState] = useState("success"); const [isSending, setIsSending] = useState(false); + const [balance, setBalance] = useState(null); + const [loadingBalance, setLoadingBalance] = useState(false); + + // Fetch balance when provider changes + useEffect(() => { + if (provider && provider.getBalance) { + setLoadingBalance(true); + provider + .getBalance() + .then((bal) => setBalance(bal)) + .catch(() => setBalance(null)) + .finally(() => setLoadingBalance(false)); + } else { + setBalance(null); + } + }, [provider]); // Update conversation model when provider changes useEffect(() => { @@ -274,6 +290,16 @@ export function LLMChatViewer({ conversationId }: LLMChatViewerProps) { totalCost: prev.totalCost + (response.cost || 0), }; }); + + // Refresh balance after successful message + if (provider.getBalance) { + provider + .getBalance() + .then((bal) => setBalance(bal)) + .catch(() => { + /* ignore errors */ + }); + } } catch (error) { console.error("Failed to send message:", error); // Add error message @@ -307,7 +333,30 @@ export function LLMChatViewer({ conversationId }: LLMChatViewerProps) {
{conversation.title} -
+
+ {/* Balance Display */} + {balance !== null && ( + + + +
+ Balance: + + ${balance.toFixed(2)} + + {loadingBalance && ( + + )} +
+
+ +

Account balance (updates after each query)

+
+
+
+ )} + + {/* Token Counter */} diff --git a/src/lib/llm/providers/ppq-provider.ts b/src/lib/llm/providers/ppq-provider.ts index 00747f2..d9c601d 100644 --- a/src/lib/llm/providers/ppq-provider.ts +++ b/src/lib/llm/providers/ppq-provider.ts @@ -305,4 +305,47 @@ export class PPQProviderAdapter implements LLMProviderAdapter { // Rough estimate: ~4 characters per token return Math.ceil(text.length / 4); } + + async getBalance(): Promise { + try { + // PPQ balance endpoint + // Documentation: https://ppq.ai/api-topups + const response = await fetch(`${this.baseUrl}/credits/balance`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + // Try with empty body first - Bearer auth might be sufficient + body: JSON.stringify({}), + }); + + if (!response.ok) { + console.warn(`Failed to fetch balance: ${response.status}`); + return null; + } + + const data = await response.json(); + + // Handle various possible response formats + if (typeof data === "number") { + return data; + } + if (data.balance !== undefined) { + return data.balance; + } + if (data.credits !== undefined) { + return data.credits; + } + if (data.amount !== undefined) { + return data.amount; + } + + console.warn("Unknown balance response format:", data); + return null; + } catch (error) { + console.error("Failed to get balance:", error); + return null; + } + } } diff --git a/src/lib/llm/types.ts b/src/lib/llm/types.ts index 3213ad7..9ed6300 100644 --- a/src/lib/llm/types.ts +++ b/src/lib/llm/types.ts @@ -125,4 +125,9 @@ export interface LLMProviderAdapter { * Count tokens in text */ countTokens?(text: string, model: string): Promise; + + /** + * Get account balance (if supported) + */ + getBalance?(): Promise; }