Add hiding of documents to feedback page (#585)

This commit is contained in:
Chris Weaver
2023-10-17 20:06:12 -07:00
committed by GitHub
parent e73739547a
commit 5da81a3d0d
16 changed files with 422 additions and 207 deletions

View File

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

View 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>
);
};

View File

@@ -0,0 +1,2 @@
export const numPages = 8;
export const numToDisplay = 10;

View 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;
};

View File

@@ -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 {

View 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>
);
};

View File

@@ -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}

View File

@@ -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";

View 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";
};