fix: preserve Fast Refresh component boundaries
This commit is contained in:
@@ -23,7 +23,7 @@ import { AnimatePresence, MotionConfig, motion, useReducedMotion } from "motion/
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useStickToBottomContext } from "use-stick-to-bottom";
|
import { useStickToBottomContext } from "use-stick-to-bottom";
|
||||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import { Agent, AgentContent } from "@/shared/ai-elements/agent";
|
import { Agent, AgentContent } from "@/shared/ai-elements/agent";
|
||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
import { toast, Toaster } from "sonner";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { MAP_ICON_CELL_RADIUS_CLASS_NAME } from "./map-control-styles";
|
||||||
|
import { MapNoticeCloseIcon, MapNoticeContent } from "./notice-presentation";
|
||||||
|
import type { MapNoticeOptions, MapNoticePosition } from "./notice-types";
|
||||||
|
|
||||||
|
type SonnerPosition = NonNullable<ComponentProps<typeof Toaster>["position"]>;
|
||||||
|
|
||||||
|
const DEFAULT_POSITION: MapNoticePosition = "top-center";
|
||||||
|
|
||||||
|
const positionClassNames: Record<MapNoticePosition, string> = {
|
||||||
|
"top-center": "left-1/2! right-auto! top-[84px] [translate:-50%_0]",
|
||||||
|
"top-right": "top-[84px] right-[72px]",
|
||||||
|
"bottom-left": "bottom-12 left-4",
|
||||||
|
"bottom-right": "bottom-12 right-4"
|
||||||
|
};
|
||||||
|
|
||||||
|
const sonnerPositions: Record<MapNoticePosition, SonnerPosition> = {
|
||||||
|
"top-center": "top-center",
|
||||||
|
"top-right": "top-right",
|
||||||
|
"bottom-left": "bottom-left",
|
||||||
|
"bottom-right": "bottom-right"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function showMapNotice(options: MapNoticeOptions) {
|
||||||
|
const tone = options.tone ?? "info";
|
||||||
|
const position = options.position ?? DEFAULT_POSITION;
|
||||||
|
const duration = options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity);
|
||||||
|
const dismissible = options.dismissible ?? tone !== "loading";
|
||||||
|
|
||||||
|
return toast.custom(
|
||||||
|
() => <MapNoticeContent {...options} tone={tone} />,
|
||||||
|
{
|
||||||
|
id: options.id,
|
||||||
|
duration,
|
||||||
|
position: sonnerPositions[position],
|
||||||
|
dismissible,
|
||||||
|
cancel: dismissible
|
||||||
|
? {
|
||||||
|
label: <MapNoticeCloseIcon />,
|
||||||
|
onClick: (event) => event.stopPropagation()
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
classNames: {
|
||||||
|
cancelButton: cn(
|
||||||
|
"absolute right-2 top-2 z-10 grid h-7 w-7 place-items-center border border-transparent bg-transparent p-0 text-slate-400 transition hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25",
|
||||||
|
MAP_ICON_CELL_RADIUS_CLASS_NAME
|
||||||
|
)
|
||||||
|
},
|
||||||
|
className: cn("fixed! z-[60]! w-[min(420px,calc(100vw-24px))]!", positionClassNames[position])
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dismissMapNotice(id?: string | number) {
|
||||||
|
toast.dismiss(id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from "lucide-react";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||||
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||||
|
MAP_READABLE_RADIUS_CLASS_NAME
|
||||||
|
} from "./map-control-styles";
|
||||||
|
import type { MapNoticeOptions, MapNoticeTone } from "./notice-types";
|
||||||
|
|
||||||
|
const toneClassNames: Record<MapNoticeTone, string> = {
|
||||||
|
info: "acrylic-panel text-slate-900",
|
||||||
|
success: "acrylic-panel text-slate-900",
|
||||||
|
warning: "acrylic-panel text-slate-900",
|
||||||
|
error: "acrylic-panel text-slate-900",
|
||||||
|
loading: "acrylic-panel text-slate-900"
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconClassNames: Record<MapNoticeTone, string> = {
|
||||||
|
info: "text-blue-600",
|
||||||
|
success: "text-green-600",
|
||||||
|
warning: "text-orange-500",
|
||||||
|
error: "text-red-500",
|
||||||
|
loading: "text-blue-600"
|
||||||
|
};
|
||||||
|
|
||||||
|
const noticeAccentClassNames: Record<MapNoticeTone, string> = {
|
||||||
|
info: "bg-blue-600",
|
||||||
|
success: "bg-green-500",
|
||||||
|
warning: "bg-orange-500",
|
||||||
|
error: "bg-red-500",
|
||||||
|
loading: "bg-blue-600"
|
||||||
|
};
|
||||||
|
|
||||||
|
const noticeIconSurfaceClassNames: Record<MapNoticeTone, string> = {
|
||||||
|
info: "border-blue-100 bg-blue-50",
|
||||||
|
success: "border-green-100 bg-green-50",
|
||||||
|
warning: "border-orange-100 bg-orange-50",
|
||||||
|
error: "border-red-100 bg-red-50",
|
||||||
|
loading: "border-blue-100 bg-blue-50"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MapNoticeContent({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
tone = "info",
|
||||||
|
actionLabel,
|
||||||
|
onAction
|
||||||
|
}: MapNoticeOptions) {
|
||||||
|
const Icon = getNoticeIcon(tone);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MapNoticeFrame tone={tone}>
|
||||||
|
<MapNoticeIcon tone={tone} icon={Icon} />
|
||||||
|
<MapNoticeBody
|
||||||
|
title={title}
|
||||||
|
message={message}
|
||||||
|
action={actionLabel && onAction ? <MapNoticeAction label={actionLabel} onClick={onAction} /> : null}
|
||||||
|
/>
|
||||||
|
</MapNoticeFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MapNoticeCloseIcon() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="sr-only">关闭通知</span>
|
||||||
|
<X size={15} aria-hidden="true" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapNoticeFrame({ tone, children }: { tone: MapNoticeTone; children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role={tone === "error" || tone === "warning" ? "alert" : "status"}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full items-start gap-2.5 overflow-hidden border py-2.5 pl-3 pr-10 text-sm",
|
||||||
|
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||||
|
toneClassNames[tone]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={cn("absolute bottom-0 left-0 top-0 w-1", noticeAccentClassNames[tone])} aria-hidden="true" />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapNoticeIcon({ tone, icon: Icon }: { tone: MapNoticeTone; icon: ReturnType<typeof getNoticeIcon> }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"mt-0.5 grid h-7 w-7 shrink-0 place-items-center border",
|
||||||
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||||
|
iconClassNames[tone],
|
||||||
|
noticeIconSurfaceClassNames[tone]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon size={17} aria-hidden="true" className={tone === "loading" ? "animate-spin" : undefined} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapNoticeBody({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
action
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
action?: ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
{title ? <span className="block text-sm font-semibold leading-5 text-slate-950">{title}</span> : null}
|
||||||
|
<span className={cn("block text-sm leading-5 text-slate-600", title && "mt-0.5")}>{message}</span>
|
||||||
|
{action}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapNoticeAction({ label, onClick }: { label: string; onClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className={cn("mt-2 inline-flex h-7 items-center justify-center border border-blue-100 bg-blue-50 px-2.5 text-xs font-semibold text-blue-700 transition hover:bg-blue-100", MAP_COMPACT_RADIUS_CLASS_NAME)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNoticeIcon(tone: MapNoticeTone) {
|
||||||
|
if (tone === "success") {
|
||||||
|
return CheckCircle2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tone === "warning") {
|
||||||
|
return AlertTriangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tone === "error") {
|
||||||
|
return XCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tone === "loading") {
|
||||||
|
return Loader2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Info;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export type MapNoticeTone = "info" | "success" | "warning" | "error" | "loading";
|
||||||
|
export type MapNoticePosition = "top-center" | "top-right" | "bottom-left" | "bottom-right";
|
||||||
|
|
||||||
|
export type MapNoticeOptions = {
|
||||||
|
id?: string | number;
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
tone?: MapNoticeTone;
|
||||||
|
duration?: number;
|
||||||
|
position?: MapNoticePosition;
|
||||||
|
actionLabel?: string;
|
||||||
|
onAction?: () => void;
|
||||||
|
dismissible?: boolean;
|
||||||
|
};
|
||||||
@@ -1,80 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from "lucide-react";
|
|
||||||
import type { ComponentProps, ReactNode } from "react";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { toast, Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { dismissMapNotice, showMapNotice } from "./notice-actions";
|
||||||
import {
|
import type { MapNoticeTone } from "./notice-types";
|
||||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
|
||||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
||||||
MAP_READABLE_RADIUS_CLASS_NAME
|
|
||||||
} from "./map-control-styles";
|
|
||||||
|
|
||||||
export type MapNoticeTone = "info" | "success" | "warning" | "error" | "loading";
|
export type { MapNoticeOptions, MapNoticePosition, MapNoticeTone } from "./notice-types";
|
||||||
export type MapNoticePosition = "top-center" | "top-right" | "bottom-left" | "bottom-right";
|
|
||||||
|
|
||||||
export type MapNoticeOptions = {
|
|
||||||
id?: string | number;
|
|
||||||
title?: string;
|
|
||||||
message: string;
|
|
||||||
tone?: MapNoticeTone;
|
|
||||||
duration?: number;
|
|
||||||
position?: MapNoticePosition;
|
|
||||||
actionLabel?: string;
|
|
||||||
onAction?: () => void;
|
|
||||||
dismissible?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SonnerPosition = NonNullable<ComponentProps<typeof Toaster>["position"]>;
|
|
||||||
|
|
||||||
const DEFAULT_POSITION: MapNoticePosition = "top-center";
|
|
||||||
|
|
||||||
const positionClassNames: Record<MapNoticePosition, string> = {
|
|
||||||
"top-center": "left-1/2! right-auto! top-[84px] [translate:-50%_0]",
|
|
||||||
"top-right": "top-[84px] right-[72px]",
|
|
||||||
"bottom-left": "bottom-12 left-4",
|
|
||||||
"bottom-right": "bottom-12 right-4"
|
|
||||||
};
|
|
||||||
|
|
||||||
const sonnerPositions: Record<MapNoticePosition, SonnerPosition> = {
|
|
||||||
"top-center": "top-center",
|
|
||||||
"top-right": "top-right",
|
|
||||||
"bottom-left": "bottom-left",
|
|
||||||
"bottom-right": "bottom-right"
|
|
||||||
};
|
|
||||||
|
|
||||||
const toneClassNames: Record<MapNoticeTone, string> = {
|
|
||||||
info: "acrylic-panel text-slate-900",
|
|
||||||
success: "acrylic-panel text-slate-900",
|
|
||||||
warning: "acrylic-panel text-slate-900",
|
|
||||||
error: "acrylic-panel text-slate-900",
|
|
||||||
loading: "acrylic-panel text-slate-900"
|
|
||||||
};
|
|
||||||
|
|
||||||
const iconClassNames: Record<MapNoticeTone, string> = {
|
|
||||||
info: "text-blue-600",
|
|
||||||
success: "text-green-600",
|
|
||||||
warning: "text-orange-500",
|
|
||||||
error: "text-red-500",
|
|
||||||
loading: "text-blue-600"
|
|
||||||
};
|
|
||||||
|
|
||||||
const noticeAccentClassNames: Record<MapNoticeTone, string> = {
|
|
||||||
info: "bg-blue-600",
|
|
||||||
success: "bg-green-500",
|
|
||||||
warning: "bg-orange-500",
|
|
||||||
error: "bg-red-500",
|
|
||||||
loading: "bg-blue-600"
|
|
||||||
};
|
|
||||||
|
|
||||||
const noticeIconSurfaceClassNames: Record<MapNoticeTone, string> = {
|
|
||||||
info: "border-blue-100 bg-blue-50",
|
|
||||||
success: "border-green-100 bg-green-50",
|
|
||||||
warning: "border-orange-100 bg-orange-50",
|
|
||||||
error: "border-red-100 bg-red-50",
|
|
||||||
loading: "border-blue-100 bg-blue-50"
|
|
||||||
};
|
|
||||||
|
|
||||||
export function MapToaster() {
|
export function MapToaster() {
|
||||||
return (
|
return (
|
||||||
@@ -82,7 +13,7 @@ export function MapToaster() {
|
|||||||
closeButton={false}
|
closeButton={false}
|
||||||
expand
|
expand
|
||||||
visibleToasts={4}
|
visibleToasts={4}
|
||||||
position={sonnerPositions[DEFAULT_POSITION]}
|
position="top-center"
|
||||||
toastOptions={{
|
toastOptions={{
|
||||||
unstyled: true,
|
unstyled: true,
|
||||||
classNames: {
|
classNames: {
|
||||||
@@ -95,40 +26,6 @@ export function MapToaster() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showMapNotice(options: MapNoticeOptions) {
|
|
||||||
const tone = options.tone ?? "info";
|
|
||||||
const position = options.position ?? DEFAULT_POSITION;
|
|
||||||
const duration = options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity);
|
|
||||||
const dismissible = options.dismissible ?? tone !== "loading";
|
|
||||||
|
|
||||||
return toast.custom(
|
|
||||||
() => <MapNoticeContent {...options} tone={tone} />,
|
|
||||||
{
|
|
||||||
id: options.id,
|
|
||||||
duration,
|
|
||||||
position: sonnerPositions[position],
|
|
||||||
dismissible,
|
|
||||||
cancel: dismissible
|
|
||||||
? {
|
|
||||||
label: <MapNoticeCloseIcon />,
|
|
||||||
onClick: (event) => event.stopPropagation()
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
classNames: {
|
|
||||||
cancelButton: cn(
|
|
||||||
"absolute right-2 top-2 z-10 grid h-7 w-7 place-items-center border border-transparent bg-transparent p-0 text-slate-400 transition hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25",
|
|
||||||
MAP_ICON_CELL_RADIUS_CLASS_NAME
|
|
||||||
)
|
|
||||||
},
|
|
||||||
className: cn("fixed! z-[60]! w-[min(420px,calc(100vw-24px))]!", positionClassNames[position])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dismissMapNotice(id?: string | number) {
|
|
||||||
toast.dismiss(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MapErrorNotice({ message, id = "map-error" }: { message: string; id?: string | number }) {
|
export function MapErrorNotice({ message, id = "map-error" }: { message: string; id?: string | number }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
showMapNotice({
|
showMapNotice({
|
||||||
@@ -206,117 +103,6 @@ export function MapSourceStatusNotice({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MapNoticeContent({
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
tone = "info",
|
|
||||||
actionLabel,
|
|
||||||
onAction
|
|
||||||
}: MapNoticeOptions) {
|
|
||||||
const Icon = getNoticeIcon(tone);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MapNoticeFrame tone={tone}>
|
|
||||||
<MapNoticeIcon tone={tone} icon={Icon} />
|
|
||||||
<MapNoticeBody
|
|
||||||
title={title}
|
|
||||||
message={message}
|
|
||||||
action={actionLabel && onAction ? <MapNoticeAction label={actionLabel} onClick={onAction} /> : null}
|
|
||||||
/>
|
|
||||||
</MapNoticeFrame>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapNoticeFrame({ tone, children }: { tone: MapNoticeTone; children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role={tone === "error" || tone === "warning" ? "alert" : "status"}
|
|
||||||
className={cn(
|
|
||||||
"relative flex w-full items-start gap-2.5 overflow-hidden border py-2.5 pl-3 pr-10 text-sm",
|
|
||||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
|
||||||
toneClassNames[tone]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className={cn("absolute bottom-0 left-0 top-0 w-1", noticeAccentClassNames[tone])} aria-hidden="true" />
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapNoticeIcon({ tone, icon: Icon }: { tone: MapNoticeTone; icon: ReturnType<typeof getNoticeIcon> }) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"mt-0.5 grid h-7 w-7 shrink-0 place-items-center border",
|
|
||||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
||||||
iconClassNames[tone],
|
|
||||||
noticeIconSurfaceClassNames[tone]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon size={17} aria-hidden="true" className={tone === "loading" ? "animate-spin" : undefined} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapNoticeBody({
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
action
|
|
||||||
}: {
|
|
||||||
title?: string;
|
|
||||||
message: string;
|
|
||||||
action?: ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<span className="min-w-0 flex-1">
|
|
||||||
{title ? <span className="block text-sm font-semibold leading-5 text-slate-950">{title}</span> : null}
|
|
||||||
<span className={cn("block text-sm leading-5 text-slate-600", title && "mt-0.5")}>{message}</span>
|
|
||||||
{action}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapNoticeAction({ label, onClick }: { label: string; onClick: () => void }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClick}
|
|
||||||
className={cn("mt-2 inline-flex h-7 items-center justify-center border border-blue-100 bg-blue-50 px-2.5 text-xs font-semibold text-blue-700 transition hover:bg-blue-100", MAP_COMPACT_RADIUS_CLASS_NAME)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapNoticeCloseIcon() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span className="sr-only">关闭通知</span>
|
|
||||||
<X size={15} aria-hidden="true" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNoticeIcon(tone: MapNoticeTone) {
|
|
||||||
if (tone === "success") {
|
|
||||||
return CheckCircle2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tone === "warning") {
|
|
||||||
return AlertTriangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tone === "error") {
|
|
||||||
return XCircle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tone === "loading") {
|
|
||||||
return Loader2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Info;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSourceStatusTone(status: MapSourceStatus): MapNoticeTone {
|
function getSourceStatusTone(status: MapSourceStatus): MapNoticeTone {
|
||||||
if (status === "online") {
|
if (status === "online") {
|
||||||
return "success";
|
return "success";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
MAP_FOCUS_SURFACE_CLASS_NAME,
|
MAP_FOCUS_SURFACE_CLASS_NAME,
|
||||||
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME
|
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME
|
||||||
} from "@/features/map/core/components/map-control-styles";
|
} from "@/features/map/core/components/map-control-styles";
|
||||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { WaterNetworkSourceId } from "../map/sources";
|
import type { WaterNetworkSourceId } from "../map/sources";
|
||||||
import type { DetailFeature } from "../types";
|
import type { DetailFeature } from "../types";
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import {
|
import {
|
||||||
getRunningWorkflowDefinition,
|
getRunningWorkflowDefinition,
|
||||||
getRunningWorkflowState,
|
getRunningWorkflowState,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { MAP_TOOL_PANEL_SURFACE_CLASS_NAME } from "@/features/map/core/components/map-control-styles";
|
import { MAP_TOOL_PANEL_SURFACE_CLASS_NAME } from "@/features/map/core/components/map-control-styles";
|
||||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import { isGeneratedScheduledConditionSessionId } from "@/features/workbench/data/scheduled-conditions";
|
import { isGeneratedScheduledConditionSessionId } from "@/features/workbench/data/scheduled-conditions";
|
||||||
import type { ScheduledConditionItem } from "@/features/workbench/types";
|
import type { ScheduledConditionItem } from "@/features/workbench/types";
|
||||||
import { createConditionConversationPrompt } from "@/features/workbench/utils/scheduled-condition-prompts";
|
import { createConditionConversationPrompt } from "@/features/workbench/utils/scheduled-condition-prompts";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { DefaultChatTransport } from "ai";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import useSWRImmutable from "swr/immutable";
|
import useSWRImmutable from "swr/immutable";
|
||||||
import type { PersonaState } from "@/shared/ai-elements/persona";
|
import type { PersonaState } from "@/shared/ai-elements/persona";
|
||||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import type {
|
import type {
|
||||||
AgentApprovalMode,
|
AgentApprovalMode,
|
||||||
AgentChatMessage,
|
AgentChatMessage,
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import {
|
|||||||
MapErrorNotice,
|
MapErrorNotice,
|
||||||
MapLoadingNotice,
|
MapLoadingNotice,
|
||||||
MapSourceStatusNotice,
|
MapSourceStatusNotice,
|
||||||
showMapNotice,
|
|
||||||
type MapSourceStatus
|
type MapSourceStatus
|
||||||
} from "@/features/map/core/components/notice";
|
} from "@/features/map/core/components/notice";
|
||||||
|
import { showMapNotice } from "@/features/map/core/components/notice-actions";
|
||||||
import { MapScaleLine } from "@/features/map/core/components/scale-line";
|
import { MapScaleLine } from "@/features/map/core/components/scale-line";
|
||||||
import { MapToolbar, type MapToolbarItem } from "@/features/map/core/components/toolbar";
|
import { MapToolbar, type MapToolbarItem } from "@/features/map/core/components/toolbar";
|
||||||
import { MapZoom } from "@/features/map/core/components/zoom";
|
import { MapZoom } from "@/features/map/core/components/zoom";
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const createRawTokens = (code: string): TokenizedCode => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Synchronous highlight with callback for async results
|
// Synchronous highlight with callback for async results
|
||||||
export const highlightCode = (
|
const highlightCode = (
|
||||||
code: string,
|
code: string,
|
||||||
language: BundledLanguage,
|
language: BundledLanguage,
|
||||||
// oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)
|
// oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ const defaultFormatMessage = (message: UIMessage): string => {
|
|||||||
return `**${roleLabel}:** ${getMessageText(message)}`;
|
return `**${roleLabel}:** ${getMessageText(message)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const messagesToMarkdown = (
|
const messagesToMarkdown = (
|
||||||
messages: UIMessage[],
|
messages: UIMessage[],
|
||||||
formatMessage: (
|
formatMessage: (
|
||||||
message: UIMessage,
|
message: UIMessage,
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ const ProviderAttachmentsContext = createContext<AttachmentsContext | null>(
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
export const usePromptInputController = () => {
|
const usePromptInputController = () => {
|
||||||
const ctx = useContext(PromptInputController);
|
const ctx = useContext(PromptInputController);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -224,7 +224,7 @@ export const usePromptInputController = () => {
|
|||||||
const useOptionalPromptInputController = () =>
|
const useOptionalPromptInputController = () =>
|
||||||
useContext(PromptInputController);
|
useContext(PromptInputController);
|
||||||
|
|
||||||
export const useProviderAttachments = () => {
|
const useProviderAttachments = () => {
|
||||||
const ctx = useContext(ProviderAttachmentsContext);
|
const ctx = useContext(ProviderAttachmentsContext);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -371,7 +371,7 @@ export const PromptInputProvider = ({
|
|||||||
|
|
||||||
const LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);
|
const LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);
|
||||||
|
|
||||||
export const usePromptInputAttachments = () => {
|
const usePromptInputAttachments = () => {
|
||||||
// Prefer local context (inside PromptInput) as it has validation, fall back to provider
|
// Prefer local context (inside PromptInput) as it has validation, fall back to provider
|
||||||
const provider = useOptionalProviderAttachments();
|
const provider = useOptionalProviderAttachments();
|
||||||
const local = useContext(LocalAttachmentsContext);
|
const local = useContext(LocalAttachmentsContext);
|
||||||
@@ -395,10 +395,10 @@ export interface ReferencedSourcesContext {
|
|||||||
clear: () => void;
|
clear: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LocalReferencedSourcesContext =
|
const LocalReferencedSourcesContext =
|
||||||
createContext<ReferencedSourcesContext | null>(null);
|
createContext<ReferencedSourcesContext | null>(null);
|
||||||
|
|
||||||
export const usePromptInputReferencedSources = () => {
|
const usePromptInputReferencedSources = () => {
|
||||||
const ctx = useContext(LocalReferencedSourcesContext);
|
const ctx = useContext(LocalReferencedSourcesContext);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
export { Badge }
|
||||||
|
|||||||
@@ -79,5 +79,4 @@ export {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
ButtonGroupSeparator,
|
ButtonGroupSeparator,
|
||||||
ButtonGroupText,
|
ButtonGroupText,
|
||||||
buttonGroupVariants,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
)
|
)
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button"
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button }
|
||||||
|
|||||||
Reference in New Issue
Block a user