Sync CourtListener verification and document safety updates

- Refine CourtListener citation verification, bulk lookup logging, and API fallback behavior
- Persist cancelled chat stream output and render cancellation as the final assistant message
- Add document/version deletion safety fixes and shared warning/modal UI updates
- Sync document panel, case law panel, and response UI styling refinements
- Harden OSS sync script to preserve local env, dependency, and generated files
This commit is contained in:
willchen96 2026-06-09 01:46:58 +08:00
parent 44e868eb42
commit f32a194b33
24 changed files with 2494 additions and 1222 deletions

View file

@ -2,7 +2,7 @@
import { createPortal } from "react-dom";
import type { ReactNode } from "react";
import { Loader2 } from "lucide-react";
import { Loader2, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
type ConfirmStatus = "idle" | "loading" | "complete";
@ -37,14 +37,20 @@ export function ConfirmPopup({
const resolvedConfirmDisabled = confirmDisabled || confirmStatus !== "idle";
const normalizedConfirmLabel =
typeof confirmLabel === "string" ? confirmLabel : "Confirm";
const isDeleteAction = normalizedConfirmLabel.toLowerCase() === "delete";
const resolvedConfirmLabel =
confirmStatus === "loading" ? (
<span className="inline-flex items-center gap-1.5">
<Loader2 className="h-3 w-3 animate-spin" />
<span className="inline-flex h-full items-center gap-1.5">
<Loader2 className="h-3 w-3 shrink-0 animate-spin" />
{progressiveLabel(normalizedConfirmLabel)}
</span>
) : confirmStatus === "complete" ? (
completedLabel(normalizedConfirmLabel)
) : isDeleteAction ? (
<span className="inline-flex h-full items-center gap-1.5">
<Trash2 className="h-3 w-3 shrink-0" />
{confirmLabel}
</span>
) : (
confirmLabel
);
@ -53,17 +59,19 @@ export function ConfirmPopup({
<div className="pointer-events-none fixed inset-x-0 bottom-5 z-[230] flex justify-center px-4">
<div
className={cn(
"pointer-events-auto w-[min(92vw,520px)] rounded-2xl border border-white/70 bg-white/58 px-4 py-3 text-sm shadow-[0_8px_24px_rgba(15,23,42,0.13),inset_0_1px_0_rgba(255,255,255,0.92),inset_0_-10px_24px_rgba(255,255,255,0.2)] backdrop-blur-2xl",
"pointer-events-auto w-[min(92vw,520px)] rounded-2xl border border-white/70 bg-white px-4 py-3 text-sm shadow-[0_4px_14px_rgba(15,23,42,0.08),inset_0_1px_0_rgba(255,255,255,0.92)] backdrop-blur-2xl",
className,
)}
>
{title && (
<div className="text-sm font-medium text-gray-950">
<div className="text-sm font-medium text-gray-950 mb-3">
{title}
</div>
)}
{message && (
<div className={cn("text-xs text-gray-700", title && "mt-1")}>
<div
className={cn("text-xs text-gray-700", title && "mt-1")}
>
{message}
</div>
)}
@ -71,7 +79,7 @@ export function ConfirmPopup({
<button
type="button"
onClick={onCancel}
className="rounded-full px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
className="px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:text-gray-950"
>
{cancelLabel}
</button>
@ -79,7 +87,12 @@ export function ConfirmPopup({
type="button"
onClick={onConfirm}
disabled={resolvedConfirmDisabled}
className="rounded-full bg-gray-950 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-40"
className={cn(
"inline-flex h-7 items-center justify-center rounded-full px-3.5 text-xs font-medium leading-none text-white backdrop-blur-xl transition-all active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100",
isDeleteAction
? "border border-red-700/35 bg-red-600/90 shadow-[0_3px_9px_rgba(127,29,29,0.16),inset_0_1px_0_rgba(255,255,255,0.22),inset_0_-4px_9px_rgba(127,29,29,0.18)] hover:bg-red-600"
: "border border-gray-700/40 bg-gray-950/88 shadow-[0_3px_9px_rgba(15,23,42,0.16),inset_0_1px_0_rgba(255,255,255,0.22),inset_0_-4px_9px_rgba(15,23,42,0.2)] hover:bg-gray-900/90",
)}
aria-busy={confirmBusy}
>
{resolvedConfirmLabel}

View file

@ -77,9 +77,9 @@ export function Modal({
>
<div
className={cn(
"w-full rounded-2xl shadow-2xl flex h-[600px] flex-col",
"w-full rounded-2xl flex h-[600px] flex-col",
sizeClassName[size],
"border border-white/70 bg-white/80 shadow-[0_24px_80px_rgba(15,23,42,0.18)] backdrop-blur-2xl",
"border border-white/70 bg-white/94 shadow-[0_12px_36px_rgba(15,23,42,0.1)] backdrop-blur-2xl",
className,
)}
onClick={(e) => e.stopPropagation()}
@ -123,7 +123,7 @@ export function Modal({
{hasFooter && (
<div
className={cn(
"flex items-center gap-3 px-4 py-3",
"flex items-center gap-3 p-4",
secondaryAction || footerInfo
? "justify-between"
: "justify-end",
@ -181,14 +181,14 @@ function ModalActionButton({
return (
<button
className={cn(
"inline-flex items-center justify-center gap-1.5 rounded-lg px-4 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40",
"inline-flex items-center justify-center gap-1.5 px-4 py-1.5 text-sm font-medium transition-all disabled:cursor-not-allowed disabled:opacity-40",
variant === "primary" &&
"bg-gray-900 text-white hover:bg-gray-700",
variant === "secondary" && "text-gray-600 hover:bg-gray-100",
"rounded-full border border-gray-700/40 bg-gray-950/88 text-white shadow-[0_3px_9px_rgba(15,23,42,0.16),inset_0_1px_0_rgba(255,255,255,0.22),inset_0_-4px_9px_rgba(15,23,42,0.2)] backdrop-blur-xl hover:bg-gray-900/90 active:scale-[0.98] disabled:active:scale-100",
variant === "secondary" && "text-gray-600 hover:text-gray-950",
fallbackVariant === "secondary" &&
"border border-gray-200 hover:bg-gray-50",
"rounded-full border border-gray-200/80 bg-gray-100/70 shadow-[0_1px_4px_rgba(15,23,42,0.045),inset_0_1px_0_rgba(255,255,255,0.78),inset_0_-3px_8px_rgba(148,163,184,0.14)] backdrop-blur-xl hover:bg-gray-100",
variant === "danger" &&
"bg-red-600 text-white hover:bg-red-700",
"rounded-full border border-red-700/35 bg-red-600/90 text-white shadow-[0_3px_9px_rgba(127,29,29,0.16),inset_0_1px_0_rgba(255,255,255,0.22),inset_0_-4px_9px_rgba(127,29,29,0.18)] backdrop-blur-xl hover:bg-red-600 active:scale-[0.98] disabled:active:scale-100",
)}
{...props}
>

View file

@ -44,8 +44,7 @@ export function OwnerOnlyModal({
onClose={onClose}
title={title}
message={body}
icon={<Lock className="mt-0.5 h-3.5 w-3.5 shrink-0 text-red-600" />}
primaryAction={{ label: "OK", onClick: onClose }}
icon={<Lock className="h-3.5 w-3.5 shrink-0 text-red-600" />}
>
{ownerEmail && (
<p className="mt-1 text-xs text-gray-600">

View file

@ -36,6 +36,10 @@ export function WarningPopup({
}: WarningPopupProps) {
if (!open) return null;
const warningIcon = icon ?? (
<AlertCircle className="h-3 w-3 shrink-0 text-red-600" />
);
return createPortal(
<div className="pointer-events-none fixed left-1/2 top-5 z-[220] w-[min(92vw,520px)] -translate-x-1/2 px-4">
<div
@ -44,16 +48,21 @@ export function WarningPopup({
className,
)}
>
{icon ?? (
<AlertCircle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-red-600" />
)}
<div className="min-w-0 flex-1 self-center text-gray-900">
<div className="min-w-0 flex-1 self-center text-red-600">
{title && (
<div className="font-medium text-gray-950">
<div className="flex items-center gap-1.5 font-medium mb-1">
{warningIcon}
{title}
</div>
)}
{message && <div>{message}</div>}
{message && (
<div
className={cn(!title && "flex items-start gap-1.5")}
>
{!title && warningIcon}
<span className="min-w-0">{message}</span>
</div>
)}
{children}
{(primaryAction || secondaryAction) && (
<div className="mt-2 flex items-center gap-2">
@ -72,7 +81,7 @@ export function WarningPopup({
<button
type="button"
onClick={onClose}
className="shrink-0 text-gray-700 transition-colors hover:text-gray-950"
className="shrink-0 text-red-700 transition-colors hover:text-red-500"
aria-label="Dismiss warning"
>
<X className="h-3.5 w-3.5" />

View file

@ -207,7 +207,6 @@ export type AssistantEvent =
url: string;
pdfUrl?: string | null;
dateFiled?: string | null;
judges?: string | null;
case?: Extract<AssistantEvent, { type: "case_opinions" }>["case"];
}
| {
@ -288,7 +287,6 @@ export type CaseCitationAnnotation = {
url?: string | null;
pdfUrl?: string | null;
dateFiled?: string | null;
judges?: string | null;
quotes: CaseCitationQuote[];
};