Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 112 additions & 4 deletions webapp/_webapp/src/components/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cn, Tab, Tabs as NextTabs } from "@heroui/react";
import { Icon } from "@iconify/react";
import { ReactNode, forwardRef, useImperativeHandle, useCallback } from "react";
import { ReactNode, forwardRef, useImperativeHandle, useCallback, useRef, useEffect } from "react";
import { useConversationUiStore } from "../stores/conversation/conversation-ui-store";
import { useAuthStore } from "../stores/auth-store";
import { Avatar } from "./avatar";
Expand All @@ -22,16 +22,112 @@ type TabProps = {
items: TabItem[];
};

// Constants for width limits
const MIN_TAB_ITEMS_WIDTH = 64; // Minimum width (w-16 = 64px)
const MAX_TAB_ITEMS_WIDTH = 200; // Maximum width
const COLLAPSE_THRESHOLD = 113; // Width threshold to auto-collapse text

export const Tabs = forwardRef<TabRef, TabProps>(({ items }, ref) => {
const { user } = useAuthStore();
const { activeTab, setActiveTab, sidebarCollapsed } = useConversationUiStore();
const {
activeTab,
setActiveTab,
sidebarCollapsed,
setSidebarCollapsed,
tabItemsWidth,
setTabItemsWidth
} = useConversationUiStore();
const { hideAvatar } = useSettingStore();
const { minimalistMode } = useSettingStore();

const resizeHandleRef = useRef<HTMLDivElement>(null);
const isResizingRef = useRef(false);
const tabItemsWidthRef = useRef(tabItemsWidth);
const mouseMoveHandlerRef = useRef<((e: MouseEvent) => void) | null>(null);
const mouseUpHandlerRef = useRef<(() => void) | null>(null);

// Keep ref in sync with tabItemsWidth
useEffect(() => {
tabItemsWidthRef.current = tabItemsWidth;
}, [tabItemsWidth]);

// Auto-collapse based on width
useEffect(() => {
const shouldCollapse = tabItemsWidth < COLLAPSE_THRESHOLD;
// Get current state to avoid stale closure
const currentCollapsed = useConversationUiStore.getState().sidebarCollapsed;
// Only update if the state doesn't match the desired state
if (shouldCollapse !== currentCollapsed) {
setSidebarCollapsed(shouldCollapse);
}
}, [tabItemsWidth, setSidebarCollapsed]); // Only depend on tabItemsWidth to avoid loops

useImperativeHandle(ref, () => ({
setSelectedTab: setActiveTab,
}));

// Cleanup function to reset resize state and remove event listeners
const cleanupResizeState = useCallback(() => {
isResizingRef.current = false;
document.body.style.cursor = "default";
document.body.style.userSelect = "";
if (resizeHandleRef.current) {
resizeHandleRef.current.classList.remove("resizing");
}
// Remove event listeners if they exist
if (mouseMoveHandlerRef.current) {
document.removeEventListener("mousemove", mouseMoveHandlerRef.current);
mouseMoveHandlerRef.current = null;
}
if (mouseUpHandlerRef.current) {
document.removeEventListener("mouseup", mouseUpHandlerRef.current);
mouseUpHandlerRef.current = null;
}
}, []);

// Cleanup on unmount to prevent leaks if component unmounts during resize
useEffect(() => {
return () => {
if (isResizingRef.current) {
cleanupResizeState();
}
};
}, [cleanupResizeState]);

// Handle resize drag
const handleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

isResizingRef.current = true;
const startX = e.clientX;
const startWidth = tabItemsWidthRef.current;

const handleMouseMove = (e: MouseEvent) => {
if (!isResizingRef.current) return;
e.preventDefault();
const delta = e.clientX - startX;
const newWidth = Math.max(MIN_TAB_ITEMS_WIDTH, Math.min(MAX_TAB_ITEMS_WIDTH, startWidth + delta));
setTabItemsWidth(newWidth);
};

const handleMouseUp = () => {
cleanupResizeState();
};

// Store handlers in refs so they can be cleaned up on unmount
mouseMoveHandlerRef.current = handleMouseMove;
mouseUpHandlerRef.current = handleMouseUp;

document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
if (resizeHandleRef.current) {
resizeHandleRef.current.classList.add("resizing");
}
}, [setTabItemsWidth, cleanupResizeState]);

