mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-21 06:10:19 +02:00
Add hiding of documents to feedback page (#585)
This commit is contained in:
@@ -96,14 +96,15 @@ const MainSection = () => {
|
||||
<>
|
||||
<p className="text-sm mb-4">
|
||||
To use the Document360 connector, you must first provide the API
|
||||
token and portal ID corresponding to your Document360 setup. See setup guide{" "}
|
||||
token and portal ID corresponding to your Document360 setup. See
|
||||
setup guide{" "}
|
||||
<a
|
||||
className="text-blue-500"
|
||||
href="https://docs.danswer.dev/connectors/document360"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
{" "}for more detail.
|
||||
</a>{" "}
|
||||
for more detail.
|
||||
</p>
|
||||
<div className="border-solid border-gray-600 border rounded-md p-6 mt-2">
|
||||
<CredentialForm<Document360CredentialJson>
|
||||
|
243
web/src/app/admin/documents/feedback/DocumentFeedbackTable.tsx
Normal file
243
web/src/app/admin/documents/feedback/DocumentFeedbackTable.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
import { DocumentBoostStatus } from "@/lib/types";
|
||||
import { updateBoost, updateHiddenStatus } from "./lib";
|
||||
import { CheckmarkIcon, EditIcon } from "@/components/icons/icons";
|
||||
import { numToDisplay } from "./constants";
|
||||
import { FiCheck, FiCheckSquare, FiEye, FiEyeOff, FiX } from "react-icons/fi";
|
||||
import { getErrorMsg } from "@/lib/fetchUtils";
|
||||
import { HoverPopup } from "@/components/HoverPopup";
|
||||
import { CustomCheckbox } from "@/components/CustomCheckbox";
|
||||
|
||||
const IsVisibleSection = ({
|
||||
document,
|
||||
onUpdate,
|
||||
}: {
|
||||
document: DocumentBoostStatus;
|
||||
onUpdate: (response: Response) => void;
|
||||
}) => {
|
||||
return (
|
||||
<HoverPopup
|
||||
mainContent={
|
||||
document.hidden ? (
|
||||
<div
|
||||
onClick={async () => {
|
||||
const response = await updateHiddenStatus(
|
||||
document.document_id,
|
||||
false
|
||||
);
|
||||
onUpdate(response);
|
||||
}}
|
||||
className="flex text-red-700 cursor-pointer hover:bg-gray-700 py-1 px-2 w-fit rounded-full"
|
||||
>
|
||||
<div className="select-none">Hidden</div>
|
||||
<div className="ml-1 my-auto">
|
||||
<CustomCheckbox checked={false} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
onClick={async () => {
|
||||
const response = await updateHiddenStatus(
|
||||
document.document_id,
|
||||
true
|
||||
);
|
||||
onUpdate(response);
|
||||
}}
|
||||
className="flex text-gray-400 cursor-pointer hover:bg-gray-700 py-1 px-2 w-fit rounded-full"
|
||||
>
|
||||
<div className="text-gray-400 my-auto select-none">Visible</div>
|
||||
<div className="ml-1 my-auto">
|
||||
<CustomCheckbox checked={true} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
popupContent={
|
||||
<div className="text-xs text-gray-300">
|
||||
{document.hidden ? (
|
||||
<div className="flex">
|
||||
<FiEye className="my-auto mr-1" /> Unhide
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<FiEyeOff className="my-auto mr-1" />
|
||||
Hide
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
direction="left"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ScoreSection = ({
|
||||
documentId,
|
||||
initialScore,
|
||||
setPopup,
|
||||
refresh,
|
||||
}: {
|
||||
documentId: string;
|
||||
initialScore: number;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
refresh: () => void;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [score, setScore] = useState(initialScore.toString());
|
||||
|
||||
const onSubmit = async () => {
|
||||
const numericScore = Number(score);
|
||||
if (isNaN(numericScore)) {
|
||||
setPopup({
|
||||
message: "Score must be a number",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMsg = await updateBoost(documentId, numericScore);
|
||||
if (errorMsg) {
|
||||
setPopup({
|
||||
message: errorMsg,
|
||||
type: "error",
|
||||
});
|
||||
} else {
|
||||
setPopup({
|
||||
message: "Updated score!",
|
||||
type: "success",
|
||||
});
|
||||
refresh();
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
return (
|
||||
<div className="m-auto flex">
|
||||
<input
|
||||
value={score}
|
||||
onChange={(e) => {
|
||||
setScore(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
onSubmit();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setIsOpen(false);
|
||||
setScore(initialScore.toString());
|
||||
}
|
||||
}}
|
||||
className="border bg-slate-700 text-gray-200 border-gray-300 rounded py-1 px-3 w-16"
|
||||
/>
|
||||
<div onClick={onSubmit} className="cursor-pointer my-auto ml-2">
|
||||
<CheckmarkIcon size={20} className="text-green-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex my-auto">
|
||||
<div className="w-6 flex">
|
||||
<div className="ml-auto">{initialScore}</div>
|
||||
</div>
|
||||
<div className="cursor-pointer ml-2" onClick={() => setIsOpen(true)}>
|
||||
<EditIcon size={20} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DocumentFeedbackTable = ({
|
||||
documents,
|
||||
refresh,
|
||||
}: {
|
||||
documents: DocumentBoostStatus[];
|
||||
refresh: () => void;
|
||||
}) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
<BasicTable
|
||||
columns={[
|
||||
{
|
||||
header: "Document Name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
header: "Is Searchable?",
|
||||
key: "visible",
|
||||
},
|
||||
{
|
||||
header: "Score",
|
||||
key: "score",
|
||||
alignment: "right",
|
||||
},
|
||||
]}
|
||||
data={documents
|
||||
.slice((page - 1) * numToDisplay, page * numToDisplay)
|
||||
.map((document) => {
|
||||
return {
|
||||
name: (
|
||||
<a
|
||||
className="text-blue-600"
|
||||
href={document.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{document.semantic_id}
|
||||
</a>
|
||||
),
|
||||
visible: (
|
||||
<IsVisibleSection
|
||||
document={document}
|
||||
onUpdate={async (response) => {
|
||||
if (response.ok) {
|
||||
refresh();
|
||||
} else {
|
||||
setPopup({
|
||||
message: `Error updating hidden status - ${getErrorMsg(
|
||||
response
|
||||
)}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
score: (
|
||||
<div className="ml-auto flex w-16">
|
||||
<div key={document.document_id} className="h-10 ml-auto mr-8">
|
||||
<ScoreSection
|
||||
documentId={document.document_id}
|
||||
initialScore={document.boost}
|
||||
refresh={refresh}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<div className="mt-3 flex">
|
||||
<div className="mx-auto">
|
||||
<PageSelector
|
||||
totalPages={Math.ceil(documents.length / numToDisplay)}
|
||||
currentPage={page}
|
||||
onPageChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
2
web/src/app/admin/documents/feedback/constants.ts
Normal file
2
web/src/app/admin/documents/feedback/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const numPages = 8;
|
||||
export const numToDisplay = 10;
|
34
web/src/app/admin/documents/feedback/lib.ts
Normal file
34
web/src/app/admin/documents/feedback/lib.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const updateBoost = async (documentId: string, boost: number) => {
|
||||
const response = await fetch("/api/manage/admin/doc-boosts", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
document_id: documentId,
|
||||
boost,
|
||||
}),
|
||||
});
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
return responseJson.message || responseJson.detail || "Unknown error";
|
||||
};
|
||||
|
||||
export const updateHiddenStatus = async (
|
||||
documentId: string,
|
||||
isHidden: boolean
|
||||
) => {
|
||||
const response = await fetch("/api/manage/admin/doc-hidden", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
document_id: documentId,
|
||||
hidden: isHidden,
|
||||
}),
|
||||
});
|
||||
return response;
|
||||
};
|
@@ -1,187 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import {
|
||||
CheckmarkIcon,
|
||||
EditIcon,
|
||||
ThumbsUpIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { ThumbsUpIcon } from "@/components/icons/icons";
|
||||
import { useMostReactedToDocuments } from "@/lib/hooks";
|
||||
import { DocumentBoostStatus, User } from "@/lib/types";
|
||||
import { useState } from "react";
|
||||
|
||||
const numPages = 8;
|
||||
const numToDisplay = 10;
|
||||
|
||||
const updateBoost = async (documentId: string, boost: number) => {
|
||||
const response = await fetch("/api/manage/admin/doc-boosts", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
document_id: documentId,
|
||||
boost,
|
||||
}),
|
||||
});
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
return responseJson.message || responseJson.detail || "Unknown error";
|
||||
};
|
||||
|
||||
const ScoreSection = ({
|
||||
documentId,
|
||||
initialScore,
|
||||
setPopup,
|
||||
refresh,
|
||||
}: {
|
||||
documentId: string;
|
||||
initialScore: number;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
refresh: () => void;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [score, setScore] = useState(initialScore.toString());
|
||||
|
||||
const onSubmit = async () => {
|
||||
const numericScore = Number(score);
|
||||
if (isNaN(numericScore)) {
|
||||
setPopup({
|
||||
message: "Score must be a number",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMsg = await updateBoost(documentId, numericScore);
|
||||
if (errorMsg) {
|
||||
setPopup({
|
||||
message: errorMsg,
|
||||
type: "error",
|
||||
});
|
||||
} else {
|
||||
setPopup({
|
||||
message: "Updated score!",
|
||||
type: "success",
|
||||
});
|
||||
refresh();
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
return (
|
||||
<div className="m-auto flex">
|
||||
<input
|
||||
value={score}
|
||||
onChange={(e) => {
|
||||
setScore(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
onSubmit();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setIsOpen(false);
|
||||
setScore(initialScore.toString());
|
||||
}
|
||||
}}
|
||||
className="border bg-slate-700 text-gray-200 border-gray-300 rounded py-1 px-3 w-16"
|
||||
/>
|
||||
<div onClick={onSubmit} className="cursor-pointer my-auto ml-2">
|
||||
<CheckmarkIcon size={20} className="text-green-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex my-auto">
|
||||
<div className="w-6 flex">
|
||||
<div className="ml-auto">{initialScore}</div>
|
||||
</div>
|
||||
<div className="cursor-pointer ml-2" onClick={() => setIsOpen(true)}>
|
||||
<EditIcon size={20} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface DocumentFeedbackTableProps {
|
||||
documents: DocumentBoostStatus[];
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const DocumentFeedbackTable = ({
|
||||
documents,
|
||||
refresh,
|
||||
}: DocumentFeedbackTableProps) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
<BasicTable
|
||||
columns={[
|
||||
{
|
||||
header: "Document Name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
header: "Score",
|
||||
key: "score",
|
||||
alignment: "right",
|
||||
},
|
||||
]}
|
||||
data={documents
|
||||
.slice((page - 1) * numToDisplay, page * numToDisplay)
|
||||
.map((document) => {
|
||||
return {
|
||||
name: (
|
||||
<a
|
||||
className="text-blue-600"
|
||||
href={document.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{document.semantic_id}
|
||||
</a>
|
||||
),
|
||||
score: (
|
||||
<div className="ml-auto flex w-16">
|
||||
<div key={document.document_id} className="h-10 ml-auto mr-8">
|
||||
<ScoreSection
|
||||
documentId={document.document_id}
|
||||
initialScore={document.boost}
|
||||
refresh={refresh}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<div className="mt-3 flex">
|
||||
<div className="mx-auto">
|
||||
<PageSelector
|
||||
totalPages={Math.ceil(documents.length / numToDisplay)}
|
||||
currentPage={page}
|
||||
onPageChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { DocumentFeedbackTable } from "./DocumentFeedbackTable";
|
||||
import { numPages, numToDisplay } from "./constants";
|
||||
|
||||
const Main = () => {
|
||||
const {
|
||||
|
34
web/src/components/CustomCheckbox.tsx
Normal file
34
web/src/components/CustomCheckbox.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
export const CustomCheckbox = ({
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
checked: boolean;
|
||||
onChange?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="hidden"
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span className="relative">
|
||||
<span
|
||||
className={`block w-3 h-3 border border-gray-600 rounded ${
|
||||
checked ? "bg-green-700" : "bg-gray-800"
|
||||
} transition duration-300`}
|
||||
>
|
||||
{checked && (
|
||||
<svg
|
||||
className="absolute top-0 left-0 w-3 h-3 fill-current text-gray-200"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M0 11l2-2 5 5L18 3l2 2L7 18z" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
};
|
@@ -4,26 +4,42 @@ interface HoverPopupProps {
|
||||
mainContent: string | JSX.Element;
|
||||
popupContent: string | JSX.Element;
|
||||
classNameModifications?: string;
|
||||
direction?: "left" | "bottom";
|
||||
}
|
||||
|
||||
export const HoverPopup = ({
|
||||
mainContent,
|
||||
popupContent,
|
||||
classNameModifications,
|
||||
direction = "bottom",
|
||||
}: HoverPopupProps) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
let popupDirectionClass;
|
||||
switch (direction) {
|
||||
case "left":
|
||||
popupDirectionClass = "top-0 left-0 transform translate-x-[-110%]";
|
||||
break;
|
||||
case "bottom":
|
||||
popupDirectionClass = "top-0 left-0 mt-8";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex"
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseEnter={() => {
|
||||
setHovered(true);
|
||||
console.log("HIII");
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{hovered && (
|
||||
<div
|
||||
className={
|
||||
`absolute top-0 left-0 mt-8 bg-gray-700 px-3 py-2 rounded shadow-lg z-30 ` +
|
||||
classNameModifications || ""
|
||||
`absolute bg-gray-700 px-3 py-2 rounded shadow-lg z-30 ` +
|
||||
(classNameModifications || "") +
|
||||
` ${popupDirectionClass}`
|
||||
}
|
||||
>
|
||||
{popupContent}
|
||||
|
@@ -1,14 +1,8 @@
|
||||
import React from "react";
|
||||
import { getSourceIcon } from "../source";
|
||||
import { Funnel } from "@phosphor-icons/react";
|
||||
import { DocumentSet, ValidSources } from "@/lib/types";
|
||||
import { Source } from "@/lib/search/interfaces";
|
||||
import {
|
||||
BookmarkIcon,
|
||||
InfoIcon,
|
||||
NotebookIcon,
|
||||
defaultTailwindCSS,
|
||||
} from "../icons/icons";
|
||||
import { InfoIcon, defaultTailwindCSS } from "../icons/icons";
|
||||
import { HoverPopup } from "../HoverPopup";
|
||||
import { FiFilter } from "react-icons/fi";
|
||||
|
||||
|
7
web/src/lib/fetchUtils.ts
Normal file
7
web/src/lib/fetchUtils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const getErrorMsg = async (response: Response) => {
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
return responseJson.message || responseJson.detail || "Unknown error";
|
||||
};
|
Reference in New Issue
Block a user