style: prettier

This commit is contained in:
肖应宇 2026-03-19 17:33:47 +08:00
parent cb0ebf41bb
commit 4df604d491
14 changed files with 371 additions and 291 deletions

View File

@ -25,7 +25,7 @@ export default async function RootLayout({
return ( return (
<html <html
lang={locale} lang={locale}
className={geist.variable+""} className={geist.variable + ""}
suppressContentEditableWarning suppressContentEditableWarning
suppressHydrationWarning suppressHydrationWarning
> >

View File

@ -16,4 +16,4 @@ export default function ChatLayout({
</ArtifactsProvider> </ArtifactsProvider>
</SubtasksProvider> </SubtasksProvider>
); );
} }

View File

@ -252,10 +252,12 @@ export default function ChatPage() {
return ( return (
<ThreadContext.Provider value={{ threadId, thread }}> <ThreadContext.Provider value={{ threadId, thread }}>
<div className={cn( <div
"m-auto flex h-screen min-h-svh overflow-hidden rounded-t-[20px] transition-[width] duration-300 ease-in-out", className={cn(
artifactsOpen ? "w-full" : "w-[50%]", "m-auto flex h-screen min-h-svh overflow-hidden rounded-t-[20px] transition-[width] duration-300 ease-in-out",
)}> artifactsOpen ? "w-full" : "w-[50%]",
)}
>
<div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]"> <div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]">
<div <div
className={cn( className={cn(
@ -264,276 +266,285 @@ export default function ChatPage() {
fullscreen && "hidden", fullscreen && "hidden",
)} )}
> >
<div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]"> <div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]">
<header <header
className={cn(
"bg-background absolute top-0 right-0 left-0 z-30 mx-4 grid h-[58px] shrink-0 grid-cols-3 items-center border-b transition-all duration-300 ease-in-out",
isNewThread && !hasSubmitted ? "hidden" : "",
)}
>
<div className="flex items-center justify-start overflow-hidden text-sm font-medium">
<Button
size="sm"
variant="ghost"
className="px-[10px] py-[5px] text-sm font-medium text-[#150033] hover:text-[#150033]/80"
onClick={() => setShowExitDialog(true)}
>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14"
stroke="#666666"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
</div>
<div className="flex items-center justify-center overflow-hidden text-sm font-bold font-medium whitespace-nowrap text-[#333333]">
{title !== "Untitled" && (
<ThreadTitle threadId={threadId} threadTitle={title} />
)}
</div>
<div className="flex items-center justify-end gap-2 overflow-hidden">
<DevTodoList
className="bg-white"
todos={thread.values.todos ?? []}
hidden={
!thread.values.todos || thread.values.todos.length === 0
}
trigger={
<Button
size="sm"
variant="ghost"
className="h-full px-[10px] py-[5px] text-sm font-medium text-[#150033] hover:text-[#150033]"
>
<ListTodoIcon className="size-4" /> To-dos
</Button>
}
/>
{artifacts?.length > 0 && !artifactsOpen && (
<Tooltip content="点击可查看生成的文件结果">
<Button
className="text-[#150033] hover:text-[#150033]/80"
variant="ghost"
onClick={() => {
setArtifactsOpen(true);
setSidebarOpen(false);
}}
>
<FilesIcon />
{t.common.artifacts}
</Button>
</Tooltip>
)}
</div>
</header>
<main
className={cn(
"flex min-h-0 max-w-full grow flex-col",
isNewThread && !hasSubmitted ? "bg-white" : "bg-background",
)}
>
<div className="flex size-full justify-center">
<MessageList
className={cn(
"size-full",
(!isNewThread || hasSubmitted) && "pt-10",
)}
threadId={threadId}
thread={thread}
suppressThreadLoading={suppressExistingThreadPrefetchUi}
messagesOverride={
suppressExistingThreadPrefetchUi
? []
: !thread.isLoading && finalState?.messages
? (finalState.messages as Message[])
: undefined
}
paddingBottom={todoListCollapsed ? 160 : 280}
/>
</div>
</main>
</div>
</div>
<div
className={cn(
"bg-background ml-[20px] rounded-t-[20px] transition-all duration-300 ease-in-out",
!artifactsOpen && "opacity-0",
artifactPanelOpen
? fullscreen
? "ml-0 w-full"
: "w-[70%]"
: "w-0",
)}
>
<div
className={cn( className={cn(
"bg-background absolute top-0 right-0 left-0 z-30 mx-4 grid h-[58px] shrink-0 grid-cols-3 items-center border-b transition-all duration-300 ease-in-out", "h-full w-full transition-transform duration-300 ease-in-out",
isNewThread && !hasSubmitted ? "hidden" : "", artifactPanelOpen ? "translate-x-0" : "translate-x-full",
)} )}
> >
<div className="flex items-center justify-start overflow-hidden text-sm font-medium"> {selectedArtifact ? (
<Button <ArtifactFileDetail
size="sm" className="size-full"
variant="ghost" filepath={selectedArtifact}
className="px-[10px] py-[5px] text-sm font-medium text-[#150033] hover:text-[#150033]/80" threadId={threadId}
onClick={() => setShowExitDialog(true)}
>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14"
stroke="#666666"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
</div>
<div className="flex items-center justify-center whitespace-nowrap text-[#333333] font-bold overflow-hidden text-sm font-medium">
{title !== "Untitled" && (
<ThreadTitle threadId={threadId} threadTitle={title} />
)}
</div>
<div className="flex items-center justify-end gap-2 overflow-hidden">
<DevTodoList
className="bg-white"
todos={thread.values.todos ?? []}
hidden={
!thread.values.todos || thread.values.todos.length === 0
}
trigger={
<Button
size="sm"
variant="ghost"
className="h-full px-[10px] py-[5px] text-sm font-medium text-[#150033] hover:text-[#150033]"
>
<ListTodoIcon className="size-4" /> To-dos
</Button>
}
/> />
) : (
{artifacts?.length > 0 && !artifactsOpen && ( <div className="relative flex size-full justify-center px-[20px]">
<Tooltip content="点击可查看生成的文件结果"> <div className="absolute top-2 right-2 z-30">
<Button <Button
className="text-[#150033] hover:text-[#150033]/80" size="icon-sm"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
setArtifactsOpen(true); setArtifactsOpen(false);
setSidebarOpen(false);
}} }}
> >
<FilesIcon /> <XIcon />
{t.common.artifacts}
</Button> </Button>
</Tooltip> </div>
)} {thread.values.artifacts?.length === 0 ? (
</div> <ConversationEmptyState
</header> icon={<FilesIcon />}
<main title="No artifact selected"
className={cn( description="Select an artifact to view its details"
"flex min-h-0 max-w-full grow flex-col", />
isNewThread && !hasSubmitted ? "bg-white" : "bg-background", ) : (
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4">
<header className="shrink-0">
<h2 className="text-[14px] font-bold text-[#333333]">
{t.common.artifacts}
</h2>
</header>
<main className="min-h-0 grow">
<ArtifactFileList
className="max-w-(--container-width-sm) p-4 pt-12"
files={thread.values.artifacts ?? []}
threadId={threadId}
/>
</main>
</div>
)}
</div>
)} )}
> </div>
<div className="flex size-full justify-center">
<MessageList
className={cn("size-full", (!isNewThread || hasSubmitted) && "pt-10")}
threadId={threadId}
thread={thread}
suppressThreadLoading={suppressExistingThreadPrefetchUi}
messagesOverride={
suppressExistingThreadPrefetchUi
? []
: !thread.isLoading && finalState?.messages
? (finalState.messages as Message[])
: undefined
}
paddingBottom={todoListCollapsed ? 160 : 280}
/>
</div>
</main>
</div> </div>
</div> </div>
{/* Fixed 底部居中输入框容器 */}
<div <div
className={cn( className={cn(
"bg-background ml-[20px] rounded-t-[20px] transition-all duration-300 ease-in-out", "pointer-events-none fixed right-0 bottom-3 left-0 z-30 flex justify-center px-4",
!artifactsOpen && "opacity-0", "transition-all duration-300 ease-in-out",
artifactPanelOpen ? (fullscreen ? "w-full ml-0" : "w-[70%]") : "w-0", fullscreen ? "hidden" : "",
)} )}
> >
<div <div
className={cn( className={cn(
"h-full w-full transition-transform duration-300 ease-in-out", "pointer-events-auto relative w-full max-w-[720px]",
artifactPanelOpen ? "translate-x-0" : "translate-x-full", isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]",
)} )}
> >
{selectedArtifact ? ( <InputBox
<ArtifactFileDetail className={cn("w-full rounded-[20px] bg-[#FBFAFC]")}
className="size-full" isNewThread={isNewThread}
filepath={selectedArtifact} hasSubmitted={hasSubmitted}
threadId={threadId} autoFocus={isNewThread}
/> status={
) : ( suppressExistingThreadPrefetchUi
<div className="relative flex size-full justify-center px-[20px]"> ? "ready"
<div className="absolute top-2 right-2 z-30"> : thread.isLoading
<Button ? "streaming"
size="icon-sm" : "ready"
variant="ghost" }
onClick={() => { context={settings.context}
setArtifactsOpen(false); extraHeader={
}} <div className="flex flex-col gap-4">
> {isNewThread && !hasSubmitted && (
<XIcon /> <Welcome mode={settings.context.mode} />
</Button> )}
</div> </div>
{thread.values.artifacts?.length === 0 ? ( }
<ConversationEmptyState disabled={
icon={<FilesIcon />} env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
title="No artifact selected" isSelectedSkillBootstrapping
description="Select an artifact to view its details" }
/> onContextChange={(context) => setSettings("context", context)}
) : ( onSubmit={handleSubmit}
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4"> onStop={handleStop}
<header className="shrink-0"> />
<h2 className="text-[14px] text-[#333333] font-bold"> {isSelectedSkillBootstrapping && (
{t.common.artifacts} <div className="text-muted-foreground w-full translate-y-8 text-center text-xs">
</h2> Skill ...
</header> </div>
<main className="min-h-0 grow"> )}
<ArtifactFileList {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
className="max-w-(--container-width-sm) p-4 pt-12" <div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
files={thread.values.artifacts ?? []} {t.common.notAvailableInDemoMode}
threadId={threadId}
/>
</main>
</div>
)}
</div> </div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Fixed 底部居中输入框容器 */} {/* 退出确认对话框 */}
<div <DevDialog open={showExitDialog} onOpenChange={setShowExitDialog}>
className={cn( <DevDialogContent>
"pointer-events-none fixed right-0 bottom-3 left-0 z-30 flex justify-center px-4", <DevDialogHeader>
"transition-all duration-300 ease-in-out", <DevDialogTitle></DevDialogTitle>
fullscreen ? "hidden" : "", </DevDialogHeader>
)} <p className="text-muted-foreground text-sm">
> 退
<div </p>
className={cn( <DevDialogFooter>
"pointer-events-auto relative w-full max-w-[720px]", <Button
isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]", className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
)} variant="ghost"
onClick={() => setShowExitDialog(false)}
>
</Button>
<Button
className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
variant="ghost"
onClick={() => {
setShowExitDialog(false);
router.push("/workspace/chats/new");
}}
>
</Button>
</DevDialogFooter>
</DevDialogContent>
</DevDialog>
{/* selectedSkill 失败:错误弹窗 */}
<DevDialog
open={!!selectedSkillError}
onOpenChange={(open) => {
if (!open) clearSelectedSkillError();
}}
> >
<InputBox <DevDialogContent>
className={cn("w-full rounded-[20px] bg-[#FBFAFC]")} <DevDialogHeader>
isNewThread={isNewThread} <DevDialogTitle>
hasSubmitted={hasSubmitted} {selectedSkillError?.title ?? "技能加载失败"}
autoFocus={isNewThread} </DevDialogTitle>
status={ </DevDialogHeader>
suppressExistingThreadPrefetchUi <p className="text-muted-foreground text-sm">
? "ready" {selectedSkillError?.message ?? "发生了未知错误,请稍后重试。"}
: thread.isLoading </p>
? "streaming" <DevDialogFooter singleColumn>
: "ready" <Button
} className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
context={settings.context} variant="ghost"
extraHeader={ onClick={clearSelectedSkillError}
<div className="flex flex-col gap-4"> >
{isNewThread && !hasSubmitted && <Welcome mode={settings.context.mode} />}
</div> </Button>
} </DevDialogFooter>
disabled={ </DevDialogContent>
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || </DevDialog>
isSelectedSkillBootstrapping
}
onContextChange={(context) => setSettings("context", context)}
onSubmit={handleSubmit}
onStop={handleStop}
/>
{isSelectedSkillBootstrapping && (
<div className="text-muted-foreground w-full translate-y-8 text-center text-xs">
Skill ...
</div>
)}
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
<div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
{t.common.notAvailableInDemoMode}
</div>
)}
</div>
</div>
{/* 退出确认对话框 */} {/* MARK: 开发测试iframe 通信功能测试面板 */}
<DevDialog open={showExitDialog} onOpenChange={setShowExitDialog}> {/* <IframeTestPanel /> */}
<DevDialogContent>
<DevDialogHeader>
<DevDialogTitle></DevDialogTitle>
</DevDialogHeader>
<p className="text-muted-foreground text-sm">
退
</p>
<DevDialogFooter>
<Button
className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
variant="ghost"
onClick={() => setShowExitDialog(false)}
>
</Button>
<Button
className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
variant="ghost"
onClick={() => {
setShowExitDialog(false);
router.push("/workspace/chats/new");
}}
>
</Button>
</DevDialogFooter>
</DevDialogContent>
</DevDialog>
{/* selectedSkill 失败:错误弹窗 */}
<DevDialog
open={!!selectedSkillError}
onOpenChange={(open) => {
if (!open) clearSelectedSkillError();
}}
>
<DevDialogContent>
<DevDialogHeader>
<DevDialogTitle>
{selectedSkillError?.title ?? "技能加载失败"}
</DevDialogTitle>
</DevDialogHeader>
<p className="text-muted-foreground text-sm">
{selectedSkillError?.message ?? "发生了未知错误,请稍后重试。"}
</p>
<DevDialogFooter singleColumn>
<Button
className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
variant="ghost"
onClick={clearSelectedSkillError}
>
</Button>
</DevDialogFooter>
</DevDialogContent>
</DevDialog>
{/* MARK: 开发测试iframe 通信功能测试面板 */}
{/* <IframeTestPanel /> */}
</div> </div>
</ThreadContext.Provider> </ThreadContext.Provider>
); );

