style: lint prettier
This commit is contained in:
parent
d82ac30b93
commit
170b5484c9
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { Ticker } from "@tombcato/smart-ticker";
|
||||
import { FilesIcon, ListTodoIcon, XIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
|
@ -22,6 +23,7 @@ import {
|
|||
} from "@/components/workspace/artifacts";
|
||||
import { useThreadChat } from "@/components/workspace/chats";
|
||||
// import { DevTodoList } from "@/components/workspace/dev-todo-list";
|
||||
import { IframeTestPanel } from "@/components/workspace/iframe-test-panel";
|
||||
import { InputBox } from "@/components/workspace/input-box";
|
||||
import { MessageList } from "@/components/workspace/messages";
|
||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
|
|
@ -39,8 +41,7 @@ import { textOfMessage } from "@/core/threads/utils";
|
|||
import { env } from "@/env";
|
||||
import { useSelectedSkillListener } from "@/hooks/use-selected-skill-listener";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { IframeTestPanel } from "@/components/workspace/iframe-test-panel";
|
||||
import { Ticker } from "@tombcato/smart-ticker";
|
||||
|
||||
import "@tombcato/smart-ticker/style.css";
|
||||
import motivationSlogans from "./motivation-slogans.json";
|
||||
|
||||
|
|
@ -489,9 +490,7 @@ export default function ChatPage() {
|
|||
/>
|
||||
) : (
|
||||
<div className="relative flex size-full justify-center px-[20px]">
|
||||
<div className="z-30">
|
||||
|
||||
</div>
|
||||
<div className="z-30"></div>
|
||||
{thread.values.artifacts?.length === 0 ? (
|
||||
<ConversationEmptyState
|
||||
icon={<FilesIcon />}
|
||||
|
|
@ -500,8 +499,8 @@ export default function ChatPage() {
|
|||
/>
|
||||
) : (
|
||||
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center">
|
||||
<header className="shrink-0 flex justify-between items-center border-b ">
|
||||
<h2 className="text-[14px] h-[58px] leading-[58px] font-bold text-[#333333]">
|
||||
<header className="flex shrink-0 items-center justify-between border-b">
|
||||
<h2 className="h-[58px] text-[14px] leading-[58px] font-bold text-[#333333]">
|
||||
<span>{t.common.artifacts}</span>
|
||||
</h2>
|
||||
<Button
|
||||
|
|
@ -655,7 +654,9 @@ export default function ChatPage() {
|
|||
<DevDialogContent>
|
||||
<DevDialogHeader>
|
||||
<DevDialogTitle>
|
||||
⚠️ {selectedSkillError?.title ?? t.chatPage.selectedSkillLoadFailed}
|
||||
⚠️{" "}
|
||||
{selectedSkillError?.title ??
|
||||
t.chatPage.selectedSkillLoadFailed}
|
||||
</DevDialogTitle>
|
||||
</DevDialogHeader>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const Message = ({
|
|||
"group flex w-full flex-col gap-2",
|
||||
from === "user"
|
||||
? cn("is-user ml-auto justify-end", !isFirstInSession && "mt-6")
|
||||
: "is-assistant bg-white rounded-[10px] p-4",
|
||||
: "is-assistant rounded-[10px] bg-white p-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export const ReasoningContent = memo(
|
|||
{...props}
|
||||
>
|
||||
{isStreaming ? (
|
||||
<div className="whitespace-pre-wrap break-words">{children}</div>
|
||||
<div className="break-words whitespace-pre-wrap">{children}</div>
|
||||
) : (
|
||||
<Streamdown
|
||||
isAnimating={false}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||
import * as React from "react";
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function ContextMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
|
||||
}
|
||||
|
||||
function ContextMenuTrigger({
|
||||
|
|
@ -17,7 +17,7 @@ function ContextMenuTrigger({
|
|||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuGroup({
|
||||
|
|
@ -25,7 +25,7 @@ function ContextMenuGroup({
|
|||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuPortal({
|
||||
|
|
@ -33,13 +33,13 @@ function ContextMenuPortal({
|
|||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
|
||||
}
|
||||
|
||||
function ContextMenuRadioGroup({
|
||||
|
|
@ -50,7 +50,7 @@ function ContextMenuRadioGroup({
|
|||
data-slot="context-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
|
|
@ -59,22 +59,22 @@ function ContextMenuSubTrigger({
|
|||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSubContent({
|
||||
|
|
@ -85,12 +85,12 @@ function ContextMenuSubContent({
|
|||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
"bg-popover text-popover-foreground 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuContent({
|
||||
|
|
@ -102,13 +102,13 @@ function ContextMenuContent({
|
|||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-0 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
"bg-popover text-popover-foreground 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-0 shadow-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuItem({
|
||||
|
|
@ -117,8 +117,8 @@ function ContextMenuItem({
|
|||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Item
|
||||
|
|
@ -126,12 +126,12 @@ function ContextMenuItem({
|
|||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
||||
className
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive! relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItem({
|
||||
|
|
@ -144,8 +144,8 @@ function ContextMenuCheckboxItem({
|
|||
<ContextMenuPrimitive.CheckboxItem
|
||||
data-slot="context-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
|
|
@ -157,7 +157,7 @@ function ContextMenuCheckboxItem({
|
|||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuRadioItem({
|
||||
|
|
@ -169,8 +169,8 @@ function ContextMenuRadioItem({
|
|||
<ContextMenuPrimitive.RadioItem
|
||||
data-slot="context-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -181,7 +181,7 @@ function ContextMenuRadioItem({
|
|||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuLabel({
|
||||
|
|
@ -189,19 +189,19 @@ function ContextMenuLabel({
|
|||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
data-slot="context-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium text-foreground data-[inset]:pl-8",
|
||||
className
|
||||
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({
|
||||
|
|
@ -211,10 +211,10 @@ function ContextMenuSeparator({
|
|||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
data-slot="context-menu-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuShortcut({
|
||||
|
|
@ -225,12 +225,12 @@ function ContextMenuShortcut({
|
|||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
@ -249,4 +249,4 @@ export {
|
|||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ export function DropdownSelector<T extends string>({
|
|||
>
|
||||
{/* {truncateMiddle(option.label,50)} */}
|
||||
{truncateMiddle("hfiqwertyuiopasdfghjklxcvbnm.html", 20)}
|
||||
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
import * as React from "react";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
|
|
@ -20,8 +20,8 @@ function Slider({
|
|||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
[value, defaultValue, min, max]
|
||||
)
|
||||
[value, defaultValue, min, max],
|
||||
);
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
|
|
@ -32,20 +32,20 @@ function Slider({
|
|||
max={max}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
className={cn(
|
||||
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
||||
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
|
||||
)}
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className={cn(
|
||||
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
||||
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
||||
)}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
|
|
@ -53,11 +53,11 @@ function Slider({
|
|||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Slider }
|
||||
export { Slider };
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ExcelJS from "exceljs";
|
||||
import JSZip from "jszip";
|
||||
import {
|
||||
DownloadIcon,
|
||||
|
|
@ -17,7 +18,6 @@ import {
|
|||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Streamdown } from "streamdown";
|
||||
import ExcelJS from "exceljs";
|
||||
|
||||
import {
|
||||
Artifact,
|
||||
|
|
@ -73,13 +73,11 @@ let revoGridLoaderPromise: Promise<void> | null = null;
|
|||
function ensureRevoGridDefined() {
|
||||
if (typeof window === "undefined") return Promise.resolve();
|
||||
if (window.customElements.get("revo-grid")) return Promise.resolve();
|
||||
if (!revoGridLoaderPromise) {
|
||||
revoGridLoaderPromise = import("@revolist/revogrid/loader").then(
|
||||
revoGridLoaderPromise ??= import("@revolist/revogrid/loader").then(
|
||||
({ defineCustomElements }) => {
|
||||
defineCustomElements(window);
|
||||
},
|
||||
);
|
||||
}
|
||||
return revoGridLoaderPromise;
|
||||
}
|
||||
|
||||
|
|
@ -99,18 +97,45 @@ function toGridCellText(cell: ExcelJS.Cell): string {
|
|||
const value = cell.value;
|
||||
if (value == null) return "";
|
||||
if (value instanceof Date) return value.toISOString();
|
||||
if (
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ||
|
||||
typeof value === "bigint"
|
||||
) {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
if ("result" in value && value.result != null) {
|
||||
return String(value.result);
|
||||
const result = value.result;
|
||||
if (
|
||||
typeof result === "string" ||
|
||||
typeof result === "number" ||
|
||||
typeof result === "boolean" ||
|
||||
typeof result === "bigint"
|
||||
) {
|
||||
return String(result);
|
||||
}
|
||||
}
|
||||
if ("text" in value && value.text) {
|
||||
return String(value.text);
|
||||
const text = value.text;
|
||||
if (
|
||||
typeof text === "string" ||
|
||||
typeof text === "number" ||
|
||||
typeof text === "boolean" ||
|
||||
typeof text === "bigint"
|
||||
) {
|
||||
return String(text);
|
||||
}
|
||||
}
|
||||
if ("hyperlink" in value && value.hyperlink) {
|
||||
return String(value.hyperlink);
|
||||
const hyperlink = value.hyperlink;
|
||||
if (typeof hyperlink === "string") {
|
||||
return hyperlink;
|
||||
}
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function toRevoGridSheetData(worksheet: ExcelJS.Worksheet): RevoGridSheetData {
|
||||
|
|
@ -469,7 +494,6 @@ export function ArtifactFileDetail({
|
|||
{isWriteFile ? (
|
||||
<div className="w-full overflow-hidden px-2 text-center text-ellipsis whitespace-nowrap">
|
||||
{truncateMiddle(getFileName(filepath), 20)}
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<DropdownSelector
|
||||
|
|
@ -1703,12 +1727,18 @@ export const ArtifactZoomSelector = ({
|
|||
</svg>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={8} className="w-52 p-[20px] ">
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="w-52 p-[20px]"
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{ZOOM_LEVELS[0]}%
|
||||
</span>
|
||||
<span className="text-foreground text-xs font-medium">{value}%</span>
|
||||
<span className="text-foreground text-xs font-medium">
|
||||
{value}%
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
min={0}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,9 @@ const ChatBox: React.FC<{
|
|||
) : (
|
||||
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4 pt-8">
|
||||
<header className="shrink-0">
|
||||
<h2 className="text-lg font-medium">{t.common.artifacts}</h2>
|
||||
<h2 className="text-lg font-medium">
|
||||
{t.common.artifacts}
|
||||
</h2>
|
||||
</header>
|
||||
<main className="min-h-0 grow">
|
||||
<ArtifactFileList
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import type { ChatStatus } from "ai";
|
||||
import {
|
||||
CheckIcon,
|
||||
|
|
@ -15,6 +13,8 @@ import {
|
|||
XIcon,
|
||||
ZapIcon,
|
||||
} from "lucide-react";
|
||||
import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import {
|
||||
useCallback,
|
||||
|
|
@ -26,6 +26,7 @@ import {
|
|||
type KeyboardEvent,
|
||||
type ComponentProps,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
PromptInput,
|
||||
|
|
@ -71,15 +72,14 @@ import { useI18n } from "@/core/i18n/hooks";
|
|||
import type { SelectedSkillPayloadItem } from "@/core/i18n/locales/types";
|
||||
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import type { AgentThreadContext } from "@/core/threads";
|
||||
import {
|
||||
MENTION_REFERENCE_EVENT,
|
||||
type MentionReferenceEventDetail,
|
||||
} from "@/core/threads/reference-events";
|
||||
import type { AgentThreadContext } from "@/core/threads";
|
||||
import { useUploadedFiles } from "@/core/uploads/hooks";
|
||||
import { useIframeSkill } from "@/hooks/use-iframe-skill";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
ModelSelector,
|
||||
|
|
@ -91,12 +91,12 @@ import {
|
|||
ModelSelectorTrigger,
|
||||
} from "../ai-elements/model-selector";
|
||||
import { Suggestion, Suggestions } from "../ai-elements/suggestion";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
|
||||
import { useThread } from "./messages/context";
|
||||
import { ModeHoverGuide } from "./mode-hover-guide";
|
||||
import { Tooltip } from "./tooltip";
|
||||
import { useThread } from "./messages/context";
|
||||
import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
|
||||
|
||||
const MAX_REFERENCES_PER_MESSAGE = 10;
|
||||
|
||||
|
|
@ -373,7 +373,14 @@ export function InputBox({
|
|||
});
|
||||
setReferences([]);
|
||||
},
|
||||
[showWelcomeStyle, onSubmit, onStop, references, status, iframeSkill.selectedSkills],
|
||||
[
|
||||
showWelcomeStyle,
|
||||
onSubmit,
|
||||
onStop,
|
||||
references,
|
||||
status,
|
||||
iframeSkill.selectedSkills,
|
||||
],
|
||||
);
|
||||
|
||||
const requestFormSubmit = useCallback(() => {
|
||||
|
|
@ -381,7 +388,8 @@ export function InputBox({
|
|||
form?.requestSubmit();
|
||||
}, []);
|
||||
|
||||
const addMentionReference = useCallback((reference: PromptInputReference) => {
|
||||
const addMentionReference = useCallback(
|
||||
(reference: PromptInputReference) => {
|
||||
setReferences((prev) => {
|
||||
const exists = prev.some(
|
||||
(item) =>
|
||||
|
|
@ -398,7 +406,9 @@ export function InputBox({
|
|||
}
|
||||
return prev.concat(reference);
|
||||
});
|
||||
}, [t.inputBox.maxReferencesReached]);
|
||||
},
|
||||
[t.inputBox.maxReferencesReached],
|
||||
);
|
||||
|
||||
const selectMentionCandidate = useCallback(
|
||||
(candidate: MentionCandidate) => {
|
||||
|
|
@ -433,7 +443,7 @@ export function InputBox({
|
|||
useEffect(() => {
|
||||
const onMentionReference = (event: Event) => {
|
||||
const detail = (event as CustomEvent<MentionReferenceEventDetail>).detail;
|
||||
if (!detail || detail.threadId !== threadIdFromProps) {
|
||||
if (detail?.threadId !== threadIdFromProps) {
|
||||
return;
|
||||
}
|
||||
addMentionReference({
|
||||
|
|
@ -493,12 +503,13 @@ export function InputBox({
|
|||
}
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
setActiveMentionIndex((prev) =>
|
||||
(prev + 1) % filteredMentionCandidates.length,
|
||||
setActiveMentionIndex(
|
||||
(prev) => (prev + 1) % filteredMentionCandidates.length,
|
||||
);
|
||||
} else if (event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
setActiveMentionIndex((prev) =>
|
||||
setActiveMentionIndex(
|
||||
(prev) =>
|
||||
(prev - 1 + filteredMentionCandidates.length) %
|
||||
filteredMentionCandidates.length,
|
||||
);
|
||||
|
|
@ -693,7 +704,7 @@ export function InputBox({
|
|||
align="start"
|
||||
side="top"
|
||||
sideOffset={8}
|
||||
className="w-[min(32rem,var(--radix-dropdown-menu-trigger-width)+28rem)] max-h-[400px] overflow-y-hidden p-[20px]"
|
||||
className="max-h-[400px] w-[min(32rem,var(--radix-dropdown-menu-trigger-width)+28rem)] overflow-y-hidden p-[20px]"
|
||||
data-testid="mention-candidate-panel"
|
||||
onCloseAutoFocus={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -704,7 +715,7 @@ export function InputBox({
|
|||
{t.inputBox.addReference}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator className="mx-0 mt-[20px] mb-0" />
|
||||
<DropdownMenuGroup className="flex pt-[20px] px-0 max-h-[480px] flex-col gap-[10px]">
|
||||
<DropdownMenuGroup className="flex max-h-[480px] flex-col gap-[10px] px-0 pt-[20px]">
|
||||
<ScrollArea className="h-[480px]" data-state="hidden">
|
||||
{filteredMentionCandidates.map((candidate, index) => {
|
||||
const detail = [candidate.typeLabel, candidate.pathTail]
|
||||
|
|
@ -717,7 +728,9 @@ export function InputBox({
|
|||
"flex items-center justify-between gap-3 rounded-md px-2 py-2 text-left",
|
||||
index === activeMentionIndex && "bg-accent",
|
||||
)}
|
||||
data-active={index === activeMentionIndex ? "true" : "false"}
|
||||
data-active={
|
||||
index === activeMentionIndex ? "true" : "false"
|
||||
}
|
||||
data-candidate-key={candidate.key}
|
||||
data-testid="mention-candidate-item"
|
||||
aria-label={`${candidate.filename} ${candidate.typeLabel}${candidate.pathTail ? ` ${candidate.pathTail}` : ""}`}
|
||||
|
|
@ -781,11 +794,13 @@ export function InputBox({
|
|||
/>
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu> */}
|
||||
{showWelcomeStyle && <HistoryButton
|
||||
{showWelcomeStyle && (
|
||||
<HistoryButton
|
||||
className="px-2!"
|
||||
router={router}
|
||||
threadId={threadIdFromProps}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
<AddAttachmentsButton className="px-2!" />
|
||||
<IframeSkillDialogButton
|
||||
className="px-2!"
|
||||
|
|
@ -1096,7 +1111,6 @@ function HistoryButton({
|
|||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
@ -1205,7 +1219,10 @@ function AttachmentPreviewBar({
|
|||
</PromptInputAttachments>
|
||||
)}
|
||||
{hasReferences && (
|
||||
<div className="inline-flex flex-row flex-wrap items-center gap-2 rounded-xl p-2" data-testid="reference-inline-preview">
|
||||
<div
|
||||
className="inline-flex flex-row flex-wrap items-center gap-2 rounded-xl p-2"
|
||||
data-testid="reference-inline-preview"
|
||||
>
|
||||
{references.map((reference) => {
|
||||
const referenceUrl =
|
||||
threadId && reference.path
|
||||
|
|
@ -1215,7 +1232,7 @@ function AttachmentPreviewBar({
|
|||
})
|
||||
: null;
|
||||
const filename = reference.filename ?? "reference";
|
||||
const imageMatch = filename.match(/\.(png|jpe?g|gif|webp|bmp|svg)$/i);
|
||||
const imageMatch = /\.(png|jpe?g|gif|webp|bmp|svg)$/i.exec(filename);
|
||||
const extension = imageMatch?.[1]?.toLowerCase();
|
||||
const mediaType = extension
|
||||
? extension === "jpg"
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ function parseTableData(table: HTMLTableElement): TableData {
|
|||
(cell.textContent ?? "").trim(),
|
||||
);
|
||||
const rows = Array.from(table.querySelectorAll("tbody tr")).map((row) =>
|
||||
Array.from(row.querySelectorAll("td")).map(
|
||||
(cell) => (cell.textContent ?? "").trim(),
|
||||
Array.from(row.querySelectorAll("td")).map((cell) =>
|
||||
(cell.textContent ?? "").trim(),
|
||||
),
|
||||
);
|
||||
return { headers, rows };
|
||||
|
|
@ -70,7 +70,9 @@ function MarkdownTable({
|
|||
|
||||
const handleCopy = useCallback(
|
||||
async (event: MouseEvent<HTMLButtonElement>) => {
|
||||
const wrapper = event.currentTarget.closest('[data-streamdown="table-wrapper"]');
|
||||
const wrapper = event.currentTarget.closest(
|
||||
'[data-streamdown="table-wrapper"]',
|
||||
);
|
||||
const table = wrapper?.querySelector("table");
|
||||
if (!(table instanceof HTMLTableElement)) return;
|
||||
|
||||
|
|
@ -89,10 +91,13 @@ function MarkdownTable({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="my-4 flex flex-col space-y-2" data-streamdown="table-wrapper">
|
||||
<div
|
||||
className="my-4 flex flex-col space-y-2"
|
||||
data-streamdown="table-wrapper"
|
||||
>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button
|
||||
className="cursor-pointer p-1 text-muted-foreground transition-all hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="text-muted-foreground hover:text-foreground cursor-pointer p-1 transition-all disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isLoading}
|
||||
onClick={handleCopy}
|
||||
title={copyLabel}
|
||||
|
|
@ -103,7 +108,10 @@ function MarkdownTable({
|
|||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table
|
||||
className={cn("w-full border-collapse border border-border", className)}
|
||||
className={cn(
|
||||
"border-border w-full border-collapse border",
|
||||
className,
|
||||
)}
|
||||
data-streamdown="table"
|
||||
{...props}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import { Tooltip } from "../tooltip";
|
|||
|
||||
import { MarkdownContent } from "./markdown-content";
|
||||
|
||||
|
||||
export function MessageGroup({
|
||||
className,
|
||||
messages,
|
||||
|
|
@ -87,11 +86,7 @@ export function MessageGroup({
|
|||
const rehypePlugins = useRehypeSplitWordsIntoSpans(false);
|
||||
const thinkingComponents = useMemo(
|
||||
() => ({
|
||||
code: ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<"code">) => {
|
||||
code: ({ className, children, ...props }: ComponentProps<"code">) => {
|
||||
const isBlock =
|
||||
typeof className === "string" && className.includes("language-");
|
||||
if (!isBlock) {
|
||||
|
|
@ -126,7 +121,7 @@ export function MessageGroup({
|
|||
<Button
|
||||
key="above"
|
||||
// 等宋
|
||||
className="w-full items-start justify-start text-left h-auto! py-4"
|
||||
className="h-auto! w-full items-start justify-start py-4 text-left"
|
||||
variant="ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ import {
|
|||
stripUploadedFilesTag,
|
||||
type FileInMessage,
|
||||
} from "@/core/messages/utils";
|
||||
import { dispatchMentionReference } from "@/core/threads/reference-events";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { materializeSkillYaml } from "@/core/skills";
|
||||
import { humanMessagePlugins } from "@/core/streamdown";
|
||||
import { dispatchMentionReference } from "@/core/threads/reference-events";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { CopyButton } from "../copy-button";
|
||||
|
|
|
|||
|
|
@ -261,7 +261,8 @@ export const zhCN: Translations = {
|
|||
noArtifactSelectedTitle: "未选择生成文件",
|
||||
noArtifactSelectedDescription: "请选择一个生成文件以查看详情",
|
||||
exitDialogTitle: "提示",
|
||||
exitDialogDescription: "历史记录每七天自动删除,现在将返回欢迎页,是否继续?",
|
||||
exitDialogDescription:
|
||||
"历史记录每七天自动删除,现在将返回欢迎页,是否继续?",
|
||||
exitDialogConfirm: "确定",
|
||||
selectedSkillLoadFailed: "技能加载失败",
|
||||
unknownErrorRetry: "发生了未知错误,请稍后重试。",
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import type { UploadedFileInfo } from "../uploads";
|
|||
import { listUploadedFiles, uploadFiles } from "../uploads";
|
||||
import type { UploadTarget } from "../uploads/api";
|
||||
|
||||
import { buildFilesForSubmit } from "./submit-files";
|
||||
import { buildPriorityHintText, composeSubmitText } from "./priority-hint";
|
||||
import { buildFilesForSubmit } from "./submit-files";
|
||||
import type {
|
||||
AgentThread,
|
||||
AgentThreadContext,
|
||||
|
|
@ -268,8 +268,7 @@ export function useThreadStream({
|
|||
const now = Date.now();
|
||||
const lastToast = lastErrorToastRef.current;
|
||||
if (
|
||||
lastToast &&
|
||||
lastToast.message === message &&
|
||||
lastToast?.message === message &&
|
||||
now - lastToast.timestamp < STREAM_ERROR_TOAST_DEDUPE_WINDOW_MS
|
||||
) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
|||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import {
|
||||
POST_MESSAGE_TYPES,
|
||||
RECEIVE_MESSAGE_TYPES,
|
||||
|
|
@ -10,7 +11,6 @@ import {
|
|||
type SelectedSkillPayloadItem,
|
||||
sendToParent,
|
||||
} from "@/core/iframe-messages";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { bootstrapRemoteSkill } from "@/core/skills/api";
|
||||
|
||||
// Skill 数据类型
|
||||
|
|
@ -39,8 +39,20 @@ function parseStoredSkills(raw: string | null): SkillData[] {
|
|||
.map((item) => {
|
||||
if (typeof item !== "object" || item === null) return null;
|
||||
const record = item as Record<string, unknown>;
|
||||
const skillId = String(record.skill_id ?? "").trim();
|
||||
const title = String(record.title ?? "").trim();
|
||||
const rawSkillId = record.skill_id;
|
||||
const skillId =
|
||||
typeof rawSkillId === "string"
|
||||
? rawSkillId.trim()
|
||||
: typeof rawSkillId === "number"
|
||||
? String(rawSkillId)
|
||||
: "";
|
||||
const rawTitle = record.title;
|
||||
const title =
|
||||
typeof rawTitle === "string"
|
||||
? rawTitle.trim()
|
||||
: typeof rawTitle === "number"
|
||||
? String(rawTitle)
|
||||
: "";
|
||||
if (!skillId || !title) return null;
|
||||
return { skill_id: skillId, title };
|
||||
})
|
||||
|
|
@ -84,7 +96,11 @@ export function useIframeSkill(
|
|||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const threadIdFromQuery = searchParams.get("thread_id");
|
||||
const threadId = options?.threadId?.trim() || threadIdFromQuery;
|
||||
const threadIdFromOptions = options?.threadId?.trim();
|
||||
const threadId =
|
||||
threadIdFromOptions && threadIdFromOptions.length > 0
|
||||
? threadIdFromOptions
|
||||
: threadIdFromQuery;
|
||||
const isChattingFromQuery = searchParams.get("is_chatting");
|
||||
const lastThreadIdRef = useRef<string | null>(null);
|
||||
|
||||
|
|
@ -316,7 +332,8 @@ export function useIframeSkill(
|
|||
setSelectedSkills(normalizedSkills);
|
||||
|
||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||
description: result.message || t.skills.createdFiles(result.created_files),
|
||||
description:
|
||||
result.message || t.skills.createdFiles(result.created_files),
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
@ -325,7 +342,9 @@ export function useIframeSkill(
|
|||
removeFailedSkills(failedIds);
|
||||
toast.dismiss("suggest-skill-bootstrap");
|
||||
const message =
|
||||
error instanceof Error ? error.message : t.skills.networkRequestFailed;
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t.skills.networkRequestFailed;
|
||||
toast.error(t.skills.loadFailedWithTitle(title), {
|
||||
description: message,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { useSearchParams } from "next/navigation";
|
|||
import { useEffect, useCallback, useState, useRef } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import {
|
||||
isSelectedSkillMessage,
|
||||
isSelectedSkillsMessage,
|
||||
type SelectedSkillPayloadItem,
|
||||
} from "@/core/iframe-messages";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { bootstrapRemoteSkill } from "@/core/skills/api";
|
||||
|
||||
/** 技能基础数据 */
|
||||
|
|
@ -105,7 +105,8 @@ export function useSelectedSkillListener({
|
|||
if (result.success) {
|
||||
skillBootstrappedKeyRef.current = initKey;
|
||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||
description: result.message || t.skills.createdFiles(result.created_files),
|
||||
description:
|
||||
result.message || t.skills.createdFiles(result.created_files),
|
||||
duration: 4000,
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
|
|
|
|||
|
|
@ -77,27 +77,32 @@
|
|||
"Segoe UI Symbol", "Noto Color Emoji";
|
||||
|
||||
--animate-fade-in: fade-in 1.1s;
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
--animate-fade-in-up: fade-in-up 0.15s ease-in-out forwards;
|
||||
|
||||
@keyframes fade-in-up {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(1rem) scale(1.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
--animate-bouncing: bouncing 0.5s infinite alternate;
|
||||
|
||||
@keyframes bouncing {
|
||||
to {
|
||||
opacity: 0.1;
|
||||
|
|
@ -106,11 +111,13 @@
|
|||
}
|
||||
|
||||
--animate-skeleton-entrance: skeleton-entrance 0.35s ease-out forwards;
|
||||
|
||||
@keyframes skeleton-entrance {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
|
|
@ -118,11 +125,13 @@
|
|||
}
|
||||
|
||||
--animate-suggestion-in: suggestion-in 0.2s ease-out forwards;
|
||||
|
||||
@keyframes suggestion-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-1.25rem);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
|
@ -130,17 +139,21 @@
|
|||
}
|
||||
|
||||
--animate-wave: wave 0.6s ease-in-out 2;
|
||||
|
||||
@keyframes wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
|
@ -188,36 +201,45 @@
|
|||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-tooltip-background: var(--tooltip-background);
|
||||
--animate-aurora: aurora 8s ease-in-out infinite alternate;
|
||||
|
||||
@keyframes aurora {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
transform: rotate(-5deg) scale(0.9);
|
||||
}
|
||||
|
||||
25% {
|
||||
background-position: 50% 100%;
|
||||
transform: rotate(5deg) scale(1.1);
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
transform: rotate(-3deg) scale(0.95);
|
||||
}
|
||||
|
||||
75% {
|
||||
background-position: 50% 0%;
|
||||
transform: rotate(3deg) scale(1.05);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
transform: rotate(-5deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
--animate-shine: shine var(--duration) infinite linear;
|
||||
|
||||
@keyframes shine {
|
||||
0% {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
|
|
@ -308,21 +330,26 @@
|
|||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.container-md {
|
||||
width: 100%;
|
||||
|
||||
@media (width >=40rem) {
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
@media (width >=48rem) {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
@media (width >=64rem) {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
@media (width >=80rem) {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
|
@ -375,9 +402,11 @@
|
|||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 400% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
|
@ -436,6 +465,7 @@ body {
|
|||
p {
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
/* 特别指定,代码块和正文一样的字体 */
|
||||
code,
|
||||
kbd,
|
||||
|
|
@ -444,6 +474,7 @@ pre {
|
|||
font-family:
|
||||
"Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-radius: 5px;
|
||||
padding: 12px 16px;
|
||||
|
|
@ -463,12 +494,14 @@ pre{
|
|||
|
||||
/* 二三级标题 - 16px */
|
||||
[data-streamdown="heading-2"],
|
||||
[data-streamdown="heading-3"],[data-streamdown="heading-4"] {
|
||||
[data-streamdown="heading-3"],
|
||||
[data-streamdown="heading-4"] {
|
||||
font-size: calc(16px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
/* 代码块 - 14px */
|
||||
[data-streamdown="code-block"] pre,code {
|
||||
[data-streamdown="code-block"] pre,
|
||||
code {
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
|
|
@ -485,54 +518,67 @@ pre{
|
|||
font-size: calc(14px * var(--zoom-scale));
|
||||
height: calc(42px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
[data-streamdown="table-header"] {
|
||||
background: #9c9b9b26;
|
||||
height: calc(50px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
[data-streamdown="table-header"] th {
|
||||
text-align: center;
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
[data-slot="hover-card-trigger"] [data-slot="badge"] {
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
|
||||
/* 表格四角圆角:由四个角单元格承担视觉圆角 */
|
||||
[data-streamdown="table-header"] tr:first-child > [data-streamdown="table-header-cell"]:first-child {
|
||||
[data-streamdown="table-header"]
|
||||
tr:first-child
|
||||
> [data-streamdown="table-header-cell"]:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
[data-streamdown="table-header"] tr:first-child > [data-streamdown="table-header-cell"]:last-child {
|
||||
[data-streamdown="table-header"]
|
||||
tr:first-child
|
||||
> [data-streamdown="table-header-cell"]:last-child {
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
[data-streamdown="table-body"] tr:first-child td {
|
||||
line-height: calc(14px * var(--zoom-scale));
|
||||
padding-top: calc(20px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
/* 行分隔线 */
|
||||
[data-streamdown="table-body"] tr{
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
[data-streamdown="table-body"] tr:last-child > [data-streamdown="table-cell"]:first-child {
|
||||
/* [data-streamdown="table-body"] tr {
|
||||
border-bottom: 1px solid black;
|
||||
} */
|
||||
|
||||
[data-streamdown="table-body"]
|
||||
tr:last-child
|
||||
> [data-streamdown="table-cell"]:first-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
[data-streamdown="table-body"] tr:last-child > [data-streamdown="table-cell"]:last-child {
|
||||
[data-streamdown="table-body"]
|
||||
tr:last-child
|
||||
> [data-streamdown="table-cell"]:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
[data-streamdown="table-body"] tr:last-child {
|
||||
padding-top: calc(50px * var(--zoom-scale));
|
||||
|
||||
[data-streamdown="table-body"] tr:last-child td {
|
||||
line-height: calc(14px * var(--zoom-scale));
|
||||
padding-bottom: calc(20px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
[data-streamdown="table-row"] > [data-streamdown="table-cell"] {
|
||||
line-height: 14px;
|
||||
line-height: calc(42px * var(--zoom-scale));
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.cm-line {
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
white-space: pre-wrap;
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export async function rewriteFirstReferenceAsArtifact(
|
|||
return false;
|
||||
}
|
||||
|
||||
let fiber = ((element as unknown as Record<string, unknown>)[fiberKey]) as
|
||||
let fiber = (element as unknown as Record<string, unknown>)[fiberKey] as
|
||||
| {
|
||||
return?: unknown;
|
||||
memoizedState?: unknown;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { newChatEntry, openChat, sendMessage } from "./support/chat-helpers";
|
|||
|
||||
function logProgress(message: string) {
|
||||
const timestamp = new Date().toISOString();
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
console.log(`[DF-SEC][${timestamp}] ${message}`);
|
||||
}
|
||||
|
||||
|
|
@ -21,10 +21,7 @@ function parseForbiddenPrefixes() {
|
|||
return prefixes;
|
||||
}
|
||||
|
||||
async function assertNoForbiddenPrefixOnScreen(
|
||||
page: Page,
|
||||
prefixes: string[],
|
||||
) {
|
||||
async function assertNoForbiddenPrefixOnScreen(page: Page, prefixes: string[]) {
|
||||
if (prefixes.length === 0) return;
|
||||
const leaked = await page.evaluate((items) => {
|
||||
const text = document.body?.innerText ?? "";
|
||||
|
|
@ -64,9 +61,7 @@ async function waitForConditionWithLeakCheck({
|
|||
const now = Date.now();
|
||||
if (now - lastLogAt >= logEveryMs) {
|
||||
lastLogAt = now;
|
||||
logProgress(
|
||||
`${label}… (${Math.round((now - start) / 1000)}s elapsed)`,
|
||||
);
|
||||
logProgress(`${label}… (${Math.round((now - start) / 1000)}s elapsed)`);
|
||||
}
|
||||
}
|
||||
await page.waitForTimeout(stepMs);
|
||||
|
|
@ -113,7 +108,10 @@ async function waitForArtifactCards({
|
|||
label,
|
||||
condition: async () => {
|
||||
// Cards only render when the panel is open. Try to open opportunistically.
|
||||
if ((await fileList.count()) === 0 || !(await fileList.first().isVisible())) {
|
||||
if (
|
||||
(await fileList.count()) === 0 ||
|
||||
!(await fileList.first().isVisible())
|
||||
) {
|
||||
await openArtifactsPanelIfPossible(page);
|
||||
}
|
||||
if ((await cards.count()) < minCount) return false;
|
||||
|
|
@ -169,11 +167,7 @@ async function sendMessageSafely({
|
|||
});
|
||||
await textarea.evaluate((element) => {
|
||||
const target = element as HTMLTextAreaElement;
|
||||
const setter = Object.getOwnPropertyDescriptor(
|
||||
HTMLTextAreaElement.prototype,
|
||||
"value",
|
||||
)?.set;
|
||||
setter?.call(target, "");
|
||||
target.value = "";
|
||||
target.dispatchEvent(new InputEvent("input", { bubbles: true }));
|
||||
});
|
||||
await page.keyboard.insertText(text);
|
||||
|
|
@ -241,7 +235,8 @@ test.describe("安全 / 思考块与敏感信息泄露", () => {
|
|||
timeoutMs: 40_000,
|
||||
label: "Wait for steps signal",
|
||||
condition: async () =>
|
||||
(await stepsSignal.count()) > 0 && (await stepsSignal.first().isVisible()),
|
||||
(await stepsSignal.count()) > 0 &&
|
||||
(await stepsSignal.first().isVisible()),
|
||||
});
|
||||
|
||||
// 按需求:40s 内未出现思考块则中断后续检查(标记为 skip)。
|
||||
|
|
@ -256,9 +251,10 @@ test.describe("安全 / 思考块与敏感信息泄露", () => {
|
|||
minCount: 1,
|
||||
label: "Wait for first artifact card",
|
||||
});
|
||||
expect(firstArtifacts.ok, "未检测到 artifact-file-card,图片可能未生成完成").toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
firstArtifacts.ok,
|
||||
"未检测到 artifact-file-card,图片可能未生成完成",
|
||||
).toBe(true);
|
||||
logProgress(
|
||||
`First artifact ready (count=${await firstArtifacts.cards.count()}).`,
|
||||
);
|
||||
|
|
@ -279,7 +275,10 @@ test.describe("安全 / 思考块与敏感信息泄露", () => {
|
|||
minCount: beforeSecondCount + 1,
|
||||
label: "Wait for second artifact card",
|
||||
});
|
||||
expect(secondArtifacts.ok, "未检测到新的产物生成(artifact 数量未增加)").toBe(true);
|
||||
expect(
|
||||
secondArtifacts.ok,
|
||||
"未检测到新的产物生成(artifact 数量未增加)",
|
||||
).toBe(true);
|
||||
logProgress(
|
||||
`Second artifact ready (count=${await secondArtifacts.cards.count()}).`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue