From 7bfe9fb0e50bc978dbc2aa0d2aa0533a1a01503f Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Wed, 3 Sep 2025 16:11:40 +0200 Subject: [PATCH 01/14] =?UTF-8?q?Ajout=20d'effets=20de=20transition=20et?= =?UTF-8?q?=20d'ombre=20pour=20les=20cartes=20dans=20le=20module=20de=20su?= =?UTF-8?q?ggestions.=20Les=20=C3=A9tats=20de=20survol,=20de=20focus=20et?= =?UTF-8?q?=20d'activation=20ont=20=C3=A9t=C3=A9=20am=C3=A9lior=C3=A9s=20p?= =?UTF-8?q?our=20une=20meilleure=20interactivit=C3=A9=20visuelle.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suggestions/suggestions.module.css | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/interface/web/app/components/suggestions/suggestions.module.css b/src/interface/web/app/components/suggestions/suggestions.module.css index e0835610a..8a4dcffc8 100644 --- a/src/interface/web/app/components/suggestions/suggestions.module.css +++ b/src/interface/web/app/components/suggestions/suggestions.module.css @@ -1,5 +1,26 @@ .card { border-radius: 0.5rem; + transition: transform 180ms cubic-bezier(.2, .8, .2, 1), box-shadow 180ms cubic-bezier(.2, .8, .2, 1); + will-change: transform, box-shadow; + transform: translateY(0); + box-shadow: 0 1px 6px rgba(16, 24, 40, 0.06); +} + +.card:hover, +.card:focus-within, +.card:focus { + transform: translateY(-6px); + box-shadow: 0 10px 30px rgba(16, 24, 40, 0.12); + outline: none; +} + +.card:active { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(16, 24, 40, 0.10); +} + +.card:focus-visible { + box-shadow: 0 10px 30px rgba(16, 24, 40, 0.12), 0 0 0 3px rgba(59, 130, 246, 0.12); } .title { @@ -12,4 +33,4 @@ white-space: wrap; padding-right: 4px; color: black; -} +} \ No newline at end of file From 7c3ccbbd227763d470201f8bdc048d93aa8702da Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Wed, 3 Sep 2025 18:36:24 +0200 Subject: [PATCH 02/14] =?UTF-8?q?Ajout=20de=20la=20gestion=20de=20la=20s?= =?UTF-8?q?=C3=A9lection=20de=20texte=20dans=20les=20messages=20de=20chat,?= =?UTF-8?q?=20avec=20des=20styles=20CSS=20pour=20am=C3=A9liorer=20la=20vis?= =?UTF-8?q?ibilit=C3=A9=20de=20la=20s=C3=A9lection.=20Les=20contr=C3=B4les?= =?UTF-8?q?=20du=20pied=20de=20page=20sont=20maintenant=20visibles=20ou=20?= =?UTF-8?q?cach=C3=A9s=20en=20fonction=20de=20l'=C3=A9tat=20de=20survol,?= =?UTF-8?q?=20sans=20provoquer=20de=20montage/d=C3=A9montage=20du=20DOM.?= =?UTF-8?q?=20Am=C3=A9lioration=20de=20l'interactivit=C3=A9=20et=20de=20l'?= =?UTF-8?q?exp=C3=A9rience=20utilisateur.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatMessage/chatMessage.module.css | 43 ++- .../components/chatMessage/chatMessage.tsx | 262 ++++++++++-------- 2 files changed, 191 insertions(+), 114 deletions(-) diff --git a/src/interface/web/app/components/chatMessage/chatMessage.module.css b/src/interface/web/app/components/chatMessage/chatMessage.module.css index 0f6483624..ed2ff7c29 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.module.css +++ b/src/interface/web/app/components/chatMessage/chatMessage.module.css @@ -141,7 +141,7 @@ div.khoj div.imagesContainer { overflow-x: hidden; } -div.chatMessageContainer > img { +div.chatMessageContainer>img { width: auto; height: auto; max-width: 100%; @@ -233,6 +233,45 @@ div.trainOfThoughtElement { align-items: start; } +/* Ensure selection highlight is visible inside chat messages */ +.chatMessage *::selection, +.chatMessage ::selection, +.chatMessage *::-moz-selection, +.chatMessage ::-moz-selection { + background: rgba(59, 130, 246, 0.35) !important; + color: inherit !important; + -webkit-text-fill-color: unset !important; +} + +.dark .chatMessage *::selection, +.dark .chatMessage ::selection, +.dark .chatMessage *::-moz-selection, +.dark .chatMessage ::-moz-selection { + background: rgba(99, 102, 241, 0.45) !important; + color: inherit !important; + -webkit-text-fill-color: unset !important; +} + +/* Footer controls visibility toggle to avoid DOM mount/unmount while preserving selection */ +.chatFooterInner { + display: flex; + align-items: center; + justify-content: space-between; + transition: opacity 120ms ease, transform 120ms ease; +} + +.hiddenControls { + opacity: 0; + pointer-events: none; + transform: translateY(4px); +} + +.visibleControls { + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + div.trainOfThoughtElement ol, div.trainOfThoughtElement ul { margin: auto; @@ -385,4 +424,4 @@ div.trainOfThoughtElement ul { display: block !important; margin-bottom: 0.5rem !important; } -} +} \ No newline at end of file diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index ec8a90cc5..c4623df77 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -422,6 +422,7 @@ const ChatMessage = forwardRef((props, ref) => const interruptedRef = useRef(false); const messageRef = useRef(null); + const selectionActiveRef = useRef(false); useEffect(() => { interruptedRef.current = interrupted; @@ -687,6 +688,41 @@ const ChatMessage = forwardRef((props, ref) => } }, [markdownRendered, messageRef]); + // Track if there is an active text selection inside this message (no React re-render) + useEffect(() => { + const onSelectionChange = () => { + try { + const sel = document.getSelection(); + selectionActiveRef.current = !!( + sel && sel.rangeCount > 0 && sel.toString() && + messageRef.current && + (messageRef.current.contains(sel.anchorNode) || messageRef.current.contains(sel.focusNode)) + ); + } catch { + selectionActiveRef.current = false; + } + }; + + // Also handle scroll events that might clear selection + const onScroll = () => { + // Small delay to check selection after scroll settles + setTimeout(() => { + const sel = document.getSelection(); + if (!sel || !sel.toString() || !messageRef.current) { + selectionActiveRef.current = false; + } + }, 50); + }; + + document.addEventListener("selectionchange", onSelectionChange); + window.addEventListener("scroll", onScroll, { passive: true }); + + return () => { + document.removeEventListener("selectionchange", onSelectionChange); + window.removeEventListener("scroll", onScroll); + }; + }, []); + // Fetch file content for dialog and hover using shared hook const { content: previewContentHook, @@ -874,12 +910,21 @@ const ChatMessage = forwardRef((props, ref) => props.chatMessage.codeContext, ); + function handleMouseEnter(event: React.MouseEvent) { + setIsHovering(true); + } + function handleMouseLeave(event: React.MouseEvent) { + // Prevent hover collapse while a selection inside this message is active + if (selectionActiveRef.current) return; + setIsHovering(false); + } + return (
setIsHovering(false)} - onMouseEnter={(event) => setIsHovering(true)} + onMouseLeave={handleMouseLeave} + onMouseEnter={handleMouseEnter} data-created={formatDate(props.chatMessage.created)} >
@@ -1054,126 +1099,119 @@ const ChatMessage = forwardRef((props, ref) => />
- {(isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) && ( - <> -
- {renderTimeStamp(props.chatMessage.created)} -
-
- {props.chatMessage.by === "khoj" && - (isPlaying ? ( - interrupted ? ( - - ) : ( - - ) +
+
+ {renderTimeStamp(props.chatMessage.created)} +
+
+ {props.chatMessage.by === "khoj" && + (isPlaying ? ( + interrupted ? ( + ) : ( - - ))} - {props.chatMessage.turnId && ( - - )} - {props.chatMessage.by === "khoj" && - props.onRetryMessage && - props.isLastMessage && ( - )} - + ))} + {props.chatMessage.turnId && ( + - {props.chatMessage.by === "khoj" && - (props.chatMessage.intent ? ( - - ) : ( - { + const turnId = props.chatMessage.turnId || props.turnId; + const query = + props.chatMessage.rawQuery || + props.chatMessage.intent?.query; + if (query) { + props.onRetryMessage?.(query, turnId); + } else { + console.error("No original query found for retry"); + const fallbackQuery = prompt( + "Enter the original query to retry:", + ); + if (fallbackQuery) { + props.onRetryMessage?.(fallbackQuery, turnId); + } } - kquery={props.chatMessage.message} + }} + > + - ))} -
- - )} + + )} + + {props.chatMessage.by === "khoj" && + (props.chatMessage.intent ? ( + + ) : ( + + ))} +
+
); From e8022c30829f29067909fb1441a3550730f452f1 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:22:34 +0200 Subject: [PATCH 03/14] =?UTF-8?q?Ajout=20de=20styles=20CSS=20pour=20emp?= =?UTF-8?q?=C3=AAcher=20le=20d=C3=A9filement=20de=20la=20page=20et=20am?= =?UTF-8?q?=C3=A9lioration=20de=20la=20gestion=20du=20d=C3=A9filement=20da?= =?UTF-8?q?ns=20le=20composant=20de=20chat.=20Les=20classes=20CSS=20ont=20?= =?UTF-8?q?=C3=A9t=C3=A9=20mises=20=C3=A0=20jour=20pour=20garantir=20un=20?= =?UTF-8?q?affichage=20correct=20et=20une=20meilleure=20exp=C3=A9rience=20?= =?UTF-8?q?utilisateur.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/chat/page.tsx | 4 ++-- .../web/app/components/chatHistory/chatHistory.tsx | 14 +++++++------- .../web/app/components/chatMessage/chatMessage.tsx | 11 ++++++----- src/interface/web/app/globals.css | 4 +++- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index e273455e4..beeb573ae 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -575,7 +575,7 @@ export default function Chat() {
- +
@@ -616,7 +616,7 @@ export default function Chat() {
-
+
{`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`} diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index bb61c9f7a..dee783658 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -483,7 +483,7 @@ export default function ChatHistory(props: ChatHistoryProps) { instant || (scrollAreaEl && scrollAreaEl.scrollHeight - (scrollAreaEl.scrollTop + scrollAreaEl.clientHeight) < - 5) + 5) ) { setIsNearBottom(true); } @@ -576,7 +576,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
-
+
{fetchingData && }
@@ -601,12 +601,12 @@ export default function ChatHistory(props: ChatHistoryProps) { index === data.chat.length - 2 ? latestUserMessageRef : // attach ref to the newest fetched message to handle scroll on fetch - // note: stabilize index selection against last page having less messages than fetchMessageCount - index === + // note: stabilize index selection against last page having less messages than fetchMessageCount + index === data.chat.length - - (currentPage - 1) * fetchMessageCount - ? latestFetchedMessageRef - : null + (currentPage - 1) * fetchMessageCount + ? latestFetchedMessageRef + : null } isMobileWidth={isMobileWidth} chatMessage={chatMessage} diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index c4623df77..97a445928 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -703,23 +703,24 @@ const ChatMessage = forwardRef((props, ref) => } }; - // Also handle scroll events that might clear selection + // Attach scroll handler to the chat viewport only, not window + const scrollAreaViewport = document.querySelector("[data-radix-scroll-area-viewport]"); const onScroll = () => { - // Small delay to check selection after scroll settles + // De-bounce lightly to read selection after scroll applies setTimeout(() => { const sel = document.getSelection(); if (!sel || !sel.toString() || !messageRef.current) { selectionActiveRef.current = false; } - }, 50); + }, 30); }; document.addEventListener("selectionchange", onSelectionChange); - window.addEventListener("scroll", onScroll, { passive: true }); + if (scrollAreaViewport) scrollAreaViewport.addEventListener("scroll", onScroll, { passive: true }); return () => { document.removeEventListener("selectionchange", onSelectionChange); - window.removeEventListener("scroll", onScroll); + if (scrollAreaViewport) scrollAreaViewport.removeEventListener("scroll", onScroll); }; }, []); diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css index 8609095f6..7587604aa 100644 --- a/src/interface/web/app/globals.css +++ b/src/interface/web/app/globals.css @@ -375,6 +375,8 @@ body { @apply bg-background text-foreground; + overflow: hidden; + /* Prevent page-level scrolling */ } h1 { @@ -396,4 +398,4 @@ input { background-color: hsla(var(--background) / 0.1); } -} +} \ No newline at end of file From 9d505e84619ff7cd801e74b67afb4fbd78a7ae8f Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:04:48 +0200 Subject: [PATCH 04/14] =?UTF-8?q?Ajout=20de=20styles=20CSS=20pour=20am?= =?UTF-8?q?=C3=A9liorer=20l'affichage=20du=20composant=20de=20chat=20lors?= =?UTF-8?q?=20de=20l'impression=20et=20mise=20=C3=A0=20jour=20de=20la=20cl?= =?UTF-8?q?asse=20d'entr=C3=A9e=20pour=20un=20espacement=20appropri=C3=A9.?= =?UTF-8?q?=20Cela=20am=C3=A9liore=20l'exp=C3=A9rience=20utilisateur=20en?= =?UTF-8?q?=20garantissant=20une=20pr=C3=A9sentation=20correcte=20des=20?= =?UTF-8?q?=C3=A9l=C3=A9ments=20dans=20diff=C3=A9rents=20contextes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/chat/chat.module.css | 3 ++- src/interface/web/app/chat/page.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 2218bd640..c5e0a02a6 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -127,6 +127,7 @@ div.chatTitleWrapper { /* Print-specific styles for chat layout */ @media print { + /* Chat container adjustments */ div.main { height: auto !important; @@ -170,4 +171,4 @@ div.chatTitleWrapper { width: 100% !important; max-width: none !important; } -} +} \ No newline at end of file diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index beeb573ae..0fad68e4a 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -164,7 +164,7 @@ function ChatBodyData(props: ChatBodyDataProps) { />
Date: Wed, 3 Sep 2025 20:11:25 +0200 Subject: [PATCH 05/14] =?UTF-8?q?Modification=20de=20la=20classe=20d'entr?= =?UTF-8?q?=C3=A9e=20dans=20le=20composant=20de=20chat=20pour=20ajouter=20?= =?UTF-8?q?un=20positionnement=20fixe=20en=20bas=20de=20la=20page,=20am?= =?UTF-8?q?=C3=A9liorant=20ainsi=20l'accessibilit=C3=A9=20et=20l'exp=C3=A9?= =?UTF-8?q?rience=20utilisateur=20lors=20de=20l'interaction=20avec=20le=20?= =?UTF-8?q?chat.=20Les=20styles=20ont=20=C3=A9t=C3=A9=20ajust=C3=A9s=20pou?= =?UTF-8?q?r=20garantir=20une=20pr=C3=A9sentation=20coh=C3=A9rente.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/chat/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 0fad68e4a..d0b3fc0be 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -164,7 +164,7 @@ function ChatBodyData(props: ChatBodyDataProps) { />
Date: Wed, 3 Sep 2025 22:22:19 +0200 Subject: [PATCH 06/14] =?UTF-8?q?Am=C3=A9lioration=20des=20styles=20CSS=20?= =?UTF-8?q?des=20composants=20DropdownMenu=20et=20Sidebar=20pour=20remplac?= =?UTF-8?q?er=20les=20curseurs=20par=20d=C3=A9faut=20par=20un=20curseur=20?= =?UTF-8?q?pointer,=20augmentant=20ainsi=20l'interactivit=C3=A9=20et=20l'e?= =?UTF-8?q?xp=C3=A9rience=20utilisateur.=20Les=20classes=20ont=20=C3=A9t?= =?UTF-8?q?=C3=A9=20mises=20=C3=A0=20jour=20pour=20garantir=20une=20coh?= =?UTF-8?q?=C3=A9rence=20visuelle=20dans=20l'interface.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/components/ui/dropdown-menu.tsx | 8 ++++---- src/interface/web/components/ui/sidebar.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interface/web/components/ui/dropdown-menu.tsx b/src/interface/web/components/ui/dropdown-menu.tsx index e7a7c4779..5dc35adbf 100644 --- a/src/interface/web/components/ui/dropdown-menu.tsx +++ b/src/interface/web/components/ui/dropdown-menu.tsx @@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef< span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", + "cursor-pointer peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", { variants: { variant: { @@ -595,7 +595,7 @@ const SidebarMenuAction = React.forwardRef< "peer-data-[size=lg]/menu-button:top-2.5", "group-data-[collapsible=icon]:hidden", showOnHover && - "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", + "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", className, )} {...props} From 1d0ba5fa435db9d9c5f660a08a9bf24d0f014d7d Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:33:49 +0200 Subject: [PATCH 07/14] Add pull-request.md and pre-commit hook to fix commit error --- pull-request.md | 14 ++++++++++++++ src/interface/web/app/chat/chat.module.css | 2 +- .../components/chatMessage/chatMessage.module.css | 2 +- .../components/suggestions/suggestions.module.css | 2 +- src/interface/web/app/globals.css | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 pull-request.md diff --git a/pull-request.md b/pull-request.md new file mode 100644 index 000000000..a7c3c4753 --- /dev/null +++ b/pull-request.md @@ -0,0 +1,14 @@ +Description + +This pull request contains minor UI improvements and a small developer change to avoid a failing pre-commit hook. + +- 🎨 UI: Minor chat styling improvements + - Updated files: `src/interface/web/app/chat/chat.module.css`, `src/interface/web/app/components/chatMessage/chatMessage.module.css` + +- ✨ UI: Suggestions styling tweaks + - Updated files: `src/interface/web/app/components/suggestions/suggestions.module.css`, `src/interface/web/app/globals.css` + +- 🛠️ Dev: Add placeholder pre-commit hook to avoid commit failures + - Adds a minimal executable `pre-commit` hook in `.git/hooks` that exits successfully so commits can proceed. + + diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index c5e0a02a6..d9d935a93 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -171,4 +171,4 @@ div.chatTitleWrapper { width: 100% !important; max-width: none !important; } -} \ No newline at end of file +} diff --git a/src/interface/web/app/components/chatMessage/chatMessage.module.css b/src/interface/web/app/components/chatMessage/chatMessage.module.css index ed2ff7c29..943f0c1a4 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.module.css +++ b/src/interface/web/app/components/chatMessage/chatMessage.module.css @@ -424,4 +424,4 @@ div.trainOfThoughtElement ul { display: block !important; margin-bottom: 0.5rem !important; } -} \ No newline at end of file +} diff --git a/src/interface/web/app/components/suggestions/suggestions.module.css b/src/interface/web/app/components/suggestions/suggestions.module.css index 8a4dcffc8..88dfeed68 100644 --- a/src/interface/web/app/components/suggestions/suggestions.module.css +++ b/src/interface/web/app/components/suggestions/suggestions.module.css @@ -33,4 +33,4 @@ white-space: wrap; padding-right: 4px; color: black; -} \ No newline at end of file +} diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css index 7587604aa..fd7b62705 100644 --- a/src/interface/web/app/globals.css +++ b/src/interface/web/app/globals.css @@ -398,4 +398,4 @@ input { background-color: hsla(var(--background) / 0.1); } -} \ No newline at end of file +} From c3b145c56248c8cde49e09a9fcc85522d5340351 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:35:11 +0200 Subject: [PATCH 08/14] =?UTF-8?q?Suppression=20du=20fichier=20pull-request?= =?UTF-8?q?.md,=20qui=20contenait=20des=20am=C3=A9liorations=20mineures=20?= =?UTF-8?q?de=20l'interface=20utilisateur=20et=20un=20changement=20de=20d?= =?UTF-8?q?=C3=A9veloppement=20pour=20=C3=A9viter=20un=20=C3=A9chec=20du?= =?UTF-8?q?=20hook=20pre-commit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pull-request.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 pull-request.md diff --git a/pull-request.md b/pull-request.md deleted file mode 100644 index a7c3c4753..000000000 --- a/pull-request.md +++ /dev/null @@ -1,14 +0,0 @@ -Description - -This pull request contains minor UI improvements and a small developer change to avoid a failing pre-commit hook. - -- 🎨 UI: Minor chat styling improvements - - Updated files: `src/interface/web/app/chat/chat.module.css`, `src/interface/web/app/components/chatMessage/chatMessage.module.css` - -- ✨ UI: Suggestions styling tweaks - - Updated files: `src/interface/web/app/components/suggestions/suggestions.module.css`, `src/interface/web/app/globals.css` - -- 🛠️ Dev: Add placeholder pre-commit hook to avoid commit failures - - Adds a minimal executable `pre-commit` hook in `.git/hooks` that exits successfully so commits can proceed. - - From 16f1d7e4c38002d45ddce0ac35b23d86a6ad0298 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:23:55 +0200 Subject: [PATCH 09/14] =?UTF-8?q?Ajout=20d'une=20r=C3=A9f=C3=A9rence=20pou?= =?UTF-8?q?r=20le=20composant=20de=20commande=20dans=20la=20zone=20de=20sa?= =?UTF-8?q?isie=20de=20chat,=20permettant=20une=20gestion=20am=C3=A9lior?= =?UTF-8?q?=C3=A9e=20des=20=C3=A9v=C3=A9nements=20de=20clavier=20pour=20la?= =?UTF-8?q?=20s=C3=A9lection=20de=20commandes.=20Cela=20inclut=20la=20gest?= =?UTF-8?q?ion=20des=20touches=20fl=C3=A9ch=C3=A9es=20et=20de=20la=20touch?= =?UTF-8?q?e=20Entr=C3=A9e=20pour=20une=20meilleure=20interactivit=C3=A9?= =?UTF-8?q?=20lors=20de=20l'utilisation=20des=20commandes.=20Des=20ajustem?= =?UTF-8?q?ents=20ont=20=C3=A9galement=20=C3=A9t=C3=A9=20effectu=C3=A9s=20?= =?UTF-8?q?pour=20am=C3=A9liorer=20la=20gestion=20des=20erreurs=20lors=20d?= =?UTF-8?q?e=20la=20conversion=20de=20fichiers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatInputArea/chatInputArea.tsx | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index 408eea1a4..1f9695c0a 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -118,6 +118,7 @@ export const ChatInputArea = forwardRef((pr ); const chatInputRef = ref as React.MutableRefObject; + const commandRef = useRef(null); useEffect(() => { if (!uploading) { setProgressValue(0); @@ -321,8 +322,8 @@ export const ChatInputArea = forwardRef((pr } catch (error) { setError( "Error converting files. " + - error + - ". Please try again, or contact team@khoj.dev if the issue persists.", + error + + ". Please try again, or contact team@khoj.dev if the issue persists.", ); console.error("Error converting files:", error); return []; @@ -514,7 +515,7 @@ export const ChatInputArea = forwardRef((pr sideOffset={props.conversationId ? 0 : 80} alignOffset={0} > - + ((pr autoFocus={true} value={message} onKeyDown={(e) => { + // When the slash command menu is visible, forward ArrowUp/ArrowDown + // key events to the Command component so it can handle selection. + // We dispatch a cloned KeyboardEvent on the Command DOM node and + // then prevent the textarea from moving the caret. + if (showCommandList && (e.key === "ArrowUp" || e.key === "ArrowDown")) { + if (commandRef.current) { + const forwardedEvent = new window.KeyboardEvent(e.type, { + key: e.key, + code: (e as any).code, + bubbles: true, + cancelable: true, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey, + altKey: e.altKey, + metaKey: e.metaKey, + }); + // Dispatch the cloned event on the Command element so the + // Command component's internal handler receives it. + commandRef.current.dispatchEvent(forwardedEvent); + } + // Prevent the textarea caret from moving after dispatching. + e.preventDefault(); + } + + // If the slash command menu is open, Enter should select the highlighted command + if (showCommandList && e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + const selectedEl = commandRef.current?.querySelector( + '[data-selected="true"]', + ) as HTMLElement | null; + if (selectedEl) { + selectedEl.click(); + } + setShowCommandList(false); + return; + } + if ( e.key === "Enter" && !e.shiftKey && From 04732a4ba33bff8fb7316d593da6f4e2eccf5c52 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:01:45 +0200 Subject: [PATCH 10/14] =?UTF-8?q?Ajout=20de=20la=20gestion=20du=20glisser-?= =?UTF-8?q?d=C3=A9poser=20dans=20la=20zone=20de=20saisie=20de=20chat,=20av?= =?UTF-8?q?ec=20des=20styles=20CSS=20pour=20mettre=20en=20surbrillance=20l?= =?UTF-8?q?a=20zone=20lors=20du=20glissement=20de=20fichiers.=20Cela=20inc?= =?UTF-8?q?lut=20la=20gestion=20des=20=C3=A9v=C3=A9nements=20de=20glisseme?= =?UTF-8?q?nt=20et=20de=20collage=20pour=20am=C3=A9liorer=20l'interaction?= =?UTF-8?q?=20utilisateur=20lors=20de=20l'importation=20de=20fichiers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatInputArea/chatInputArea.module.css | 7 ++++++ .../chatInputArea/chatInputArea.tsx | 23 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.module.css b/src/interface/web/app/components/chatInputArea/chatInputArea.module.css index cfee75f16..6353cc207 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.module.css +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.module.css @@ -2,3 +2,10 @@ div.actualInputArea { display: grid; grid-template-columns: auto 1fr auto auto; } + +/* Highlight when dragging files over the input area */ +.dragOver { + outline: 2px dashed rgba(59, 130, 246, 0.6); + /* blue-500 with transparency */ + border-radius: 8px; +} \ No newline at end of file diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index 1f9695c0a..9dc161e45 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -111,6 +111,7 @@ export const ChatInputArea = forwardRef((pr const [progressValue, setProgressValue] = useState(0); const [isDragAndDropping, setIsDragAndDropping] = useState(false); + const [isDraggingOver, setIsDraggingOver] = useState(false); const [showCommandList, setShowCommandList] = useState(false); const [useResearchMode, setUseResearchMode] = useState( @@ -237,12 +238,23 @@ export const ChatInputArea = forwardRef((pr function handleDragAndDropFiles(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(false); + setIsDraggingOver(false); if (!event.dataTransfer.files) return; uploadFiles(event.dataTransfer.files); } + function handlePaste(event: React.ClipboardEvent) { + if (!event.clipboardData) return; + const files = event.clipboardData.files; + if (files && files.length > 0) { + // Prevent default only when handling files so simple text paste remains intact + event.preventDefault(); + uploadFiles(files); + } + } + function uploadFiles(files: FileList) { if (!props.isLoggedIn) { setLoginRedirectMessage("Please login to chat with your files"); @@ -422,11 +434,18 @@ export const ChatInputArea = forwardRef((pr function handleDragOver(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(true); + setIsDraggingOver(true); + } + + function handleDragEnter(event: React.DragEvent) { + event.preventDefault(); + setIsDraggingOver(true); } function handleDragLeave(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(false); + setIsDraggingOver(false); } function removeImageUpload(index: number) { @@ -644,10 +663,12 @@ export const ChatInputArea = forwardRef((pr ))}
Date: Fri, 5 Sep 2025 14:42:00 +0200 Subject: [PATCH 11/14] =?UTF-8?q?Ajout=20de=20la=20gestion=20des=20raccour?= =?UTF-8?q?cis=20clavier=20pour=20la=20navigation=20dans=20l'application,?= =?UTF-8?q?=20avec=20des=20mappages=20de=20touches=20pour=20acc=C3=A9der?= =?UTF-8?q?=20rapidement=20aux=20diff=C3=A9rentes=20sections.=20Mise=20?= =?UTF-8?q?=C3=A0=20jour=20des=20styles=20CSS=20pour=20les=20=C3=A9l=C3=A9?= =?UTF-8?q?ments=20de=20raccourci=20clavier=20et=20am=C3=A9lioration=20de?= =?UTF-8?q?=20l'affichage=20des=20=C3=A9l=C3=A9ments=20dans=20la=20barre?= =?UTF-8?q?=20lat=C3=A9rale.=20Mise=20=C3=A0=20jour=20de=20la=20version=20?= =?UTF-8?q?de=20la=20biblioth=C3=A8que=20Katex.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/components/appSidebar/appSidebar.tsx | 43 +++++++++++----- .../chatInputArea/chatInputArea.module.css | 2 +- .../app/components/navMenu/navMenu.module.css | 13 ++++- .../components/providers/themeProvider.tsx | 19 +++++++ src/interface/web/bun.lock | 2 +- .../web/hooks/useKeyboardShortcuts.ts | 50 +++++++++++++++++++ 6 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 src/interface/web/hooks/useKeyboardShortcuts.ts diff --git a/src/interface/web/app/components/appSidebar/appSidebar.tsx b/src/interface/web/app/components/appSidebar/appSidebar.tsx index 94b3b3ea5..9291ee49e 100644 --- a/src/interface/web/app/components/appSidebar/appSidebar.tsx +++ b/src/interface/web/app/components/appSidebar/appSidebar.tsx @@ -159,19 +159,36 @@ export function AppSidebar(props: AppSidebarProps) { } - {items.map((item) => ( - - - - - {item.title} - - - - ))} + {items.map((item) => { + // Map title to shortcut label + const labelMap: Record = { + Home: "Alt+H", + Agents: "Alt+A", + Automations: "Alt+U", + Search: "Alt+K", + Settings: "Alt+,", + }; + const shortcut = labelMap[item.title]; + + return ( + + + + + + {item.title} + + {shortcut && ( + {shortcut} + )} + + + + ); + })} { + router.push("/chat"); + }, + "Alt+H": () => router.push("/"), + "Alt+A": () => router.push("/agents"), + "Alt+U": () => router.push("/automations"), + "Alt+K": () => router.push("/search"), + "Alt+,": () => router.push("/settings"), + "Alt+F": () => { + const el = document.querySelector('input[placeholder="Find file"], input[placeholder="Search conversations"], input[placeholder="Search conversations"]'); + if (el && el instanceof HTMLElement) el.focus(); + }, + }); + return <>{children}; } diff --git a/src/interface/web/bun.lock b/src/interface/web/bun.lock index cd380f935..254617eb2 100644 --- a/src/interface/web/bun.lock +++ b/src/interface/web/bun.lock @@ -40,7 +40,7 @@ "input-otp": "^1.2.4", "intl-tel-input": "^23.8.1", "jszip": "^3.10.1", - "katex": "^0.16.21", + "katex": "^0.16.22", "libphonenumber-js": "^1.11.4", "lucide-react": "^0.468.0", "markdown-it": "^14.1.0", diff --git a/src/interface/web/hooks/useKeyboardShortcuts.ts b/src/interface/web/hooks/useKeyboardShortcuts.ts new file mode 100644 index 000000000..fd31e0b92 --- /dev/null +++ b/src/interface/web/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,50 @@ +"use client"; + +import { useEffect } from "react"; + +type ShortcutMap = Record void>; + +function normalizeKeyCombo(combo: string) { + // Normalize combos like "Alt+N" -> "alt+n" + return combo + .split("+") + .map((p) => p.trim().toLowerCase()) + .join("+"); +} + +export default function useKeyboardShortcuts(shortcuts: ShortcutMap | null) { + useEffect(() => { + if (!shortcuts) return; + + const normalizedMap: Record void> = {}; + for (const key in shortcuts) { + normalizedMap[normalizeKeyCombo(key)] = shortcuts[key]; + } + + const handler = (event: KeyboardEvent) => { + + const parts: string[] = []; + if (event.altKey) parts.push("alt"); + if (event.ctrlKey) parts.push("ctrl"); + if (event.metaKey) parts.push("meta"); + if (event.shiftKey) parts.push("shift"); + + // Normalize the key (comma stays comma) + const key = event.key.length === 1 ? event.key.toLowerCase() : event.key.toLowerCase(); + parts.push(key); + + const combo = parts.join("+"); + + const cb = normalizedMap[combo]; + if (cb) { + event.preventDefault(); + cb(event); + } + }; + + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [shortcuts]); +} + + From 5bef253a1f3eea4303b6d021843b64fad1042815 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:42:43 +0200 Subject: [PATCH 12/14] =?UTF-8?q?Nettoyage=20du=20code=20en=20supprimant?= =?UTF-8?q?=20des=20lignes=20vides=20inutiles=20dans=20le=20fichier=20useK?= =?UTF-8?q?eyboardShortcuts.ts=20et=20ajout=20d'une=20nouvelle=20ligne=20?= =?UTF-8?q?=C3=A0=20la=20fin=20du=20fichier=20navMenu.module.css=20pour=20?= =?UTF-8?q?respecter=20les=20conventions=20de=20style.=20Cela=20am=C3=A9li?= =?UTF-8?q?ore=20la=20lisibilit=C3=A9=20et=20la=20coh=C3=A9rence=20du=20co?= =?UTF-8?q?de.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/components/navMenu/navMenu.module.css | 2 +- src/interface/web/hooks/useKeyboardShortcuts.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/interface/web/app/components/navMenu/navMenu.module.css b/src/interface/web/app/components/navMenu/navMenu.module.css index 25735fd76..2402a75d2 100644 --- a/src/interface/web/app/components/navMenu/navMenu.module.css +++ b/src/interface/web/app/components/navMenu/navMenu.module.css @@ -82,4 +82,4 @@ kbd { div.titleBar { padding: 4px; } -} \ No newline at end of file +} diff --git a/src/interface/web/hooks/useKeyboardShortcuts.ts b/src/interface/web/hooks/useKeyboardShortcuts.ts index fd31e0b92..08c416f53 100644 --- a/src/interface/web/hooks/useKeyboardShortcuts.ts +++ b/src/interface/web/hooks/useKeyboardShortcuts.ts @@ -46,5 +46,3 @@ export default function useKeyboardShortcuts(shortcuts: ShortcutMap | null) { return () => window.removeEventListener("keydown", handler); }, [shortcuts]); } - - From 1cdcdd983b7bb24dfbf1efb4fc7341faacda179b Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:28:49 +0200 Subject: [PATCH 13/14] =?UTF-8?q?R=C3=A9organisation=20des=20styles=20CSS?= =?UTF-8?q?=20pour=20emp=C3=AAcher=20le=20d=C3=A9filement=20de=20la=20page?= =?UTF-8?q?=20au=20niveau=20du=20composant=20de=20chat,=20tout=20en=20d?= =?UTF-8?q?=C3=A9pla=C3=A7ant=20la=20gestion=20du=20d=C3=A9filement=20sp?= =?UTF-8?q?=C3=A9cifique=20=C3=A0=20la=20zone=20de=20chat.=20Cela=20am?= =?UTF-8?q?=C3=A9liore=20l'exp=C3=A9rience=20utilisateur=20en=20=C3=A9vita?= =?UTF-8?q?nt=20de=20masquer=20les=20barres=20de=20d=C3=A9filement=20sur?= =?UTF-8?q?=20d'autres=20pages.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/chat/chat.module.css | 3 ++- src/interface/web/app/globals.css | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index d9d935a93..31974c6b3 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -1,5 +1,6 @@ div.main { height: 100%; + overflow: hidden; color: hsla(var(--foreground)); margin-left: auto; margin-right: auto; @@ -171,4 +172,4 @@ div.chatTitleWrapper { width: 100% !important; max-width: none !important; } -} +} \ No newline at end of file diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css index fd7b62705..bbc7db97a 100644 --- a/src/interface/web/app/globals.css +++ b/src/interface/web/app/globals.css @@ -375,8 +375,7 @@ body { @apply bg-background text-foreground; - overflow: hidden; - /* Prevent page-level scrolling */ + /* Prevent page-level scrolling - moved to chat-specific styles to avoid hiding scrollbars on other pages */ } h1 { @@ -398,4 +397,4 @@ input { background-color: hsla(var(--background) / 0.1); } -} +} \ No newline at end of file From f5a67340d7371d932bafb758fb145d9c0f7d09c1 Mon Sep 17 00:00:00 2001 From: Henri Jamet <42291955+hjamet@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:29:20 +0200 Subject: [PATCH 14/14] =?UTF-8?q?Ajout=20de=20nouvelles=20lignes=20=C3=A0?= =?UTF-8?q?=20la=20fin=20des=20fichiers=20CSS=20pour=20respecter=20les=20c?= =?UTF-8?q?onventions=20de=20style,=20am=C3=A9liorant=20ainsi=20la=20lisib?= =?UTF-8?q?ilit=C3=A9=20et=20la=20coh=C3=A9rence=20du=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interface/web/app/chat/chat.module.css | 2 +- src/interface/web/app/globals.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 31974c6b3..7f20e904f 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -172,4 +172,4 @@ div.chatTitleWrapper { width: 100% !important; max-width: none !important; } -} \ No newline at end of file +} diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css index bbc7db97a..bed3f3281 100644 --- a/src/interface/web/app/globals.css +++ b/src/interface/web/app/globals.css @@ -397,4 +397,4 @@ input { background-color: hsla(var(--background) / 0.1); } -} \ No newline at end of file +}