From 5afe834b537d2b3ed8cdd892371c27dbf4d6e8b6 Mon Sep 17 00:00:00 2001
From: MT-Mint <798521692@qq.com>
Date: Tue, 17 Mar 2026 14:43:27 +0800
Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E9=87=8D=E6=9E=84=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E6=A1=86=E9=99=84=E4=BB=B6=E9=A2=84=E8=A7=88=E4=B8=8E?=
=?UTF-8?q?=E5=B8=83=E5=B1=80=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将附件预览改为方形缩略图样式,图片支持悬浮遮罩和删除按钮
- 输入框宽度固定为 720px,附件预览区域移至输入框上方
- 提交按钮添加禁用状态逻辑(无内容或流式传输时禁用)
- 添加 Streamdown Markdown 样式(标题、列表项字号)
- 调整图标颜色、圆角和间距细节
---
.../app/workspace/chats/[thread_id]/page.tsx | 10 +-
frontend/src/app/workspace/layout.tsx | 3 +-
.../src/components/ai-elements/artifact.tsx | 4 +-
.../ai-elements/chain-of-thought.tsx | 2 +-
.../components/ai-elements/prompt-input.tsx | 159 ++++++++++--------
.../artifacts/artifact-file-detail.tsx | 6 +-
.../src/components/workspace/input-box.tsx | 96 +++++++++--
.../workspace/messages/message-group.tsx | 2 +-
frontend/src/styles/globals.css | 26 +++
9 files changed, 209 insertions(+), 99 deletions(-)
diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx
index a5fa0dc2..1b3ccdce 100644
--- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx
+++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx
@@ -132,11 +132,11 @@ export default function ChatPage() {
-
+ {/* TODO: !!!!必须注释!!!!! */}
+
{children}
diff --git a/frontend/src/components/ai-elements/artifact.tsx b/frontend/src/components/ai-elements/artifact.tsx
index 9b5e7697..8d84e9bf 100644
--- a/frontend/src/components/ai-elements/artifact.tsx
+++ b/frontend/src/components/ai-elements/artifact.tsx
@@ -143,8 +143,10 @@ export const ArtifactContent = ({
className,
...props
}: ArtifactContentProps) => (
+
);
diff --git a/frontend/src/components/ai-elements/chain-of-thought.tsx b/frontend/src/components/ai-elements/chain-of-thought.tsx
index 0c4ffff2..752cfc2e 100644
--- a/frontend/src/components/ai-elements/chain-of-thought.tsx
+++ b/frontend/src/components/ai-elements/chain-of-thought.tsx
@@ -147,7 +147,7 @@ export const ChainOfThoughtStep = memo(
{...props}
>
- {isValidElement(Icon) ? Icon :
}
+ {isValidElement(Icon) ? Icon :
}
diff --git a/frontend/src/components/ai-elements/prompt-input.tsx b/frontend/src/components/ai-elements/prompt-input.tsx
index 9bcf46a8..6ff86345 100644
--- a/frontend/src/components/ai-elements/prompt-input.tsx
+++ b/frontend/src/components/ai-elements/prompt-input.tsx
@@ -295,81 +295,81 @@ export function PromptInputAttachment({
data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
const isImage = mediaType === "image";
- const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
+ const truncateFilename = (name: string, maxLen: number = 10) => {
+ if (name.length <= maxLen) return name;
+ const ext = name.slice(name.lastIndexOf("."));
+ const baseName = name.slice(0, name.lastIndexOf("."));
+ const truncated = baseName.slice(0, maxLen - ext.length - 3);
+ return truncated + "..." + ext;
+ };
return (
-
-
-
-
-
- {isImage ? (
-

- ) : (
-
- )}
-
-
-
-
-
- {isImage && (
-
-

-
- )}
-
-
-
- {filename || (isImage ? "Image" : "Attachment")}
-
- {data.mediaType && (
-
- {data.mediaType}
-
- )}
-
+ >
+ ) : (
+ <>
+
+
+
+ {truncateFilename(filename)}
+
-
-
-
+ {/* 关闭按钮 - 右上角 */}
+
{
+ e.stopPropagation();
+ attachments.remove(data.id);
+ }}
+ type="button"
+ >
+
+ Remove
+
+ >
+ )}
+
);
}
@@ -393,13 +393,14 @@ export function PromptInputAttachments({
return (
{attachments.files.map((file) => (
-
- {children(file)}
-
+
{children(file)}
))}
);
@@ -1032,9 +1033,22 @@ export const PromptInputSubmit = ({
variant = "default",
size = "sm",
status,
+ disabled,
children,
...props
}: PromptInputSubmitProps) => {
+ const controller = useOptionalPromptInputController();
+
+ // 判断是否有内容可发送
+ const hasContent = controller
+ ? controller.textInput.value.trim().length > 0 || controller.attachments.files.length > 0
+ : false;
+
+ // 正在 streaming 时不允许发送
+ const isStreaming = status === "streaming" || status === "submitted";
+
+ const isDisabled = disabled || !hasContent || isStreaming;
+
let Icon = ;
if (status === "submitted") {
@@ -1049,10 +1063,17 @@ export const PromptInputSubmit = ({
{children ?? Icon}
diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx
index 2fcc7f78..62cc3ab2 100644
--- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx
+++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx
@@ -225,7 +225,8 @@ export function ArtifactFileDetail({
-
+
+
{isSupportPreview &&
viewMode === "preview" &&
(language === "markdown" || language === "html") && (
@@ -247,7 +248,6 @@ export function ArtifactFileDetail({
src={urlOfArtifact({ filepath, threadId, isMock })}
/>
)}
- {/* */}
);
@@ -262,7 +262,7 @@ export function ArtifactFilePreview({
}) {
if (language === "markdown") {
return (
-
+
(null);
const textareaRef = useRef(null);
+ const attachments = usePromptInputAttachments();
const [followups, setFollowups] = useState([]);
const [followupsHidden, setFollowupsHidden] = useState(false);
@@ -398,10 +400,13 @@ export function InputBox({
return (
{ promptRootRef.current = el; containerRef.current = el; }} className="relative">
+ {/* 附件预览区域 - 在输入框上方 */}
+
+
{extraHeader && (
-
+ 0}>
{extraHeader}
-
+
)}
{/* 输入框主容器 */}
-
- {(attachment) => }
-
*/}
{/* Skill 选择按钮 (iframe 与宿主页通信) */}
-
+
{/* [已禁用] 模式选择下拉菜单内容 */}
{/*
@@ -785,7 +795,7 @@ export function InputBox({
{/* 移动出来 */}
{/* 小惊喜等 */}
{isNewThread && searchParams.get("mode") !== "skill" && (
-
-
-
+
)}
{!disabled &&
@@ -861,11 +869,19 @@ export function InputBox({
);
}
+// SuggestionList 容器
+function SuggestionListContainer({ sendSelectSkill }: { sendSelectSkill: (skill_id: string) => void }) {
+ return (
+
+
+
+ );
+}
+
// 快速选择skillbutton
-function SuggestionList() {
+function SuggestionList({ sendSelectSkill }: { sendSelectSkill: (skill_id: string) => void }) {
const { t } = useI18n();
const { textInput } = usePromptInputController();
- const { sendSelectSkill } = useIframeSkill();
const handleSuggestionClick = useCallback(
(suggestion: { prompt: string; skill_id?: string }) => {
@@ -958,9 +974,18 @@ function AddAttachmentsButton({ className }: { className?: string }) {
);
}
// 启动iframeSkillDialog
-function IframeSkillDialogButton({ className }: { className?: string }) {
+function IframeSkillDialogButton({
+ className,
+ selectedSkill,
+ openSkillDialog,
+ clearSkill,
+}: {
+ className?: string;
+ selectedSkill: { skill_id: string; title: string } | null;
+ openSkillDialog: () => void;
+ clearSkill: () => void;
+}) {
const { t } = useI18n();
- const { selectedSkill, openSkillDialog, clearSkill } = useIframeSkill();
return (
@@ -988,3 +1013,38 @@ function IframeSkillDialogButton({ className }: { className?: string }) {
);
}
+
+// 附件预览栏 - 在输入框上方显示
+function AttachmentPreviewBar() {
+ const attachments = usePromptInputAttachments();
+
+ if (!attachments.files.length) {
+ return null;
+ }
+
+ return (
+
+
+ {(attachment) => }
+
+
+ );
+}
+
+// ExtraHeader 容器 - 有附件时上浮
+function ExtraHeaderContainer({
+ hasAttachments,
+ children
+}: {
+ hasAttachments: boolean;
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/components/workspace/messages/message-group.tsx b/frontend/src/components/workspace/messages/message-group.tsx
index 45c57211..56268f20 100644
--- a/frontend/src/components/workspace/messages/message-group.tsx
+++ b/frontend/src/components/workspace/messages/message-group.tsx
@@ -151,7 +151,7 @@ export function MessageGroup({
}
>