mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
feat: autumn migration (#902)
This commit is contained in:
parent
35c606028c
commit
debac33eba
13 changed files with 139 additions and 220 deletions
|
|
@ -169,7 +169,7 @@ function AuthConnectContent() {
|
|||
setIsUpgrading(true)
|
||||
const safeSuccessUrl = `${window.location.origin}${window.location.pathname}?callback=${encodeURIComponent(callback ?? "")}&client=${encodeURIComponent(validClient ?? "")}`
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: safeSuccessUrl,
|
||||
})
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ interface ConnectContentProps {
|
|||
export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
||||
const queryClient = useQueryClient()
|
||||
const autumn = useCustomer()
|
||||
const isProUser = hasActivePlan(autumn.customer?.products, "api_pro")
|
||||
const isProUser = hasActivePlan(autumn.data?.subscriptions, "api_pro")
|
||||
const [connectingProvider, setConnectingProvider] =
|
||||
useState<ConnectorProvider | null>(null)
|
||||
const [gdriveSyncScope, setGdriveSyncScope] =
|
||||
|
|
@ -244,7 +244,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
setIsUpgrading(true)
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: window.location.href,
|
||||
})
|
||||
} catch (error) {
|
||||
|
|
@ -254,9 +254,9 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const connectionsFeature = autumn.customer?.features?.connections
|
||||
const connectionsUsed = connectionsFeature?.usage ?? 0
|
||||
const connectionsLimit = connectionsFeature?.included_usage ?? 10
|
||||
const connectionsBalance = autumn.data?.balances?.connections
|
||||
const connectionsUsed = connectionsBalance?.usage ?? 0
|
||||
const connectionsLimit = connectionsBalance?.granted ?? 10
|
||||
const canAddConnection = connectionsUsed < connectionsLimit
|
||||
|
||||
// Fetch connections
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { toast } from "sonner"
|
|||
import { useDocumentMutations } from "../../hooks/use-document-mutations"
|
||||
import { useCustomer } from "autumn-js/react"
|
||||
import { useTokenUsage } from "@/hooks/use-token-usage"
|
||||
import { tokensToCredits, formatUsageNumber } from "@/lib/billing-utils"
|
||||
import { formatUsageNumber } from "@/lib/billing-utils"
|
||||
import { SpaceSelector } from "../space-selector"
|
||||
import { useIsMobile } from "@hooks/use-mobile"
|
||||
import { addDocumentParam } from "@/lib/search-params"
|
||||
|
|
@ -127,11 +127,8 @@ export function AddDocument({
|
|||
const autumn = useCustomer()
|
||||
const {
|
||||
tokensUsed,
|
||||
tokensLimit,
|
||||
tokensPercent,
|
||||
searchesUsed,
|
||||
searchesLimit,
|
||||
searchesPercent,
|
||||
planUsagePct,
|
||||
hasPaidPlan,
|
||||
isLoading: isLoadingUsage,
|
||||
} = useTokenUsage(autumn)
|
||||
|
|
@ -298,72 +295,46 @@ export function AddDocument({
|
|||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
Credits
|
||||
Plan usage
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
"text-sm font-medium tabular-nums",
|
||||
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
{isLoadingUsage
|
||||
? "…"
|
||||
: `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`}
|
||||
: `${planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)}% used`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px]"
|
||||
style={{
|
||||
width: `${tokensPercent}%`,
|
||||
width: `${planUsagePct}%`,
|
||||
background:
|
||||
tokensPercent > 80
|
||||
planUsagePct > 80
|
||||
? "#ef4444"
|
||||
: hasPaidPlan
|
||||
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
|
||||
: "#0054AD",
|
||||
}}
|
||||
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span
|
||||
{!isLoadingUsage && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-[#FAFAFA] text-sm font-medium",
|
||||
"text-xs text-[#737373] tabular-nums",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
Search Queries
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
{isLoadingUsage
|
||||
? "…"
|
||||
: `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px]"
|
||||
style={{
|
||||
width: `${searchesPercent}%`,
|
||||
background:
|
||||
searchesPercent > 80
|
||||
? "#ef4444"
|
||||
: hasPaidPlan
|
||||
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
|
||||
: "#0054AD",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{formatUsageNumber(tokensUsed)} tokens ·{" "}
|
||||
{formatUsageNumber(searchesUsed)} queries
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!hasPaidPlan && (
|
||||
|
|
@ -373,7 +344,7 @@ export function AddDocument({
|
|||
setIsUpgrading(true)
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: "https://app.supermemory.ai/settings#account",
|
||||
})
|
||||
window.location.reload()
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export function IntegrationsView() {
|
|||
const [, setAddDoc] = useQueryState("add", addDocumentParam)
|
||||
const { org } = useAuth()
|
||||
const autumn = useCustomer()
|
||||
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
|
||||
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
|
||||
|
||||
const { data: connections = [] } = useQuery({
|
||||
queryKey: ["connections"],
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ export function PluginsDetail() {
|
|||
})
|
||||
const [keyCopied, setKeyCopied] = useState(false)
|
||||
|
||||
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
|
||||
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
|
||||
|
||||
const { data: pluginsData } = useQuery({
|
||||
queryFn: async () => {
|
||||
|
|
@ -496,7 +496,7 @@ export function PluginsDetail() {
|
|||
const handleUpgrade = async () => {
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: "https://app.supermemory.ai/?view=integrations",
|
||||
})
|
||||
window.location.reload()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@/hooks/use-account-settings"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
|
||||
import { useTokenUsage } from "@/hooks/use-token-usage"
|
||||
import { formatUsageNumber, tokensToCredits } from "@/lib/billing-utils"
|
||||
import { formatUsageNumber } from "@/lib/billing-utils"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -153,11 +153,8 @@ export default function Account() {
|
|||
|
||||
const {
|
||||
tokensUsed,
|
||||
tokensLimit,
|
||||
tokensPercent,
|
||||
searchesUsed,
|
||||
searchesLimit,
|
||||
searchesPercent,
|
||||
planUsagePct,
|
||||
currentPlan,
|
||||
hasPaidPlan,
|
||||
isLoading: isCheckingStatus,
|
||||
|
|
@ -176,7 +173,7 @@ export default function Account() {
|
|||
setIsUpgrading(true)
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: "https://app.supermemory.ai/settings#account",
|
||||
})
|
||||
window.location.reload()
|
||||
|
|
@ -388,7 +385,7 @@ export default function Account() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Credits Usage Progress */}
|
||||
{/* Plan usage (unified) */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p
|
||||
|
|
@ -397,84 +394,56 @@ export default function Account() {
|
|||
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Credits Used
|
||||
Plan usage
|
||||
</p>
|
||||
<span
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA]",
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA] tabular-nums",
|
||||
)}
|
||||
>
|
||||
{tokensToCredits(tokensUsed)} /{" "}
|
||||
{tokensToCredits(tokensLimit)}
|
||||
{planUsagePct < 1 && planUsagePct > 0
|
||||
? "< 1"
|
||||
: Math.round(planUsagePct)}
|
||||
% used
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px]"
|
||||
style={{
|
||||
width: `${tokensPercent}%`,
|
||||
width: `${planUsagePct}%`,
|
||||
background:
|
||||
tokensPercent > 80
|
||||
planUsagePct > 80
|
||||
? "#ef4444"
|
||||
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
|
||||
}}
|
||||
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Queries Progress */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Search Queries
|
||||
</p>
|
||||
<span
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA]",
|
||||
)}
|
||||
>
|
||||
{formatUsageNumber(searchesUsed)} /{" "}
|
||||
{formatUsageNumber(searchesLimit)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px]"
|
||||
style={{
|
||||
width: `${searchesPercent}%`,
|
||||
background:
|
||||
searchesPercent > 80
|
||||
? "#ef4444"
|
||||
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Days remaining indicator */}
|
||||
{daysRemaining !== null && (
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-sm text-[#737373]",
|
||||
"text-sm tracking-[-0.14px] text-[#737373] tabular-nums",
|
||||
)}
|
||||
>
|
||||
Resets in {daysRemaining} day
|
||||
{daysRemaining !== 1 ? "s" : ""}
|
||||
{formatUsageNumber(tokensUsed)} tokens ·{" "}
|
||||
{formatUsageNumber(searchesUsed)} queries
|
||||
{daysRemaining !== null && (
|
||||
<>
|
||||
{" · resets in "}
|
||||
{daysRemaining} day{daysRemaining !== 1 ? "s" : ""}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
autumn.openBillingPortal?.()
|
||||
autumn.openCustomerPortal?.({
|
||||
returnUrl: "https://app.supermemory.ai/settings#account",
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
"relative w-full h-11 rounded-full flex items-center justify-center gap-2",
|
||||
|
|
@ -501,10 +470,7 @@ export default function Account() {
|
|||
Free plan
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<PlanFeatureRow
|
||||
icon="check"
|
||||
text="10 credits / 1M tokens"
|
||||
/>
|
||||
<PlanFeatureRow icon="check" text="1M tokens / month" />
|
||||
<PlanFeatureRow icon="check" text="10K search queries" />
|
||||
<PlanFeatureRow icon="x" text="No connections" />
|
||||
<PlanFeatureRow icon="check" text="Basic support" />
|
||||
|
|
@ -536,7 +502,7 @@ export default function Account() {
|
|||
<div className="flex flex-col gap-2">
|
||||
<PlanFeatureRow
|
||||
icon="check"
|
||||
text="30 credits / 3M tokens"
|
||||
text="3M tokens / month"
|
||||
variant="highlight"
|
||||
/>
|
||||
<PlanFeatureRow
|
||||
|
|
@ -580,7 +546,7 @@ export default function Account() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Credits Usage Progress */}
|
||||
{/* Plan usage (unified) */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p
|
||||
|
|
@ -589,74 +555,46 @@ export default function Account() {
|
|||
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Credits Used
|
||||
Plan usage
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#737373]",
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#737373] tabular-nums",
|
||||
)}
|
||||
>
|
||||
{tokensToCredits(tokensUsed)} /{" "}
|
||||
{tokensToCredits(tokensLimit)}
|
||||
{planUsagePct < 1 && planUsagePct > 0
|
||||
? "< 1"
|
||||
: Math.round(planUsagePct)}
|
||||
% used
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px] transition-all"
|
||||
style={{
|
||||
width: `${tokensPercent}%`,
|
||||
background: tokensPercent > 80 ? "#ef4444" : "#0054AD",
|
||||
width: `${planUsagePct}%`,
|
||||
background: planUsagePct > 80 ? "#ef4444" : "#0054AD",
|
||||
}}
|
||||
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Queries Progress */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
|
||||
)}
|
||||
>
|
||||
Search Queries
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"font-medium text-[16px] tracking-[-0.16px] text-[#737373]",
|
||||
)}
|
||||
>
|
||||
{formatUsageNumber(searchesUsed)} /{" "}
|
||||
{formatUsageNumber(searchesLimit)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-[40px] transition-all"
|
||||
style={{
|
||||
width: `${searchesPercent}%`,
|
||||
background:
|
||||
searchesPercent > 80 ? "#ef4444" : "#0054AD",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Days remaining indicator */}
|
||||
{daysRemaining !== null && (
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-sm text-[#737373]",
|
||||
"text-sm tracking-[-0.14px] text-[#737373] tabular-nums",
|
||||
)}
|
||||
>
|
||||
Resets in {daysRemaining} day
|
||||
{daysRemaining !== 1 ? "s" : ""}
|
||||
{formatUsageNumber(tokensUsed)} tokens ·{" "}
|
||||
{formatUsageNumber(searchesUsed)} queries
|
||||
{daysRemaining !== null && (
|
||||
<>
|
||||
{" · resets in "}
|
||||
{daysRemaining} day{daysRemaining !== 1 ? "s" : ""}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -701,10 +639,7 @@ export default function Account() {
|
|||
Free plan
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<PlanFeatureRow
|
||||
icon="check"
|
||||
text="10 credits / 1M tokens"
|
||||
/>
|
||||
<PlanFeatureRow icon="check" text="1M tokens / month" />
|
||||
<PlanFeatureRow icon="check" text="10K search queries" />
|
||||
<PlanFeatureRow icon="x" text="No connections" />
|
||||
<PlanFeatureRow icon="check" text="Basic support" />
|
||||
|
|
@ -737,7 +672,7 @@ export default function Account() {
|
|||
<div className="flex flex-col gap-2">
|
||||
<PlanFeatureRow
|
||||
icon="check"
|
||||
text="30 credits / 3M tokens"
|
||||
text="3M tokens / month"
|
||||
variant="highlight"
|
||||
/>
|
||||
<PlanFeatureRow
|
||||
|
|
|
|||
|
|
@ -346,12 +346,11 @@ export default function ConnectionsMCP() {
|
|||
const projects = (queryClient.getQueryData<Project[]>(["projects"]) ||
|
||||
[]) as Project[]
|
||||
|
||||
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
|
||||
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
|
||||
|
||||
// Get connections data directly from autumn customer
|
||||
const connectionsFeature = autumn.customer?.features?.connections
|
||||
const connectionsUsed = connectionsFeature?.usage ?? 0
|
||||
const connectionsLimit = connectionsFeature?.included_usage ?? 10
|
||||
const connectionsBalance = autumn.data?.balances?.connections
|
||||
const connectionsUsed = connectionsBalance?.usage ?? 0
|
||||
const connectionsLimit = connectionsBalance?.granted ?? 10
|
||||
|
||||
const canAddConnection = connectionsUsed < connectionsLimit
|
||||
|
||||
|
|
@ -425,7 +424,7 @@ export default function ConnectionsMCP() {
|
|||
const handleUpgrade = async () => {
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "api_pro",
|
||||
planId: "api_pro",
|
||||
successUrl: "https://app.supermemory.ai/settings#connections",
|
||||
})
|
||||
window.location.reload()
|
||||
|
|
|
|||
|
|
@ -4,8 +4,15 @@ import { calculateUsagePercent, getDaysRemaining } from "@/lib/billing-utils"
|
|||
|
||||
export type PlanType = "free" | "pro" | "scale" | "enterprise"
|
||||
|
||||
const TOKEN_METER_IDS = [
|
||||
"sm_tokens_text",
|
||||
"sm_tokens_rich",
|
||||
"sm_superrag_text",
|
||||
"sm_superrag_rich",
|
||||
] as const
|
||||
|
||||
export function useTokenUsage(autumn: ReturnType<typeof useCustomer>) {
|
||||
const status = getSubscriptionStatus(autumn.customer?.products)
|
||||
const status = getSubscriptionStatus(autumn.data?.subscriptions)
|
||||
|
||||
let currentPlan: PlanType = "free"
|
||||
if (isAllowedFrom(status, "api_enterprise")) {
|
||||
|
|
@ -18,30 +25,36 @@ export function useTokenUsage(autumn: ReturnType<typeof useCustomer>) {
|
|||
|
||||
const hasPaidPlan = currentPlan !== "free"
|
||||
|
||||
// Get token usage from autumn customer features
|
||||
const tokensFeature = autumn.customer?.features?.api_tokens
|
||||
const tokensUsed = tokensFeature?.usage ?? 0
|
||||
const tokensLimit = tokensFeature?.included_usage ?? 0
|
||||
const tokensResetAt = tokensFeature?.next_reset_at
|
||||
const balances = autumn.data?.balances ?? {}
|
||||
|
||||
// Get search queries usage from autumn customer features
|
||||
const searchesFeature = autumn.customer?.features?.api_search_queries
|
||||
const searchesUsed = searchesFeature?.usage ?? 0
|
||||
const searchesLimit = searchesFeature?.included_usage ?? 0
|
||||
const tokensUsed = TOKEN_METER_IDS.reduce((sum, id) => {
|
||||
const balance = balances[id]
|
||||
return sum + (balance?.usage ?? 0)
|
||||
}, 0)
|
||||
|
||||
const searchesBalance = balances.sm_search_queries
|
||||
const searchesUsed = searchesBalance?.usage ?? 0
|
||||
|
||||
const usdBalance = balances.usd_credits
|
||||
const usdIncluded = usdBalance?.granted ?? 0
|
||||
const usdSpent = usdBalance?.usage ?? 0
|
||||
|
||||
const planUsagePct =
|
||||
usdIncluded > 0 ? calculateUsagePercent(usdSpent, usdIncluded) : 0
|
||||
|
||||
const resetAt =
|
||||
usdBalance?.nextResetAt ??
|
||||
balances.sm_tokens_text?.nextResetAt ??
|
||||
searchesBalance?.nextResetAt ??
|
||||
undefined
|
||||
const daysRemaining = getDaysRemaining(resetAt)
|
||||
|
||||
const isLoading = autumn.isLoading
|
||||
|
||||
const tokensPercent = calculateUsagePercent(tokensUsed, tokensLimit)
|
||||
const searchesPercent = calculateUsagePercent(searchesUsed, searchesLimit)
|
||||
const daysRemaining = getDaysRemaining(tokensResetAt)
|
||||
|
||||
return {
|
||||
tokensUsed,
|
||||
tokensLimit,
|
||||
tokensPercent,
|
||||
searchesUsed,
|
||||
searchesLimit,
|
||||
searchesPercent,
|
||||
planUsagePct,
|
||||
currentPlan,
|
||||
hasPaidPlan,
|
||||
isLoading,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
/**
|
||||
* Convert tokens to credits
|
||||
* 100,000 tokens = 1 credit
|
||||
* Uses floor rounding (2.9 credits shows as 2, only shows 3 when it hits 3.0)
|
||||
*/
|
||||
export function tokensToCredits(tokens: number): number {
|
||||
return Math.floor(tokens / 100_000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number with K/M suffix for display
|
||||
* @example formatUsageNumber(1500000) => "1.5M"
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
"@types/dompurify": "^3.2.0",
|
||||
"agents": "^0.4.0",
|
||||
"ai": "^6.0.168",
|
||||
"autumn-js": "0.0.116",
|
||||
"autumn-js": "1.2.12",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
|
|
|
|||
16
bun.lock
16
bun.lock
|
|
@ -184,7 +184,7 @@
|
|||
"@types/dompurify": "^3.2.0",
|
||||
"agents": "^0.4.0",
|
||||
"ai": "^6.0.168",
|
||||
"autumn-js": "0.0.116",
|
||||
"autumn-js": "1.2.12",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
|
|
@ -315,7 +315,7 @@
|
|||
},
|
||||
"packages/tools": {
|
||||
"name": "@supermemory/tools",
|
||||
"version": "1.4.7",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
|
|
@ -2345,7 +2345,7 @@
|
|||
|
||||
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
||||
|
||||
"autumn-js": ["autumn-js@0.0.116", "", { "dependencies": { "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^3.24.1" }, "peerDependencies": { "better-auth": "^1.2.12", "better-call": "^1.0.12" }, "optionalPeers": ["better-auth", "better-call"] }, "sha512-h/kZKDTuUk7MOAnRSjyWuf++2BVH5HF/yIeTOFEL3YwLXtWHBjqlYSsHlQK7BH7R1TC1fPsXGKMRGRjgcJD/tA=="],
|
||||
"autumn-js": ["autumn-js@1.2.12", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.6.0", "better-call": "^1.0.12", "express": "^5.2.1", "hono": "^4.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["better-auth", "better-call", "express", "hono", "next", "react"] }, "sha512-UALRS5oD2pkPQqMfgM5op7EoAZazbLJl8+sAyFeNzGi+sGUdEUen/SopnQtqiXxtWpTDlXf8UbJqL2LkeG+ltg=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
|
|
@ -2713,6 +2713,8 @@
|
|||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||
|
||||
"decode-uri-component": ["decode-uri-component@0.4.1", "", {}, "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ=="],
|
||||
|
||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||
|
||||
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
|
||||
|
|
@ -3025,6 +3027,8 @@
|
|||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"filter-obj": ["filter-obj@5.1.0", "", {}, "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng=="],
|
||||
|
||||
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
|
||||
|
||||
"find-process": ["find-process@2.1.0", "", { "dependencies": { "chalk": "~4.1.2", "commander": "^12.1.0", "loglevel": "^1.9.2" }, "bin": { "find-process": "dist/cjs/bin/find-process.js" } }, "sha512-uf1F6mFDAltKaVHf4/zlXstW/k01qPwv+OB6bMRnuIycO6dTmQo+sUW1Uj/xJzGmmVarYW4ZM7TBBbBy6f/y0g=="],
|
||||
|
|
@ -4205,6 +4209,8 @@
|
|||
|
||||
"query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="],
|
||||
|
||||
"query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
|
|
@ -4519,6 +4525,8 @@
|
|||
|
||||
"split": ["split@1.0.1", "", { "dependencies": { "through": "2" } }, "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg=="],
|
||||
|
||||
"split-on-first": ["split-on-first@3.0.0", "", {}, "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
|
@ -5535,6 +5543,8 @@
|
|||
|
||||
"atmn/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||
|
||||
"autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
const setActiveOrg = useCallback(async (slug: string) => {
|
||||
if (!slug) return
|
||||
|
||||
const activeOrg = await authClient.organization.setActive({
|
||||
const res = await authClient.organization.setActive({
|
||||
organizationSlug: slug,
|
||||
})
|
||||
setOrg(activeOrg)
|
||||
setOrg(res?.data ?? null)
|
||||
localStorage.setItem(STORAGE_KEY, slug)
|
||||
}, [])
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
if (!one) return
|
||||
if (activeOrgId === one.id) {
|
||||
const full = await authClient.organization.getFullOrganization()
|
||||
if (!cancelled) setOrg(full)
|
||||
if (!cancelled) setOrg(full?.data ?? null)
|
||||
} else {
|
||||
await setActiveOrg(one.slug)
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
if (match) {
|
||||
if (activeOrgId === match.id) {
|
||||
const full = await authClient.organization.getFullOrganization()
|
||||
if (!cancelled) setOrg(full)
|
||||
if (!cancelled) setOrg(full?.data ?? null)
|
||||
} else {
|
||||
await setActiveOrg(savedSlug)
|
||||
}
|
||||
|
|
@ -138,13 +138,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
const fromList = orgs.find((o) => o.id === activeOrgId)
|
||||
if (fromList) {
|
||||
const full = await authClient.organization.getFullOrganization()
|
||||
if (!cancelled) setOrg(full)
|
||||
if (!cancelled) setOrg(full?.data ?? null)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const full = await authClient.organization.getFullOrganization()
|
||||
if (!cancelled) setOrg(full)
|
||||
if (!cancelled) setOrg(full?.data ?? null)
|
||||
} catch (error) {
|
||||
console.error("Failed to restore organization:", error)
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -33,28 +33,28 @@ export function isAllowedFrom(
|
|||
}
|
||||
|
||||
export function getSubscriptionStatus(
|
||||
products: Array<{ id: string; status: string }> | undefined,
|
||||
subscriptions: Array<{ planId: string; status: string }> | undefined,
|
||||
): SubscriptionStatusMap {
|
||||
const statusMap: SubscriptionStatusMap = { ...DEFAULT_SUBSCRIPTION_STATUS }
|
||||
if (!products) return statusMap
|
||||
if (!subscriptions) return statusMap
|
||||
|
||||
const productMap = new Map(products.map((p) => [p.id, p]))
|
||||
const subMap = new Map(subscriptions.map((s) => [s.planId, s]))
|
||||
|
||||
for (const tier of PLAN_TIERS) {
|
||||
const product = productMap.get(tier)
|
||||
const sub = subMap.get(tier)
|
||||
statusMap[tier] = {
|
||||
allowed: product?.status === "active",
|
||||
status: product?.status ?? null,
|
||||
allowed: sub?.status === "active",
|
||||
status: sub?.status ?? null,
|
||||
}
|
||||
}
|
||||
return statusMap
|
||||
}
|
||||
|
||||
export function hasActivePlan(
|
||||
products: Array<{ id: string; status: string }> | undefined,
|
||||
subscriptions: Array<{ planId: string; status: string }> | undefined,
|
||||
minimumTier: PlanTier,
|
||||
): boolean {
|
||||
return isAllowedFrom(getSubscriptionStatus(products), minimumTier)
|
||||
return isAllowedFrom(getSubscriptionStatus(subscriptions), minimumTier)
|
||||
}
|
||||
|
||||
export const useDeleteDocument = (selectedProject: string) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue