Merge branch 'zhujingyi' into 'master'
Zhujingyi See merge request knowledge-base-bj/deerflow-server!76
This commit is contained in:
commit
d40ee80680
23 files changed
+594
-269
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -14,7 +14,7 @@ const buttonVariants = cva(
|
||||
destructive:
|
||||
"cursor-pointer bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
"cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-[#213D5E] dark:bg-[#022248] dark:text-[#CCD2DA] dark:hover:border-[#213D5E] dark:hover:bg-[#0a2f58] dark:hover:text-[#CCD2DA] dark:[&_svg]:text-[#CCD2DA]",
|
||||
secondary:
|
||||
"cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost:
|
||||
@@ -37,27 +37,27 @@ const buttonVariants = cva(
|
||||
},
|
||||
);
|
||||
|
||||
const Button = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
data-slot="button"
|
||||
{...(variant !== undefined && { "data-variant": variant })}
|
||||
{...(size !== undefined && { "data-size": size })}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
const Button = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
data-slot="button"
|
||||
{...(variant !== undefined && { "data-variant": variant })}
|
||||
{...(size !== undefined && { "data-size": size })}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -34,7 +34,7 @@ const emptyMediaVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6 dark:text-[#55A6FE] dark:bg-[#022248]",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -62,7 +62,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-title"
|
||||
className={cn("text-lg font-medium tracking-tight", className)}
|
||||
className={cn("text-lg font-medium tracking-tight dark:text-[#AACEFF]", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -73,7 +73,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
<div
|
||||
data-slot="empty-description"
|
||||
className={cn(
|
||||
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
||||
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 dark:text-[#55A6FE]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -3,6 +3,14 @@ import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
const usesDarkNativePicker =
|
||||
type === "number" ||
|
||||
type === "date" ||
|
||||
type === "datetime-local" ||
|
||||
type === "time" ||
|
||||
type === "month" ||
|
||||
type === "week";
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
@@ -10,7 +18,8 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
className={cn(
|
||||
"h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
||||
"border-input placeholder:text-muted-foreground",
|
||||
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] rounded-[4px] text-[16px]",
|
||||
"dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] dark:rounded-[4px] dark:text-[16px] md:dark:text-[16px]",
|
||||
usesDarkNativePicker && "dark:[color-scheme:dark]",
|
||||
"file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
||||
|
||||
@@ -24,69 +24,70 @@ function SelectValue({
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||
}
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default";
|
||||
}
|
||||
>(({ className, size = "default", children, ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default";
|
||||
}
|
||||
>(({ className, size = "default", children, ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEE2] dark:data-[placeholder]:text-[#566B7F] dark:hover:bg-[#022248] dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
align={align}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
);
|
||||
});
|
||||
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF]",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
align={align}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
);
|
||||
});
|
||||
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
@@ -95,40 +96,44 @@ function SelectLabel({
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
data-slot="select-label"
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
className={cn(
|
||||
"text-muted-foreground px-2 py-1.5 text-xs dark:text-[#566B7F]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
data-slot="select-item-indicator"
|
||||
className="absolute right-2 flex size-3.5 items-center justify-center"
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
"dark:text-[#AACEFF] dark:focus:bg-[#0062D9] dark:focus:text-white dark:data-[highlighted]:bg-[#0062D9] dark:data-[highlighted]:text-white dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus:[&_svg:not([class*='text-'])]:text-white dark:data-[highlighted]:[&_svg:not([class*='text-'])]:text-white",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
data-slot="select-item-indicator"
|
||||
className="absolute right-2 flex size-3.5 items-center justify-center"
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
@@ -151,7 +156,7 @@ function SelectScrollUpButton({
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -169,7 +174,7 @@ function SelectScrollDownButton({
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -13,7 +13,7 @@ function Switch({
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-[#55A6FE] dark:data-[state=checked]:bg-[#0062D9] inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -32,7 +32,8 @@ const tabsListVariants = cva(
|
||||
variant: {
|
||||
default:
|
||||
"bg-muted dark:group-data-[orientation=horizontal]/tabs:h-8! dark:items-stretch dark:gap-0 dark:overflow-hidden dark:rounded-[2px] dark:border dark:border-[#55A6FE] dark:bg-[#022248] dark:p-0",
|
||||
line: "gap-1 bg-transparent",
|
||||
line:
|
||||
"gap-1 bg-transparent dark:group-data-[orientation=horizontal]/tabs:h-8! dark:items-stretch dark:gap-0 dark:overflow-visible dark:rounded-[2px] dark:border dark:border-[#55A6FE] dark:bg-[#022248] dark:p-0",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -70,13 +71,16 @@ const TabsTrigger = React.forwardRef<
|
||||
className={cn(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"dark:text-muted-foreground dark:hover:text-foreground",
|
||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
||||
"data-[state=active]:bg-background data-[state=active]:text-foreground",
|
||||
"dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-input dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-input/30 dark:group-data-[variant=line]/tabs-list:data-[state=active]:text-foreground",
|
||||
"dark:group-data-[variant=default]/tabs-list:relative dark:group-data-[variant=default]/tabs-list:!h-full dark:group-data-[variant=default]/tabs-list:self-stretch dark:group-data-[variant=default]/tabs-list:flex-none dark:group-data-[variant=default]/tabs-list:rounded-none dark:group-data-[variant=default]/tabs-list:border-0 dark:group-data-[variant=default]/tabs-list:bg-transparent dark:group-data-[variant=default]/tabs-list:px-4 dark:group-data-[variant=default]/tabs-list:!py-0 dark:group-data-[variant=default]/tabs-list:!leading-none dark:group-data-[variant=default]/tabs-list:shadow-none dark:group-data-[variant=default]/tabs-list:focus-visible:ring-0 dark:group-data-[variant=default]/tabs-list:[&_svg]:text-[#CCD2DA]",
|
||||
"dark:group-data-[variant=line]/tabs-list:relative dark:group-data-[variant=line]/tabs-list:!h-full dark:group-data-[variant=line]/tabs-list:self-stretch dark:group-data-[variant=line]/tabs-list:flex-none dark:group-data-[variant=line]/tabs-list:rounded-none dark:group-data-[variant=line]/tabs-list:border-0 dark:group-data-[variant=line]/tabs-list:bg-transparent dark:group-data-[variant=line]/tabs-list:px-4 dark:group-data-[variant=line]/tabs-list:!py-0 dark:group-data-[variant=line]/tabs-list:!leading-none dark:group-data-[variant=line]/tabs-list:text-[#CCD2DA] dark:group-data-[variant=line]/tabs-list:shadow-none dark:group-data-[variant=line]/tabs-list:hover:text-[#CCD2DA] dark:group-data-[variant=line]/tabs-list:focus-visible:ring-0 dark:group-data-[variant=line]/tabs-list:[&_svg]:text-[#CCD2DA]",
|
||||
"dark:group-data-[variant=line]/tabs-list:not(:first-child):border-l dark:group-data-[variant=line]/tabs-list:not(:first-child):border-[#1d3961] dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-l-0",
|
||||
"dark:group-data-[variant=line]/tabs-list:data-[state=active]:rounded-none dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-0 dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-[#0062D9] dark:group-data-[variant=line]/tabs-list:data-[state=active]:text-[#FFFFFF] dark:group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:group-data-[variant=line]/tabs-list:data-[state=active]:[&_svg]:text-[#FFFFFF]",
|
||||
"dark:group-data-[variant=default]/tabs-list:flex dark:group-data-[variant=default]/tabs-list:min-h-0 dark:group-data-[variant=default]/tabs-list:!h-full dark:group-data-[variant=default]/tabs-list:flex-1 dark:group-data-[variant=default]/tabs-list:self-stretch dark:group-data-[variant=default]/tabs-list:rounded-none dark:group-data-[variant=default]/tabs-list:border-0 dark:group-data-[variant=default]/tabs-list:bg-transparent dark:group-data-[variant=default]/tabs-list:px-4 dark:group-data-[variant=default]/tabs-list:!py-0 dark:group-data-[variant=default]/tabs-list:!leading-none dark:group-data-[variant=default]/tabs-list:text-[#CCD2DA] dark:group-data-[variant=default]/tabs-list:shadow-none dark:group-data-[variant=default]/tabs-list:hover:text-[#CCD2DA] dark:group-data-[variant=default]/tabs-list:focus-visible:ring-0 dark:group-data-[variant=default]/tabs-list:[&_svg]:text-[#CCD2DA]",
|
||||
"dark:group-data-[variant=default]/tabs-list:not(:first-child):border-l dark:group-data-[variant=default]/tabs-list:not(:first-child):border-[#1d3961] dark:group-data-[variant=default]/tabs-list:data-[state=active]:border-l-0",
|
||||
"dark:group-data-[variant=default]/tabs-list:data-[state=active]:rounded-none dark:group-data-[variant=default]/tabs-list:data-[state=active]:border-0 dark:group-data-[variant=default]/tabs-list:data-[state=active]:bg-[#0062D9] dark:group-data-[variant=default]/tabs-list:data-[state=active]:text-[#FFFFFF] dark:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-none dark:group-data-[variant=default]/tabs-list:data-[state=active]:[&_svg]:text-[#FFFFFF] dark:group-data-[variant=default]/tabs-list:hover:text-[#CCD2DA]",
|
||||
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
||||
"dark:group-data-[variant=default]/tabs-list:data-[state=active]:rounded-none dark:group-data-[variant=default]/tabs-list:data-[state=active]:border-0 dark:group-data-[variant=default]/tabs-list:data-[state=active]:bg-[#0062D9] dark:group-data-[variant=default]/tabs-list:data-[state=active]:text-[#FFFFFF] dark:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-none dark:group-data-[variant=default]/tabs-list:data-[state=active]:hover:text-[#FFFFFF] dark:group-data-[variant=default]/tabs-list:data-[state=active]:[&_svg]:text-[#FFFFFF]",
|
||||
"group-data-[variant=line]/tabs-list:after:pointer-events-none group-data-[variant=line]/tabs-list:after:absolute group-data-[variant=line]/tabs-list:after:inset-x-2 group-data-[variant=line]/tabs-list:after:bottom-[-5px] group-data-[variant=line]/tabs-list:after:h-0.5 group-data-[variant=line]/tabs-list:after:bg-foreground group-data-[variant=line]/tabs-list:after:opacity-0 group-data-[variant=line]/tabs-list:after:transition-opacity group-data-[variant=line]/tabs-list:after:content-[''] group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
||||
"dark:group-data-[variant=line]/tabs-list:after:inset-x-0 dark:group-data-[variant=line]/tabs-list:after:bottom-0 dark:group-data-[variant=line]/tabs-list:after:z-[2] dark:group-data-[variant=line]/tabs-list:after:h-[2px] dark:group-data-[variant=line]/tabs-list:after:bg-[#FFFFFF]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { toast } from "sonner";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
@@ -90,7 +91,7 @@ function V1SectionField({
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium text-muted-foreground">{label}</span>
|
||||
<span className="text-xs font-medium text-muted-foreground dark:text-[#AACEE2]">{label}</span>
|
||||
{!editing && (
|
||||
<Button size="icon" variant="ghost" className="size-6" onClick={() => { setDraft(value); setEditing(true); }}>
|
||||
<Pencil className="size-3" />
|
||||
@@ -154,7 +155,7 @@ function V1FactItem({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded border p-2 space-y-1 group">
|
||||
<div className="rounded border p-2 space-y-1 group ">
|
||||
{editing ? (
|
||||
<div className="space-y-2">
|
||||
<Textarea value={draftContent} onChange={(e) => setDraftContent(e.target.value)} className="min-h-[64px] text-sm" autoFocus />
|
||||
@@ -196,7 +197,7 @@ function V1FactItem({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm whitespace-pre-wrap">{String(fact.content ?? "")}</p>
|
||||
<p className="text-sm whitespace-pre-wrap dark:text-[#AACEE2]">{String(fact.content ?? "")}</p>
|
||||
{fact.sourceError ? <p className="text-xs text-red-600 bg-red-50 rounded px-2 py-1">避免:{String(fact.sourceError)}</p> : null}
|
||||
</>
|
||||
)}
|
||||
@@ -206,6 +207,7 @@ function V1FactItem({
|
||||
|
||||
// 新增事实表单
|
||||
function V1AddFactForm({ agent, userId }: { agent: string | null; userId: string }) {
|
||||
const { theme } = useTheme();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
const [category, setCategory] = useState("context");
|
||||
@@ -222,7 +224,12 @@ function V1AddFactForm({ agent, userId }: { agent: string | null; userId: string
|
||||
|
||||
if (!open) {
|
||||
return (
|
||||
<Button variant="outline" size="sm" className="mt-1 w-full" onClick={() => setOpen(true)}>
|
||||
<Button
|
||||
variant={theme === "dark" ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="mt-1 w-full"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Plus className="size-3" />新增事实
|
||||
</Button>
|
||||
);
|
||||
@@ -276,8 +283,8 @@ export function V1BucketView({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 用户画像字段 */}
|
||||
<div className="rounded-md border p-3 space-y-3">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground">用户画像</h4>
|
||||
<div className="rounded-md border p-3 space-y-3 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground dark:text-white">用户画像</h4>
|
||||
{allCtxFields.map((field) => (
|
||||
<V1SectionField
|
||||
key={field}
|
||||
@@ -291,8 +298,8 @@ export function V1BucketView({
|
||||
</div>
|
||||
|
||||
{/* 历史摘要字段 */}
|
||||
<div className="rounded-md border p-3 space-y-3">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground">历史摘要</h4>
|
||||
<div className="rounded-md border p-3 space-y-3 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground dark:text-white">历史摘要</h4>
|
||||
{allHistoryFields.map((field) => (
|
||||
<V1SectionField
|
||||
key={field}
|
||||
@@ -306,8 +313,8 @@ export function V1BucketView({
|
||||
</div>
|
||||
|
||||
{/* 事实记录 */}
|
||||
<div className="rounded-md border p-3 space-y-2">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground">事实记录({factsRaw.length} 条)</h4>
|
||||
<div className="rounded-md border p-3 space-y-2 dark:border-[#213D5E] dark:bg-[#022248]">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground dark:text-white">事实记录({factsRaw.length} 条)</h4>
|
||||
{factsRaw.map((fact, i) => (
|
||||
<V1FactItem key={String(fact.id ?? i)} fact={fact} agent={agent} userId={userId} />
|
||||
))}
|
||||
|
||||
@@ -179,7 +179,7 @@ function UserAuditPanel({ userId }: { userId: string }) {
|
||||
<ScrollArea className="min-h-0 flex-1">
|
||||
<div className="p-4">
|
||||
{!threadId && (
|
||||
<div className="text-muted-foreground flex flex-col items-center gap-2 py-16 text-sm">
|
||||
<div className="text-muted-foreground flex flex-col items-center gap-2 py-16 text-sm dark:text-[#AACEF8]">
|
||||
<MessageSquareIcon className="size-6" />
|
||||
请从左侧选择一个会话
|
||||
</div>
|
||||
@@ -253,7 +253,7 @@ function AgentMemoryPanel({ userId }: { userId: string }) {
|
||||
<SelectTrigger className="h-8 w-48" size="sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
{/* 默认助手始终显示,对应全局记忆文件 */}
|
||||
<SelectItem value="default">默认助手</SelectItem>
|
||||
{otherBuckets.map((b) => (
|
||||
|
||||
@@ -224,16 +224,16 @@ export function TagManagementPage() {
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50">
|
||||
<tr>
|
||||
<th className="text-muted-foreground px-4 py-3 text-left font-medium">
|
||||
<th className="text-muted-foreground px-4 py-3 text-left font-medium dark:text-white">
|
||||
{t.adminTags.name}
|
||||
</th>
|
||||
<th className="text-muted-foreground px-4 py-3 text-left font-medium">
|
||||
<th className="text-muted-foreground px-4 py-3 text-left font-medium dark:text-white">
|
||||
{t.adminTags.tagDescription}
|
||||
</th>
|
||||
<th className="text-muted-foreground px-4 py-3 text-right font-medium">
|
||||
<th className="text-muted-foreground px-4 py-3 text-right font-medium dark:text-white">
|
||||
{t.adminTags.usageCount}
|
||||
</th>
|
||||
<th className="text-muted-foreground px-4 py-3 text-right font-medium">
|
||||
<th className="text-muted-foreground px-4 py-3 text-right font-medium dark:text-white">
|
||||
{t.adminTags.actions}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Download, Loader2 } from "lucide-react";
|
||||
import { AlertCircle,CheckCircle2, Download, Loader2 } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@/core/tool-metrics/api";
|
||||
import { useToolMetrics, useToolMetricsSummary } from "@/core/tool-metrics/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
@@ -66,6 +67,7 @@ function categoryLabel(category?: string | null): string {
|
||||
}
|
||||
|
||||
export function ToolMetricsPanel() {
|
||||
const { theme } = useTheme();
|
||||
const [page, setPage] = useState(0);
|
||||
const [tool, setTool] = useState("");
|
||||
const [user, setUser] = useState("");
|
||||
@@ -79,6 +81,31 @@ export function ToolMetricsPanel() {
|
||||
const [until, setUntil] = useState("");
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const [detailRow, setDetailRow] = useState<ToolMetric | null>(null);
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
|
||||
const filters = useMemo<ToolMetricsFilters>(() => {
|
||||
const out: ToolMetricsFilters = {};
|
||||
@@ -117,9 +144,9 @@ export function ToolMetricsPanel() {
|
||||
setExporting(true);
|
||||
try {
|
||||
await downloadToolMetricsExcel(filters);
|
||||
toast.success("已导出为 Excel");
|
||||
toast.success("已导出为 Excel",darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "导出失败");
|
||||
toast.error(e instanceof Error ? e.message : "导出失败",darkToastOpts.error);
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
@@ -167,7 +194,7 @@ export function ToolMetricsPanel() {
|
||||
</div>
|
||||
|
||||
{/* Summary strip */}
|
||||
<div className="flex flex-wrap items-center gap-2 rounded-xl border bg-white p-4">
|
||||
<div className="flex flex-wrap items-center gap-2 rounded-xl border bg-white p-4 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="mr-4 flex flex-col">
|
||||
<span className="text-muted-foreground text-xs">总调用</span>
|
||||
<span className="text-lg font-semibold tabular-nums">
|
||||
@@ -213,7 +240,7 @@ export function ToolMetricsPanel() {
|
||||
|
||||
{/* Per-skill breakdown — answers "was skill X invoked, and did it succeed". */}
|
||||
{summary && summary.by_skill.length > 0 && (
|
||||
<div className="rounded-xl border bg-white p-4">
|
||||
<div className="rounded-xl border bg-white p-4 ">
|
||||
<h3 className="mb-2 text-sm font-medium">各技能调用情况</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
@@ -258,7 +285,7 @@ export function ToolMetricsPanel() {
|
||||
</div>
|
||||
)}
|
||||
{summary && kind === "skill" && summary.by_skill.length === 0 && (
|
||||
<div className="text-muted-foreground rounded-xl border bg-white p-4 text-sm">
|
||||
<div className="text-muted-foreground rounded-xl border bg-white p-4 text-sm dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
当前筛选范围内没有检测到任何技能调用记录。技能只有在被 Agent 通过
|
||||
<code className="bg-muted mx-1 rounded px-1">skill_view</code>
|
||||
读取(即真正"调用")时才会被记录;仅在系统提示里被列出、但未被读取的技能不会计入。
|
||||
@@ -266,9 +293,9 @@ export function ToolMetricsPanel() {
|
||||
)}
|
||||
|
||||
{/* Filters */}
|
||||
<div className="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-3 lg:grid-cols-6">
|
||||
<div className="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-3 lg:grid-cols-6 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">调用类型</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">调用类型</label>
|
||||
<Select
|
||||
value={kind}
|
||||
onValueChange={(value) => {
|
||||
@@ -276,17 +303,17 @@ export function ToolMetricsPanel() {
|
||||
setPage(0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="dark:bg-[#0D2D54] dark:hover:bg-[#0D2D54]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="skill">技能(Agent Skill)</SelectItem>
|
||||
<SelectItem value="all">全部工具调用</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">技能名称</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">技能名称</label>
|
||||
<Input
|
||||
value={skill}
|
||||
placeholder="按技能名筛选"
|
||||
@@ -297,7 +324,7 @@ export function ToolMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">工具名</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">工具名</label>
|
||||
<Input
|
||||
value={tool}
|
||||
placeholder="按工具名筛选"
|
||||
@@ -308,7 +335,7 @@ export function ToolMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">用户名</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">用户名</label>
|
||||
<Input
|
||||
value={user}
|
||||
placeholder="@ 前部分"
|
||||
@@ -319,7 +346,7 @@ export function ToolMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">状态</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">状态</label>
|
||||
<Select
|
||||
value={status}
|
||||
onValueChange={(value) => {
|
||||
@@ -327,10 +354,10 @@ export function ToolMetricsPanel() {
|
||||
setPage(0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="w-full dark:bg-[#0D2D54] dark:hover:bg-[#0D2D54]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="success">成功</SelectItem>
|
||||
<SelectItem value="error">失败</SelectItem>
|
||||
@@ -338,7 +365,7 @@ export function ToolMetricsPanel() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">错误类型</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">错误类型</label>
|
||||
<Select
|
||||
value={category}
|
||||
onValueChange={(value) => {
|
||||
@@ -346,10 +373,10 @@ export function ToolMetricsPanel() {
|
||||
setPage(0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="w-full dark:bg-[#0D2D54] dark:hover:bg-[#0D2D54]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
{TOOL_ERROR_CATEGORIES.map((cat) => (
|
||||
<SelectItem key={cat} value={cat}>
|
||||
@@ -360,7 +387,7 @@ export function ToolMetricsPanel() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">开始时间</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">开始时间</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={since}
|
||||
@@ -371,7 +398,7 @@ export function ToolMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">结束时间</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">结束时间</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={until}
|
||||
@@ -389,10 +416,10 @@ export function ToolMetricsPanel() {
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="rounded-xl border bg-white">
|
||||
<div className="rounded-xl border bg-white dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[960px] text-sm">
|
||||
<thead className="bg-muted/60 text-muted-foreground">
|
||||
<thead className="bg-muted/60 text-muted-foreground dark:text-white">
|
||||
<tr>
|
||||
<Th>时间</Th>
|
||||
<Th>用户</Th>
|
||||
|
||||
@@ -229,7 +229,7 @@ export function MemorySettingsPage() {
|
||||
<SelectTrigger className="h-8 w-44" size="sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="default">
|
||||
{m.workNotesDefaultAgent}
|
||||
</SelectItem>
|
||||
@@ -490,7 +490,7 @@ function MemoryV1View({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* 说明横幅 */}
|
||||
<div className="rounded-md border border-dashed px-4 py-3 text-sm text-muted-foreground">
|
||||
<div className="rounded-md border border-dashed px-4 py-3 text-sm text-muted-foreground dark:border-[#213D5E] dark:bg-[#022248]">
|
||||
V1 模式:记忆由系统在对话结束后自动提取,无需手动管理。
|
||||
</div>
|
||||
|
||||
@@ -509,13 +509,13 @@ function MemoryV1View({
|
||||
|
||||
{/* 事实记录 Tab */}
|
||||
<TabsContent value="facts" className="mt-3">
|
||||
<div className="rounded-lg border p-4 space-y-3">
|
||||
<div className="rounded-lg border p-4 space-y-3 dark:border-[#213D5E] dark:bg-[#022248]">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-base font-medium">事实记录</h3>
|
||||
<span className="text-xs text-muted-foreground">{memory.memory_usage}</span>
|
||||
</div>
|
||||
{memory.facts.length === 0 ? (
|
||||
<div className="rounded-md border border-dashed p-3 text-sm text-muted-foreground">
|
||||
<div className="rounded-md border border-dashed p-3 text-sm text-muted-foreground ">
|
||||
暂无事实记录,对话后系统会自动提取。
|
||||
</div>
|
||||
) : (
|
||||
@@ -542,21 +542,21 @@ function MemoryV1View({
|
||||
</div>
|
||||
<p className="text-sm whitespace-pre-wrap [overflow-wrap:anywhere]">{desensitize(fact.content)}</p>
|
||||
{fact.sourceError && (
|
||||
<p className="text-xs text-red-600 bg-red-50 rounded px-2 py-1">
|
||||
<p className="rounded bg-red-50 px-2 py-1 text-xs text-red-600 dark:bg-[#311629] dark:text-[#EE212C]">
|
||||
避免:{desensitize(fact.sourceError)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 用户画像 & 历史摘要 Tab */}
|
||||
<TabsContent value="profile" className="mt-3 space-y-4">
|
||||
{/* 用户画像 (userContext) */}
|
||||
<div className="rounded-lg border p-4 space-y-2">
|
||||
<div className="rounded-lg border p-4 space-y-2 dark:border-[#213D5E] dark:bg-[#022248]">
|
||||
<h3 className="text-base font-medium">用户画像</h3>
|
||||
{ctxEntries.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">暂无画像信息,对话后系统会自动归纳。</p>
|
||||
@@ -575,7 +575,7 @@ function MemoryV1View({
|
||||
</div>
|
||||
|
||||
{/* 历史摘要 (history) */}
|
||||
<div className="rounded-lg border p-4 space-y-2">
|
||||
<div className="rounded-lg border p-4 space-y-2 dark:border-[#213D5E] dark:bg-[#022248]">
|
||||
<h3 className="text-base font-medium">历史摘要</h3>
|
||||
{historyEntries.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">暂无历史摘要。</p>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ClockIcon,
|
||||
FileArchive,
|
||||
GlobeIcon,
|
||||
InfoIcon,
|
||||
Loader2,
|
||||
LockIcon,
|
||||
PinIcon,
|
||||
@@ -54,6 +55,7 @@ import { SkillDetailDialog } from "@/components/workspace/skills/skill-detail-di
|
||||
import { TagChipList } from "@/components/workspace/tags/tag-chip";
|
||||
import { TagPickerDialog } from "@/components/workspace/tags/tag-picker-dialog";
|
||||
import { TagSearchBar } from "@/components/workspace/tags/tag-search-bar";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import type { ValidateSkillUploadResponse } from "@/core/skills/api";
|
||||
@@ -88,8 +90,42 @@ export function SkillSettingsPage({ onClose }: { onClose?: () => void } = {}) {
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
/** 重新扫描成功 toast:仅暗色模式使用,浅色走 Sonner 默认样式 */
|
||||
const rescanSuccessToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 重新扫描无变更 toast:仅暗色模式使用,浅色走 Sonner 默认样式 */
|
||||
const rescanInfoToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#704812] !bg-[#202222] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <InfoIcon className="size-4 shrink-0 text-[#FF9900]" />,
|
||||
} as const;
|
||||
|
||||
/** 重新扫描失败 toast:仅暗色模式使用,浅色走 Sonner 默认样式 */
|
||||
const rescanErrorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
|
||||
function SkillSettingsList({ onClose }: { onClose?: () => void }) {
|
||||
const { t } = useI18n();
|
||||
const { theme } = useTheme();
|
||||
const router = useRouter();
|
||||
const [filter, setFilter] = useState<string>("mine");
|
||||
const [installOpen, setInstallOpen] = useState(false);
|
||||
@@ -174,13 +210,21 @@ function SkillSettingsList({ onClose }: { onClose?: () => void }) {
|
||||
"{count}",
|
||||
String(result.adopted.length),
|
||||
),
|
||||
theme === "dark" ? rescanSuccessToastDarkOptions : undefined,
|
||||
);
|
||||
} else {
|
||||
toast.info(t.settings.skills.rescanSkillsNoop);
|
||||
toast.info(
|
||||
t.settings.skills.rescanSkillsNoop,
|
||||
theme === "dark" ? rescanInfoToastDarkOptions : undefined,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : String(err),
|
||||
theme === "dark" ? rescanErrorToastDarkOptions : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
@@ -398,7 +442,7 @@ export function SkillRow({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Item className="w-full" variant="outline">
|
||||
<Item className="w-full dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white" variant="outline">
|
||||
<ItemContent
|
||||
className="cursor-pointer"
|
||||
onClick={() => setDetailOpen(true)}
|
||||
|
||||
@@ -425,10 +425,15 @@ function ConfigCard() {
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="max-w-[20rem]">
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
"max-w-[20rem]",
|
||||
"dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:data-[placeholder]:text-[#566B7F] dark:hover:bg-[#0D2D54]",
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value={DEFAULT_MODEL_VALUE}>
|
||||
{tt.curatorModelDefault}
|
||||
</SelectItem>
|
||||
|
||||
@@ -9,16 +9,16 @@ function DraftWaitingPlaceholder() {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center gap-5 select-none">
|
||||
<div className="relative">
|
||||
<div className="w-16 h-16 rounded-2xl bg-muted/60 flex items-center justify-center">
|
||||
<FileText className="w-8 h-8 text-muted-foreground/40" />
|
||||
<div className="w-16 h-16 rounded-2xl bg-muted/60 flex items-center justify-center dark:bg-[#022248] ">
|
||||
<FileText className="w-8 h-8 text-muted-foreground/40 dark:text-[#55A6FE] " />
|
||||
</div>
|
||||
<div className="absolute -bottom-1.5 -right-1.5 w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<PenLine className="w-3.5 h-3.5 text-primary/60" />
|
||||
<div className="absolute -bottom-1.5 -right-1.5 w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center dark:bg-[#102D5C] ">
|
||||
<PenLine className="w-3.5 h-3.5 text-primary/60 dark:text-[#2580FF] " />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center space-y-1.5 max-w-[200px]">
|
||||
<p className="text-sm font-medium text-foreground/60">草稿将在此处显示</p>
|
||||
<p className="text-[11px] text-muted-foreground leading-relaxed">
|
||||
<p className="text-sm font-medium text-foreground/60 dark:text-[#AACEFF]">草稿将在此处显示</p>
|
||||
<p className="text-[11px] text-muted-foreground leading-relaxed dark:text-[#55A6FE]">
|
||||
完成写作流程后,AI 生成的内容将实时出现在这里
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,9 @@ interface AIWritingFormProps {
|
||||
const INPUT_CLASS =
|
||||
'flex h-12 w-full rounded-xl border border-[#e8dedd] bg-white px-4 py-2 text-sm shadow-sm ' +
|
||||
'placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 ' +
|
||||
'focus-visible:ring-ring disabled:opacity-50';
|
||||
'focus-visible:ring-ring disabled:opacity-50 ' +
|
||||
'dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] ' +
|
||||
'dark:[color-scheme:dark] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30';
|
||||
|
||||
const DEFAULT_KEYWORD_COUNT = 1;
|
||||
const DEFAULT_AUDIENCE = '普通读者';
|
||||
@@ -127,16 +129,18 @@ export function AIWritingForm({
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4 px-4 py-4">
|
||||
<div className="rounded-2xl border border-[#ebe3e2] bg-[#fcf8f8] p-4">
|
||||
<div className="flex items-center gap-2 border-b border-[#ece5e4] pb-3">
|
||||
<PenLine className="size-4 text-[#8b6f6f]" />
|
||||
<h3 className="text-[18px] font-semibold leading-none text-[#2f2f2f]">写作配置选项</h3>
|
||||
<div className="rounded-2xl border border-[#ebe3e2] bg-[#fcf8f8] p-4 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white [&_h3]:dark:text-white [&_label]:dark:text-white">
|
||||
<div className="flex items-center gap-2 border-b border-[#ece5e4] pb-3 dark:border-[#213D5E]">
|
||||
<PenLine className="size-4 text-[#8b6f6f] dark:text-white" />
|
||||
<h3 className="text-[18px] font-semibold leading-none text-[#2f2f2f] dark:text-white">
|
||||
写作配置选项
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className={FIELD_LABEL_CLASS}>写作意图</label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-muted-foreground dark:text-white/70">
|
||||
{intent.length} / {MAX_INTENT_LENGTH} 字
|
||||
</span>
|
||||
</div>
|
||||
@@ -162,7 +166,7 @@ export function AIWritingForm({
|
||||
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
|
||||
<SelectValue placeholder={typesLoading ? '加载中…' : '选择类型'} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
{articleTypes.map((t) => (
|
||||
<SelectItem key={t.key} value={t.key} className="text-sm">
|
||||
{t.label}
|
||||
@@ -229,7 +233,7 @@ export function AIWritingForm({
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
{models.map((m) => (
|
||||
<SelectItem key={m.name} value={m.name} className="text-sm">
|
||||
{m.display_name || m.name}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { BotIcon, RefreshCwIcon, SearchIcon } from "lucide-react";
|
||||
import { AlertCircle, BotIcon, CheckCircle2, RefreshCwIcon, SearchIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -22,9 +22,37 @@ import {
|
||||
import { AgentCard } from "@/components/workspace/agents/agent-card";
|
||||
import { useAdminSyncAgentSkills, useAllAgentsAdmin } from "@/core/agents";
|
||||
import type { AgentSkillsSyncResult } from "@/core/agents/api";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
|
||||
export default function AdminAgentsPage() {
|
||||
const { theme } = useTheme();
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
const [search, setSearch] = useState("");
|
||||
const { agents, isLoading } = useAllAgentsAdmin(true);
|
||||
const syncSkills = useAdminSyncAgentSkills();
|
||||
@@ -39,14 +67,21 @@ export default function AdminAgentsPage() {
|
||||
setSyncResult(result);
|
||||
setSyncDialogOpen(true);
|
||||
if (result.modified === 0) {
|
||||
toast.success("所有智能体的技能均有效,无需清理");
|
||||
toast.success(
|
||||
"所有智能体的技能均有效,无需清理",
|
||||
darkToastOpts.success,
|
||||
);
|
||||
} else {
|
||||
toast.success(
|
||||
`已清理 ${result.modified} 个智能体中的失效技能引用`,
|
||||
darkToastOpts.success,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : String(err),
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { toast } from "sonner";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -70,6 +71,7 @@ const emptyForm = {
|
||||
};
|
||||
|
||||
export default function AdminScheduledTasksPage() {
|
||||
const { theme } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
// ?task= carries the task to re-select when returning from a run detail.
|
||||
@@ -227,7 +229,10 @@ export default function AdminScheduledTasksPage() {
|
||||
onChange={(event) => setSearchInput(event.target.value)}
|
||||
placeholder="按任务名称搜索"
|
||||
/>
|
||||
<Button type="submit" variant="outline">
|
||||
<Button
|
||||
type="submit"
|
||||
variant={theme === "dark" ? "default" : "outline"}
|
||||
>
|
||||
<Search className="size-4" />
|
||||
搜索
|
||||
</Button>
|
||||
|
||||
@@ -84,10 +84,10 @@ export default function AdminSkillsPage() {
|
||||
)}
|
||||
{!isLoading && !error && filteredSkills.length === 0 && (
|
||||
<div className="flex h-64 flex-col items-center justify-center gap-3 text-center">
|
||||
<div className="bg-muted flex h-14 w-14 items-center justify-center rounded-full">
|
||||
<SparklesIcon className="text-muted-foreground h-7 w-7" />
|
||||
<div className="bg-muted flex h-14 w-14 items-center justify-center rounded-full dark:bg-[#022248]">
|
||||
<SparklesIcon className="text-muted-foreground h-7 w-7 dark:text-[#AACEFF]" />
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-muted-foreground text-sm dark:text-[#AACEFF]">
|
||||
当前没有可管理的技能。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Download, Loader2 } from "lucide-react";
|
||||
import { AlertCircle,CheckCircle2, Download, Loader2 } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@/core/llm-metrics/api";
|
||||
import { useLlmMetrics } from "@/core/llm-metrics/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
@@ -74,6 +75,7 @@ function speedColor(tps?: number | null): string {
|
||||
|
||||
/** The original LLM-call metrics view, now one tab of the metrics page. */
|
||||
function LlmMetricsPanel() {
|
||||
const { theme } = useTheme();
|
||||
const [page, setPage] = useState(0);
|
||||
const [model, setModel] = useState("");
|
||||
const [user, setUser] = useState("");
|
||||
@@ -81,6 +83,31 @@ function LlmMetricsPanel() {
|
||||
const [since, setSince] = useState("");
|
||||
const [until, setUntil] = useState("");
|
||||
const [exporting, setExporting] = useState(false);
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
|
||||
const filters = useMemo<LlmMetricsFilters>(() => {
|
||||
const out: LlmMetricsFilters = {};
|
||||
@@ -103,9 +130,9 @@ function LlmMetricsPanel() {
|
||||
setExporting(true);
|
||||
try {
|
||||
await downloadLlmMetricsExcel(filters);
|
||||
toast.success("已导出为 Excel");
|
||||
toast.success("已导出为 Excel",darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "导出失败");
|
||||
toast.error(e instanceof Error ? e.message : "导出失败",darkToastOpts.error);
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
@@ -267,9 +294,9 @@ function LlmMetricsPanel() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">模型</label>
|
||||
<div className="grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-5 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="flex flex-col gap-1 ">
|
||||
<label className="text-muted-foreground text-xs dark:text-white">模型</label>
|
||||
<Input
|
||||
value={model}
|
||||
placeholder="按模型名筛选"
|
||||
@@ -280,7 +307,7 @@ function LlmMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">用户名</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">用户名</label>
|
||||
<Input
|
||||
value={user}
|
||||
placeholder="按用户名匹配(@ 前部分)"
|
||||
@@ -291,7 +318,7 @@ function LlmMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">状态</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">状态</label>
|
||||
<Select
|
||||
value={status}
|
||||
onValueChange={(value) => {
|
||||
@@ -299,10 +326,10 @@ function LlmMetricsPanel() {
|
||||
setPage(0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="w-full dark:bg-[#0D2D54] dark:hover:bg-[#0D2D54]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="success">成功</SelectItem>
|
||||
<SelectItem value="error">失败</SelectItem>
|
||||
@@ -310,7 +337,7 @@ function LlmMetricsPanel() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">开始时间</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">开始时间</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={since}
|
||||
@@ -321,7 +348,7 @@ function LlmMetricsPanel() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-muted-foreground text-xs">结束时间</label>
|
||||
<label className="text-muted-foreground text-xs dark:text-white">结束时间</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={until}
|
||||
@@ -338,10 +365,10 @@ function LlmMetricsPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-white">
|
||||
<div className="rounded-xl border bg-white dark:border-[#213D5E] dark:bg-[#02244C] ">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[1080px] text-sm">
|
||||
<thead className="bg-muted/60 text-muted-foreground">
|
||||
<thead className="bg-muted/60 text-muted-foreground dark:text-white">
|
||||
<tr>
|
||||
{visibleColumns.map((col) => (
|
||||
<Th
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2Icon, ShieldIcon } from "lucide-react";
|
||||
import { AlertCircle,CheckCircle2, Loader2Icon, ShieldIcon } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -12,13 +13,36 @@ import {
|
||||
useUpdatePrivateSquareSettings,
|
||||
} from "@/core/system-settings/hooks";
|
||||
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
|
||||
export default function PrivateSquareSettingsPage() {
|
||||
const { data, isLoading, error } = usePrivateSquareStatus();
|
||||
const update = useUpdatePrivateSquareSettings();
|
||||
|
||||
const { theme } = useTheme();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
|
||||
// The status response never contains the password itself, so re-syncing
|
||||
// ``enabled`` from a refetch wouldn't clobber any in-progress edit — but we
|
||||
// still gate the seed on a one-shot ref to match the prompt-prefix page's
|
||||
@@ -31,10 +55,14 @@ export default function PrivateSquareSettingsPage() {
|
||||
setEnabled(data.enabled);
|
||||
initializedRef.current = true;
|
||||
}, [data]);
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
|
||||
const handleSave = async () => {
|
||||
if (password && password !== confirm) {
|
||||
toast.error("两次输入的密码不一致");
|
||||
toast.error("两次输入的密码不一致", darkToastOpts.error);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -48,9 +76,9 @@ export default function PrivateSquareSettingsPage() {
|
||||
});
|
||||
setPassword("");
|
||||
setConfirm("");
|
||||
toast.success("已保存");
|
||||
toast.success("已保存",darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "保存失败");
|
||||
toast.error(e instanceof Error ? e.message : "保存失败",darkToastOpts.error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,9 +88,9 @@ export default function PrivateSquareSettingsPage() {
|
||||
}
|
||||
try {
|
||||
await update.mutateAsync({ password: "" });
|
||||
toast.success("已清除密码");
|
||||
toast.success("已清除密码",darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "清除失败");
|
||||
toast.error(e instanceof Error ? e.message : "清除失败", darkToastOpts.error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,7 +113,7 @@ export default function PrivateSquareSettingsPage() {
|
||||
加载失败:{error.message}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-5 rounded-xl border bg-white px-5 py-5">
|
||||
<div className="flex flex-col gap-5 rounded-xl border bg-white px-5 py-5 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">启用私密广场</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2Icon } from "lucide-react";
|
||||
import { Loader2Icon,AlertCircle,CheckCircle2, } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -12,11 +12,33 @@ import {
|
||||
usePromptPrefixSettings,
|
||||
useUpdatePromptPrefixSettings,
|
||||
} from "@/core/system-settings/hooks";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
export default function PromptPrefixSettingsPage() {
|
||||
const { data, isLoading, error } = usePromptPrefixSettings();
|
||||
const update = useUpdatePromptPrefixSettings();
|
||||
|
||||
const { theme } = useTheme();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [switchLabel, setSwitchLabel] = useState("");
|
||||
const [promptPrefix, setPromptPrefix] = useState("");
|
||||
@@ -34,13 +56,18 @@ export default function PromptPrefixSettingsPage() {
|
||||
initializedRef.current = true;
|
||||
}, [data]);
|
||||
|
||||
const handleSave = async () => {
|
||||
const handleSave = async () => {
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
|
||||
if (enabled && !switchLabel.trim()) {
|
||||
toast.error("开启时必须填写开关名称");
|
||||
toast.error("开启时必须填写开关名称",darkToastOpts.error);
|
||||
return;
|
||||
}
|
||||
if (enabled && !promptPrefix.trim()) {
|
||||
toast.error("开启时必须填写拼接的前缀内容");
|
||||
toast.error("开启时必须填写拼接的前缀内容",darkToastOpts.error);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -49,9 +76,9 @@ export default function PromptPrefixSettingsPage() {
|
||||
switch_label: switchLabel,
|
||||
prompt_prefix: promptPrefix,
|
||||
});
|
||||
toast.success("已保存");
|
||||
toast.success("已保存",darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "保存失败");
|
||||
toast.error(e instanceof Error ? e.message : "保存失败",darkToastOpts.error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,7 +101,7 @@ export default function PromptPrefixSettingsPage() {
|
||||
加载失败:{error.message}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-5 rounded-xl border bg-white px-5 py-5">
|
||||
<div className="flex flex-col gap-5 rounded-xl border bg-white px-5 py-5 dark:border-[#213D5E] dark:bg-[#02244C] dark:text-white">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">向用户暴露开关</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
GripVerticalIcon,
|
||||
Loader2Icon,
|
||||
PencilIcon,
|
||||
@@ -11,6 +13,7 @@ import {
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -34,7 +37,30 @@ type DialogState =
|
||||
| { mode: "edit"; question: RecommendedQuestion }
|
||||
| null;
|
||||
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
|
||||
export default function RecommendedQuestionsPage() {
|
||||
const { theme } = useTheme();
|
||||
const { questions, isLoading, error } = useRecommendedQuestions();
|
||||
const createMutation = useCreateRecommendedQuestion();
|
||||
const updateMutation = useUpdateRecommendedQuestion();
|
||||
@@ -70,7 +96,7 @@ export default function RecommendedQuestionsPage() {
|
||||
const title = formTitle.trim();
|
||||
const content = formContent.trim();
|
||||
if (!title || !content) {
|
||||
toast.error("标题和内容不能为空");
|
||||
toast.error("标题和内容不能为空", darkToastOpts.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,17 +107,20 @@ export default function RecommendedQuestionsPage() {
|
||||
content,
|
||||
sort_order: questions.length,
|
||||
});
|
||||
toast.success("问题已添加");
|
||||
toast.success("问题已添加", darkToastOpts.success);
|
||||
} else if (dialog?.mode === "edit") {
|
||||
await updateMutation.mutateAsync({
|
||||
id: dialog.question.id,
|
||||
body: { title, content },
|
||||
});
|
||||
toast.success("问题已更新");
|
||||
toast.success("问题已更新", darkToastOpts.success);
|
||||
}
|
||||
closeDialog();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "操作失败");
|
||||
toast.error(
|
||||
e instanceof Error ? e.message : "操作失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +128,12 @@ export default function RecommendedQuestionsPage() {
|
||||
try {
|
||||
await deleteMutation.mutateAsync(q.id);
|
||||
setDeleteTarget(null);
|
||||
toast.success("问题已删除");
|
||||
toast.success("问题已删除", darkToastOpts.success);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "删除失败");
|
||||
toast.error(
|
||||
e instanceof Error ? e.message : "删除失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +160,7 @@ export default function RecommendedQuestionsPage() {
|
||||
try {
|
||||
await reorderMutation.mutateAsync(next.map((q) => q.id));
|
||||
} catch {
|
||||
toast.error("排序保存失败");
|
||||
toast.error("排序保存失败", darkToastOpts.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +170,11 @@ export default function RecommendedQuestionsPage() {
|
||||
deleteMutation.isPending ||
|
||||
reorderMutation.isPending;
|
||||
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-col gap-6 px-6 py-10">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -162,15 +199,15 @@ export default function RecommendedQuestionsPage() {
|
||||
<p>加载失败:{error.message}</p>
|
||||
</div>
|
||||
) : questions.length === 0 ? (
|
||||
<div className="text-muted-foreground flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-sm">
|
||||
<div className="text-muted-foreground flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-sm dark:text-[#7FACF2] dark:!border-[#213F62]">
|
||||
<p>暂无推荐问题</p>
|
||||
<Button variant="outline" size="sm" onClick={openAdd}>
|
||||
<Button size="sm" onClick={openAdd}>
|
||||
<PlusIcon className="mr-1.5 size-4" />
|
||||
添加第一个问题
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 ">
|
||||
{questions.map((q, index) => (
|
||||
<div
|
||||
key={q.id}
|
||||
@@ -182,18 +219,18 @@ export default function RecommendedQuestionsPage() {
|
||||
setDragIndex(null);
|
||||
setDragOverIndex(null);
|
||||
}}
|
||||
className={`flex items-start gap-3 rounded-xl border bg-white px-4 py-3.5 transition-colors ${
|
||||
className={`flex items-start gap-3 rounded-xl border bg-white px-4 py-3.5 transition-colors dark:bg-[#022248] dark:!border-[#213D5E] ${
|
||||
dragOverIndex === index && dragIndex !== index
|
||||
? "border-blue-400 bg-blue-50"
|
||||
? "border-blue-400 bg-blue-50 dark:border-[#2580FF] dark:bg-[#0D2D54]"
|
||||
: "border-[#e4e4e7]"
|
||||
}`}
|
||||
>
|
||||
<GripVerticalIcon className="text-muted-foreground/40 mt-0.5 size-4 shrink-0 cursor-grab active:cursor-grabbing" />
|
||||
<GripVerticalIcon className="text-muted-foreground/40 mt-0.5 size-4 shrink-0 cursor-grab active:cursor-grabbing dark:text-[#566B7F]" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm font-medium text-[#18181b]">
|
||||
<div className="truncate text-sm font-medium text-[#18181b] dark:text-white">
|
||||
{q.title}
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-0.5 line-clamp-2 text-xs">
|
||||
<div className="text-muted-foreground mt-0.5 line-clamp-2 text-xs dark:text-[#7FACF2]">
|
||||
{q.content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AlertCircle,
|
||||
BellOff,
|
||||
BellRing,
|
||||
CheckCircle2,
|
||||
Download,
|
||||
Edit3,
|
||||
Eye,
|
||||
@@ -62,9 +64,32 @@ import {
|
||||
statusLabel,
|
||||
statusVariant,
|
||||
} from "@/components/workspace/scheduled-tasks/schedule-builder";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/** 成功 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const successToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#106455] !bg-[#052934] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <CheckCircle2 className="size-4 shrink-0 text-[#2FD69A]" />,
|
||||
} as const;
|
||||
|
||||
/** 失败 toast:仅暗色模式,浅色走 Sonner 默认 */
|
||||
const errorToastDarkOptions = {
|
||||
classNames: {
|
||||
toast:
|
||||
"!rounded-[2px] !border !border-[#7F1A2A] !bg-[#311629] !text-[#FFFFFF] shadow-md",
|
||||
title: "!text-[#FFFFFF]",
|
||||
description: "!text-[#FFFFFF]",
|
||||
},
|
||||
icon: <AlertCircle className="size-4 shrink-0 text-[#F5222D]" />,
|
||||
} as const;
|
||||
|
||||
function formatDate(value?: string | null) {
|
||||
return value ? new Date(value).toLocaleString() : "暂无";
|
||||
}
|
||||
@@ -88,6 +113,11 @@ const emptyForm = {
|
||||
};
|
||||
|
||||
export default function ScheduledTasksPage() {
|
||||
const { theme } = useTheme();
|
||||
const darkToastOpts =
|
||||
theme === "dark"
|
||||
? { success: successToastDarkOptions, error: errorToastDarkOptions }
|
||||
: { success: undefined, error: undefined };
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
// When returning from a run-detail page the task id is carried in ?task=,
|
||||
@@ -207,7 +237,7 @@ export default function ScheduledTasksPage() {
|
||||
require_markdown: form.require_markdown,
|
||||
});
|
||||
setSelectedTaskId(task.task_id);
|
||||
toast.success("任务已创建");
|
||||
toast.success("任务已创建", darkToastOpts.success);
|
||||
} else if (editing) {
|
||||
await actions.update.mutateAsync({
|
||||
taskId: editing.task_id,
|
||||
@@ -221,11 +251,14 @@ export default function ScheduledTasksPage() {
|
||||
require_markdown: form.require_markdown,
|
||||
},
|
||||
});
|
||||
toast.success("任务已更新");
|
||||
toast.success("任务已更新", darkToastOpts.success);
|
||||
}
|
||||
closeDialog();
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "保存失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "保存失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,16 +266,22 @@ export default function ScheduledTasksPage() {
|
||||
try {
|
||||
await downloadScheduledTaskRun(runId);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "下载失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "下载失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRun = async (runId: string) => {
|
||||
try {
|
||||
await actions.removeRun.mutateAsync(runId);
|
||||
toast.success("执行记录已删除");
|
||||
toast.success("执行记录已删除", darkToastOpts.success);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "删除失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "删除失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,14 +312,18 @@ export default function ScheduledTasksPage() {
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="dark:bg-[#0D2D54] dark:hover:!bg-[#2580FF] dark:hover:text-white"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await actions.updateDeliveryProfile.mutateAsync({
|
||||
element_user_id: elementUserId || null,
|
||||
});
|
||||
toast.success("Element 配置已保存");
|
||||
toast.success("Element 配置已保存", darkToastOpts.success);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "保存失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "保存失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -437,7 +480,7 @@ export default function ScheduledTasksPage() {
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
actions.trigger.mutate(task.task_id);
|
||||
toast.success("已触发执行");
|
||||
toast.success("已触发执行", darkToastOpts.success);
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="size-4" />
|
||||
@@ -451,13 +494,19 @@ export default function ScheduledTasksPage() {
|
||||
try {
|
||||
if (task.published) {
|
||||
await actions.unpublish.mutateAsync(task.task_id);
|
||||
toast.success("已取消发布(已订阅者保留)");
|
||||
toast.success(
|
||||
"已取消发布(已订阅者保留)",
|
||||
darkToastOpts.success,
|
||||
);
|
||||
} else {
|
||||
await actions.publish.mutateAsync(task.task_id);
|
||||
toast.success("已发布到广场");
|
||||
toast.success("已发布到广场", darkToastOpts.success);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "操作失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "操作失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -488,9 +537,15 @@ export default function ScheduledTasksPage() {
|
||||
taskId: task.task_id,
|
||||
notifyElement: !notifyOn,
|
||||
});
|
||||
toast.success(notifyOn ? "已关闭 Element 通知" : "已开启 Element 通知");
|
||||
toast.success(
|
||||
notifyOn ? "已关闭 Element 通知" : "已开启 Element 通知",
|
||||
darkToastOpts.success,
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "操作失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "操作失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -505,9 +560,12 @@ export default function ScheduledTasksPage() {
|
||||
event.stopPropagation();
|
||||
try {
|
||||
await actions.unsubscribe.mutateAsync(task.task_id);
|
||||
toast.success("已取消收藏");
|
||||
toast.success("已取消收藏", darkToastOpts.success);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "操作失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "操作失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -581,7 +639,7 @@ export default function ScheduledTasksPage() {
|
||||
<SelectTrigger className="h-7 w-28 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||
<SelectItem value="all">全部时间</SelectItem>
|
||||
<SelectItem value="today">今天</SelectItem>
|
||||
<SelectItem value="7d">近 7 天</SelectItem>
|
||||
@@ -747,12 +805,15 @@ export default function ScheduledTasksPage() {
|
||||
taskId: subscribeTarget.task_id,
|
||||
notifyElement: subscribeNotify,
|
||||
});
|
||||
toast.success("已收藏到我的任务");
|
||||
toast.success("已收藏到我的任务", darkToastOpts.success);
|
||||
setSubscribeTarget(null);
|
||||
setSubscribeNotify(false);
|
||||
setActiveTab("mine");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "收藏失败");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "收藏失败",
|
||||
darkToastOpts.error,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user