View File

@ -76,4 +76,4 @@ export default function WorkspaceLayout({
/> />
</QueryClientProvider> </QueryClientProvider>
); );
} }

View File

@ -30,10 +30,7 @@ export const ArtifactHeader = ({
...props ...props
}: ArtifactHeaderProps) => ( }: ArtifactHeaderProps) => (
<div <div
className={cn( className={cn("mb-[10px] flex items-center justify-between", className)}
"mb-[10px] flex items-center justify-between",
className,
)}
{...props} {...props}
/> />
); );

View File

@ -150,7 +150,7 @@ export const ChainOfThoughtStep = memo(
{isValidElement(Icon) ? ( {isValidElement(Icon) ? (
Icon Icon
) : ( ) : (
<Icon className="size-4 stroke-[1.5px] stroke-[#333333] text-[#333333]" /> <Icon className="size-4 stroke-[#333333] stroke-[1.5px] text-[#333333]" />
)} )}
<div className="bg-border absolute top-7 bottom-0 left-1/2 -mx-px w-px" /> <div className="bg-border absolute top-7 bottom-0 left-1/2 -mx-px w-px" />
</div> </div>

View File

@ -62,7 +62,7 @@ export const Suggestion = ({
<Button <Button
className={cn( className={cn(
"cursor-pointer rounded-full px-[20px] py-[15px] text-xs font-normal", "cursor-pointer rounded-full px-[20px] py-[15px] text-xs font-normal",
"bg-[#F9F8FA] text-[#666666] border-none", "border-none bg-[#F9F8FA] text-[#666666]",
"hover:bg-[#EAE9EB] hover:text-[#150033]", "hover:bg-[#EAE9EB] hover:text-[#150033]",
className, className,
)} )}

View File

@ -69,8 +69,8 @@ function ToggleGroupItem({
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, size: context.size || size,
}), }),
"w-[50px] h-full min-w-0 shrink-0 bg-white cursor-pointer px-3 focus:z-10 focus-visible:z-10", "h-full w-[50px] min-w-0 shrink-0 cursor-pointer bg-white px-3 focus:z-10 focus-visible:z-10",
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]", "data-[spacing=0]:data-[variant=outline] data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md",
className, className,
)} )}
{...props} {...props}

