Skip to content

Commit 2a2c68f

Browse files
author
dolphin
committed
fix: some bugfix
1 parent 91a19b9 commit 2a2c68f

5 files changed

Lines changed: 148 additions & 68 deletions

File tree

src/frontend/client/src/components/Chat/Input/ChatKnowledge.tsx

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,19 @@ function useDebounce<T>(value: T, delay: number): T {
5050
const MAX_SUB_HEIGHT = 320;
5151
const BOTTOM_GAP = 8;
5252

53+
/**
54+
* Compute an alignOffset so the sub-content top aligns with the parent menu top,
55+
* and clamp maxH so the sub-content never overflows the viewport on either side.
56+
*
57+
* After the sub-content is rendered by Radix, we also observe its real position
58+
* and re-clamp maxH based on the actual top (handles Radix collision shifting).
59+
*/
5360
function useSubMenuLayout(menuRef: React.RefObject<HTMLDivElement | null>, triggerKey: string, open: boolean) {
5461
const [alignOffset, setAlignOffset] = useState(0);
5562
const [maxH, setMaxH] = useState<number>(MAX_SUB_HEIGHT);
63+
const subContentRef = useRef<HTMLDivElement | null>(null);
5664

65+
// Phase 1 — compute alignOffset & an initial maxH from parent menu rect
5766
useLayoutEffect(() => {
5867
if (!open) return;
5968

@@ -71,15 +80,73 @@ function useSubMenuLayout(menuRef: React.RefObject<HTMLDivElement | null>, trigg
7180
setAlignOffset(0);
7281
}
7382

74-
const available = window.innerHeight - menuRect.top - BOTTOM_GAP;
75-
setMaxH(Math.min(Math.max(available, 0), MAX_SUB_HEIGHT));
83+
// Initial estimate — will be refined in Phase 2
84+
const spaceBelow = window.innerHeight - menuRect.top - BOTTOM_GAP;
85+
const spaceAbove = menuRect.bottom - BOTTOM_GAP;
86+
const available = Math.max(spaceBelow, spaceAbove);
87+
setMaxH(Math.min(Math.max(available, 120), MAX_SUB_HEIGHT));
7688
};
7789

7890
requestAnimationFrame(update);
7991
window.addEventListener('resize', update);
8092
return () => window.removeEventListener('resize', update);
8193
}, [open, menuRef, triggerKey]);
8294

95+
// Phase 2 — once Radix renders the actual sub-content, observe its real
96+
// position and clamp maxH so it stays within the viewport.
97+
useEffect(() => {
98+
if (!open) {
99+
subContentRef.current = null;
100+
return;
101+
}
102+
103+
// Radix renders sub-content in a portal; locate it by role + data attribute
104+
const findSubContent = (): HTMLElement | null => {
105+
// Look for the sub-content element associated with this trigger
106+
const menuEl = menuRef.current;
107+
if (!menuEl) return null;
108+
109+
const trigger = menuEl.querySelector<HTMLElement>(`[data-sub-key="${triggerKey}"]`);
110+
if (!trigger) return null;
111+
112+
// The sub-content is rendered in a portal; we find it via the Radix
113+
// data-state="open" attribute on [role="menu"] elements in the document
114+
const allMenus = document.querySelectorAll<HTMLElement>('[role="menu"][data-state="open"]');
115+
// Pick the deepest nested one that is NOT the parent menu
116+
for (const m of Array.from(allMenus)) {
117+
if (m !== menuEl && !menuEl.contains(m)) {
118+
return m;
119+
}
120+
}
121+
return null;
122+
};
123+
124+
const clampToViewport = () => {
125+
const el = subContentRef.current || findSubContent();
126+
if (!el) return;
127+
subContentRef.current = el;
128+
129+
const rect = el.getBoundingClientRect();
130+
const spaceBelow = window.innerHeight - rect.top - BOTTOM_GAP;
131+
const finalH = Math.min(Math.max(spaceBelow, 120), MAX_SUB_HEIGHT);
132+
setMaxH(finalH);
133+
};
134+
135+
// Wait a tick for Radix portal to mount
136+
const rafId = requestAnimationFrame(() => {
137+
requestAnimationFrame(clampToViewport);
138+
});
139+
140+
window.addEventListener('resize', clampToViewport);
141+
window.addEventListener('scroll', clampToViewport, true);
142+
143+
return () => {
144+
cancelAnimationFrame(rafId);
145+
window.removeEventListener('resize', clampToViewport);
146+
window.removeEventListener('scroll', clampToViewport, true);
147+
};
148+
}, [open, menuRef, triggerKey]);
149+
83150
return { alignOffset, maxH };
84151
}
85152

