diff --git a/ai/TOOLS.md b/ai/TOOLS.md index 0dd27cd7a..0ff0036e6 100644 --- a/ai/TOOLS.md +++ b/ai/TOOLS.md @@ -7,7 +7,6 @@ This document lists all available tools that AlphaHuman can use to interact with AlphaHuman has access to **4 tools** across **3 integrations** organized into **6 categories**. **Quick Statistics:** - - **Telegram**: 2 tools - **Notion**: 1 tools - **Gmail**: 1 tools @@ -74,7 +73,6 @@ This skill provides 1 tool for gmail integration. **Description**: Send an email via Gmail **Parameters**: - - **body** (string) **(required)**: Email body content - **subject** (string) **(required)**: Email subject line - **to** (string) **(required)**: Recipient email address @@ -82,11 +80,14 @@ This skill provides 1 tool for gmail integration. **Usage Context**: Available in all environments **Example**: - ```json { "tool": "send_email", - "parameters": { "body": "example_body", "subject": "example_subject", "to": "example_to" } + "parameters": { + "body": "example_body", + "subject": "example_subject", + "to": "example_to" + } } ``` @@ -103,7 +104,6 @@ This skill provides 1 tool for notion integration. **Description**: Create a new page in Notion workspace **Parameters**: - - **content** (array): Page content blocks - **parent_id** (string) **(required)**: Parent database or page ID - **title** (string) **(required)**: Page title @@ -111,11 +111,14 @@ This skill provides 1 tool for notion integration. **Usage Context**: Available in all environments **Example**: - ```json { "tool": "create_page", - "parameters": { "content": [], "parent_id": "example_parent_id", "title": "example_title" } + "parameters": { + "content": [], + "parent_id": "example_parent_id", + "title": "example_title" + } } ``` @@ -132,7 +135,6 @@ This skill provides 2 tools for telegram integration. **Description**: Send a message to a Telegram chat or user **Parameters**: - - **chat_id** (string) **(required)**: Telegram chat ID or username - **message** (string) **(required)**: Message text to send - **parse_mode** (string): Message formatting mode @@ -140,7 +142,6 @@ This skill provides 2 tools for telegram integration. **Usage Context**: Available in all environments **Example**: - ```json { "tool": "send_message", @@ -159,16 +160,20 @@ This skill provides 2 tools for telegram integration. **Description**: Retrieve message history from a Telegram chat **Parameters**: - - **chat_id** (string) **(required)**: Telegram chat ID or username - **limit** (number): Number of messages to retrieve **Usage Context**: Available in all environments **Example**: - ```json -{ "tool": "get_chat_history", "parameters": { "chat_id": "example_chat_id", "limit": 10 } } +{ + "tool": "get_chat_history", + "parameters": { + "chat_id": "example_chat_id", + "limit": 10 + } +} ``` --- @@ -176,42 +181,36 @@ This skill provides 2 tools for telegram integration. ## Tool Usage Guidelines ### Authentication - - All tools require proper authentication setup through the Skills system - OAuth credentials are managed securely and refreshed automatically - API keys are stored encrypted in the application keychain - Test credentials are available for development and testing environments ### Rate Limiting - - Tools automatically respect API rate limits of external services - Intelligent retry logic handles temporary failures with exponential backoff - Bulk operations are automatically chunked to avoid hitting limits - Rate limit status is monitored and reported in real-time ### Error Handling - - All tools return structured error responses with detailed information - Network failures trigger automatic retry with configurable attempts - Invalid parameters return clear validation messages with examples - Tool execution timeouts are handled gracefully with partial results ### Security & Privacy - - Input validation is performed on all parameters using JSON Schema - Output sanitization prevents injection attacks and data leakage - Sensitive data is never logged or exposed in error messages - All API communications use secure protocols (HTTPS/TLS) ### Performance Optimization - - Tool results are cached when appropriate to reduce API calls - Parallel execution is used for independent operations - Connection pooling minimizes overhead for repeated API calls - Background sync keeps data fresh without blocking operations ### Monitoring & Observability - - Tool execution metrics are collected for performance analysis - Error rates and response times are monitored continuously - Debug logging is available in development environments @@ -230,21 +229,18 @@ Tools are provided by Skills, which are JavaScript modules running in a secure V ## Integration Architecture ### V8 Runtime - - Skills execute in isolated V8 JavaScript contexts on desktop platforms - Mobile platforms use lightweight alternatives with server-side execution - Memory limits and execution timeouts prevent resource exhaustion - Inter-skill communication is managed through secure message passing ### API Bridge - - Tools communicate with external services through standardized API bridges - Rate limiting, retry logic, and error handling are implemented at the bridge level - Authentication tokens are managed centrally and shared across tools - Response caching and optimization are handled transparently ### Data Flow - 1. Tool request received from AI agent 2. Input validation and parameter processing 3. Skill execution in secure V8 context @@ -255,21 +251,18 @@ Tools are provided by Skills, which are JavaScript modules running in a secure V ## Support & Troubleshooting ### Common Issues - 1. **Tool Not Available**: Check if the associated skill is enabled in Settings → Skills 2. **Authentication Errors**: Verify credentials in the skill's configuration panel 3. **Rate Limit Exceeded**: Wait for the limit to reset or upgrade your API plan 4. **Invalid Parameters**: Review the parameter documentation and examples above ### Getting Help - - **Skill Documentation**: Each skill has detailed setup and usage instructions - **Debug Logs**: Enable verbose logging in development mode for detailed error information - **Community Support**: Join our Discord community for help from other users - **Technical Support**: Contact our support team for critical issues ### Contributing - - **New Tools**: Submit tool requests through our GitHub repository - **Bug Reports**: Report issues with specific tools and include error logs - **Improvements**: Suggest enhancements to existing tools and their documentation @@ -277,11 +270,10 @@ Tools are provided by Skills, which are JavaScript modules running in a secure V --- **Tool Statistics** - - Total Tools: 4 - Active Skills: 3 - Categories: 6 -- Last Updated: 2026-03-11T23:23:47.633Z +- Last Updated: 2026-03-16T12:17:45.377Z -_This file was automatically generated at build time from the V8 skills runtime._ -_For the most up-to-date information, regenerate this file by running `yarn tools:generate`._ +*This file was automatically generated at build time from the V8 skills runtime.* +*For the most up-to-date information, regenerate this file by running `yarn tools:generate`.* diff --git a/package.json b/package.json index bf986b8f7..dfdd1b3c2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "tauri": "tauri", "tauri:dev": "RUST_LOG=debug source scripts/load-dotenv.sh 2>/dev/null; (cd src-tauri && bash scripts/download-tdlib.sh) && LOCAL_TDLIB_PATH=$(pwd)/src-tauri/tdlib-cache/macos-$(uname -m | sed 's/arm64/aarch64/; s/x86_64/x86_64/') tauri dev", "macos:build:debug": "yarn macos:build:release --debug", + "macos:x64:build:debug": "yarn macos:x64:build:release --debug", + "macos:x64:build:release": "source scripts/load-dotenv.sh 2>/dev/null; (cd src-tauri && bash scripts/download-tdlib.sh) && LOCAL_TDLIB_PATH=$(pwd)/src-tauri/tdlib-cache/macos-x86_64 tauri build --target x86_64-apple-darwin --bundles app dmg", "macos:build:release": "source scripts/load-dotenv.sh 2>/dev/null; (cd src-tauri && bash scripts/download-tdlib.sh) && LOCAL_TDLIB_PATH=$(pwd)/src-tauri/tdlib-cache/macos-$(uname -m | sed 's/arm64/aarch64/; s/x86_64/x86_64/') tauri build --bundles app dmg", "macos:build:sign:release": "source scripts/load-env.sh && tauri build --bundles app dmg", "macos:run": "open 'src-tauri/target/debug/bundle/macos/AlphaHuman.app'", diff --git a/src/components/settings/panels/BillingPanel.tsx b/src/components/settings/panels/BillingPanel.tsx index 0d704211b..443dcfd43 100644 --- a/src/components/settings/panels/BillingPanel.tsx +++ b/src/components/settings/panels/BillingPanel.tsx @@ -1,6 +1,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { billingApi } from '../../../services/api/billingApi'; +import { + type CreditBalance, + type TeamUsage, + creditsApi, +} from '../../../services/api/creditsApi'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { fetchCurrentUser } from '../../../store/userSlice'; import type { PlanTier } from '../../../types/api'; @@ -31,7 +36,12 @@ const BillingPanel = () => { const currentTier: PlanTier = activeTeam?.team.subscription?.plan ?? 'FREE'; const hasActive = activeTeam?.team.subscription?.hasActiveSubscription ?? false; const planExpiry = activeTeam?.team.subscription?.planExpiry; - const usage = user?.usage; + + // Credits & usage state + const [creditBalance, setCreditBalance] = useState(null); + const [teamUsage, setTeamUsage] = useState(null); + const [isLoadingCredits, setIsLoadingCredits] = useState(false); + const [isToppingUp, setIsToppingUp] = useState(false); // Local state const [billingInterval, setBillingInterval] = useState<'monthly' | 'annual'>('monthly'); @@ -43,9 +53,18 @@ const BillingPanel = () => { const pollStartRef = useRef(0); const timeoutRef = useRef(null); - // Fetch current plan on mount + // Fetch current plan, credits balance, and team usage on mount useEffect(() => { billingApi.getCurrentPlan().catch(console.error); + + setIsLoadingCredits(true); + Promise.all([creditsApi.getBalance(), creditsApi.getTeamUsage()]) + .then(([balance, usage]) => { + setCreditBalance(balance); + setTeamUsage(usage); + }) + .catch(console.error) + .finally(() => setIsLoadingCredits(false)); }, []); // When crypto is selected, force annual @@ -102,7 +121,6 @@ const BillingPanel = () => { currentTierRef.current = currentTier; }, [currentTier]); - // eslint-disable-next-line react-hooks/preserve-manual-memoization const startPolling = useCallback(() => { if (pollRef.current) clearInterval(pollRef.current); pollStartRef.current = Date.now(); @@ -162,6 +180,18 @@ const BillingPanel = () => { } }; + const handleTopUp = async (amountUsd: number) => { + setIsToppingUp(true); + try { + const result = await creditsApi.topUp(amountUsd, 'stripe'); + await openUrl(result.url); + } catch (err) { + console.error('Top-up failed:', err); + } finally { + setIsToppingUp(false); + } + }; + // ── JSX ───────────────────────────────────────────────────────────── return (
@@ -174,40 +204,22 @@ const BillingPanel = () => { {/*
*/}
-
-
+
+ {/* ── Current Plan Header ───────────────────────────────── */} +

- Your Current Plan {currentTier} + Current Plan — {currentTier}

- {usage && ( - - {Math.round((usage.spentThisCycleUsd / usage.cycleBudgetUsd) * 100)}% used - - )} -
- - {hasActive && ( -
- {planExpiry && ( -

- Renews{' '} - {new Date(planExpiry).toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric', - })} -

- )} + {hasActive && ( -
- )} - {/* Renewal date (for non-active subscriptions) */} - {!hasActive && planExpiry && ( + )} +
+ {planExpiry && (

Renews{' '} {new Date(planExpiry).toLocaleDateString('en-US', { @@ -217,20 +229,96 @@ const BillingPanel = () => { })}

)} - {usage && ( -
-
-
+
+ + {/* ── Inference Budget (Team Usage) ─────────────────────── */} +
+
+

Inference Budget

+ {isLoadingCredits && ( + Loading… + )} + {teamUsage && !isLoadingCredits && ( + + ${teamUsage.remainingUsd.toFixed(2)} / ${teamUsage.cycleBudgetUsd.toFixed(2)} remaining + + )} +
+ {teamUsage ? ( + <> +
+
+
+
+ + Daily usage: ${teamUsage.dailyUsage.toFixed(3)} + + + {((teamUsage.totalInputTokensThisCycle + teamUsage.totalOutputTokensThisCycle) / 1000).toFixed(1)}k tokens this cycle + +
+ {teamUsage.remainingUsd <= 0 && ( +

+ Budget exhausted — top up your credits to continue using AI features. +

+ )} + + ) : isLoadingCredits ? ( +
+ ) : ( +

Unable to load usage data

)}
+ + {/* ── Credits Balance & Top-up ──────────────────────────── */} +
+

Credits Balance

+ {creditBalance ? ( +
+
+ General credits + + ${creditBalance.balanceUsd.toFixed(2)} + +
+
+ Top-up credits + + ${creditBalance.topUpBalanceUsd.toFixed(2)} + +
+
+ ) : isLoadingCredits ? ( +
+
+
+
+ ) : ( +

Unable to load balance

+ )} +
+ {[5, 10, 25].map(amount => ( + + ))} +
+
{/* ── Interval toggle ──────────────────────────────────── */} diff --git a/src/pages/Conversations.tsx b/src/pages/Conversations.tsx index f43fbce55..297ea8c6c 100644 --- a/src/pages/Conversations.tsx +++ b/src/pages/Conversations.tsx @@ -18,6 +18,7 @@ import { type ModelInfo, type Tool, } from '../services/api/inferenceApi'; +import { type TeamUsage, creditsApi } from '../services/api/creditsApi'; import { useAppDispatch, useAppSelector } from '../store/hooks'; import type { NotionPageSummary, NotionSummary, NotionUserProfile } from '../store/notionSlice'; import { @@ -141,6 +142,10 @@ const Conversations = () => { const [isLoadingModels, setIsLoadingModels] = useState(false); const [isSending, setIsSending] = useState(false); const [sendError, setSendError] = useState(null); + // Budget state + const [teamUsage, setTeamUsage] = useState(null); + const [isLoadingBudget, setIsLoadingBudget] = useState(false); + const isDragging = useRef(false); const messagesEndRef = useRef(null); const lastPanelWidthRef = useRef(panelWidth); @@ -219,6 +224,18 @@ const Conversations = () => { .finally(() => setIsLoadingModels(false)); }, []); + // Fetch inference budget on mount + useEffect(() => { + setIsLoadingBudget(true); + creditsApi + .getTeamUsage() + .then(data => setTeamUsage(data)) + .catch(() => { + // Budget unavailable — silently ignore + }) + .finally(() => setIsLoadingBudget(false)); + }, []); + // Remove thread fetching - threads are now loaded from Redux persist // Sync URL → Redux: when URL has a threadId param, select that thread @@ -995,6 +1012,34 @@ const Conversations = () => { {/* Message Input */}
+ {/* Budget depleted banner — show top-up CTA */} + {teamUsage && teamUsage.remainingUsd <= 0 && ( +
+
+ + + +

+ Daily inference budget exhausted. Top up to continue. +

+
+ +
+ )} + {/* Show warning if another thread is active */} {activeThreadId && activeThreadId !== selectedThreadId && (
@@ -1004,7 +1049,7 @@ const Conversations = () => {

)} - {/* Model selector */} + {/* Model selector + budget indicator */}
{isLoadingModels ? ( Loading models… @@ -1030,6 +1075,48 @@ const Conversations = () => { )} +
+ {/* Budget indicator — circular */} + {(isLoadingBudget || teamUsage) && (() => { + const size = 22; + const r = 9; + const circ = 2 * Math.PI * r; + const pct = teamUsage + ? Math.min(1, teamUsage.remainingUsd / teamUsage.cycleBudgetUsd) + : 0; + const dash = pct * circ; + return ( +
+ + + {teamUsage ? ( + + ) : ( + + )} + + {teamUsage && ( + + ${teamUsage.remainingUsd.toFixed(2)} + + )} +
+ ); + })()}
{sendError && (
diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx index 7d53f9060..8bbf56bf8 100644 --- a/src/pages/onboarding/Onboarding.tsx +++ b/src/pages/onboarding/Onboarding.tsx @@ -8,7 +8,6 @@ import { setOnboardedForUser } from '../../store/authSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import FeaturesStep from './steps/FeaturesStep'; import GetStartedStep from './steps/GetStartedStep'; -import InviteCodeStep from './steps/InviteCodeStep'; import PrivacyStep from './steps/PrivacyStep'; const Onboarding = () => { @@ -16,14 +15,13 @@ const Onboarding = () => { const dispatch = useAppDispatch(); const user = useAppSelector(state => state.user.user); const [currentStep, setCurrentStep] = useState(0); - const totalSteps = 4; + const totalSteps = 3; // Lottie animation files for each step const stepAnimations = [ - '/lottie/trophy.json', // Step 1 - Invite Code - '/lottie/wave.json', // Step 2 - Features - '/lottie/safe3.json', // Step 3 - Privacy - '/lottie/trophy.json', // Step 4 - Get Started + '/lottie/wave.json', // Step 1 - Features + '/lottie/safe3.json', // Step 2 - Privacy + '/lottie/trophy.json', // Step 3 - Get Started ]; const handleNext = () => { @@ -54,15 +52,11 @@ const Onboarding = () => { const renderStep = () => { switch (currentStep) { case 1: - return ; - case 2: - return ; - case 3: return ; - case 4: + case 2: return ; default: - return ; + return ; } }; diff --git a/src/services/api/creditsApi.ts b/src/services/api/creditsApi.ts index 05375a5a6..a29002f4e 100644 --- a/src/services/api/creditsApi.ts +++ b/src/services/api/creditsApi.ts @@ -1,25 +1,38 @@ import type { ApiResponse } from '../../types/api'; import { apiClient } from '../apiClient'; -interface CreditBalance { - balance: number; - currency: string; +export interface CreditBalance { + balanceUsd: number; + topUpBalanceUsd: number; } -interface CreditTransaction { +export interface TeamUsage { + remainingUsd: number; + cycleBudgetUsd: number; + dailyUsage: number; + totalInputTokensThisCycle: number; + totalOutputTokensThisCycle: number; +} + +export interface TopUpResult { + url: string; + gatewayTransactionId: string; + amountUsd: number; + gateway: string; +} + +export interface CreditTransaction { id: string; - amount: number; - type: 'credit' | 'debit'; - description: string; + type: 'EARN' | 'SPEND'; + action: string; + amountUsd: number; + balanceAfterUsd: number; createdAt: string; - // Add other fields based on backend schema } -interface PaginatedTransactions { +export interface PaginatedTransactions { transactions: CreditTransaction[]; - totalCount: number; - page: number; - limit: number; + total: number; } /** @@ -27,11 +40,35 @@ interface PaginatedTransactions { */ export const creditsApi = { /** - * Get the current user's credit balance + * Get the current user's credit balance (general + top-up) * GET /credits/balance */ getBalance: async (): Promise => { - const response = await apiClient.get>('/credits/balance'); + const response = await apiClient.get>('/payments/credits/balance'); + return response.data; + }, + + /** + * Get team inference budget usage for the current billing cycle + * GET /teams/me/usage + */ + getTeamUsage: async (): Promise => { + const response = await apiClient.get>('/teams/me/usage'); + return response.data; + }, + + /** + * Start a top-up (get Stripe or Coinbase payment URL) + * POST /credits/top-up + */ + topUp: async ( + amountUsd: number, + gateway: 'stripe' | 'coinbase' = 'stripe' + ): Promise => { + const response = await apiClient.post>('/payments/credits/top-up', { + amountUsd, + gateway, + }); return response.data; }, @@ -39,9 +76,9 @@ export const creditsApi = { * Get paginated credit transaction history * GET /credits/transactions */ - getTransactions: async (page = 1, limit = 50): Promise => { + getTransactions: async (limit = 20, offset = 0): Promise => { const response = await apiClient.get>( - `/credits/transactions?page=${page}&limit=${limit}` + `/credits/transactions?limit=${limit}&offset=${offset}` ); return response.data; },