View File

@ -65,7 +65,8 @@ export function ArtifactFileDetail({
threadId: string; threadId: string;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const { artifacts, setOpen, select, fullscreen, setFullscreen } = useArtifacts(); const { artifacts, setOpen, select, fullscreen, setFullscreen } =
useArtifacts();
const isWriteFile = useMemo(() => { const isWriteFile = useMemo(() => {
return filepathFromProps.startsWith("write-file:"); return filepathFromProps.startsWith("write-file:");
}, [filepathFromProps]); }, [filepathFromProps]);
@ -117,7 +118,10 @@ export function ArtifactFileDetail({
const handleFullscreenToggle = useCallback(() => { const handleFullscreenToggle = useCallback(() => {
const newFullscreen = !fullscreen; const newFullscreen = !fullscreen;
setFullscreen(newFullscreen); setFullscreen(newFullscreen);
sendToParent({ type: POST_MESSAGE_TYPES.FULLSCREEN, fullscreen: newFullscreen }); sendToParent({
type: POST_MESSAGE_TYPES.FULLSCREEN,
fullscreen: newFullscreen,
});
}, [fullscreen, setFullscreen]); }, [fullscreen, setFullscreen]);
useEffect(() => { useEffect(() => {
@ -167,18 +171,47 @@ export function ArtifactFileDetail({
}} }}
> >
<ToggleGroupItem value="code"> <ToggleGroupItem value="code">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg
<path d="M5 6L2 9L5 12" stroke="#150033" stroke-linecap="round" stroke-linejoin="round"/> width="18"
<path d="M11 3L7 15" stroke="#150033" stroke-linecap="round" stroke-linejoin="round"/> height="18"
<path d="M13 6L16 9L13 12" stroke="#150033" stroke-linecap="round" stroke-linejoin="round"/> viewBox="0 0 18 18"
</svg> fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 6L2 9L5 12"
stroke="#150033"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M11 3L7 15"
stroke="#150033"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13 6L16 9L13 12"
stroke="#150033"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="preview"> <ToggleGroupItem value="preview">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="10" viewBox="0 0 16 10" fill="none"> <svg
<path d="M8 0.5C10.4943 0.5 12.8473 1.84466 14.792 4.21973C15.1644 4.67466 15.1644 5.32534 14.792 5.78027C12.8473 8.15534 10.4943 9.5 8 9.5C5.50561 9.49989 3.15269 8.15543 1.20801 5.78027C0.835561 5.32534 0.835562 4.67466 1.20801 4.21973C3.15269 1.84457 5.50561 0.500106 8 0.5Z" stroke="#666666"/> xmlns="http://www.w3.org/2000/svg"
<circle cx="8" cy="5" r="1.5" stroke="#666666"/> width="16"
</svg> height="10"
viewBox="0 0 16 10"
fill="none"
>
<path
d="M8 0.5C10.4943 0.5 12.8473 1.84466 14.792 4.21973C15.1644 4.67466 15.1644 5.32534 14.792 5.78027C12.8473 8.15534 10.4943 9.5 8 9.5C5.50561 9.49989 3.15269 8.15543 1.20801 5.78027C0.835561 5.32534 0.835562 4.67466 1.20801 4.21973C3.15269 1.84457 5.50561 0.500106 8 0.5Z"
stroke="#666666"
/>
<circle cx="8" cy="5" r="1.5" stroke="#666666" />
</svg>
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
)} )}
@ -199,7 +232,6 @@ export function ArtifactFileDetail({
</ArtifactTitle> </ArtifactTitle>
</div> </div>
<div className="flex items-center justify-end overflow-hidden"> <div className="flex items-center justify-end overflow-hidden">
<ArtifactActions> <ArtifactActions>
{isCodeFile && ( {isCodeFile && (
<ArtifactAction <ArtifactAction
@ -464,7 +496,7 @@ export const ArtifactZoomSelector = ({
return ( return (
<div <div
className={cn( className={cn(
"inline-flex items-center gap-1 rounded-[10px] h-[28px] bg-white backdrop-blur-sm", "inline-flex h-[28px] items-center gap-1 rounded-[10px] bg-white backdrop-blur-sm",
"dark:border-gray-700/50 dark:bg-gray-800/90", "dark:border-gray-700/50 dark:bg-gray-800/90",
className, className,
)} )}
@ -475,18 +507,32 @@ export const ArtifactZoomSelector = ({
onClick={handleZoomIn} onClick={handleZoomIn}
disabled={!canZoomIn} disabled={!canZoomIn}
className={cn( className={cn(
"flex h-full w-10 py-1 items-center justify-center rounded transition-colors", "flex h-full w-10 items-center justify-center rounded py-1 transition-colors",
"text-gray-400 hover:bg-gray-100 hover:text-gray-600", "text-gray-400 hover:bg-gray-100 hover:text-gray-600",
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent", "disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
"dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-gray-300", "dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-gray-300",
)} )}
aria-label="放大" aria-label="放大"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <svg
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="#666666"/> xmlns="http://www.w3.org/2000/svg"
<path d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z" fill="#666666"/> width="16"
<path d="M5.33325 7.5H9.7777M7.55547 5V10" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/> height="16"
</svg> viewBox="0 0 16 16"
fill="none"
>
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="#666666" />
<path
d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z"
fill="#666666"
/>
<path
d="M5.33325 7.5H9.7777M7.55547 5V10"
stroke="#666666"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button> </button>
<span <span
className={cn( className={cn(
@ -508,11 +554,25 @@ export const ArtifactZoomSelector = ({
)} )}
aria-label="缩小" aria-label="缩小"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <svg
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="#666666"/> xmlns="http://www.w3.org/2000/svg"
<path d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z" fill="#666666"/> width="16"
<path d="M4.99927 7.5H9.99927" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/> height="16"
</svg> viewBox="0 0 16 16"
fill="none"
>
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="#666666" />
<path
d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z"
fill="#666666"
/>
<path
d="M4.99927 7.5H9.99927"
stroke="#666666"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button> </button>
</div> </div>
); );

View File

@ -142,7 +142,8 @@ export function InputBox({
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
// isNewThread 时禁用收缩,始终保持展开(除非已提交消息) // isNewThread 时禁用收缩,始终保持展开(除非已提交消息)
const effectiveIsFocused = (isNewThread ?? false) && !hasSubmitted || isFocused; const effectiveIsFocused =
((isNewThread ?? false) && !hasSubmitted) || isFocused;
// 点击外部区域时收起输入框 // 点击外部区域时收起输入框
useEffect(() => { useEffect(() => {

View File

@ -57,7 +57,7 @@ export function MessageList({
<Conversation <Conversation
className={cn("flex size-full flex-col justify-center", className)} className={cn("flex size-full flex-col justify-center", className)}
> >
<ConversationContent className="px-[20px] w-full gap-8 pt-12"> <ConversationContent className="w-full gap-8 px-[20px] pt-12">
{groupMessages(messages, (group) => { {groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") { if (group.type === "human" || group.type === "assistant") {
return ( return (

View File

@ -105,7 +105,8 @@ export const zhCN: Translations = {
suggestions: [ suggestions: [
{ {
suggestion: "自媒体文案", suggestion: "自媒体文案",
prompt: "为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。", prompt:
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
icon: PenLineIcon, icon: PenLineIcon,
skill_id: "432", skill_id: "432",
}, },

View File

@ -22,8 +22,10 @@ export const RECEIVE_MESSAGE_TYPES = {
} as const; } as const;
// 消息类型 // 消息类型
export type PostMessageType = (typeof POST_MESSAGE_TYPES)[keyof typeof POST_MESSAGE_TYPES]; export type PostMessageType =
export type ReceiveMessageType = (typeof RECEIVE_MESSAGE_TYPES)[keyof typeof RECEIVE_MESSAGE_TYPES]; (typeof POST_MESSAGE_TYPES)[keyof typeof POST_MESSAGE_TYPES];
export type ReceiveMessageType =
(typeof RECEIVE_MESSAGE_TYPES)[keyof typeof RECEIVE_MESSAGE_TYPES];
// 消息数据类型 // 消息数据类型
export interface FullscreenMessage { export interface FullscreenMessage {
@ -48,8 +50,10 @@ export interface SelectedSkillMessage {
} }
// 发送消息的辅助函数 // 发送消息的辅助函数
export function sendToParent(message: FullscreenMessage | SelectSkillMessage | OpenSkillDialogMessage): void { export function sendToParent(
message: FullscreenMessage | SelectSkillMessage | OpenSkillDialogMessage,
): void {
if (window.parent !== window) { if (window.parent !== window) {
window.parent.postMessage(message, "*"); window.parent.postMessage(message, "*");
} }
} }

View File

@ -70,7 +70,9 @@ export function useSelectedSkillListener({
return; return;
} }
console.log(`[useSelectedSkillListener] 开始初始化技能: ${title} (${id})`); console.log(
`[useSelectedSkillListener] 开始初始化技能: ${title} (${id})`,
);
setIsBootstrapping(true); setIsBootstrapping(true);
toast.loading(`正在加载技能「${title}」...`, { id: "skill-bootstrap" }); toast.loading(`正在加载技能「${title}」...`, { id: "skill-bootstrap" });
@ -88,7 +90,8 @@ export function useSelectedSkillListener({
if (result.success) { if (result.success) {
skillBootstrappedKeyRef.current = initKey; skillBootstrappedKeyRef.current = initKey;
toast.success(`技能「${title}」加载成功`, { toast.success(`技能「${title}」加载成功`, {
description: result.message || `已创建 ${result.created_files} 个文件`, description:
result.message || `已创建 ${result.created_files} 个文件`,
duration: 4000, duration: 4000,
}); });
} else { } else {
@ -127,7 +130,10 @@ export function useSelectedSkillListener({
if (data?.type !== "selectedSkill") return; if (data?.type !== "selectedSkill") return;
const { id, title } = data; const { id, title } = data;
console.log("[useSelectedSkillListener] 收到 postMessage selectedSkill:", data); console.log(
"[useSelectedSkillListener] 收到 postMessage selectedSkill:",
data,
);
setSelectedSkill({ skill_id: String(id), title }); setSelectedSkill({ skill_id: String(id), title });
void performBootstrap(id, title); void performBootstrap(id, title);