feat(ai): integrate AI usage/credits into dashboard and chat widget
This commit is contained in:
parent
82700a68c9
commit
491ade57c0
3 changed files with 514 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<UsageStatus | null>(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() {
|
|||
<p style={{ margin: 0, color: "#fff", "font-weight": "700", "font-size": "15px" }}>
|
||||
AI Assistant
|
||||
</p>
|
||||
<Show when={usage()}>
|
||||
{(u) => (
|
||||
<p style={{ margin: "2px 0 0", color: "rgba(255,255,255,0.85)", "font-size": "11px" }}>
|
||||
{u().plan_name} · {u().remaining_credits} credits · {u().remaining_daily_actions}/{u().daily_action_limit} today
|
||||
</p>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -26,8 +26,38 @@ type Payment = {
|
|||
package_name?: string;
|
||||
};
|
||||
|
||||
type AiCreditPackage = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
credits: number;
|
||||
price_inr: number;
|
||||
};
|
||||
|
||||
type AiUsageLog = {
|
||||
id: string;
|
||||
feature_code: string;
|
||||
model_alias: string;
|
||||
credits_charged: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
type AiUsageSummary = {
|
||||
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;
|
||||
purchased_credits_total: number;
|
||||
purchased_credits_used: number;
|
||||
};
|
||||
|
||||
type CheckoutState = {
|
||||
package: Package | null;
|
||||
aiCreditPackage: AiCreditPackage | null;
|
||||
orderId: string | null;
|
||||
step: "form" | "processing" | "success" | "error";
|
||||
error: string;
|
||||
|
|
@ -55,15 +85,19 @@ export default function CreditsPage(props: Props) {
|
|||
const [ledger, setLedger] = createSignal<any[]>([]);
|
||||
const [packages, setPackages] = createSignal<Package[]>([]);
|
||||
const [payments, setPayments] = createSignal<Payment[]>([]);
|
||||
const [aiPackages, setAiPackages] = createSignal<AiCreditPackage[]>([]);
|
||||
const [aiUsage, setAiUsage] = createSignal<AiUsageLog[]>([]);
|
||||
const [aiSummary, setAiSummary] = createSignal<AiUsageSummary | null>(null);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
const [loadingPayments, setLoadingPayments] = createSignal(false);
|
||||
const [err, setErr] = createSignal("");
|
||||
const [msg, setMsg] = createSignal("");
|
||||
const [activeTab, setActiveTab] = createSignal<"overview" | "buy_credits" | "transactions" | "usage_history">("overview");
|
||||
const [activeTab, setActiveTab] = createSignal<"overview" | "buy_credits" | "buy_ai_credits" | "transactions" | "usage_history" | "ai_usage">("overview");
|
||||
|
||||
// Checkout modal state
|
||||
const [checkout, setCheckout] = createSignal<CheckoutState>({
|
||||
package: null,
|
||||
aiCreditPackage: null,
|
||||
orderId: null,
|
||||
step: "form",
|
||||
error: "",
|
||||
|
|
@ -157,10 +191,68 @@ export default function CreditsPage(props: Props) {
|
|||
}
|
||||
};
|
||||
|
||||
const loadAiPackages = async () => {
|
||||
try {
|
||||
const res = await apiFetch("/api/ai-credits");
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
setAiPackages(
|
||||
Array.isArray(data?.packages)
|
||||
? data.packages.map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
credits: p.credits,
|
||||
price_inr: p.price_inr,
|
||||
}))
|
||||
: []
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
setAiPackages([]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAiUsage = async () => {
|
||||
try {
|
||||
const [summaryRes, logsRes] = await Promise.all([
|
||||
apiFetch("/api/ai/usage"),
|
||||
apiFetch("/api/admin/ai/users/me/usage"),
|
||||
]);
|
||||
const summaryData = await summaryRes.json().catch(() => ({}));
|
||||
const logsData = await logsRes.json().catch(() => ({}));
|
||||
if (summaryRes.ok && summaryData.plan) {
|
||||
const p = summaryData.plan;
|
||||
setAiSummary({
|
||||
remaining_credits: p.remaining_credits ?? 0,
|
||||
remaining_daily_actions: p.remaining_daily_actions ?? 0,
|
||||
daily_action_limit: p.daily_action_limit ?? 0,
|
||||
plan_code: p.plan_code ?? "free",
|
||||
plan_name: p.plan_name ?? "Free",
|
||||
monthly_credits_total: p.monthly_credits_total ?? 0,
|
||||
monthly_credits_used: p.monthly_credits_used ?? 0,
|
||||
purchased_credits_total: p.purchased_credits_total ?? 0,
|
||||
purchased_credits_used: p.purchased_credits_used ?? 0,
|
||||
});
|
||||
}
|
||||
if (logsRes.ok) {
|
||||
setAiUsage(Array.isArray(logsData?.logs) ? logsData.logs : []);
|
||||
}
|
||||
} catch {
|
||||
setAiUsage([]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAllData = async () => {
|
||||
setLoading(true);
|
||||
setErr("");
|
||||
await Promise.all([loadWalletData(), loadPackages(), loadPayments()]);
|
||||
await Promise.all([
|
||||
loadWalletData(),
|
||||
loadPackages(),
|
||||
loadPayments(),
|
||||
loadAiPackages(),
|
||||
loadAiUsage(),
|
||||
]);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
|
@ -171,15 +263,97 @@ export default function CreditsPage(props: Props) {
|
|||
setCardExpiry("");
|
||||
setCardCvv("");
|
||||
setCardName("");
|
||||
setCheckout({ package: pkg, orderId: null, step: "form", error: "" });
|
||||
setCheckout({ package: pkg, aiCreditPackage: null, orderId: null, step: "form", error: "" });
|
||||
};
|
||||
|
||||
const openAiCreditCheckout = (pkg: AiCreditPackage) => {
|
||||
setCardNumber("");
|
||||
setCardExpiry("");
|
||||
setCardCvv("");
|
||||
setCardName("");
|
||||
setCheckout({ package: null, aiCreditPackage: pkg, orderId: null, step: "form", error: "" });
|
||||
};
|
||||
|
||||
const closeCheckout = () => {
|
||||
setCheckout({ package: null, orderId: null, step: "form", error: "" });
|
||||
setCheckout({ package: null, aiCreditPackage: null, orderId: null, step: "form", error: "" });
|
||||
};
|
||||
|
||||
const processAiCreditPayment = async (aiPkg: AiCreditPackage) => {
|
||||
// Validate card fields
|
||||
if (cardNumber().replace(/\s/g, "").length < 16) {
|
||||
setCheckout((c) => ({ ...c, error: "Please enter a valid card number" }));
|
||||
return;
|
||||
}
|
||||
if (!cardExpiry() || cardExpiry().length < 5) {
|
||||
setCheckout((c) => ({ ...c, error: "Please enter expiry date (MM/YY)" }));
|
||||
return;
|
||||
}
|
||||
if (cardCvv().length < 3) {
|
||||
setCheckout((c) => ({ ...c, error: "Please enter valid CVV" }));
|
||||
return;
|
||||
}
|
||||
|
||||
setCheckout((c) => ({ ...c, step: "processing", error: "" }));
|
||||
|
||||
try {
|
||||
const orderRes = await apiFetch("/api/ai-credits/order", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ package_id: aiPkg.id }),
|
||||
});
|
||||
const orderData = await orderRes.json().catch(() => ({}));
|
||||
|
||||
if (!orderRes.ok) {
|
||||
setCheckout((c) => ({
|
||||
...c,
|
||||
step: "error",
|
||||
error: orderData.error || orderData.message || "Failed to create order",
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const orderId = orderData.order_id;
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
const verifyRes = await apiFetch("/api/ai-credits/verify", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
order_id: orderId,
|
||||
payment_id: "pay_mock_" + Date.now(),
|
||||
signature: "mock_signature",
|
||||
}),
|
||||
});
|
||||
const verifyData = await verifyRes.json().catch(() => ({}));
|
||||
|
||||
if (verifyRes.ok && verifyData.verified) {
|
||||
setCheckout((c) => ({ ...c, step: "success", orderId }));
|
||||
setMsg(`Payment successful! ${verifyData.credits_added} AI credits have been added.`);
|
||||
loadAllData();
|
||||
} else {
|
||||
setCheckout((c) => ({
|
||||
...c,
|
||||
step: "error",
|
||||
error: verifyData.error || "Payment verification failed",
|
||||
}));
|
||||
}
|
||||
} catch (e: any) {
|
||||
setCheckout((c) => ({
|
||||
...c,
|
||||
step: "error",
|
||||
error: e.message || "Payment failed. Please try again.",
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const processPayment = async () => {
|
||||
const pkg = checkout().package;
|
||||
const aiPkg = checkout().aiCreditPackage;
|
||||
if (!pkg && !aiPkg) return;
|
||||
|
||||
if (aiPkg) {
|
||||
await processAiCreditPayment(aiPkg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pkg) return;
|
||||
|
||||
// Validate card fields
|
||||
|
|
@ -291,7 +465,11 @@ export default function CreditsPage(props: Props) {
|
|||
const CheckoutModal = () => {
|
||||
const state = checkout();
|
||||
const pkg = state.package;
|
||||
if (!pkg) return null;
|
||||
const aiPkg = state.aiCreditPackage;
|
||||
if (!pkg && !aiPkg) return null;
|
||||
|
||||
const title = pkg ? `${pkg.tracecoins_amount} Tracecoins` : `${aiPkg!.credits} AI Credits`;
|
||||
const price = pkg ? pkg.price : aiPkg!.price_inr;
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
|
|
@ -373,7 +551,7 @@ export default function CreditsPage(props: Props) {
|
|||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{pkg.name}
|
||||
{pkg ? pkg.name : aiPkg?.name}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
|
|
@ -383,10 +561,10 @@ export default function CreditsPage(props: Props) {
|
|||
color: "#FF5E13",
|
||||
}}
|
||||
>
|
||||
{formatCurrency(pkg.price)}
|
||||
{formatCurrency(price)}
|
||||
</p>
|
||||
<p style={{ margin: "8px 0 0", "font-size": "14px", color: "#15803D" }}>
|
||||
+{pkg.tracecoins_amount} Tracecoins
|
||||
+{title}
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
@ -399,7 +577,7 @@ export default function CreditsPage(props: Props) {
|
|||
Payment successful!
|
||||
</p>
|
||||
<p style={{ margin: "0 0 16px", "font-size": "14px", color: "#6B7280" }}>
|
||||
{pkg.tracecoins_amount} Tracecoins have been credited to your wallet.
|
||||
{title} have been credited to your account.
|
||||
</p>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#9CA3AF" }}>
|
||||
Order ID: {state.orderId}
|
||||
|
|
@ -623,7 +801,7 @@ export default function CreditsPage(props: Props) {
|
|||
"font-weight": "700",
|
||||
}}
|
||||
>
|
||||
Pay {formatCurrency(pkg.price)}
|
||||
Pay {formatCurrency(price)}
|
||||
</button>
|
||||
|
||||
<p
|
||||
|
|
@ -756,6 +934,38 @@ export default function CreditsPage(props: Props) {
|
|||
>
|
||||
Buy Credits
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("buy_ai_credits")}
|
||||
style={{
|
||||
padding: "10px 16px",
|
||||
"border-bottom": activeTab() === "buy_ai_credits" ? "2px solid #FF5E13" : "2px solid transparent",
|
||||
background: "transparent",
|
||||
color: activeTab() === "buy_ai_credits" ? "#FF5E13" : "#6B7280",
|
||||
"font-size": "14px",
|
||||
"font-weight": activeTab() === "buy_ai_credits" ? "700" : "500",
|
||||
cursor: "pointer",
|
||||
"margin-bottom": "-1px",
|
||||
}}
|
||||
>
|
||||
AI Credits
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("ai_usage")}
|
||||
style={{
|
||||
padding: "10px 16px",
|
||||
"border-bottom": activeTab() === "ai_usage" ? "2px solid #FF5E13" : "2px solid transparent",
|
||||
background: "transparent",
|
||||
color: activeTab() === "ai_usage" ? "#FF5E13" : "#6B7280",
|
||||
"font-size": "14px",
|
||||
"font-weight": activeTab() === "ai_usage" ? "700" : "500",
|
||||
cursor: "pointer",
|
||||
"margin-bottom": "-1px",
|
||||
}}
|
||||
>
|
||||
AI Usage
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("transactions")}
|
||||
|
|
@ -843,6 +1053,33 @@ export default function CreditsPage(props: Props) {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "12px",
|
||||
"letter-spacing": "0.06em",
|
||||
"text-transform": "uppercase",
|
||||
color: "#6B7280",
|
||||
}}
|
||||
>
|
||||
AI Plan
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "6px 0 0",
|
||||
"font-size": "24px",
|
||||
"font-weight": "800",
|
||||
color: NAVY,
|
||||
}}
|
||||
>
|
||||
{aiSummary()?.plan_name ?? "Free"}
|
||||
</p>
|
||||
<p style={{ margin: "6px 0 0", "font-size": "13px", color: "#6B7280" }}>
|
||||
{aiSummary()?.remaining_credits ?? 0} credits remaining · {aiSummary()?.remaining_daily_actions ?? 0}/{aiSummary()?.daily_action_limit ?? 0} daily actions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
|
|
@ -1149,6 +1386,206 @@ export default function CreditsPage(props: Props) {
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Buy AI Credits Tab */}
|
||||
<Show when={!loading() && activeTab() === "buy_ai_credits"}>
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0 0 10px",
|
||||
"font-size": "16px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
Purchase AI Credits
|
||||
</p>
|
||||
<p style={{ margin: "0 0 16px", "font-size": "13px", color: "#6B7280" }}>
|
||||
Buy additional AI credits to power Ask Ash, cover letters, auto-apply, and more.
|
||||
</p>
|
||||
|
||||
<Show when={aiPackages().length === 0}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280" }}>
|
||||
No AI credit packages available.
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
<Show when={aiPackages().length > 0}>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
"grid-template-columns": "repeat(auto-fill, minmax(260px, 1fr))",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<For each={aiPackages()}>
|
||||
{(pkg) => (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #E5E7EB",
|
||||
"border-radius": "16px",
|
||||
padding: "20px",
|
||||
background: "#fff",
|
||||
display: "flex",
|
||||
"flex-direction": "column",
|
||||
gap: "12px",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "18px",
|
||||
"font-weight": "800",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{pkg.name}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "28px",
|
||||
"font-weight": "800",
|
||||
color: "#FF5E13",
|
||||
}}
|
||||
>
|
||||
{formatCurrency(pkg.price_inr)}
|
||||
</p>
|
||||
<p style={{ margin: "0", "font-size": "14px", color: "#15803D", "font-weight": "700" }}>
|
||||
+{pkg.credits} AI Credits
|
||||
</p>
|
||||
<Show when={pkg.description}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280", flex: "1" }}>
|
||||
{pkg.description}
|
||||
</p>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openAiCreditCheckout(pkg)}
|
||||
style={{
|
||||
...BTN_PRIMARY,
|
||||
width: "100%",
|
||||
"margin-top": "auto",
|
||||
padding: "12px 16px",
|
||||
"font-size": "14px",
|
||||
"border-radius": "10px",
|
||||
}}
|
||||
>
|
||||
Buy Now
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* AI Usage Tab */}
|
||||
<Show when={!loading() && activeTab() === "ai_usage"}>
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0 0 10px",
|
||||
"font-size": "16px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
AI Usage History
|
||||
</p>
|
||||
<Show when={aiSummary()}>
|
||||
{(s) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
"grid-template-columns": "repeat(auto-fit, minmax(140px, 1fr))",
|
||||
gap: "12px",
|
||||
margin: "0 0 16px",
|
||||
}}
|
||||
>
|
||||
<div style={{ background: "#F9FAFB", "border-radius": "10px", padding: "12px" }}>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#6B7280" }}>Plan</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "16px", "font-weight": "700", color: "#111827" }}>
|
||||
{s().plan_name}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ background: "#F9FAFB", "border-radius": "10px", padding: "12px" }}>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#6B7280" }}>Credits Remaining</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "16px", "font-weight": "700", color: "#111827" }}>
|
||||
{s().remaining_credits}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ background: "#F9FAFB", "border-radius": "10px", padding: "12px" }}>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#6B7280" }}>Daily Actions</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "16px", "font-weight": "700", color: "#111827" }}>
|
||||
{s().remaining_daily_actions}/{s().daily_action_limit}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ background: "#F9FAFB", "border-radius": "10px", padding: "12px" }}>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#6B7280" }}>Monthly Used</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "16px", "font-weight": "700", color: "#111827" }}>
|
||||
{s().monthly_credits_used}/{s().monthly_credits_total}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Show when={aiUsage().length === 0}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280" }}>
|
||||
No AI usage recorded yet.
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
<Show when={aiUsage().length > 0}>
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<For each={aiUsage()}>
|
||||
{(item) => (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #E5E7EB",
|
||||
"border-radius": "10px",
|
||||
padding: "12px",
|
||||
background: "#FCFCFD",
|
||||
display: "flex",
|
||||
"justify-content": "space-between",
|
||||
"align-items": "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "13px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{item.feature_code}
|
||||
</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "12px", color: "#6B7280" }}>
|
||||
{item.model_alias} · {formatDate(item.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "14px",
|
||||
"font-weight": "800",
|
||||
color: item.status === "success" ? "#B91C1C" : "#6B7280",
|
||||
}}
|
||||
>
|
||||
-{item.credits_charged} credits
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Transactions Tab */}
|
||||
<Show when={!loading() && activeTab() === "transactions"}>
|
||||
<div style={CARD}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue