From 8af71c5c507648adb1c13b322b7b9177c5f34416 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 02:51:31 +0800 Subject: [PATCH 01/15] feat: embed --- webapp/_webapp/src/index.css | 35 +++ .../conversation/conversation-ui-store.ts | 11 +- webapp/_webapp/src/views/body.tsx | 8 + webapp/_webapp/src/views/embed-sidebar.tsx | 201 ++++++++++++++++++ webapp/_webapp/src/views/index.tsx | 154 +++----------- .../_webapp/src/views/window-controller.tsx | 124 +++++++++++ 6 files changed, 401 insertions(+), 132 deletions(-) create mode 100644 webapp/_webapp/src/views/body.tsx create mode 100644 webapp/_webapp/src/views/embed-sidebar.tsx create mode 100644 webapp/_webapp/src/views/window-controller.tsx diff --git a/webapp/_webapp/src/index.css b/webapp/_webapp/src/index.css index e2c0714d..4355fa87 100644 --- a/webapp/_webapp/src/index.css +++ b/webapp/_webapp/src/index.css @@ -576,3 +576,38 @@ 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: fixed; + height: unset !important; +} + +.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; + z-index: 10000; + pointer-events: auto; +} + +.pd-embed-resize-handle:hover { + background-color: var(--pd-primary-color, #3b82f6); +} \ No newline at end of file diff --git a/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts b/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts index 6a97156f..1e0422a2 100644 --- a/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts +++ b/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts @@ -8,6 +8,7 @@ export const DISPLAY_MODES = [ { key: "floating", label: "Floating" }, { key: "right-fixed", label: "Right Fixed" }, { key: "bottom-fixed", label: "Bottom Fixed" }, + { key: "embed", label: "Embed Sidebar" }, ] as const; export type DisplayMode = (typeof DISPLAY_MODES)[number]["key"]; @@ -41,6 +42,9 @@ interface ConversationUiStore { rightFixedWidth: number; setRightFixedWidth: (rightFixedWidth: number) => void; + embedWidth: number; + setEmbedWidth: (embedWidth: number) => void; + isOpen: boolean; // for the main drawer setIsOpen: (isOpen: boolean) => void; @@ -91,6 +95,9 @@ export const useConversationUiStore = create()( rightFixedWidth: 580, setRightFixedWidth: (rightFixedWidth: number) => set({ rightFixedWidth }), + embedWidth: 480, + setEmbedWidth: (embedWidth: number) => set({ embedWidth }), + isOpen: false, setIsOpen: (isOpen: boolean) => set({ isOpen }), @@ -110,8 +117,8 @@ export const useConversationUiStore = create()( set({ floatingX: 100, floatingY: 100, - floatingWidth: 620, - floatingHeight: 200, + floatingWidth: 500, + floatingHeight: 500, displayMode: "floating", }); }, diff --git a/webapp/_webapp/src/views/body.tsx b/webapp/_webapp/src/views/body.tsx new file mode 100644 index 00000000..7cd30d20 --- /dev/null +++ b/webapp/_webapp/src/views/body.tsx @@ -0,0 +1,8 @@ +import { useAuthStore } from "../stores/auth-store"; +import { PaperDebugger } from "../paperdebugger"; +import { Login } from "./login"; + +export const Body = () => { + const { isAuthenticated } = useAuthStore(); + return isAuthenticated() ? : ; +}; diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx new file mode 100644 index 00000000..e2adc0c2 --- /dev/null +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -0,0 +1,201 @@ +import { useEffect, useState, useRef } from "react"; +import { createPortal } from "react-dom"; +import { useConversationUiStore } from "../stores/conversation/conversation-ui-store"; +import { PdAppContainer } from "../components/pd-app-container"; +import { WindowController } from "./window-controller"; +import { Body } from "./body"; +import { onElementAppeared } from "../libs/helpers"; + +export const EmbedSidebar = () => { + const [container, setContainer] = useState(null); + const { embedWidth, isOpen, setEmbedWidth } = useConversationUiStore(); + const resizeHandleRef = useRef(null); + const embedWidthRef = useRef(embedWidth); + const originalBodyStyleRef = useRef<{ + display?: string; + flexDirection?: string; + }>({}); + + // Keep ref in sync with embedWidth + useEffect(() => { + embedWidthRef.current = embedWidth; + }, [embedWidth]); + + // Update container width when embedWidth changes + useEffect(() => { + if (container) { + container.style.width = `${embedWidth}px`; + } + }, [container, embedWidth]); + + useEffect(() => { + if (!isOpen) return; + + // Try to find Overleaf's body element first (extension mode) + const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; + + // If not found, use the main body for dev:chat mode + const targetElement = ideBody || document.body; + + if (!targetElement) return; + + // Store original styles + originalBodyStyleRef.current = { + display: targetElement.style.display || "", + flexDirection: targetElement.style.flexDirection || "", + }; + + // Create sidebar container + const sidebarDiv = document.createElement("div"); + sidebarDiv.id = "pd-embed-sidebar"; + sidebarDiv.style.width = `${embedWidth}px`; + sidebarDiv.style.height = "100vh"; + sidebarDiv.style.display = "flex"; + sidebarDiv.style.flexDirection = "column"; + sidebarDiv.style.borderLeft = "1px solid var(--pd-border-color)"; + sidebarDiv.style.flexShrink = "0"; + sidebarDiv.style.position = ideBody ? "relative" : "fixed"; + sidebarDiv.style.right = "0"; + sidebarDiv.style.top = "0"; + + // Modify parent container to flex layout (only in extension mode) + if (ideBody) { + targetElement.style.display = "flex"; + targetElement.style.flexDirection = "row"; + + // Find the main content area and ensure it can grow + const mainContent = targetElement.querySelector(".ide-redesign-toolbar-menu-bar, .editor-area"); + if (mainContent) { + (mainContent as HTMLElement).style.flex = "1"; + (mainContent as HTMLElement).style.minWidth = "0"; + } + } else { + // In dev:chat mode, adjust body to make room for sidebar + const rootPaperDebugger = document.getElementById("root-paper-debugger"); + if (rootPaperDebugger) { + (rootPaperDebugger as HTMLElement).style.marginRight = `${embedWidth}px`; + (rootPaperDebugger as HTMLElement).style.transition = "margin-right 0.2s"; + } + } + + // Append sidebar to target element + targetElement.appendChild(sidebarDiv); + setContainer(sidebarDiv); + + return () => { + // Cleanup + const sidebarDiv = document.getElementById("pd-embed-sidebar"); + if (sidebarDiv) { + sidebarDiv.remove(); + } + + // Restore original styles for extension mode + const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; + if (ideBody && originalBodyStyleRef.current) { + ideBody.style.display = originalBodyStyleRef.current.display || ""; + ideBody.style.flexDirection = originalBodyStyleRef.current.flexDirection || ""; + } + + // Restore margin for dev:chat mode + const rootPaperDebugger = document.getElementById("root-paper-debugger"); + if (rootPaperDebugger) { + (rootPaperDebugger as HTMLElement).style.marginRight = ""; + } + + setContainer(null); + }; + }, [isOpen]); + + // Handle resize - only set up when container exists + useEffect(() => { + if (!container || !isOpen) return; + + const handleRef = resizeHandleRef.current; + if (!handleRef) return; + + let isResizing = false; + let startX = 0; + let startWidth = embedWidthRef.current; + + const handleMouseDown = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + isResizing = true; + startX = e.clientX; + startWidth = embedWidthRef.current; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!isResizing) return; + e.preventDefault(); + const delta = e.clientX - startX; + const newWidth = Math.max(300, Math.min(800, startWidth - delta)); // min 300px, max 800px + setEmbedWidth(newWidth); + }; + + const handleMouseUp = () => { + isResizing = false; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "default"; + document.body.style.userSelect = ""; + }; + + handleRef.addEventListener("mousedown", handleMouseDown); + + return () => { + handleRef.removeEventListener("mousedown", handleMouseDown); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "default"; + document.body.style.userSelect = ""; + }; + }, [container, isOpen, setEmbedWidth]); + + if (!container || !isOpen) return null; + + return createPortal( +
+ {/* Resize handle on the left */} +
{ + (e.currentTarget as HTMLElement).style.backgroundColor = "var(--pd-primary-color, #3b82f6)"; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLElement).style.backgroundColor = "transparent"; + }} + /> + + + + +
, + container, + ); +}; diff --git a/webapp/_webapp/src/views/index.tsx b/webapp/_webapp/src/views/index.tsx index ff0e1910..3d6a8187 100644 --- a/webapp/_webapp/src/views/index.tsx +++ b/webapp/_webapp/src/views/index.tsx @@ -1,129 +1,16 @@ -import { cn, Tooltip } from "@heroui/react"; +import { cn } from "@heroui/react"; import { Rnd } from "react-rnd"; import { useCallback, useState, useEffect, useMemo } from "react"; -import { PaperDebugger } from "../paperdebugger"; import { createPortal } from "react-dom"; import { COLLAPSED_HEIGHT, useConversationUiStore } from "../stores/conversation/conversation-ui-store"; -import { Icon } from "@iconify/react/dist/iconify.js"; import { debounce } from "../libs/helpers"; -import { Login } from "./login"; import { PdAppContainer } from "../components/pd-app-container"; -import { PdAppControlTitleBar } from "../components/pd-app-control-title-bar"; -import { PdAppSmallControlButton } from "../components/pd-app-small-control-button"; -import { useAuthStore } from "../stores/auth-store"; import { useSettingStore } from "../stores/setting-store"; +import { WindowController } from "./window-controller"; +import { Body } from "./body"; +import { EmbedSidebar } from "./embed-sidebar"; -const PositionController = () => { - const { - sidebarCollapsed, - floatingHeight, - bottomFixedHeight, - setHeightCollapseRequired, - setDisplayMode, - displayMode, - } = useConversationUiStore(); - return ( -
- - { - if (floatingHeight < COLLAPSED_HEIGHT) { - setHeightCollapseRequired(true); - } else { - setHeightCollapseRequired(false); - } - setDisplayMode("floating"); - }} - > - - - - - { - if (bottomFixedHeight < COLLAPSED_HEIGHT) { - setHeightCollapseRequired(true); - } else { - setHeightCollapseRequired(false); - } - setDisplayMode("bottom-fixed"); - }} - > - - - - - { - if (window.innerHeight < COLLAPSED_HEIGHT) { - setHeightCollapseRequired(true); - } else { - setHeightCollapseRequired(false); - } - setDisplayMode("right-fixed"); - }} - > - - - -
- ); -}; - -const WindowController = () => { - const { sidebarCollapsed, setSidebarCollapsed, setIsOpen } = useConversationUiStore(); - const CompactHeader = useMemo(() => { - return ( - -
-
- - setIsOpen(false)}> - - - - - - setSidebarCollapsed(!sidebarCollapsed)}> - - - -
-
-
- ); - }, [sidebarCollapsed, setSidebarCollapsed, setIsOpen]); - return CompactHeader; -}; - -const Body = () => { - const { isAuthenticated } = useAuthStore(); - return isAuthenticated() ? : ; -}; export const MainDrawer = () => { const { displayMode, isOpen } = useConversationUiStore(); @@ -149,6 +36,26 @@ export const MainDrawer = () => { return () => window.removeEventListener("resize", handleResize); }, []); + const handleResize = useCallback( + (...args: unknown[]) => { + const [, , /* _e */ /* _dir */ ref] = args; + if (ref && ref instanceof HTMLElement && ref.offsetHeight < COLLAPSED_HEIGHT) { + setHeightCollapseRequired(true); + } else { + setHeightCollapseRequired(false); + } + }, + [setHeightCollapseRequired], + ); + const debouncedHandleResize = useMemo(() => debounce(handleResize, 100), [handleResize]); + + // Handle embed mode separately + if (displayMode === "embed") { + return ; + } + + // Handle other modes with Rnd + // Layout configs for each mode type RndProps = React.ComponentProps; let rndProps: Partial = {}; @@ -184,19 +91,6 @@ export const MainDrawer = () => { }; } - const handleResize = useCallback( - (...args: unknown[]) => { - const [, , /* _e */ /* _dir */ ref] = args; - if (ref && ref instanceof HTMLElement && ref.offsetHeight < COLLAPSED_HEIGHT) { - setHeightCollapseRequired(true); - } else { - setHeightCollapseRequired(false); - } - }, - [setHeightCollapseRequired], - ); - const debouncedHandleResize = useMemo(() => debounce(handleResize, 100), [handleResize]); - return createPortal( { + const { sidebarCollapsed, setSidebarCollapsed, setIsOpen } = useConversationUiStore(); + const CompactHeader = useMemo(() => { + return ( + +
+
+ + setIsOpen(false)}> + + + + + + setSidebarCollapsed(!sidebarCollapsed)}> + + + +
+
+
+ ); + }, [sidebarCollapsed, setSidebarCollapsed, setIsOpen]); + return CompactHeader; +}; + +const PositionController = () => { + const { + sidebarCollapsed, + floatingHeight, + bottomFixedHeight, + setHeightCollapseRequired, + setDisplayMode, + displayMode, + } = useConversationUiStore(); + return ( +
+ + { + if (floatingHeight < COLLAPSED_HEIGHT) { + setHeightCollapseRequired(true); + } else { + setHeightCollapseRequired(false); + } + setDisplayMode("floating"); + }} + > + + + + + { + if (bottomFixedHeight < COLLAPSED_HEIGHT) { + setHeightCollapseRequired(true); + } else { + setHeightCollapseRequired(false); + } + setDisplayMode("bottom-fixed"); + }} + > + + + + + { + if (window.innerHeight < COLLAPSED_HEIGHT) { + setHeightCollapseRequired(true); + } else { + setHeightCollapseRequired(false); + } + setDisplayMode("right-fixed"); + }} + > + + + + + { + setDisplayMode("embed"); + }} + > + + + +
+ ); +}; From 03dca9c739514dec665a67e0156bd15e1e4aa5e8 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 02:58:24 +0800 Subject: [PATCH 02/15] chore: devtools enhance --- webapp/_webapp/src/devtool/VariableInput.tsx | 2 +- webapp/_webapp/src/devtool/app.tsx | 4 +- webapp/_webapp/src/devtool/index.html | 48 ++-- webapp/_webapp/src/views/devtools/index.tsx | 240 +++++++++++-------- 4 files changed, 177 insertions(+), 117 deletions(-) diff --git a/webapp/_webapp/src/devtool/VariableInput.tsx b/webapp/_webapp/src/devtool/VariableInput.tsx index 7c0c29c0..88595742 100644 --- a/webapp/_webapp/src/devtool/VariableInput.tsx +++ b/webapp/_webapp/src/devtool/VariableInput.tsx @@ -32,7 +32,7 @@ export const VariableInput = ({ value={value} onChange={(e) => setValue(e.target.value)} /> -
+
current: {value}
diff --git a/webapp/_webapp/src/devtool/app.tsx b/webapp/_webapp/src/devtool/app.tsx index 577c4d57..cd75e17c 100644 --- a/webapp/_webapp/src/devtool/app.tsx +++ b/webapp/_webapp/src/devtool/app.tsx @@ -68,9 +68,9 @@ const App = () => { />
-
+
-
+
{JSON.stringify( { projectId, diff --git a/webapp/_webapp/src/devtool/index.html b/webapp/_webapp/src/devtool/index.html index c4092bb1..f20bbcb1 100644 --- a/webapp/_webapp/src/devtool/index.html +++ b/webapp/_webapp/src/devtool/index.html @@ -9,24 +9,38 @@ Paper Debugger Dev Tools - -
-
-
PaperDebugger Dev Tools
+ +
+ +
+
+
PaperDebugger Dev Tools
+
+
+ + +
+ + +
+ + +
+

Paper Abstract

+
+

+ 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. +

+
-
-
-
-
- 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.
diff --git a/webapp/_webapp/src/views/devtools/index.tsx b/webapp/_webapp/src/views/devtools/index.tsx index f07f5f4e..b4ed5612 100644 --- a/webapp/_webapp/src/views/devtools/index.tsx +++ b/webapp/_webapp/src/views/devtools/index.tsx @@ -230,109 +230,155 @@ export const DevTools = () => { }; // --- Render --- + const maxWidth = typeof window !== 'undefined' ? window.innerWidth / 2 : 800; + const defaultWidth = Math.min(800, maxWidth); + return ( -
-

DevTools

- {/* Conversation section */} -
-

- Conversation ( - {isEmptyConversation() ? ( - empty - ) : ( - not empty - )} - ) - -

-
-

Selected Text

- - -
-
-

- Finalized Message ({currentConversation.messages.length}) - -

-
- - - - - +
+ {/* Header */} +
+

DevTools

+
+ + {/* Scrollable content */} +
+
+ {/* Conversation section */} +
+
+

+ Conversation ( + {isEmptyConversation() ? ( + empty + ) : ( + not empty + )} + ) +

+ +
+ + {/* Selected Text */} +
+

Selected Text

+
+ + +
+
+ + {/* Finalized Messages */} +
+
+

+ Finalized Messages ({currentConversation.messages.length}) +

+ +
+
+ + + + + +
+
-
- {/* Streaming Message section */} -
-

- Streaming Message -
- ({streamingMessage.parts.length} total, - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.PREPARING).length}{" "} - preparing, - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.FINALIZED).length}{" "} - finalized, - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.INCOMPLETE).length}{" "} - incomplete, - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.STALE).length} stale ) + + {/* Streaming Message section */} +
+
+

