diff --git a/src/core/controller/account/getUserCredits.ts b/src/core/controller/account/getUserCredits.ts index d6e239e844..9af32936d0 100644 --- a/src/core/controller/account/getUserCredits.ts +++ b/src/core/controller/account/getUserCredits.ts @@ -26,10 +26,19 @@ export async function getUserCredits(controller: Controller, request: EmptyReque throw new Error("Failed to fetch user credits data") } + const packedPaymentTransactions = (paymentTransactions || []).map((tx) => { + return { + paidAt: JSON.stringify(tx), + creatorId: tx.id, + amountCents: tx.amountCents, + credits: tx.credits, + } + }) + return UserCreditsData.create({ balance: balance ? { currentBalance: balance.balance / 100 } : { currentBalance: 0 }, usageTransactions: usageTransactions, - paymentTransactions: paymentTransactions, + paymentTransactions: packedPaymentTransactions, }) } catch (error) { console.error(`Failed to fetch user credits data: ${error}`) diff --git a/src/core/controller/models/refreshGroqModels.ts b/src/core/controller/models/refreshGroqModels.ts index 1937c2982c..1bfe245b96 100644 --- a/src/core/controller/models/refreshGroqModels.ts +++ b/src/core/controller/models/refreshGroqModels.ts @@ -78,7 +78,7 @@ export async function refreshGroqModels(controller: Controller, request: EmptyRe inputPrice: staticModelInfo?.inputPrice || 0, outputPrice: staticModelInfo?.outputPrice || 0, cacheWritesPrice: (staticModelInfo as any)?.cacheWritesPrice || 0, - cacheReadsPrice: (staticModelInfo as any).cacheReadsPrice || 0, + cacheReadsPrice: (staticModelInfo as any)?.cacheReadsPrice || 0, description: generateModelDescription(rawModel, staticModelInfo), } diff --git a/src/services/account/ClineAccountService.ts b/src/services/account/ClineAccountService.ts index ae7d4a7060..9ad61ccd6a 100644 --- a/src/services/account/ClineAccountService.ts +++ b/src/services/account/ClineAccountService.ts @@ -129,10 +129,8 @@ export class ClineAccountService { console.error("Failed to fetch user ID for usage transactions") return undefined } - const data = await this.authenticatedRequest<{ paymentTransactions: PaymentTransaction[] }>( - `/api/v1/users/${me.id}/payments`, - ) - return data.paymentTransactions + const data = await this.authenticatedRequest<{ items: PaymentTransaction[] }>(`/api/v1/users/${me.id}/payments`) + return data.items } catch (error) { console.error("Failed to fetch payment transactions (RPC):", error) return undefined diff --git a/src/shared/ClineAccount.ts b/src/shared/ClineAccount.ts index 72371805eb..cbc79d7914 100644 --- a/src/shared/ClineAccount.ts +++ b/src/shared/ClineAccount.ts @@ -43,10 +43,24 @@ export interface UsageTransaction { } export interface PaymentTransaction { - paidAt: string - creatorId: string + id: string + transactionId: string + userId: string amountCents: number credits: number + type: string + status: string + providerReference: string + metadata: { + customer_id: string + event_id: string + payment_id: string + provider: string + subscription_id: string + } + createdAt: string + updatedAt: string + completedAt: string } export interface OrganizationBalanceResponse { diff --git a/webview-ui/src/components/account/AccountView.tsx b/webview-ui/src/components/account/AccountView.tsx index a78df1a136..c5d187ade4 100644 --- a/webview-ui/src/components/account/AccountView.tsx +++ b/webview-ui/src/components/account/AccountView.tsx @@ -11,7 +11,7 @@ import VSCodeButtonLink from "../common/VSCodeButtonLink" import { AccountWelcomeView } from "./AccountWelcomeView" import { CreditBalance } from "./CreditBalance" import CreditsHistoryTable from "./CreditsHistoryTable" -import { convertProtoUsageTransactions, getClineUris, getMainRole } from "./helpers" +import { convertProtoPaymentTransactions, convertProtoUsageTransactions, getClineUris, getMainRole } from "./helpers" type AccountViewProps = { clineUser: ClineUser | null @@ -112,7 +112,7 @@ export const ClineAccountView = ({ clineUser, userOrganizations, activeOrganizat setBalance(newBalance ?? null) const newUsage = convertProtoUsageTransactions(response.usageTransactions) setUsageData((prev) => (deepEqual(newUsage, prev) ? prev : newUsage)) - const newPaymentsData = response.paymentTransactions + const newPaymentsData = convertProtoPaymentTransactions(response.paymentTransactions) setPaymentsData((prev) => (deepEqual(newPaymentsData, prev) ? prev : newPaymentsData)) } catch (error) { console.error("Failed to fetch user credit:", error) diff --git a/webview-ui/src/components/account/CreditsHistoryTable.tsx b/webview-ui/src/components/account/CreditsHistoryTable.tsx index d9060ca91d..93fbb0639e 100644 --- a/webview-ui/src/components/account/CreditsHistoryTable.tsx +++ b/webview-ui/src/components/account/CreditsHistoryTable.tsx @@ -1,4 +1,4 @@ -import type { PaymentTransaction, UsageTransaction } from "@shared/ClineAccount" +import type { PaymentTransaction as ClinePaymentTransaction, UsageTransaction } from "@shared/ClineAccount" import { VSCodeDataGrid, VSCodeDataGridCell, VSCodeDataGridRow } from "@vscode/webview-ui-toolkit/react" import { useState } from "react" import { formatDollars, formatTimestamp } from "@/utils/format" @@ -7,7 +7,7 @@ import { TabButton } from "../mcp/configuration/McpConfigurationView" interface CreditsHistoryTableProps { isLoading: boolean usageData: UsageTransaction[] - paymentsData: PaymentTransaction[] + paymentsData: ClinePaymentTransaction[] showPayments?: boolean } @@ -93,7 +93,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments {paymentsData.map((row, index) => ( - {formatTimestamp(row.paidAt)} + {formatTimestamp(row.completedAt)} {`$${formatDollars(row.amountCents)}`} {`${row.credits}`} diff --git a/webview-ui/src/components/account/helpers.ts b/webview-ui/src/components/account/helpers.ts index 1273c2f47c..53ad139b90 100644 --- a/webview-ui/src/components/account/helpers.ts +++ b/webview-ui/src/components/account/helpers.ts @@ -1,5 +1,11 @@ -import type { UsageTransaction as ClineAccountUsageTransaction } from "@shared/ClineAccount" -import type { UsageTransaction as ProtoUsageTransaction } from "@shared/proto/cline/account" +import type { + PaymentTransaction as ClineAccountPaymentTransaction, + UsageTransaction as ClineAccountUsageTransaction, +} from "@shared/ClineAccount" +import type { + PaymentTransaction as ProtoPaymentTransaction, + UsageTransaction as ProtoUsageTransaction, +} from "@shared/proto/cline/account" export const getMainRole = (roles?: string[]) => { if (!roles) return undefined @@ -45,3 +51,38 @@ export function convertProtoUsageTransaction(protoTransaction: ProtoUsageTransac export function convertProtoUsageTransactions(protoTransactions: ProtoUsageTransaction[]): ClineAccountUsageTransaction[] { return protoTransactions.map(convertProtoUsageTransaction) } + +/** + * Converts a protobuf PaymentTransaction to a ClineAccount PaymentTransaction + * This is a temporary workaround for the fact that the protobuf definition is out of sync with the API response. + */ +export function convertProtoPaymentTransaction(protoTransaction: ProtoPaymentTransaction): ClineAccountPaymentTransaction { + try { + const unpackedData = JSON.parse(protoTransaction.paidAt) + return unpackedData as ClineAccountPaymentTransaction + } catch (error) { + console.error("Failed to parse packed payment transaction:", error) + // Return a default/empty object that won't crash the UI + return { + id: "", + transactionId: "", + userId: "", + amountCents: 0, + credits: 0, + type: "", + status: "", + providerReference: "", + metadata: {}, + createdAt: "", + updatedAt: "", + completedAt: "", + } as unknown as ClineAccountPaymentTransaction + } +} + +/** + * Converts an array of protobuf PaymentTransactions to ClineAccount PaymentTransactions + */ +export function convertProtoPaymentTransactions(protoTransactions: ProtoPaymentTransaction[]): ClineAccountPaymentTransaction[] { + return protoTransactions.map(convertProtoPaymentTransaction) +} diff --git a/webview-ui/src/components/chat/chat-view/hooks/useButtonState.ts b/webview-ui/src/components/chat/chat-view/hooks/useButtonState.ts index 497e5e2c83..740babe5f9 100644 --- a/webview-ui/src/components/chat/chat-view/hooks/useButtonState.ts +++ b/webview-ui/src/components/chat/chat-view/hooks/useButtonState.ts @@ -1,7 +1,6 @@ import { useEffect } from "react" import { ClineMessage, ClineSayTool } from "@shared/ExtensionMessage" import { ChatState } from "../types/chatTypes" -import { useDeepCompareEffect } from "react-use" /** * Custom hook for managing button state based on messages @@ -19,7 +18,7 @@ export function useButtonState(messages: ClineMessage[], chatState: ChatState) { } = chatState // Update button state based on last message - useDeepCompareEffect(() => { + useEffect(() => { if (lastMessage) { switch (lastMessage.type) { case "ask": @@ -146,7 +145,20 @@ export function useButtonState(messages: ClineMessage[], chatState: ChatState) { break } } - }, [lastMessage, secondLastMessage]) + }, [ + lastMessage?.type, + lastMessage?.partial, + lastMessage?.ask, + lastMessage?.text, + lastMessage?.say, + secondLastMessage?.ask, + setSendingDisabled, + setEnableButtons, + setPrimaryButtonText, + setSecondaryButtonText, + setDidClickCancel, + chatState, + ]) // Reset button state when no messages useEffect(() => {