Skip to content

Commit 6ba1e1d

Browse files
authored
Make knowledge source details card usable on touch devices (#24)
Previously, one could not access the knowledge source detail card on touch devices, because there is no hover and a click opened the source file. With this change, the card will open on click on touch devices, and a "file symlink" button allows to access the file from within the card. A click outside of the card will close it again.
1 parent e94f781 commit 6ba1e1d

3 files changed

Lines changed: 71 additions & 27 deletions

File tree

src/hooks/useIsTouch.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { useMemo } from "react";
2+
3+
export function useIsTouch() {
4+
return useMemo(
5+
() => "ontouchstart" in window || navigator.maxTouchPoints > 0,
6+
[],
7+
);
8+
}

src/views/chatbot/KnowledgeSource.tsx

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import useDownloadFile from "@/hooks/useDownloadFile";
44
import { Knowledge, RagSourceRecord } from "@/api";
55
import { HoverCard, HoverCardTrigger } from "@/components/ui/hover-card";
66
import { KnowledgeSourceDetail } from "./KnowledgeSourceDetail";
7-
import { useState } from "react";
7+
import React, { useState } from "react";
8+
import { useIsTouch } from "@/hooks/useIsTouch.ts";
9+
import { useHandleClickOutside } from "@/hooks/useHandleClickOutside.ts";
810

911
interface KnowledgeSourceProps {
1012
knowledgeId: string | null;
@@ -25,12 +27,17 @@ export function KnowledgeSource({
2527
openPdf,
2628
}: Readonly<KnowledgeSourceProps>) {
2729
const { t } = useTranslation();
28-
const { downloadFile } = useDownloadFile();
30+
const isTouch = useIsTouch();
31+
const [open, setOpen] = useState(false);
32+
const ref = useHandleClickOutside<HTMLDivElement>(() => {
33+
setOpen(false);
34+
});
2935
const [hoverProps, setHoverProps] = useState<HoverProps>({
3036
source: null,
3137
knowledge: null,
3238
});
3339

40+
const { downloadFile } = useDownloadFile();
3441
const { knowledge, error } = useGetKnowledgeApi({ uuid: knowledgeId });
3542

3643
const getDocType = () => {
@@ -57,7 +64,21 @@ export function KnowledgeSource({
5764
(source) => source.embeddingId === embeddingId,
5865
);
5966

60-
const handleHover = () => {
67+
const handleClick = () => {
68+
if (isTouch) {
69+
return;
70+
}
71+
void handleFileSource();
72+
};
73+
74+
const handleTouch = (event: React.TouchEvent) => {
75+
event.preventDefault();
76+
handleHover();
77+
setOpen(true);
78+
};
79+
80+
const handleHover = (event?: React.MouseEvent) => {
81+
event?.preventDefault();
6182
setHoverProps({
6283
knowledge: knowledge ?? null,
6384
source: sourceContent ?? null,
@@ -73,23 +94,29 @@ export function KnowledgeSource({
7394
}
7495

7596
return (
76-
<HoverCard>
77-
{knowledge?.label ? (
78-
<HoverCardTrigger onMouseEnter={handleHover}>
79-
<span
80-
onClick={() => void handleFileSource()}
81-
className="inline-flex items-center px-2 py-0.5 rounded border text-sm font-medium text-secondary-foreground hover:bg-foreground/20 bg-foreground/5"
82-
>
83-
{getDocType()} | {knowledge.label}
97+
<div ref={ref}>
98+
<HoverCard open={isTouch ? open : undefined}>
99+
{knowledge?.label ? (
100+
<HoverCardTrigger onMouseEnter={handleHover}>
101+
<span
102+
onClick={handleClick}
103+
onTouchStart={handleTouch}
104+
className="inline-flex items-center px-2 py-0.5 rounded border text-sm font-medium text-secondary-foreground hover:bg-foreground/20 bg-foreground/5"
105+
>
106+
{getDocType()} | {knowledge.label}
107+
</span>
108+
</HoverCardTrigger>
109+
) : (
110+
<span className="inline-flex items-center px-2 py-0.5 rounded border text-sm font-medium text-secondary-foreground hover:bg-foreground/20 bg-foreground/5">
111+
{getDocType()} | {t("chatbot.chat.source.loading")}
84112
</span>
85-
</HoverCardTrigger>
86-
) : (
87-
<span className="inline-flex items-center px-2 py-0.5 rounded border text-sm font-medium text-secondary-foreground hover:bg-foreground/20 bg-foreground/5">
88-
{getDocType()} | {t("chatbot.chat.source.loading")}
89-
</span>
90-
)}
113+
)}
91114

92-
<KnowledgeSourceDetail hoverProps={hoverProps} />
93-
</HoverCard>
115+
<KnowledgeSourceDetail
116+
onOpenFile={() => void handleFileSource()}
117+
hoverProps={hoverProps}
118+
/>
119+
</HoverCard>
120+
</div>
94121
);
95122
}

src/views/chatbot/KnowledgeSourceDetail.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,38 @@ import {
1414
translateUser,
1515
} from "../knowledge/KnowledgePermissions";
1616
import { HoverProps } from "./KnowledgeSource";
17+
import { FileSymlink } from "lucide-react";
18+
import { Button } from "@/components/ui/button.tsx";
1719

1820
interface KnowledgeCardProps {
21+
onOpenFile: () => void;
1922
hoverProps: HoverProps;
2023
}
2124

2225
export function KnowledgeSourceDetail({
26+
onOpenFile,
2327
hoverProps,
2428
}: Readonly<KnowledgeCardProps>) {
2529
const knowledge = hoverProps.knowledge;
2630
const sourceContent =
2731
hoverProps.source?.content ?? "No source content available";
2832

2933
return (
30-
<HoverCardContent className="w-3/4 max-h-[450px] items-center justify-center overflow-auto">
31-
<CardHeader>
34+
<HoverCardContent className="w-[500px] p-6 max-h-[450px] items-center justify-center overflow-auto">
35+
<CardHeader className="p-0 pb-4">
3236
{knowledge && (
33-
<>
34-
<CardTitle className="underline">{knowledge.label}</CardTitle>
35-
<CardDescription>ID: {knowledge.id}</CardDescription>
36-
</>
37+
<div className="flex flex-row justify-between">
38+
<div>
39+
<CardTitle className="underline">{knowledge.label}</CardTitle>
40+
<CardDescription>ID: {knowledge.id}</CardDescription>
41+
</div>
42+
<Button onClick={onOpenFile} className="h-10 w-10">
43+
<FileSymlink className="h-8 w-8" />
44+
</Button>
45+
</div>
3746
)}
3847
</CardHeader>
39-
<CardContent className="grid gap-4">
48+
<CardContent className="grid gap-4 p-0">
4049
{knowledge && (
4150
<>
4251
<div className="grid grid-cols-2 gap-2">
@@ -83,7 +92,7 @@ export function KnowledgeSourceDetail({
8392
)}
8493
<hr />
8594
</CardContent>
86-
<CardFooter>
95+
<CardFooter className="p-0 pt-4">
8796
<div className="flex flex-col items-start ">
8897
<h3 className="font-bold mb-2 underline">
8998
{t("knowledge.sourceContent")}

0 commit comments

Comments
 (0)