Streaming Message

+ +
+
+ ({streamingMessage.parts.length} total, + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.PREPARING).length}{" "} + preparing, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.FINALIZED).length}{" "} + finalized, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.INCOMPLETE).length}{" "} + incomplete, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.STALE).length} stale + + ) +
+ + {/* Preparing delay */} +
+ + setPreparingDelay(Number(e.target.value) || 0)} + /> +
+ + {/* Streaming buttons */} +
+ + + + + +
- -

-
-

Preparing delay (seconds):

- setPreparingDelay(Number(e.target.value) || 0)} - />
- - - - - -
From c24545937da3df479ceab109adca90816c526635 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 03:22:57 +0800 Subject: [PATCH 03/15] chore: reduce code for devtools --- webapp/_webapp/src/devtool/app.tsx | 8 +- webapp/_webapp/src/devtool/index.html | 24 +- webapp/_webapp/src/main.tsx | 4 - webapp/_webapp/src/views/devtools/index.tsx | 254 +++++++++----------- 4 files changed, 130 insertions(+), 160 deletions(-) diff --git a/webapp/_webapp/src/devtool/app.tsx b/webapp/_webapp/src/devtool/app.tsx index cd75e17c..a2597d65 100644 --- a/webapp/_webapp/src/devtool/app.tsx +++ b/webapp/_webapp/src/devtool/app.tsx @@ -3,13 +3,16 @@ import { useAuthStore } from "../stores/auth-store"; import { useCallback, useEffect, useState } from "react"; import { getCookies } from "../intermediate"; import { TooltipArea } from "./tooltip"; +import { DevTools } from "../views/devtools"; +import { useDevtoolStore } from "../stores/devtool-store"; const App = () => { const { token, refreshToken, setToken, setRefreshToken } = useAuthStore(); 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 ?? localStorage.getItem("pd.auth.overleafSession") ?? ""); @@ -85,6 +88,9 @@ const App = () => {
+
+ {import.meta.env.DEV && showTool && } +
); }; diff --git a/webapp/_webapp/src/devtool/index.html b/webapp/_webapp/src/devtool/index.html index f20bbcb1..deccbe0d 100644 --- a/webapp/_webapp/src/devtool/index.html +++ b/webapp/_webapp/src/devtool/index.html @@ -10,7 +10,7 @@ Paper Debugger Dev Tools -
+
@@ -19,28 +19,10 @@
-
+
-
- - -
-

