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