From ff0c25db54419f17048fbe61269652c0c6c9bbcc Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Tue, 24 Mar 2026 10:28:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=96=B0=E5=A2=9E=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E6=88=AA=E6=96=AD=E5=B7=A5=E5=85=B7=E5=8F=8A?= =?UTF-8?q?=E4=B8=8B=E6=8B=89=E8=8F=9C=E5=8D=95=E6=96=87=E6=9C=AC=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E4=BC=98=E5=8C=96=20-=20=E6=96=B0=E5=A2=9E=20truncate?= =?UTF-8?q?Middle=20=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=B8=AD=E8=8B=B1=E6=96=87=E6=B7=B7=E5=90=88=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E6=8C=89=E8=A7=86=E8=A7=89=E5=AE=BD=E5=BA=A6?= =?UTF-8?q?=E6=88=AA=E6=96=AD=20-=20artifact-file-list=20=E5=92=8C=20dropd?= =?UTF-8?q?own-selector=20=E5=BA=94=E7=94=A8=E6=88=AA=E6=96=AD=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E9=81=BF=E5=85=8D=E9=95=BF=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=20-=20dropdown-menu=20=E5=92=8C=20ArtifactTi?= =?UTF-8?q?tle=20=E6=B7=BB=E5=8A=A0=E6=96=87=E6=9C=AC=E6=BA=A2=E5=87=BA?= =?UTF-8?q?=E7=9C=81=E7=95=A5=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ai-elements/artifact.tsx | 2 +- frontend/src/components/ui/dropdown-menu.tsx | 2 +- .../src/components/ui/dropdown-selector.tsx | 19 +++++--- .../artifacts/artifact-file-detail.tsx | 4 +- .../artifacts/artifact-file-list.tsx | 12 +++-- frontend/src/lib/utils.ts | 46 +++++++++++++++++++ 6 files changed, 69 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/ai-elements/artifact.tsx b/frontend/src/components/ai-elements/artifact.tsx index 9d273e2d..96db33d8 100644 --- a/frontend/src/components/ai-elements/artifact.tsx +++ b/frontend/src/components/ai-elements/artifact.tsx @@ -63,7 +63,7 @@ export type ArtifactTitleProps = HTMLAttributes; export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
); diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx index 3fe3d08d..1270e58f 100644 --- a/frontend/src/components/ui/dropdown-menu.tsx +++ b/frontend/src/components/ui/dropdown-menu.tsx @@ -130,7 +130,7 @@ function DropdownMenuRadioItem({ { value: T; @@ -76,26 +77,30 @@ export function DropdownSelector({ - - {selectedOption?.label ?? value} + + {truncateMiddle(selectedOption?.label ?? value, 50)} {isOpen ? : } - + onChange(v as T)} > {options.map((option) => ( - - {option.label} + + {truncateMiddle(option.label)} ))} ); -} +} \ No newline at end of file diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 8017ed8c..76860325 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -34,7 +34,7 @@ import { installSkill } from "@/core/skills/api"; import { streamdownPlugins } from "@/core/streamdown"; import { checkCodeFile, getFileName } from "@/core/utils/files"; import { useMarkdownDownload } from "@/core/utils/markdown-download"; -import { cn, copyToClipboard } from "@/lib/utils"; +import { cn, copyToClipboard, truncateMiddle } from "@/lib/utils"; import { CitationLink } from "../citations/citation-link"; @@ -236,7 +236,7 @@ export function ArtifactFileDetail({
{isWriteFile ? ( -
{getFileName(filepath)}
+
{truncateMiddle(getFileName(filepath), 50)}
) : ( handleClick(file)} > - -
{getFileName(file)}
-
- {getFileIcon(file, "size-6 stroke-[1.5px] stroke-[#333333]")} + +
+ {truncateMiddle(getFileName(file), 50)}
+
+ {getFileIcon(file, "size-6 stroke-[1.5px] stroke-[#333333]")} +
{getFileExtensionDisplayName(file)} file diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 36369100..db85669d 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -33,3 +33,49 @@ export async function copyToClipboard(text: string): Promise { console.log("[copyToClipboard] direct mode", message); await navigator.clipboard.writeText(text); } + +/** + * 计算字符串的视觉宽度(中文算2,英文算1) + */ +export function getVisualWidth(text: string): number { + let width = 0; + for (const char of text) { + // 中文字符范围:\u4e00-\u9fff(基本汉字) + width += /[\u4e00-\u9fff]/.test(char) ? 2 : 1; + } + return width; +} + +/** + * 截断字符串中间部分,保留开头和结尾 + * 例如: "very-long-file-name.txt" -> "very-lon...me.txt" + * 中文按视觉宽度计算(中文算2,英文算1) + */ +export function truncateMiddle(text: string, maxVisualWidth: number = 30): string { + const visualWidth = getVisualWidth(text); + if (visualWidth <= maxVisualWidth) return text; + + const startWidth = Math.ceil(maxVisualWidth * 0.6); + const endWidth = Math.floor(maxVisualWidth * 0.4) - 3; // -3 for "..." + + let startPart = ""; + let currentWidth = 0; + for (const char of text) { + const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1; + if (currentWidth + charWidth > startWidth) break; + startPart += char; + currentWidth += charWidth; + } + + let endPart = ""; + currentWidth = 0; + for (let i = text.length - 1; i >= 0; i--) { + const char = text[i]!; + const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1; + if (currentWidth + charWidth > endWidth) break; + endPart = char + endPart; + currentWidth += charWidth; + } + + return `${startPart}...${endPart}`; +}