Paper Abstract

-
-

- 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. -

-
-
+
diff --git a/webapp/_webapp/src/main.tsx b/webapp/_webapp/src/main.tsx index 95f2f0bc..1f13c841 100644 --- a/webapp/_webapp/src/main.tsx +++ b/webapp/_webapp/src/main.tsx @@ -13,11 +13,9 @@ import apiclient, { apiclientV2, getEndpointFromLocalStorage } from "./libs/apic import { Providers } from "./providers"; import { useAuthStore } from "./stores/auth-store"; import { useConversationUiStore } from "./stores/conversation/conversation-ui-store"; -import { useDevtoolStore } from "./stores/devtool-store"; import { useSelectionStore } from "./stores/selection-store"; import { useSettingStore } from "./stores/setting-store"; import { MainDrawer } from "./views"; -import { DevTools } from "./views/devtools"; import { usePromptLibraryStore } from "./stores/prompt-library-store"; import { TopMenuButton } from "./components/top-menu-button"; import { Logo } from "./components/logo"; @@ -68,7 +66,6 @@ export const Main = () => { } = useSelectionStore(); const [menuElement, setMenuElement] = useState(null); const { isOpen, setIsOpen } = useConversationUiStore(); - const { showTool: showDevTool } = useDevtoolStore(); const { settings, loadSettings, disableLineWrap } = useSettingStore(); const { login } = useAuthStore(); const { loadPrompts } = usePromptLibraryStore(); @@ -214,7 +211,6 @@ export const Main = () => { {buttonPortal} - {import.meta.env.DEV && showDevTool && } ); diff --git a/webapp/_webapp/src/views/devtools/index.tsx b/webapp/_webapp/src/views/devtools/index.tsx index b4ed5612..1772a411 100644 --- a/webapp/_webapp/src/views/devtools/index.tsx +++ b/webapp/_webapp/src/views/devtools/index.tsx @@ -1,4 +1,3 @@ -import { Rnd } from "react-rnd"; import { useSelectionStore } from "../../stores/selection-store"; import { Button, Input } from "@heroui/react"; import { useStreamingMessageStore } from "../../stores/streaming-message-store"; @@ -230,158 +229,145 @@ export const DevTools = () => { }; // --- Render --- - const maxWidth = typeof window !== 'undefined' ? window.innerWidth / 2 : 800; - const defaultWidth = Math.min(800, maxWidth); - return ( - -
- {/* Header */} -
-