@@ -338,7 +405,7 @@ export const ChatKnowledge = ({
338405
</div>
339406
</DropdownMenuTrigger>
340407

341-
<DropdownMenuContent ref={menuContentRef} align="start" className="w-[200px] p-1.5 rounded-2xl shadow-xl border-slate-100">
408+
<DropdownMenuContent ref={menuContentRef} align="start" collisionPadding={BOTTOM_GAP} className="w-[200px] p-1.5 rounded-2xl shadow-xl border-slate-100">
342409

343410
{/* 知识空间 */}
344411
<DropdownMenuSub
@@ -368,12 +435,12 @@ export const ChatKnowledge = ({
368435

369436
<DropdownMenuSubContent
370437
alignOffset={spaceLayout.alignOffset}
438+
collisionPadding={BOTTOM_GAP}
371439
className="w-[280px] p-3 rounded-2xl shadow-2xl ml-2 border-slate-100 bg-white flex flex-col overflow-hidden"
372440
style={{
373441
'--tw-enter-duration': '0.35s',
374442
'--tw-enter-easing': 'ease-in-out',
375-
height: spaceLayout.maxH,
376-
maxHeight: MAX_SUB_HEIGHT,
443+
maxHeight: spaceLayout.maxH,
377444
} as React.CSSProperties}
378445
>
379446
<p className="text-sm leading-5 py-1.5 mb-1 font-medium shrink-0">{localize('com_ui_knowledge_space')}</p>
@@ -420,12 +487,12 @@ export const ChatKnowledge = ({
420487

421488
<DropdownMenuSubContent
422489
alignOffset={orgLayout.alignOffset}
490+
collisionPadding={BOTTOM_GAP}
423491
className="w-[280px] p-3 rounded-2xl shadow-2xl ml-2 border-slate-100 bg-white flex flex-col overflow-hidden"
424492
style={{
425493
'--tw-enter-duration': '0.35s',
426494
'--tw-enter-easing': 'ease-in-out',
427-
height: orgLayout.maxH,
428-
maxHeight: MAX_SUB_HEIGHT,
495+
maxHeight: orgLayout.maxH,
429496
} as React.CSSProperties}
430497
>
431498
<p className="text-sm leading-5 py-1.5 mb-1 font-medium shrink-0">{localize('com_tools_org_knowledge')}</p>

src/frontend/platform/src/pages/BuildPage/bench/LingSiWork.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,9 @@ export default function index() {
754754
formData.append('file', importFiles[0]);
755755

756756
setImportFilesData([importFiles[0]]); // Only save one file
757-
const result = await sopApi.UploadSopRecord(formData);
758-
console.log('API Response:', result); // For debugging
757+
const result = await captureAndAlertRequestErrorHoc(sopApi.UploadSopRecord(formData));
758+
console.log('API Response:', result);
759+
if (!result) return; // 失败已弹 toast
759760
const { error_rows, success_rows, repeat_rows } = result
760761
if (error_rows.length) {
761762
setValidationDialog({
@@ -772,9 +773,9 @@ export default function index() {
772773
formData.append('ignore_error', 'false');
773774
formData.append('override', 'false');
774775
formData.append('save_new', 'false');
775-
const res = await sopApi.UploadSopRecord(formData);
776+
const res = await captureAndAlertRequestErrorHoc(sopApi.UploadSopRecord(formData));
777+
if (!res) return;
776778

777-
console.log(res, repeat_rows);
778779
setImportDialogOpen(true)
779780
setDuplicateNames(repeat_rows);
780781
setDuplicateDialogOpen(true);

src/frontend/platform/src/pages/KnowledgePage/components/ExcelPreview.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -245,31 +245,40 @@ const ExcelPreview = ({ filePath }) => {
245245

246246
} else if (isXLSX || fileExt === "xls") {
247247
// ---------------- Excel ----------------
248-
let workbook;
248+
// 1. 用 SheetJS 解析表格数据(对 WPS / 低版本 Excel 容错更好)
249+
let wb;
249250
try {
250-
workbook = await XlsxPopulate.fromDataAsync(arrayBuffer);
251+
wb = XLSX.read(arrayBuffer, { type: "array" });
251252
} catch (e) {
252-
console.error("XlsxPopulate解析失败:", e);
253+
console.error("SheetJS解析失败:", e);
253254
throw new Error(t('excelParseFailed'));
254255
}
255256

256-
// 解析表格数据
257-
const sheetNames = workbook.sheets().map((s: any) => s.name());
257+
const sheetNames = wb.SheetNames;
258258
const parsedData: Record<string, any[][]> = {};
259259
sheetNames.forEach(sheetName => {
260-
const sheet = workbook.sheet(sheetName);
261-
const usedRange = sheet.usedRange();
262-
parsedData[sheetName] = cleanData(usedRange?.value() || [[]]);
260+
const aoa = XLSX.utils.sheet_to_json(wb.Sheets[sheetName], {
261+
header: 1,
262+
defval: "",
263+
}) as any[][];
264+
parsedData[sheetName] = cleanData(aoa);
263265
});
264266
setExcelData(parsedData);
265267
setSheets(sheetNames);
266268
setActiveSheet(sheetNames[0] || "");
267269

268-
// 提取图片
269-
const { images, imagePositions } = await extractImagesWithPositions(workbook);
270-
setImages(images);
271-
setImagePositions(imagePositions);
272-
console.log(`[ExcelPreview] 提取到 ${images.length} 张图片`);
270+
// 2. 图片提取单独尝试,失败不影响表格展示
271+
if (isXLSX) {
272+
try {
273+
const workbook = await XlsxPopulate.fromDataAsync(arrayBuffer);
274+
const { images, imagePositions } = await extractImagesWithPositions(workbook);
275+
setImages(images);
276+
setImagePositions(imagePositions);
277+
console.log(`[ExcelPreview] 提取到 ${images.length} 张图片`);
278+
} catch (e) {
279+
console.warn("[ExcelPreview] 图片提取失败,跳过:", e);
280+
}
281+
}
273282

274283
} else {
275284
throw new Error(t('unsupportedType', { type: fileExt }));

src/frontend/platform/src/pages/KnowledgePage/components/Files.tsx

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -340,59 +340,60 @@ export default function Files({ onPreview }) {
340340
</div>
341341
)}
342342

343-
{/* Top action bar */}
344-
{selectedFileObjs.length > 0 && (
345-
<div className="absolute top-[-62px] left-0 right-0 flex justify-center items-center p-2 border-b z-10">
346-
<div className="flex items-center">
347-
<div className="flex gap-2">
343+
{/* Top action bar combined */}
344+
<div className="absolute right-0 top-[-62px] flex flex-wrap md:flex-nowrap justify-end gap-2 md:gap-4 items-center z-[999] max-w-[calc(100vw-40px)] bg-background md:bg-transparent p-1 md:p-0 rounded-lg">
345+
346+
{/* Batch Actions */}
347+
{selectedFileObjs.length > 0 && (
348+
<div className="flex items-center gap-2 mr-1 md:mr-0 pr-2 md:pr-4 border-r border-gray-200 dark:border-gray-700">
349+
<Tip content={!isEditable && t('noOperationPermission')} side='bottom'>
350+
<Button
351+
variant="outline"
352+
onClick={handleBatchDelete}
353+
disabled={!isEditable}
354+
className="flex items-center gap-1 disabled:pointer-events-auto h-9 px-2 sm:px-4"
355+
>
356+
<Trash2 size={16} />
357+
<span className="hidden sm:inline">{t('delete')}</span>
358+
</Button>
359+
</Tip>
360+
{hasSelectedFailedFiles && (
348361
<Tip content={!isEditable && t('noOperationPermission')} side='bottom'>
349362
<Button
350363
variant="outline"
351-
onClick={handleBatchDelete}
364+
onClick={handleBatchRetry}
352365
disabled={!isEditable}
353-
className="flex items-center gap-1 disabled:pointer-events-auto"
366+
className="flex items-center gap-1 disabled:pointer-events-auto h-9 px-2 sm:px-4"
354367
>
355-
<Trash2 size={16} />
356-
{t('delete')}
368+
<RotateCw size={16} />
369+
<span className="hidden sm:inline">{t('retry')}</span>
357370
</Button>
358371
</Tip>
359-
{hasSelectedFailedFiles && (
360-
<Tip content={!isEditable && t('noOperationPermission')} side='bottom'>
361-
<Button
362-
variant="outline"
363-
onClick={handleBatchRetry}
364-
disabled={!isEditable}
365-
className="flex items-center gap-1 disabled:pointer-events-auto"
366-
>
367-
<RotateCw size={16} />
368-
{t('retry')}
369-
</Button>
370-
</Tip>
371-
)}
372-
</div>
372+
)}
373373
</div>
374-
</div>
375-
)}
376-
377-
<div className="absolute right-0 top-[-62px] flex gap-4 items-center z-999">
378-
<SearchInput placeholder={t('searchFileName')} onChange={(e) => {
379-
search(e.target.value);
380-
setSelectedFileObjs([]);
381-
setIsAllSelected(false);
382-
}} />
383-
<Button
384-
variant="outline"
385-
onClick={() => setMetadataOpen(true)}
386-
className="px-4 whitespace-nowrap"
387-
>
388-
<ClipboardPenLine size={16} strokeWidth={1.5} className="mr-1" />
389-
{t('metaData')}
390-
</Button>
391-
{isEditable && (
392-
<Link to={`/filelib/upload/${id}`}>
393-
<Button className="px-8">{t('uploadFile')}</Button>
394-
</Link>
395374
)}
375+
376+
{/* Regular actions */}
377+
<div className="flex items-center gap-2 md:gap-4">
378+
<SearchInput placeholder={t('searchFileName')} onChange={(e) => {
379+
search(e.target.value);
380+
setSelectedFileObjs([]);
381+
setIsAllSelected(false);
382+
}} />
383+
<Button
384+
variant="outline"
385+
onClick={() => setMetadataOpen(true)}
386+
className="px-2 md:px-4 whitespace-nowrap h-9"
387+
>
388+
<ClipboardPenLine size={16} strokeWidth={1.5} className="mr-0 md:mr-1" />
389+
<span className="hidden md:inline">{t('metaData')}</span>
390+
</Button>
391+
{isEditable && (
392+
<Link to={`/filelib/upload/${id}`}>
393+
<Button className="px-4 md:px-8 h-9">{t('uploadFile')}</Button>
394+
</Link>
395+
)}
396+
</div>
396397
</div>
397398

398399
<div className="h-[calc(100vh-180px)] overflow-y-auto pb-20">

src/frontend/platform/src/pages/KnowledgePage/components/PreviewResult.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ export default function PreviewResult({
115115
break;
116116
case 'completed':
117117
setEtl(data.parse_type)
118+
// 记录该文件已在当前 previewCount 下解析过,下次切回时走缓存
119+
prevPreviewCountMapRef.current[currentFile.id] = previewCount;
118120
// 解析完成:处理结果(对应原 .then(res) 逻辑)
119121
handlePreviewResult(true);
120122
setChunks(data.chunks.map(chunk => ({

0 commit comments

Comments
 (0)