From 491ade57c0d4a96ee92b3b0600ee1e96dc52db27 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Mon, 15 Jun 2026 06:19:05 +0530 Subject: [PATCH] feat(ai): integrate AI usage/credits into dashboard and chat widget --- .forgejo/workflows/build.yaml | 9 + src/components/AiChatWidget.tsx | 58 +++ src/components/dashboard/CreditsPage.tsx | 457 ++++++++++++++++++++++- 3 files changed, 514 insertions(+), 10 deletions(-) diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 2585f3f..e090f47 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -13,6 +13,9 @@ concurrency: jobs: build: runs-on: docker-ready + env: + DOCKER_HOST: tcp://127.0.0.1:2375 + DOCKER_BUILDKIT: "1" steps: - name: Checkout uses: actions/checkout@v4 @@ -22,6 +25,12 @@ jobs: - name: Set up Docker Buildx run: | set -euo pipefail + for attempt in $(seq 1 30); do + if docker version >/dev/null 2>&1; then + break + fi + sleep 2 + done docker version docker buildx create --use --name nxtgauge-builder || docker buildx use nxtgauge-builder docker buildx inspect --bootstrap diff --git a/src/components/AiChatWidget.tsx b/src/components/AiChatWidget.tsx index 14e1ae8..c50145b 100644 --- a/src/components/AiChatWidget.tsx +++ b/src/components/AiChatWidget.tsx @@ -14,6 +14,19 @@ interface ChatResponse { conversation_id: string; intent: string; confidence: number; + remaining_credits?: number; + remaining_daily_actions?: number; + credits_charged?: number; +} + +interface UsageStatus { + remaining_credits: number; + remaining_daily_actions: number; + daily_action_limit: number; + plan_code: string; + plan_name: string; + monthly_credits_total: number; + monthly_credits_used: number; } export function AiChatWidget() { @@ -28,6 +41,31 @@ export function AiChatWidget() { const [input, setInput] = createSignal(""); const [isLoading, setIsLoading] = createSignal(false); const [conversationId, setConversationId] = createSignal(""); + const [usage, setUsage] = createSignal(null); + + onMount(() => { + fetchUsage(); + }); + + const fetchUsage = async () => { + try { + const res = await fetch(`${API}/api/ai/usage`); + if (!res.ok) return; + const data = await res.json(); + const plan = data.plan || {}; + setUsage({ + remaining_credits: plan.remaining_credits ?? 0, + remaining_daily_actions: plan.remaining_daily_actions ?? 0, + daily_action_limit: plan.daily_action_limit ?? 0, + plan_code: plan.plan_code ?? "free", + plan_name: plan.plan_name ?? "Free", + monthly_credits_total: plan.monthly_credits_total ?? 0, + monthly_credits_used: plan.monthly_credits_used ?? 0, + }); + } catch (err) { + console.error("Failed to fetch AI usage", err); + } + }; const toggleChat = () => setIsOpen((v) => !v); @@ -57,6 +95,19 @@ export function AiChatWidget() { setConversationId(data.conversation_id); } + // Update usage state from response if available + if (typeof data.remaining_credits === "number") { + setUsage((prev) => + prev + ? { + ...prev, + remaining_credits: data.remaining_credits!, + remaining_daily_actions: data.remaining_daily_actions ?? prev.remaining_daily_actions, + } + : null + ); + } + const assistantMessage: ChatMessage = { role: "assistant", content: data.message, @@ -154,6 +205,13 @@ export function AiChatWidget() {

AI Assistant

+ + {(u) => ( +

+ {u().plan_name} · {u().remaining_credits} credits · {u().remaining_daily_actions}/{u().daily_action_limit} today +

+ )} +

Buy Credits + + + + )} + + + + + + + {/* AI Usage Tab */} + +

+

+ AI Usage History +

+ + {(s) => ( +
+
+

Plan

+

+ {s().plan_name} +

+
+
+

Credits Remaining

+

+ {s().remaining_credits} +

+
+
+

Daily Actions

+

+ {s().remaining_daily_actions}/{s().daily_action_limit} +

+
+
+

Monthly Used

+

+ {s().monthly_credits_used}/{s().monthly_credits_total} +

+
+
+ )} +
+ + +

+ No AI usage recorded yet. +

+
+ + 0}> +
+ + {(item) => ( +
+
+

+ {item.feature_code} +

+

+ {item.model_alias} · {formatDate(item.created_at)} +

+
+

+ -{item.credits_charged} credits +

+
+ )} +
+
+
+
+ + {/* Transactions Tab */}