DevTools

-
- - {/* Scrollable content */} -
-
- {/* Conversation section */} -
-
-

- Conversation ( - {isEmptyConversation() ? ( - empty - ) : ( - not empty - )} - ) -

- -
- - {/* Selected Text */} -
-

Selected Text

-
- - -
-
+
+ {/* Header */} +
+

DevTools

+
- {/* Finalized Messages */} -
-
-

- Finalized Messages ({currentConversation.messages.length}) -

- -
-
- - - - - -
-
+ {/* Scrollable content */} +
+
+ {/* Conversation section */} +
+
+

+ Conversation ( + {isEmptyConversation() ? ( + empty + ) : ( + not empty + )} + ) +

+
- {/* Streaming Message section */} -
-
-

Streaming Message

- -
-
- ({streamingMessage.parts.length} total, - - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.PREPARING).length}{" "} - preparing, - - - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.FINALIZED).length}{" "} - finalized, - - - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.INCOMPLETE).length}{" "} - incomplete, - - - {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.STALE).length} stale - - ) -
- - {/* Preparing delay */} -
- + {/* Selected Text */} +
+

Selected Text

+
setPreparingDelay(Number(e.target.value) || 0)} + placeholder="Selected Text" + value={selectedText ?? ""} + onChange={handleSelectedTextChange} /> +
+
- {/* Streaming buttons */} -
- -
+
+ - - - -
+ + {/* Streaming Message section */} +
+
+

Streaming Message

