项目文件夹

文件
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

82 行
2.1 KiB
TypeScript

"use client";
import { createContext, useContext, type ReactNode } from "react";
import type { BudgetDenyCode } from "@/lib/xulux/usage-budget-codes";
import { isDailyLimitCode } from "@/lib/xulux/usage-budget-codes";
import { Button } from "@/components/ui/button";
export type XuluxLimitBlock = {
code: BudgetDenyCode;
message: string;
};
type XuluxUsageBudgetContextValue = {
limitBlock: XuluxLimitBlock | null;
clearLimitBlock: () => void;
};
const XuluxUsageBudgetContext =
createContext<XuluxUsageBudgetContextValue | null>(null);
export function XuluxUsageBudgetProvider({
limitBlock,
clearLimitBlock,
children,
}: {
limitBlock: XuluxLimitBlock | null;
clearLimitBlock: () => void;
children: ReactNode;
}) {
return (
<XuluxUsageBudgetContext.Provider value={{ limitBlock, clearLimitBlock }}>
{children}
</XuluxUsageBudgetContext.Provider>
);
}
export function useXuluxUsageBudget(): XuluxUsageBudgetContextValue {
const ctx = useContext(XuluxUsageBudgetContext);
if (!ctx) {
return { limitBlock: null, clearLimitBlock: () => {} };
}
return ctx;
}
export function parseXuluxLimitBlock(payload: unknown): XuluxLimitBlock | null {
if (!payload || typeof payload !== "object") return null;
const { code, error } = payload as { code?: unknown; error?: unknown };
if (typeof code !== "string" || typeof error !== "string") return null;
return { code: code as BudgetDenyCode, message: error };
}
export function XuluxUsageLimitBanner({
onNewThread,
}: {
onNewThread: () => void;
}): ReactNode {
const { limitBlock } = useXuluxUsageBudget();
if (!limitBlock) return null;
const showNewChat = !isDailyLimitCode(limitBlock.code);
return (
<div
className="border-destructive/30 bg-destructive/10 text-destructive mb-2 rounded-lg border px-3 py-2 text-xs dark:text-red-200"
role="alert"
>
<p>{limitBlock.message}</p>
{showNewChat ? (
<Button
type="button"
variant="outline"
size="sm"
className="mt-2 h-7"
onClick={onNewThread}
>
New chat
</Button>
) : null}
</div>
);
}