From 6130f12790fa23249ab6693ae2e316bd5e07dac4 Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Mon, 16 Mar 2026 17:48:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86=E4=BA=A4=E4=BA=92=E4=B8=8E=E6=B7=BB=E5=8A=A0=E6=8A=80?= =?UTF-8?q?=E8=83=BD=E9=80=89=E6=8B=A9=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构输入框展开/收缩逻辑,支持 isNewThread 时保持展开 - 添加 onFocusChange 回调支持 - 新增 IframeSkillDialogButton 替换原有的 ModeHoverGuide - 为 AddAttachmentsButton 和 IframeSkillDialogButton 添加 hover 变色效果 - 调整输入框布局结构,移出 PromptInputSubmit 到外层 - 优化 Footer 工具栏的过渡动画 - 添加 selectSkill 国际化文本 --- .../app/workspace/chats/[thread_id]/page.tsx | 28 ++--- .../ai-elements/chain-of-thought.tsx | 2 +- .../components/ai-elements/conversation.tsx | 2 +- .../src/components/ai-elements/message.tsx | 4 +- .../components/ai-elements/prompt-input.tsx | 11 +- frontend/src/components/ai-elements/queue.tsx | 4 +- .../src/components/ai-elements/suggestion.tsx | 4 +- frontend/src/components/ui/button.tsx | 2 +- frontend/src/components/ui/card.tsx | 2 +- frontend/src/components/ui/dev-dialog.tsx | 2 +- frontend/src/components/ui/dropdown-menu.tsx | 6 +- .../src/components/ui/dropdown-selector.tsx | 49 ++++++++ frontend/src/components/ui/input-group.tsx | 2 +- frontend/src/components/ui/tooltip.tsx | 2 +- .../artifacts/artifact-file-detail.tsx | 34 ++---- .../artifacts/artifact-file-list.tsx | 4 +- .../workspace/artifacts/artifact-trigger.tsx | 4 +- .../components/workspace/chats/chat-box.tsx | 17 ++- .../components/workspace/dev-todo-list.tsx | 9 +- .../src/components/workspace/flip-display.tsx | 7 +- .../src/components/workspace/input-box.tsx | 112 +++++++++++++----- .../workspace/messages/message-group.tsx | 2 +- .../workspace/messages/message-list.tsx | 3 +- .../workspace/messages/subtask-card.tsx | 2 +- frontend/src/components/workspace/welcome.tsx | 21 ++-- frontend/src/core/i18n/locales/en-US.ts | 22 ++-- frontend/src/core/i18n/locales/types.ts | 1 + frontend/src/core/i18n/locales/zh-CN.ts | 26 ++-- frontend/src/styles/globals.css | 20 +++- 29 files changed, 274 insertions(+), 130 deletions(-) create mode 100644 frontend/src/components/ui/dropdown-selector.tsx diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 8572acb7..9fb66dc7 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -19,7 +19,6 @@ import { useThreadChat, } from "@/components/workspace/chats"; import { DevTodoList } from "@/components/workspace/dev-todo-list"; -import { Tooltip } from "@/components/workspace/tooltip"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/messages"; import { ThreadContext } from "@/components/workspace/messages/context"; @@ -69,6 +68,7 @@ export default function ChatPage() { } }, }); + console.log(thread.values.todos); const handleSubmit = useCallback( (message: PromptInputMessage) => { @@ -86,25 +86,25 @@ export default function ChatPage() {
{/* 返回查看结果左箭头 */} -
-
-
+
-
+
- - + } /> @@ -143,7 +141,7 @@ export default function ChatPage() { )} > - - + diff --git a/frontend/src/components/ai-elements/chain-of-thought.tsx b/frontend/src/components/ai-elements/chain-of-thought.tsx index cd3a6b7a..0c4ffff2 100644 --- a/frontend/src/components/ai-elements/chain-of-thought.tsx +++ b/frontend/src/components/ai-elements/chain-of-thought.tsx @@ -202,7 +202,7 @@ export const ChainOfThoughtContent = memo( ( ); diff --git a/frontend/src/components/ai-elements/message.tsx b/frontend/src/components/ai-elements/message.tsx index c0071c21..6adc68e7 100644 --- a/frontend/src/components/ai-elements/message.tsx +++ b/frontend/src/components/ai-elements/message.tsx @@ -27,8 +27,8 @@ export type MessageProps = HTMLAttributes & { export const Message = ({ className, from, ...props }: MessageProps) => (
, ) => void | Promise; + // className for InputGroup (passes through to inner InputGroup component) + inputGroupClassName?: string; }; export const PromptInput = ({ className, + inputGroupClassName, accept, disabled, multiple, @@ -794,7 +797,7 @@ export const PromptInput = ({ ref={formRef} {...props} > - {children} + {children} ); @@ -1027,7 +1030,7 @@ export type PromptInputSubmitProps = ComponentProps & { export const PromptInputSubmit = ({ className, variant = "default", - size = "icon-sm", + size = "sm", status, children, ...props @@ -1045,13 +1048,15 @@ export const PromptInputSubmit = ({ return ( {children ?? Icon} + 发送 ); }; diff --git a/frontend/src/components/ai-elements/queue.tsx b/frontend/src/components/ai-elements/queue.tsx index 0c91d130..1b475948 100644 --- a/frontend/src/components/ai-elements/queue.tsx +++ b/frontend/src/components/ai-elements/queue.tsx @@ -186,8 +186,8 @@ export const QueueList = ({ className, ...props }: QueueListProps) => ( - -
+ +
    {children}
diff --git a/frontend/src/components/ai-elements/suggestion.tsx b/frontend/src/components/ai-elements/suggestion.tsx index 7c06e9ce..80cff963 100644 --- a/frontend/src/components/ai-elements/suggestion.tsx +++ b/frontend/src/components/ai-elements/suggestion.tsx @@ -61,7 +61,7 @@ export const Suggestion = ({ return ( ); diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index f332e0d9..f11b51b9 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -18,7 +18,7 @@ const buttonVariants = cva( secondary: "cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: - "cursor-pointer hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + "cursor-pointer bg-transparent hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "cursor-pointer text-primary underline-offset-4 hover:underline", }, size: { diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index b1198f32..81fb5cb3 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
) { return ( ) @@ -42,7 +44,7 @@ function DropdownMenuContent({ data-slot="dropdown-menu-content" sideOffset={sideOffset} className={cn( - "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[20px] border p-[20px] shadow-md", className )} {...props} @@ -230,7 +232,7 @@ function DropdownMenuSubContent({ { + value: T; + label: string; +} + +interface DropdownSelectorProps { + value: T; + options: DropdownSelectorOption[]; + onChange: (value: T) => void; + triggerClassName?: string; + contentClassName?: string; +} + +export function DropdownSelector({ + value, + options, + onChange, + triggerClassName, + contentClassName, +}: DropdownSelectorProps) { + const selectedOption = options.find((opt) => opt.value === value); + + return ( + + + {selectedOption?.label ?? value} + + + + {options.map((option) => ( + + {option.label} + + ))} + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/ui/input-group.tsx b/frontend/src/components/ui/input-group.tsx index e35f75b2..65ff8bf3 100644 --- a/frontend/src/components/ui/input-group.tsx +++ b/frontend/src/components/ui/input-group.tsx @@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) { data-slot="input-group" role="group" className={cn( - "group/input-group border-input/50 dark:bg-background/80 relative flex w-full items-center rounded-md border bg-white/80 shadow-xs transition-[color,box-shadow] outline-none", + "group/input-group overflow-hidden border-input/50 dark:bg-background/80 relative flex w-full items-center rounded-md border transition-[color,box-shadow] outline-none", "h-9 min-w-0 has-[>textarea]:h-auto", // Variants based on alignment. diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx index ad590050..91f395c1 100644 --- a/frontend/src/components/ui/tooltip.tsx +++ b/frontend/src/components/ui/tooltip.tsx @@ -46,7 +46,7 @@ function TooltipContent({ data-slot="tooltip-content" sideOffset={sideOffset ?? 4} className={cn( - "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-foreground text-background dark:text-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance shadow-xs dark:border-white/18 dark:bg-[#050504]", + "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-tooltip-background text-background dark:text-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance shadow-xs dark:border-white/18 dark:bg-[#050504]", className, )} {...props} diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 40a654c1..296f669c 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -20,13 +20,7 @@ import { ArtifactHeader, ArtifactTitle, } from "@/components/ai-elements/artifact"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +import { DropdownSelector } from "@/components/ui/dropdown-selector"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { CodeEditor } from "@/components/workspace/code-editor"; import { useArtifactContent } from "@/core/artifacts/hooks"; @@ -91,6 +85,13 @@ export function ArtifactFileDetail({ const displayContent = content ?? ""; + const artifactOptions = useMemo(() => { + return (artifacts ?? []).map((artifactPath) => ({ + value: artifactPath, + label: getFileName(artifactPath), + })); + }, [artifacts]); + const [viewMode, setViewMode] = useState<"code" | "preview">("code"); const [isInstalling, setIsInstalling] = useState(false); const { isMock } = useThread(); @@ -153,20 +154,11 @@ export function ArtifactFileDetail({ {isWriteFile ? (
{getFileName(filepath)}
) : ( - - - {getFileName(filepath)} - - - - {(artifacts ?? []).map((artifactPath) => ( - - {getFileName(artifactPath)} - - ))} - - - + )}
diff --git a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx index 99b2e374..8b4a84a5 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx @@ -76,14 +76,14 @@ export function ArtifactFileList({ {files.map((file) => ( handleClick(file)} >
{getFileName(file)}
- {getFileIcon(file, "size-6")} + {getFileIcon(file, "text-[#333333] size-6 stroke-[1.5px]")}
diff --git a/frontend/src/components/workspace/artifacts/artifact-trigger.tsx b/frontend/src/components/workspace/artifacts/artifact-trigger.tsx index 7933c661..ec12156b 100644 --- a/frontend/src/components/workspace/artifacts/artifact-trigger.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-trigger.tsx @@ -14,9 +14,9 @@ export const ArtifactTrigger = () => { return null; } return ( - +