+ +
+
+ ({streamingMessage.parts.length} total, + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.PREPARING).length}{" "} + preparing, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.FINALIZED).length}{" "} + finalized, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.INCOMPLETE).length}{" "} + incomplete, + + + {streamingMessage.parts.filter((part) => part.status === MessageEntryStatus.STALE).length} stale + + ) +
+ + {/* Preparing delay */} +
+ + setPreparingDelay(Number(e.target.value) || 0)} + /> +
+ + {/* Streaming buttons */} +
+ + + + + + +
+
- +
); }; From 70c1ad42c30b2ec04343e68acf2df0dbad12beb0 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 03:34:20 +0800 Subject: [PATCH 04/15] embedding mode --- webapp/_webapp/src/devtool/index.html | 26 +-- webapp/_webapp/src/views/embed-sidebar.tsx | 185 ++++++++++++--------- 2 files changed, 122 insertions(+), 89 deletions(-) diff --git a/webapp/_webapp/src/devtool/index.html b/webapp/_webapp/src/devtool/index.html index deccbe0d..4116298e 100644 --- a/webapp/_webapp/src/devtool/index.html +++ b/webapp/_webapp/src/devtool/index.html @@ -10,19 +10,23 @@ Paper Debugger Dev Tools -
- -
-
-
PaperDebugger Dev Tools
-
-
+
+
+
+ +
+
+
PaperDebugger Dev Tools
+
+
- -
+ +
- - + + +
+
diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index e2adc0c2..1bfd97cb 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -1,16 +1,16 @@ -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState, useRef, useCallback } from "react"; import { createPortal } from "react-dom"; import { useConversationUiStore } from "../stores/conversation/conversation-ui-store"; import { PdAppContainer } from "../components/pd-app-container"; import { WindowController } from "./window-controller"; import { Body } from "./body"; -import { onElementAppeared } from "../libs/helpers"; export const EmbedSidebar = () => { const [container, setContainer] = useState(null); const { embedWidth, isOpen, setEmbedWidth } = useConversationUiStore(); const resizeHandleRef = useRef(null); const embedWidthRef = useRef(embedWidth); + const isResizingRef = useRef(false); const originalBodyStyleRef = useRef<{ display?: string; flexDirection?: string; @@ -21,65 +21,108 @@ export const EmbedSidebar = () => { embedWidthRef.current = embedWidth; }, [embedWidth]); + // Function to update main content area flex properties + const updateMainContentFlex = useCallback((ideBody: HTMLElement) => { + // Find or create ide-redesign-inner + let ideInner = ideBody.querySelector(".ide-redesign-inner") as HTMLElement | null; + if (!ideInner) { + ideInner = document.createElement("div"); + ideInner.className = "ide-redesign-inner"; + // Move all existing children (except sidebar) into ideInner + const children = Array.from(ideBody.children) as HTMLElement[]; + children.forEach((child) => { + if (child.id !== "pd-embed-sidebar" && !child.classList.contains("ide-redesign-inner")) { + ideInner!.appendChild(child); + } + }); + // Insert ideInner before sidebar (or at the beginning if no sidebar) + const sidebar = ideBody.querySelector("#pd-embed-sidebar"); + if (sidebar) { + ideBody.insertBefore(ideInner, sidebar); + } else { + ideBody.appendChild(ideInner); + } + } + + // Set flex properties for ide-redesign-inner + ideInner.style.flex = "1"; + ideInner.style.minWidth = "0"; + ideInner.style.overflow = "hidden"; + }, []); + // Update container width when embedWidth changes useEffect(() => { if (container) { container.style.width = `${embedWidth}px`; } - }, [container, embedWidth]); + + // Also update layout when embedWidth changes + if (isOpen) { + const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; + if (ideBody) { + // Re-apply flex layout + updateMainContentFlex(ideBody); + } + } + }, [container, embedWidth, isOpen, updateMainContentFlex]); + + // Handle window resize to ensure layout stays correct + useEffect(() => { + if (!isOpen || !container) return; + + const handleResize = () => { + const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; + if (ideBody) { + // Re-apply flex layout on window resize + updateMainContentFlex(ideBody); + } + }; + + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [isOpen, container, updateMainContentFlex]); useEffect(() => { if (!isOpen) return; - // Try to find Overleaf's body element first (extension mode) + // Find ide-redesign-body (works in both normal and dev mode) const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; - // If not found, use the main body for dev:chat mode - const targetElement = ideBody || document.body; - - if (!targetElement) return; + if (!ideBody) { + return; + } // Store original styles originalBodyStyleRef.current = { - display: targetElement.style.display || "", - flexDirection: targetElement.style.flexDirection || "", + display: ideBody.style.display || "", + flexDirection: ideBody.style.flexDirection || "", }; + // Ensure ide-redesign-inner exists and has correct flex properties + updateMainContentFlex(ideBody); + // Create sidebar container const sidebarDiv = document.createElement("div"); sidebarDiv.id = "pd-embed-sidebar"; + sidebarDiv.className = "pd-embed-sidebar"; sidebarDiv.style.width = `${embedWidth}px`; sidebarDiv.style.height = "100vh"; sidebarDiv.style.display = "flex"; sidebarDiv.style.flexDirection = "column"; sidebarDiv.style.borderLeft = "1px solid var(--pd-border-color)"; sidebarDiv.style.flexShrink = "0"; - sidebarDiv.style.position = ideBody ? "relative" : "fixed"; - sidebarDiv.style.right = "0"; - sidebarDiv.style.top = "0"; - - // Modify parent container to flex layout (only in extension mode) - if (ideBody) { - targetElement.style.display = "flex"; - targetElement.style.flexDirection = "row"; - - // Find the main content area and ensure it can grow - const mainContent = targetElement.querySelector(".ide-redesign-toolbar-menu-bar, .editor-area"); - if (mainContent) { - (mainContent as HTMLElement).style.flex = "1"; - (mainContent as HTMLElement).style.minWidth = "0"; - } - } else { - // In dev:chat mode, adjust body to make room for sidebar - const rootPaperDebugger = document.getElementById("root-paper-debugger"); - if (rootPaperDebugger) { - (rootPaperDebugger as HTMLElement).style.marginRight = `${embedWidth}px`; - (rootPaperDebugger as HTMLElement).style.transition = "margin-right 0.2s"; - } - } + sidebarDiv.style.position = "relative"; - // Append sidebar to target element - targetElement.appendChild(sidebarDiv); + // Modify parent container to flex layout + ideBody.style.display = "flex"; + ideBody.style.flexDirection = "row"; + ideBody.style.width = "100%"; + ideBody.style.overflow = "hidden"; + + // Append sidebar to ideBody (after ide-redesign-inner) + ideBody.appendChild(sidebarDiv); setContainer(sidebarDiv); return () => { @@ -89,72 +132,57 @@ export const EmbedSidebar = () => { sidebarDiv.remove(); } - // Restore original styles for extension mode - const ideBody = document.querySelector(".ide-redesign-body") as HTMLElement | null; + // Restore original styles if (ideBody && originalBodyStyleRef.current) { ideBody.style.display = originalBodyStyleRef.current.display || ""; ideBody.style.flexDirection = originalBodyStyleRef.current.flexDirection || ""; - } - - // Restore margin for dev:chat mode - const rootPaperDebugger = document.getElementById("root-paper-debugger"); - if (rootPaperDebugger) { - (rootPaperDebugger as HTMLElement).style.marginRight = ""; + ideBody.style.width = ""; + ideBody.style.overflow = ""; + + // Restore ide-redesign-inner flex properties + const ideInner = ideBody.querySelector(".ide-redesign-inner") as HTMLElement | null; + if (ideInner) { + ideInner.style.flex = ""; + ideInner.style.minWidth = ""; + ideInner.style.overflow = ""; + } } setContainer(null); }; - }, [isOpen]); + }, [isOpen, embedWidth, updateMainContentFlex]); - // Handle resize - only set up when container exists - useEffect(() => { - if (!container || !isOpen) return; + // Handle resize drag + const handleMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); - const handleRef = resizeHandleRef.current; - if (!handleRef) return; - - let isResizing = false; - let startX = 0; - let startWidth = embedWidthRef.current; - - const handleMouseDown = (e: MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - isResizing = true; - startX = e.clientX; - startWidth = embedWidthRef.current; - document.addEventListener("mousemove", handleMouseMove); - document.addEventListener("mouseup", handleMouseUp); - document.body.style.cursor = "col-resize"; - document.body.style.userSelect = "none"; - }; + isResizingRef.current = true; + const startX = e.clientX; + const startWidth = embedWidthRef.current; const handleMouseMove = (e: MouseEvent) => { - if (!isResizing) return; + if (!isResizingRef.current) return; e.preventDefault(); const delta = e.clientX - startX; - const newWidth = Math.max(300, Math.min(800, startWidth - delta)); // min 300px, max 800px + const maxWidth = window.innerWidth * 0.8; // 80% of window width + const newWidth = Math.max(300, Math.min(maxWidth, startWidth - delta)); // min 300px, max 80% of window setEmbedWidth(newWidth); }; const handleMouseUp = () => { - isResizing = false; + isResizingRef.current = false; document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.cursor = "default"; document.body.style.userSelect = ""; }; - handleRef.addEventListener("mousedown", handleMouseDown); - - return () => { - handleRef.removeEventListener("mousedown", handleMouseDown); - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - document.body.style.cursor = "default"; - document.body.style.userSelect = ""; - }; - }, [container, isOpen, setEmbedWidth]); + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + }, [setEmbedWidth]); if (!container || !isOpen) return null; @@ -184,6 +212,7 @@ export const EmbedSidebar = () => { zIndex: 10000, pointerEvents: "auto", }} + onMouseDown={handleMouseDown} onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = "var(--pd-primary-color, #3b82f6)"; }} From 705644a55f2e3bacb060a0909521135dbce96a94 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 03:40:27 +0800 Subject: [PATCH 05/15] feat: embed into side panel --- webapp/_webapp/src/index.css | 7 +++++-- webapp/_webapp/src/views/embed-sidebar.tsx | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/webapp/_webapp/src/index.css b/webapp/_webapp/src/index.css index 4355fa87..a2050f43 100644 --- a/webapp/_webapp/src/index.css +++ b/webapp/_webapp/src/index.css @@ -591,8 +591,11 @@ body { border: none; border-left: 1px solid var(--pd-border-color); background-color: var(--pd-default-bg); - position: fixed; - height: unset !important; + position: relative; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; } .pd-embed-resize-handle { diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index 1bfd97cb..bef62d77 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -108,17 +108,19 @@ export const EmbedSidebar = () => { sidebarDiv.id = "pd-embed-sidebar"; sidebarDiv.className = "pd-embed-sidebar"; sidebarDiv.style.width = `${embedWidth}px`; - sidebarDiv.style.height = "100vh"; + sidebarDiv.style.height = "100%"; // Use 100% to match parent height sidebarDiv.style.display = "flex"; sidebarDiv.style.flexDirection = "column"; sidebarDiv.style.borderLeft = "1px solid var(--pd-border-color)"; sidebarDiv.style.flexShrink = "0"; sidebarDiv.style.position = "relative"; + sidebarDiv.style.overflow = "hidden"; // Prevent overflow // Modify parent container to flex layout ideBody.style.display = "flex"; ideBody.style.flexDirection = "row"; ideBody.style.width = "100%"; + ideBody.style.height = "100vh"; // Ensure full viewport height ideBody.style.overflow = "hidden"; // Append sidebar to ideBody (after ide-redesign-inner) From 1995bbd9dc8a0c918eb35005504621852f7defac Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 04:03:55 +0800 Subject: [PATCH 06/15] chore: ux fix --- webapp/_webapp/src/components/tabs.tsx | 89 ++++++++++++++++++- webapp/_webapp/src/index.css | 61 ++++++++++++- .../conversation/conversation-ui-store.ts | 6 ++ webapp/_webapp/src/views/embed-sidebar.tsx | 23 +---- .../_webapp/src/views/window-controller.tsx | 24 ++--- 5 files changed, 162 insertions(+), 41 deletions(-) diff --git a/webapp/_webapp/src/components/tabs.tsx b/webapp/_webapp/src/components/tabs.tsx index d54dcbf2..172ec6a8 100644 --- a/webapp/_webapp/src/components/tabs.tsx +++ b/webapp/_webapp/src/components/tabs.tsx @@ -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"; @@ -22,16 +22,85 @@ 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(({ 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(null); + const isResizingRef = useRef(false); + const tabItemsWidthRef = useRef(tabItemsWidth); + + // 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, })); + // 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 = () => { + isResizingRef.current = false; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "default"; + document.body.style.userSelect = ""; + if (resizeHandleRef.current) { + resizeHandleRef.current.classList.remove("resizing"); + } + }; + + 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]); + const renderTabItem = useCallback( (item: TabItem) => { const tabTitle = ( @@ -54,10 +123,22 @@ export const Tabs = forwardRef(({ items }, ref) => { [sidebarCollapsed], ); - const width = sidebarCollapsed ? "w-16" : minimalistMode ? "w-[118px]" : "w-[140px]"; return ( <> -
+
+ {/* Resize handle on the right edge */} +
+ {/* Visual indicator line */} +
+
+ {!hideAvatar && } void; + tabItemsWidth: number; + setTabItemsWidth: (tabItemsWidth: number) => void; + heightCollapseRequired: boolean; setHeightCollapseRequired: (heightCollapseRequired: boolean) => void; @@ -107,6 +110,9 @@ export const useConversationUiStore = create()( sidebarCollapsed: false, setSidebarCollapsed: (sidebarCollapsed: boolean) => set({ sidebarCollapsed }), + tabItemsWidth: 140, // Default width in pixels + setTabItemsWidth: (tabItemsWidth: number) => set({ tabItemsWidth }), + heightCollapseRequired: false, setHeightCollapseRequired: (heightCollapseRequired: boolean) => set({ heightCollapseRequired }), diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index bef62d77..8e1450c2 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -202,26 +202,11 @@ export const EmbedSidebar = () => {
{ - (e.currentTarget as HTMLElement).style.backgroundColor = "var(--pd-primary-color, #3b82f6)"; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLElement).style.backgroundColor = "transparent"; - }} - /> + > + {/* Visual indicator line */} +
+
diff --git a/webapp/_webapp/src/views/window-controller.tsx b/webapp/_webapp/src/views/window-controller.tsx index 043798b4..ad14082b 100644 --- a/webapp/_webapp/src/views/window-controller.tsx +++ b/webapp/_webapp/src/views/window-controller.tsx @@ -6,7 +6,7 @@ import { PdAppControlTitleBar } from "../components/pd-app-control-title-bar"; import { PdAppSmallControlButton } from "../components/pd-app-small-control-button"; export const WindowController = () => { - const { sidebarCollapsed, setSidebarCollapsed, setIsOpen } = useConversationUiStore(); + const { sidebarCollapsed, setIsOpen } = useConversationUiStore(); const CompactHeader = useMemo(() => { return ( { - - setSidebarCollapsed(!sidebarCollapsed)}> - - -
); - }, [sidebarCollapsed, setSidebarCollapsed, setIsOpen]); + }, [sidebarCollapsed, setIsOpen]); return CompactHeader; }; @@ -114,7 +100,11 @@ const PositionController = () => { }} > From 163be0cd736f879482547dcc63e8f2ea8bfb3810 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 04:22:20 +0800 Subject: [PATCH 07/15] fix: overleaf compatibility hotfix --- webapp/_webapp/src/index.css | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/webapp/_webapp/src/index.css b/webapp/_webapp/src/index.css index 8f6607e1..10dc6101 100644 --- a/webapp/_webapp/src/index.css +++ b/webapp/_webapp/src/index.css @@ -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; From e76f12d0348bae2eb8154d25f63eca380947dbd5 Mon Sep 17 00:00:00 2001 From: Junyi Date: Sat, 24 Jan 2026 04:47:25 +0800 Subject: [PATCH 08/15] Update webapp/_webapp/src/index.css Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- webapp/_webapp/src/index.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/webapp/_webapp/src/index.css b/webapp/_webapp/src/index.css index f85c56e9..88ba8954 100644 --- a/webapp/_webapp/src/index.css +++ b/webapp/_webapp/src/index.css @@ -641,14 +641,6 @@ video, canvas, audio, iframe, embed, object { opacity: 1 !important; } -.pd-embed-resize-handle.resizing { - background-color: rgba(59, 130, 246, 0.2) !important; -} - -.pd-embed-resize-handle.resizing .resize-handle-indicator { - opacity: 1 !important; -} - .resize-handle-indicator { width: 2px; height: 40px; From 9dd0972f4ee2931070b2ea9d1971035d485ba259 Mon Sep 17 00:00:00 2001 From: Junyi Date: Sat, 24 Jan 2026 04:47:38 +0800 Subject: [PATCH 09/15] Update webapp/_webapp/src/views/embed-sidebar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- webapp/_webapp/src/views/embed-sidebar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index 8e1450c2..183ddb65 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -139,6 +139,7 @@ export const EmbedSidebar = () => { ideBody.style.display = originalBodyStyleRef.current.display || ""; ideBody.style.flexDirection = originalBodyStyleRef.current.flexDirection || ""; ideBody.style.width = ""; + ideBody.style.height = ""; ideBody.style.overflow = ""; // Restore ide-redesign-inner flex properties From d4c5eeb819df05514a6a72be921c56fae3268b56 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 04:50:45 +0800 Subject: [PATCH 10/15] fix: copilot suggestions --- webapp/_webapp/src/components/tabs.tsx | 45 ++++++++++--- webapp/_webapp/src/views/embed-sidebar.tsx | 73 ++++++++++++++++++++-- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/webapp/_webapp/src/components/tabs.tsx b/webapp/_webapp/src/components/tabs.tsx index 172ec6a8..1ae39151 100644 --- a/webapp/_webapp/src/components/tabs.tsx +++ b/webapp/_webapp/src/components/tabs.tsx @@ -43,6 +43,8 @@ export const Tabs = forwardRef(({ items }, ref) => { const resizeHandleRef = useRef(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(() => { @@ -64,6 +66,34 @@ export const Tabs = forwardRef(({ items }, 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(); @@ -82,16 +112,13 @@ export const Tabs = forwardRef(({ items }, ref) => { }; const handleMouseUp = () => { - isResizingRef.current = false; - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - document.body.style.cursor = "default"; - document.body.style.userSelect = ""; - if (resizeHandleRef.current) { - resizeHandleRef.current.classList.remove("resizing"); - } + 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"; @@ -99,7 +126,7 @@ export const Tabs = forwardRef(({ items }, ref) => { if (resizeHandleRef.current) { resizeHandleRef.current.classList.add("resizing"); } - }, [setTabItemsWidth]); + }, [setTabItemsWidth, cleanupResizeState]); const renderTabItem = useCallback( (item: TabItem) => { diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index 183ddb65..8d943ccb 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -15,12 +15,44 @@ export const EmbedSidebar = () => { display?: string; flexDirection?: string; }>({}); + const mouseMoveHandlerRef = useRef<((e: MouseEvent) => void) | null>(null); + const mouseUpHandlerRef = useRef<(() => void) | null>(null); + const originalCursorRef = useRef(""); + const originalUserSelectRef = useRef(""); // Keep ref in sync with embedWidth useEffect(() => { embedWidthRef.current = embedWidth; }, [embedWidth]); + // Cleanup resize handlers and body styles on unmount or when isOpen changes + useEffect(() => { + return () => { + // Clean up any active resize handlers + if (mouseMoveHandlerRef.current) { + document.removeEventListener("mousemove", mouseMoveHandlerRef.current); + mouseMoveHandlerRef.current = null; + } + if (mouseUpHandlerRef.current) { + document.removeEventListener("mouseup", mouseUpHandlerRef.current); + mouseUpHandlerRef.current = null; + } + + // Restore body styles + if (originalCursorRef.current !== "") { + document.body.style.cursor = originalCursorRef.current; + originalCursorRef.current = ""; + } + if (originalUserSelectRef.current !== "") { + document.body.style.userSelect = originalUserSelectRef.current; + originalUserSelectRef.current = ""; + } + + // Reset resizing state + isResizingRef.current = false; + }; + }, [isOpen]); + // Function to update main content area flex properties const updateMainContentFlex = useCallback((ideBody: HTMLElement) => { // Find or create ide-redesign-inner @@ -160,6 +192,14 @@ export const EmbedSidebar = () => { e.preventDefault(); e.stopPropagation(); + // Clean up any existing handlers first + if (mouseMoveHandlerRef.current) { + document.removeEventListener("mousemove", mouseMoveHandlerRef.current); + } + if (mouseUpHandlerRef.current) { + document.removeEventListener("mouseup", mouseUpHandlerRef.current); + } + isResizingRef.current = true; const startX = e.clientX; const startWidth = embedWidthRef.current; @@ -175,12 +215,37 @@ export const EmbedSidebar = () => { const handleMouseUp = () => { isResizingRef.current = false; - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - document.body.style.cursor = "default"; - document.body.style.userSelect = ""; + + // Remove event listeners + if (mouseMoveHandlerRef.current) { + document.removeEventListener("mousemove", mouseMoveHandlerRef.current); + mouseMoveHandlerRef.current = null; + } + if (mouseUpHandlerRef.current) { + document.removeEventListener("mouseup", mouseUpHandlerRef.current); + mouseUpHandlerRef.current = null; + } + + // Restore body styles + if (originalCursorRef.current !== "") { + document.body.style.cursor = originalCursorRef.current; + originalCursorRef.current = ""; + } + if (originalUserSelectRef.current !== "") { + document.body.style.userSelect = originalUserSelectRef.current; + originalUserSelectRef.current = ""; + } }; + // Store handlers in refs for cleanup + mouseMoveHandlerRef.current = handleMouseMove; + mouseUpHandlerRef.current = handleMouseUp; + + // Save original body styles + originalCursorRef.current = document.body.style.cursor || ""; + originalUserSelectRef.current = document.body.style.userSelect || ""; + + // Apply new styles and attach listeners document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); document.body.style.cursor = "col-resize"; From 58c1f254e67841a33e0dc1ad759f6c690e11796d Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 04:52:56 +0800 Subject: [PATCH 11/15] fix: full screen --- webapp/_webapp/src/stores/conversation/conversation-ui-store.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts b/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts index e2341a89..8728c1fd 100644 --- a/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts +++ b/webapp/_webapp/src/stores/conversation/conversation-ui-store.ts @@ -9,6 +9,7 @@ export const DISPLAY_MODES = [ { key: "right-fixed", label: "Right Fixed" }, { key: "bottom-fixed", label: "Bottom Fixed" }, { key: "embed", label: "Embed Sidebar" }, + { key: "fullscreen", label: "Full Screen" }, ] as const; export type DisplayMode = (typeof DISPLAY_MODES)[number]["key"]; From 25d4d0317e0cb20aa6658a71c0fdc4e969006b33 Mon Sep 17 00:00:00 2001 From: Junyi Date: Sat, 24 Jan 2026 05:16:56 +0800 Subject: [PATCH 12/15] Update webapp/_webapp/src/views/embed-sidebar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- webapp/_webapp/src/views/embed-sidebar.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index 8d943ccb..8eb88259 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -226,15 +226,11 @@ export const EmbedSidebar = () => { mouseUpHandlerRef.current = null; } - // Restore body styles - if (originalCursorRef.current !== "") { - document.body.style.cursor = originalCursorRef.current; - originalCursorRef.current = ""; - } - if (originalUserSelectRef.current !== "") { - document.body.style.userSelect = originalUserSelectRef.current; - originalUserSelectRef.current = ""; - } + // Restore body styles (including clearing inline styles when originally unset) + document.body.style.cursor = originalCursorRef.current; + document.body.style.userSelect = originalUserSelectRef.current; + originalCursorRef.current = ""; + originalUserSelectRef.current = ""; }; // Store handlers in refs for cleanup From 3574d20617d9ad64d37dd48c202028a6995626d6 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 05:19:26 +0800 Subject: [PATCH 13/15] fix: rendering issue --- webapp/_webapp/src/views/embed-sidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/_webapp/src/views/embed-sidebar.tsx b/webapp/_webapp/src/views/embed-sidebar.tsx index 8eb88259..5d65c106 100644 --- a/webapp/_webapp/src/views/embed-sidebar.tsx +++ b/webapp/_webapp/src/views/embed-sidebar.tsx @@ -139,7 +139,7 @@ export const EmbedSidebar = () => { const sidebarDiv = document.createElement("div"); sidebarDiv.id = "pd-embed-sidebar"; sidebarDiv.className = "pd-embed-sidebar"; - sidebarDiv.style.width = `${embedWidth}px`; + sidebarDiv.style.width = `${embedWidthRef.current}px`; sidebarDiv.style.height = "100%"; // Use 100% to match parent height sidebarDiv.style.display = "flex"; sidebarDiv.style.flexDirection = "column"; @@ -185,7 +185,7 @@ export const EmbedSidebar = () => { setContainer(null); }; - }, [isOpen, embedWidth, updateMainContentFlex]); + }, [isOpen, updateMainContentFlex]); // Handle resize drag const handleMouseDown = useCallback((e: React.MouseEvent) => { From 3062b9d25117b3b14682577d60ca672236c5e460 Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 05:23:40 +0800 Subject: [PATCH 14/15] code clean --- webapp/_webapp/src/libs/inline-suggestion.ts | 22 +------------------- webapp/_webapp/src/libs/overleaf-socket.ts | 1 - webapp/_webapp/src/main.tsx | 21 ++++++------------- webapp/_webapp/src/query/api.ts | 7 ------- webapp/_webapp/src/query/index.ts | 19 ----------------- webapp/_webapp/src/query/keys.ts | 6 ------ webapp/_webapp/src/stores/auth-store.ts | 9 +------- webapp/_webapp/src/views/settings/index.tsx | 1 - 8 files changed, 8 insertions(+), 78 deletions(-) diff --git a/webapp/_webapp/src/libs/inline-suggestion.ts b/webapp/_webapp/src/libs/inline-suggestion.ts index 36f09e36..2d80daa7 100644 --- a/webapp/_webapp/src/libs/inline-suggestion.ts +++ b/webapp/_webapp/src/libs/inline-suggestion.ts @@ -108,27 +108,7 @@ export async function completion(state: EditorState): Promise { return ""; } - const cursor = state.selection.main.head; - const left = state.doc.sliceString(Math.max(0, cursor - 2048), cursor); - logDebug("left", left); - - // const completion = await chatCompletion({ - // languageModel: LanguageModel.OPENAI_GPT4O_MINI, - // messages: [ - // { - // role: "developer", - // content: - // 'You are a senior PhD candidate writing in Overleaf. At [COMPLETE_AT_HERE], write a concise, context-aware sentence (15 words or fewer). The text may include LaTeX code—handle it cleanly but don’t overfocus on it. Avoid the words “ensuring,” “utilizing,” "illustrates", “showcasing,” and “necessitating.” Keep it short, direct, and natural—no need for lengthy or structured phrasing. Replace all transition words and conjunctions in the sentences with the most basic and commonly used ones. Use simple expressions,avoiding complex vocabulary. Ensure the logical connections between sentences are clear. Deletes the conclusion part in the end of the text.', - // }, - // { - // role: "user", - // content: `The paragraph is: ${left}[COMPLETE_AT_HERE]`, - // }, - // ], - // }); - // const responseText = completion.message?.content || ""; - const responseText = "Unsupported Feature"; - return responseText; + return "Unsupported Feature"; } /** diff --git a/webapp/_webapp/src/libs/overleaf-socket.ts b/webapp/_webapp/src/libs/overleaf-socket.ts index 786d1fd5..01d4615e 100644 --- a/webapp/_webapp/src/libs/overleaf-socket.ts +++ b/webapp/_webapp/src/libs/overleaf-socket.ts @@ -195,7 +195,6 @@ export async function postCommentToThread( ): Promise { const currentDomain = window.location.hostname; const threadUrl = `https://${currentDomain}/project/${projectId}/thread/${threadId}/messages`; - // console.log("Posting comment to thread:", threadUrl, comment); if (!comment || comment.length === 0) { throw new Error("Comment is empty"); diff --git a/webapp/_webapp/src/main.tsx b/webapp/_webapp/src/main.tsx index 81dfff05..4c36a9c3 100644 --- a/webapp/_webapp/src/main.tsx +++ b/webapp/_webapp/src/main.tsx @@ -234,22 +234,13 @@ if (!import.meta.env.DEV) { const root = createRoot(div); const adapter = getOverleafAdapter(); + // This block only runs in production (!DEV), so always render without StrictMode root.render( - import.meta.env.DEV ? ( - - - -
- - - - ) : ( - - -
- - - ), + + +
+ + ); googleAnalytics.firePageViewEvent( "unknown", diff --git a/webapp/_webapp/src/query/api.ts b/webapp/_webapp/src/query/api.ts index 586567c2..9f4245e2 100644 --- a/webapp/_webapp/src/query/api.ts +++ b/webapp/_webapp/src/query/api.ts @@ -25,8 +25,6 @@ import { import { GetProjectRequest, GetProjectResponseSchema, - RunProjectPaperScoreRequest, - RunProjectPaperScoreResponseSchema, UpsertProjectRequest, UpsertProjectResponseSchema, GetProjectInstructionsRequest, @@ -195,11 +193,6 @@ export const upsertUserInstructions = async (data: PlainMessage) => { - const response = await apiclient.post(`/projects/${data.projectId}/paper-score`, data); - return fromJson(RunProjectPaperScoreResponseSchema, response); -}; export const getProjectInstructions = async (data: PlainMessage) => { if (!apiclient.hasToken()) { diff --git a/webapp/_webapp/src/query/index.ts b/webapp/_webapp/src/query/index.ts index 7a78ccae..ba397ad4 100644 --- a/webapp/_webapp/src/query/index.ts +++ b/webapp/_webapp/src/query/index.ts @@ -16,7 +16,6 @@ import { listConversations, listPrompts, listSupportedModels, - runProjectPaperScore, updateConversation, updatePrompt, getUserInstructions, @@ -35,22 +34,11 @@ import { import { queryKeys } from "./keys"; import { GetProjectResponse, - RunProjectPaperScoreResponse, GetProjectInstructionsResponse, UpsertProjectInstructionsResponse, } from "../pkg/gen/apiclient/project/v1/project_pb"; import { useAuthStore } from "../stores/auth-store"; -// Deprecated -// export const useGetUserQuery = ( -// opts?: UseQueryOptionsOverride, -// ) => { -// return useQuery({ -// queryKey: queryKeys.users.getUser().queryKey, -// queryFn: getUser, -// ...opts, -// }); -// }; export const useGetProjectQuery = (projectId: string, opts?: UseQueryOptionsOverride) => { return useQuery({ @@ -118,13 +106,6 @@ export const useDeleteConversationMutation = (opts?: UseMutationOptionsOverride< }); }; -export const useRunProjectPaperScoreMutation = (opts?: UseMutationOptionsOverride) => { - return useMutation({ - mutationFn: runProjectPaperScore, - ...opts, - }); -}; - export const useGetConversationQuery = ( conversationId: string, opts?: UseQueryOptionsOverride, diff --git a/webapp/_webapp/src/query/keys.ts b/webapp/_webapp/src/query/keys.ts index e28ef91e..e09bfd7e 100644 --- a/webapp/_webapp/src/query/keys.ts +++ b/webapp/_webapp/src/query/keys.ts @@ -17,12 +17,6 @@ export const queryKeys = createQueryKeyStore({ }, projects: { getProject: (projectId: string) => ["projects", projectId], - runProjectPaperScore: (projectId: string, conversationId: string) => [ - "projects", - "paper-score", - projectId, - conversationId, - ], getProjectInstructions: (projectId: string) => ["projects", projectId, "instructions"], }, comments: { diff --git a/webapp/_webapp/src/stores/auth-store.ts b/webapp/_webapp/src/stores/auth-store.ts index acc92b08..b425c5f9 100644 --- a/webapp/_webapp/src/stores/auth-store.ts +++ b/webapp/_webapp/src/stores/auth-store.ts @@ -87,13 +87,6 @@ export const useAuthStore = create((set, get) => ({ }, initFromStorage: () => { - // const token = storage.getItem(LOCAL_STORAGE_KEY.TOKEN) ?? ""; - // const refreshToken = storage.getItem(LOCAL_STORAGE_KEY.REFRESH_TOKEN) ?? ""; - // console.log("[AuthStore] initFromStorage:", { - // hasToken: !!token, - // tokenLength: token.length, - // hasRefreshToken: !!refreshToken - // }); - // set({ token, refreshToken }); + // Function intentionally left empty - initialization handled elsewhere }, })); diff --git a/webapp/_webapp/src/views/settings/index.tsx b/webapp/_webapp/src/views/settings/index.tsx index 949ed551..9058b40a 100644 --- a/webapp/_webapp/src/views/settings/index.tsx +++ b/webapp/_webapp/src/views/settings/index.tsx @@ -32,7 +32,6 @@ export const Settings = () => {
- {/* */} From 67d90f89571c06083253fc69eeb3f3ee7bede2dd Mon Sep 17 00:00:00 2001 From: Junyi Hou Date: Sat, 24 Jan 2026 05:25:23 +0800 Subject: [PATCH 15/15] fix: ts error --- webapp/_webapp/src/libs/inline-suggestion.ts | 2 +- webapp/_webapp/src/main.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/_webapp/src/libs/inline-suggestion.ts b/webapp/_webapp/src/libs/inline-suggestion.ts index 2d80daa7..7c218832 100644 --- a/webapp/_webapp/src/libs/inline-suggestion.ts +++ b/webapp/_webapp/src/libs/inline-suggestion.ts @@ -102,7 +102,7 @@ export function debouncePromise any>( // eslint-di }; } -export async function completion(state: EditorState): Promise { +export async function completion(_state: EditorState): Promise { const settings = useSettingStore.getState().settings; if (!settings?.enableCompletion) { return ""; diff --git a/webapp/_webapp/src/main.tsx b/webapp/_webapp/src/main.tsx index 4c36a9c3..96ffb4a9 100644 --- a/webapp/_webapp/src/main.tsx +++ b/webapp/_webapp/src/main.tsx @@ -1,5 +1,5 @@ import { Extension } from "@codemirror/state"; -import { StrictMode, useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { createRoot } from "react-dom/client"; import { OnboardingGuide } from "./components/onboarding-guide";