merge: 同步远端 feat/2.5.0-sg

This commit is contained in:
GuoQing Zhang
2026-08-28 17:46:44 +08:00
7 changed files with 42 additions and 42 deletions
@@ -696,7 +696,7 @@ function FileTableHeader({
{localize("com_knowledge.file_size")}</SortableHeader>
<TableHead
className="relative bg-[#F3F4F6] p-0 text-right font-normal text-[15px] text-[#545A60]"
className="sticky top-0 z-20 bg-[#F3F4F6] p-0 font-normal text-[15px] text-[#545A60]"
style={{ width: columnWidths.totalCount, minWidth: columnWidths.totalCount, maxWidth: columnWidths.totalCount }}
>
<div className="flex h-full items-center justify-end border-l pl-3 pr-3">
@@ -3096,16 +3096,15 @@
.infoEditModalLabelWrap {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.infoEditRenameHint {
color: #f53f3f;
font-size: 12px;
line-height: 20px;
white-space: nowrap;
}
.infoEditModalOverlay {
@@ -258,7 +258,6 @@ export default function PortalKnowledgeWorkbench() {
const [folderDraft, setFolderDraft] = useState("新建文件夹");
const [activePanel, setActivePanel] = useState<PanelKey | null>(null);
const [aiDrawerOpen, setAiDrawerOpen] = useState(false);
const [openAiAfterDeepLinkRestore, setOpenAiAfterDeepLinkRestore] = useState(false);
const [summaryExpanded, setSummaryExpanded] = useState(false);
const [tagModalOpen, setTagModalOpen] = useState(false);
const [permissionOpen, setPermissionOpen] = useState(false);
@@ -455,6 +454,11 @@ export default function PortalKnowledgeWorkbench() {
if (!selectedFile || String(selectedFile.id) !== portalDeepLinkTarget.fileId) return;
if (preview.loading) return;
setRestoringDeepLinkKey(null);
// Deep-link file entry (e.g. "进入知识库") opens the AI drawer by default.
// Search-result previews mark the URL with fromSearch=1 and keep it closed.
if (portalDeepLinkTarget.fromSearch === "1") return;
setAiDrawerOpen(true);
setActivePanel(null);
}, [
portalDeepLinkTarget,
preview.error,
@@ -462,28 +466,6 @@ export default function PortalKnowledgeWorkbench() {
preview.loading,
restoringDeepLinkKey,
selectedFile,
]);
// Parent-driven "进入知识库" file deep links should open the AI drawer once restore finishes,
// regardless of whether the file lives at the root or inside a sub-folder.
useEffect(() => {
if (!openAiAfterDeepLinkRestore) return;
if (!portalDeepLinkTarget?.fileId || isDeepLinkRestoring) return;
if (
!selectedFile ||
String(selectedFile.id) !== portalDeepLinkTarget.fileId ||
String(selectedFile.spaceId) !== portalDeepLinkTarget.spaceId
) {
return;
}
setAiDrawerOpen(true);
setActivePanel(null);
setOpenAiAfterDeepLinkRestore(false);
}, [
openAiAfterDeepLinkRestore,
isDeepLinkRestoring,
portalDeepLinkTarget,
selectedFile,
setActivePanel,
setAiDrawerOpen,
]);
@@ -1491,7 +1473,6 @@ export default function PortalKnowledgeWorkbench() {
next.set("openNonce", openNonce);
return next;
}, { replace: true });
setOpenAiAfterDeepLinkRestore(true);
}
};
window.addEventListener("message", handlePortalMessage);
@@ -2187,6 +2168,7 @@ export default function PortalKnowledgeWorkbench() {
next.delete("folderName");
next.delete("fileId");
next.delete("fileName");
next.delete("fromSearch");
next.delete("documentId");
next.delete("name");
next.delete("openNonce");
@@ -2271,12 +2253,20 @@ export default function PortalKnowledgeWorkbench() {
const deepestFolder = parentFolders[parentFolders.length - 1];
const folderId = deepestFolder?.id || currentFolderId || "";
const folderName = deepestFolder?.name || currentFolderNode?.file.name || "";
const isFromSearch =
searchMode &&
searchResults.some(
(f) =>
String(f.id) === String(selectedFile.id) &&
String(f.spaceId) === String(selectedFile.spaceId),
);
if (
portalDeepLinkTarget &&
portalDeepLinkTarget.spaceId === spaceId &&
portalDeepLinkTarget.folderId === folderId &&
portalDeepLinkTarget.fileId === fileId &&
portalDeepLinkTarget.fileName === fileName
portalDeepLinkTarget.fileName === fileName &&
portalDeepLinkTarget.fromSearch === (isFromSearch ? "1" : "")
) {
return;
}
@@ -2293,6 +2283,11 @@ export default function PortalKnowledgeWorkbench() {
}
next.set("fileId", fileId);
next.set("fileName", fileName);
if (isFromSearch) {
next.set("fromSearch", "1");
} else {
next.delete("fromSearch");
}
next.delete("documentId");
next.delete("name");
next.delete("openNonce");
@@ -2304,6 +2299,8 @@ export default function PortalKnowledgeWorkbench() {
currentFolderId,
currentFolderNode?.file.name,
portalDeepLinkTarget,
searchMode,
searchResults,
selectedFile,
selectedFileParentPath,
setSearchParams,
@@ -2313,7 +2310,6 @@ export default function PortalKnowledgeWorkbench() {
setSelectedFile(null);
setActivePanel(null);
setAiDrawerOpen(false);
setOpenAiAfterDeepLinkRestore(false);
setSummaryExpanded(false);
setPreview({ loading: false, fileUrl: "", fileType: "", error: "", previewData: null });
// Deep-link open used to leave searchMode with a single-file result set.
@@ -2348,6 +2344,7 @@ export default function PortalKnowledgeWorkbench() {
}
next.delete("fileId");
next.delete("fileName");
next.delete("fromSearch");
next.delete("documentId");
next.delete("name");
next.delete("openNonce");
@@ -311,7 +311,7 @@ export function PortalFileInfoEditModal({
<div className={s.infoEditModalLabelWrap}>
<label className={s.infoEditModalLabel}></label>
<span className={s.infoEditRenameHint}>
*
*
</span>
</div>
<div className={s.infoEditModalField}>
@@ -18,6 +18,8 @@ export interface PortalDeepLinkTarget {
fileName: string;
/** Forces re-apply when the same file is opened again via postMessage. */
openNonce: string;
/** Set to "1" when the file was selected from the global search results. */
fromSearch?: string;
key: string;
}
@@ -60,6 +62,7 @@ export const resolvePortalDeepLinkTarget = (searchParams: URLSearchParams): Port
const fileId = getQueryValue(searchParams, ["fileId", "documentId", "document_id"]);
const fileName = getQueryValue(searchParams, ["name", "fileName", "documentName", "document_name"]);
const openNonce = getQueryValue(searchParams, ["openNonce", "open_nonce"]);
const fromSearch = getQueryValue(searchParams, ["fromSearch", "from_search"]);
return {
spaceId,
folderId,
@@ -67,8 +70,9 @@ export const resolvePortalDeepLinkTarget = (searchParams: URLSearchParams): Port
fileId,
fileName,
openNonce,
fromSearch,
// openNonce lets postMessage re-open the same file after the preview was closed.
key: `${spaceId}:${folderId}:${folderName}:${fileId}:${fileName}:${openNonce}`,
key: `${spaceId}:${folderId}:${folderName}:${fileId}:${fileName}:${openNonce}:${fromSearch}`,
};
};
@@ -32,14 +32,14 @@ export function SourceFileLinks({ files, max = 3 }: { files: TagConsoleSourceFil
const rest = files.length - shown.length
return (
<div className="flex min-w-0 flex-col gap-0.5">
<div className="flex w-full min-w-0 flex-col gap-0.5">
{shown.map((file) => {
if (isViolationFile(file)) {
return (
<button
key={file.file_id}
type="button"
className="block w-full truncate text-left text-[#F53F3F] hover:underline"
className="block w-full min-w-0 truncate text-left text-[#F53F3F] hover:underline"
title={t("build.tagConsole.violationBlocked", "该文件包含违规内容,无法预览")}
onClick={() => setViolating(file)}
>
@@ -50,7 +50,7 @@ export function SourceFileLinks({ files, max = 3 }: { files: TagConsoleSourceFil
const url = buildTagFileDetailUrl(file)
if (!url) {
return (
<span key={file.file_id} className="block truncate" title={file.file_name}>
<span key={file.file_id} className="block w-full min-w-0 truncate" title={file.file_name}>
{file.file_name}
</span>
)
@@ -62,7 +62,7 @@ export function SourceFileLinks({ files, max = 3 }: { files: TagConsoleSourceFil
target="_blank"
rel="noopener noreferrer"
title={file.file_name}
className="block truncate text-blue-600 hover:underline"
className="block w-full min-w-0 truncate text-blue-600 hover:underline"
>
{file.file_name}
</a>
@@ -114,25 +114,25 @@ export function TagReviewDialog({ target, libraries, saving, onClose, onApprove,
}
const field = (label: string, value: React.ReactNode) => (
<div className="flex items-start gap-3 py-1 text-sm">
<div className="flex min-w-0 items-start gap-3 py-1 text-sm">
<span className="w-28 shrink-0 leading-6 text-muted-foreground">{label}</span>
<span className="min-w-0 flex-1 overflow-hidden leading-6">{value}</span>
<span className="w-0 min-w-0 flex-1 overflow-hidden leading-6">{value}</span>
</div>
)
return (
<Dialog open={Boolean(target)} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="gap-0 overflow-hidden p-0 sm:max-w-[640px] bg-background-login">
<DialogContent className="w-full max-w-[calc(100vw-2rem)] gap-0 p-0 sm:max-w-[640px] bg-background-login">
<DialogHeader className="border-b border-[#EBECF0] px-6 py-4">
<DialogTitle>{t("build.tagConsole.reviewTitle", "标签审核")}</DialogTitle>
</DialogHeader>
<div className="space-y-4 px-6 py-5">
<div className="min-w-0 space-y-4 px-6 py-5">
{loading ? (
<p className="py-6 text-center text-sm text-muted-foreground">{t("loading")}</p>
) : (
<>
<div className="overflow-hidden rounded-lg border border-[#ECECEC] bg-[#FAFBFC] p-4">
<div className="min-w-0 overflow-hidden rounded-lg border border-[#ECECEC] bg-[#FAFBFC] p-4">
{field(t("build.tagName", "标签名称"), detail?.name || "-")}
{field(
t("build.tagConsole.tagType", "标签类型"),