const renderTabItem = useCallback(
(item: TabItem) => {
const tabTitle = (
Expand All @@ -54,10 +150,22 @@ export const Tabs = forwardRef<TabRef, TabProps>(({ items }, ref) => {
[sidebarCollapsed],
);

const width = sidebarCollapsed ? "w-16" : minimalistMode ? "w-[118px]" : "w-[140px]";
return (
<>
<div className={cn("pd-app-tab-items noselect transition-all duration-300", width)}>
<div
className="pd-app-tab-items noselect relative"
style={{ width: `${tabItemsWidth}px` }}
>
{/* Resize handle on the right edge */}
<div
ref={resizeHandleRef}
className="pd-tab-items-resize-handle"
onMouseDown={handleMouseDown}
>
{/* Visual indicator line */}
<div className="resize-handle-indicator" />
</div>

{!hideAvatar && <Avatar name={user?.name || "User"} src={user?.picture} className="pd-avatar" />}

<NextTabs
Expand Down
2 changes: 1 addition & 1 deletion webapp/_webapp/src/devtool/VariableInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const VariableInput = ({
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<div className="flex flex-col gap-2 items-start text-xs text-gray-500 overflow-hidden text-ellipsis text-nowrap">
<div className="flex flex-col gap-2 items-start text-xs text-gray-500 break-all max-w-full">
current: {value}
</div>
</div>
Expand Down
29 changes: 17 additions & 12 deletions webapp/_webapp/src/devtool/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,35 @@ import { useAuthStore } from "../stores/auth-store";
import { useCallback, useEffect, useState } from "react";
import { getCookies } from "../intermediate";
import { TooltipArea } from "./tooltip";
import { storage } from "../libs/storage";
import { DevTools } from "../views/devtools";
import { useDevtoolStore } from "../stores/devtool-store";

const App = () => {
const { token, refreshToken, setToken, setRefreshToken } = useAuthStore();
const [projectId, setProjectId] = useState(storage.getItem("pd.projectId") ?? "");
const [overleafSession, setOverleafSession] = useState(storage.getItem("pd.auth.overleafSession") ?? "");
const [gclb, setGclb] = useState(storage.getItem("pd.auth.gclb") ?? "");

const [projectId, setProjectId] = useState(localStorage.getItem("pd.projectId") ?? "");
const [overleafSession, setOverleafSession] = useState(localStorage.getItem("pd.auth.overleafSession") ?? "");
const [gclb, setGclb] = useState(localStorage.getItem("pd.auth.gclb") ?? "");
const { showTool } = useDevtoolStore();

useEffect(() => {
getCookies(window.location.hostname).then((cookies) => {
setOverleafSession(cookies.session ?? storage.getItem("pd.auth.overleafSession") ?? "");
setGclb(cookies.gclb ?? storage.getItem("pd.auth.gclb") ?? "");
setOverleafSession(cookies.session ?? localStorage.getItem("pd.auth.overleafSession") ?? "");
setGclb(cookies.gclb ?? localStorage.getItem("pd.auth.gclb") ?? "");
});
}, []);

const setProjectId_ = useCallback((projectId: string) => {
storage.setItem("pd.projectId", projectId);
localStorage.setItem("pd.projectId", projectId);
setProjectId(projectId);
}, []);

const setOverleafSession_ = useCallback((overleafSession: string) => {
storage.setItem("pd.auth.overleafSession", overleafSession);
localStorage.setItem("pd.auth.overleafSession", overleafSession);
setOverleafSession(overleafSession);
}, []);

const setGclb_ = useCallback((gclb: string) => {
storage.setItem("pd.auth.gclb", gclb);
localStorage.setItem("pd.auth.gclb", gclb);
setGclb(gclb);
}, []);

Expand Down Expand Up @@ -69,9 +71,9 @@ const App = () => {
/>
</div>
</div>
<div className="flex flex-col gap-2 w-50%">
<div className="flex flex-col gap-2 flex-1 min-w-0">
<TooltipArea>
<div className="whitespace-pre-wrap">
<div className="whitespace-pre-wrap break-all max-w-full font-mono text-sm bg-slate-50 p-4 rounded-lg border border-slate-200">
{JSON.stringify(
{
projectId,
Expand All @@ -86,6 +88,9 @@ const App = () => {
</div>
</TooltipArea>
</div>
<div className="flex flex-col gap-2 flex-1 min-w-0">
{import.meta.env.DEV && showTool && <DevTools />}
</div>
</main>
);
};
Expand Down
34 changes: 17 additions & 17 deletions webapp/_webapp/src/devtool/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@
<link rel="stylesheet" href="/index.css" />
<title>Paper Debugger Dev Tools</title>
</head>
<body class="p-4">
<div id="anchor" class="max-w-xl mb-4 px-4 border-slate-200 border rounded-lg">
<div class="toolbar-left p-4">
<div class="toolbar-item text-xl font-bold mb-4">PaperDebugger Dev Tools</div>
<body class="p-4 bg-gray-50 min-h-screen overflow-x-hidden">
<div class="ide-redesign-body" style="display: flex; flex-direction: row; width: 100%; height: 100vh;">
<div class="ide-redesign-inner" style="flex: 1; min-width: 0; overflow: hidden;">
<div id="devtool-container" class="max-w-7xl mx-auto space-y-4 w-full">
<!-- Header -->
<div id="anchor" class="bg-white shadow-sm border-slate-200 border rounded-lg">
<div class="toolbar-left p-4">
<div class="toolbar-item text-xl font-bold mb-4">PaperDebugger Dev Tools</div>
</div>
</div>

<!-- Dev Tools Root -->
<div id="root-devtools" class="bg-white shadow-sm border-slate-200 border rounded-lg p-4 overflow-hidden"></div>

<!-- Paper Debugger Root -->
<div id="root-paper-debugger" class="hidden"></div>
</div>
</div>
</div>
<div id="root-devtools"></div>
<div id="root-paper-debugger"></div>
<div>
Vertical Federated Learning (VFL) is a crucial paradigm for training machine learning models on
feature-partitioned, distributed data. However, due to privacy restrictions, few public real-world VFL datasets
exist for algorithm evaluation, and these represent a limited array of feature distributions. Existing benchmarks
often resort to synthetic datasets, derived from arbitrary feature splits from a global set, which only capture a
subset of feature distributions, leading to inadequate algorithm performance assessment. This paper addresses
these shortcomings by introducing two key factors affecting VFL performance - feature importance and feature
correlation - and proposing associated evaluation metrics and dataset splitting methods. Additionally, we
introduce a real VFL dataset to address the deficit in image-image VFL scenarios. Our comprehensive evaluation of
cutting-edge VFL algorithms provides valuable insights for future research in the field.
</div>
<script type="module" src="index.tsx"></script>
</body>
</html>
106 changes: 106 additions & 0 deletions webapp/_webapp/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ body {
sans-serif;
}

/* Overleaf Compatibility Fixes (tricks) */
img, svg {
display: inline-block;
vertical-align: middle;
}

/* Block-level media elements */
video, canvas, audio, iframe, embed, object {
display: block;
}

.collapse {
visibility: visible !important;
}
/* Overleaf Compatibility Fixes (tricks) */


/* Others */
.noselect {
-webkit-touch-callout: none;
Expand Down Expand Up @@ -245,6 +262,8 @@ body {
gap: 1rem;
@apply bg-gray-100 px-3 pb-3 pt-12;
z-index: 888;
position: relative;
flex-shrink: 0;
}

.pd-app-tab-content {
Expand Down Expand Up @@ -576,3 +595,90 @@ body {
vertical-align: top;
text-align: right;
}
/* Embed Sidebar Styles */
#pd-embed-sidebar {
background-color: var(--pd-default-bg);
position: relative;
overflow: hidden;
flex-shrink: 0;
display: flex;
flex-direction: column;
}

#pd-embed-sidebar .pd-app-container {
border-radius: 0;
border: none;
border-left: 1px solid var(--pd-border-color);
background-color: var(--pd-default-bg);
position: relative;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}

.pd-embed-resize-handle {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
background-color: transparent;
transition: background-color 0.2s, opacity 0.2s;
z-index: 10000;
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
}

.pd-embed-resize-handle:hover {
background-color: rgba(59, 130, 246, 0.1);
}

.pd-embed-resize-handle:hover .resize-handle-indicator {
opacity: 1 !important;
}

.resize-handle-indicator {
width: 2px;
height: 40px;
background-color: var(--pd-primary-color, #3b82f6);
border-radius: 1px;
opacity: 0;
transition: opacity 0.2s;
}

/* Tab Items Resize Handle */
.pd-tab-items-resize-handle {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
background-color: transparent;
transition: background-color 0.2s, opacity 0.2s;
z-index: 1000;
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
}

.pd-tab-items-resize-handle:hover {
background-color: rgba(59, 130, 246, 0.1);
}

.pd-tab-items-resize-handle:hover .resize-handle-indicator {
opacity: 1 !important;
}

.pd-tab-items-resize-handle.resizing {
background-color: rgba(59, 130, 246, 0.2) !important;
}

.pd-tab-items-resize-handle.resizing .resize-handle-indicator {
opacity: 1 !important;
}
Loading