mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-10-02 08:50:11 +02:00
improve citations to show all text chunks referred and expand the citation to view full referenced text (#161)
* improve citations to show all text chunks referred and expand the citation to view full referenced text chunk text of same document together * remove debug
This commit is contained in:
parent
ab7837068b
commit
0a2f837fb2
@ -129,7 +129,7 @@ export default function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={paths.mailToMintplex()}
|
href={paths.mailToMintplex()}
|
||||||
className="transition-all duration-300 text-xs text-slate-200 dark:text-slate-600 hover:text-blue-600 dark:hover:text-blue-400"
|
className="transition-all duration-300 text-xs text-slate-500 dark:text-slate-600 hover:text-blue-600 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
@MintplexLabs
|
@MintplexLabs
|
||||||
</a>
|
</a>
|
||||||
@ -295,7 +295,7 @@ export function SidebarMobileHeader() {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={paths.mailToMintplex()}
|
href={paths.mailToMintplex()}
|
||||||
className="transition-all duration-300 text-xs text-slate-200 dark:text-slate-600 hover:text-blue-600 dark:hover:text-blue-400"
|
className="transition-all duration-300 text-xs text-slate-500 dark:text-slate-600 hover:text-blue-600 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
@MintplexLabs
|
@MintplexLabs
|
||||||
</a>
|
</a>
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
import { memo, useState } from "react";
|
||||||
|
import { Maximize2, Minimize2 } from "react-feather";
|
||||||
|
import { v4 } from "uuid";
|
||||||
|
import { decode as HTMLDecode } from "he";
|
||||||
|
|
||||||
|
function combineLikeSources(sources) {
|
||||||
|
const combined = {};
|
||||||
|
sources.forEach((source) => {
|
||||||
|
const { id, title, text } = source;
|
||||||
|
if (combined.hasOwnProperty(title)) {
|
||||||
|
combined[title].text += `\n\n ---- Chunk ${id || ""} ---- \n\n${text}`;
|
||||||
|
combined[title].references += 1;
|
||||||
|
} else {
|
||||||
|
combined[title] = { title, text, references: 1 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.values(combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Citations({ sources = [] }) {
|
||||||
|
if (sources.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col mt-4 justify-left">
|
||||||
|
<div className="no-scroll flex flex-col justify-left overflow-x-scroll ">
|
||||||
|
<div className="w-full flex overflow-x-scroll items-center gap-4 mt-1 doc__source">
|
||||||
|
{combineLikeSources(sources).map((source) => (
|
||||||
|
<Citation id={source?.id || v4()} source={source} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="w-fit text-gray-700 dark:text-stone-400 text-xs mt-1">
|
||||||
|
*citations may not be relevant to end result.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Citation = memo(({ source, id }) => {
|
||||||
|
const [maximized, setMaximized] = useState(false);
|
||||||
|
const { references = 0, title, text } = source;
|
||||||
|
if (title?.length === 0 || text?.length === 0) return null;
|
||||||
|
const handleMinMax = () => {
|
||||||
|
setMaximized(!maximized);
|
||||||
|
Array.from(
|
||||||
|
document?.querySelectorAll(
|
||||||
|
`div[data-citation]:not([data-citation="${id}"])`
|
||||||
|
)
|
||||||
|
).forEach((el) => {
|
||||||
|
const func = maximized ? "remove" : "add";
|
||||||
|
el.classList[func]("hidden");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={id || v4()}
|
||||||
|
data-citation={id || v4()}
|
||||||
|
className={`transition-all duration-300 relative flex flex-col w-full md:w-80 h-40 bg-gray-100 dark:bg-stone-800 border border-gray-700 dark:border-stone-800 rounded-lg shrink-0 ${
|
||||||
|
maximized ? "md:w-full h-fit pb-4" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="rounded-t-lg bg-gray-300 dark:bg-stone-900 px-4 py-2 w-full h-fit flex items-center justify-between">
|
||||||
|
<p className="text-base text-gray-800 dark:text-slate-400 italic truncate w-3/4">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={handleMinMax}
|
||||||
|
className="hover:dark:bg-stone-800 hover:bg-gray-200 dark:text-slate-400 text-gray-800 rounded-full p-1"
|
||||||
|
>
|
||||||
|
{maximized ? (
|
||||||
|
<Minimize2 className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Maximize2 className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`overflow-hidden relative w-full ${
|
||||||
|
maximized ? "overflow-y-scroll" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="px-2 py-1 text-xs whitespace-pre-line text-gray-800 dark:text-slate-300 italic">
|
||||||
|
{references > 1 && (
|
||||||
|
<p className="text-xs text-gray-500 dark:text-slate-500 mb-2">
|
||||||
|
referenced {references} times.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{HTMLDecode(text)}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className={`absolute bottom-0 flex w-full h-[20px] fade-up-border rounded-b-lg ${
|
||||||
|
maximized ? "hidden" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -1,10 +1,9 @@
|
|||||||
import { useEffect, useRef, memo, useState } from "react";
|
import { useEffect, useRef, memo } from "react";
|
||||||
import { AlertTriangle } from "react-feather";
|
import { AlertTriangle } from "react-feather";
|
||||||
import Jazzicon from "../../../../UserIcon";
|
import Jazzicon from "../../../../UserIcon";
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { decode as HTMLDecode } from "he";
|
|
||||||
import renderMarkdown from "../../../../../utils/chat/markdown";
|
import renderMarkdown from "../../../../../utils/chat/markdown";
|
||||||
import { userFromStorage } from "../../../../../utils/request";
|
import { userFromStorage } from "../../../../../utils/request";
|
||||||
|
import Citations from "../Citation";
|
||||||
|
|
||||||
function HistoricalMessage({
|
function HistoricalMessage({
|
||||||
message,
|
message,
|
||||||
@ -55,7 +54,7 @@ function HistoricalMessage({
|
|||||||
<Jazzicon size={30} user={{ uid: workspace.slug }} />
|
<Jazzicon size={30} user={{ uid: workspace.slug }} />
|
||||||
<div className="ml-2 py-3 px-4 overflow-x-scroll w-fit md:max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm">
|
<div className="ml-2 py-3 px-4 overflow-x-scroll w-fit md:max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm">
|
||||||
<span
|
<span
|
||||||
className="whitespace-pre-line text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base flex flex-col gap-y-1"
|
className="no-scroll whitespace-pre-line text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base flex flex-col gap-y-1"
|
||||||
dangerouslySetInnerHTML={{ __html: renderMarkdown(message) }}
|
dangerouslySetInnerHTML={{ __html: renderMarkdown(message) }}
|
||||||
/>
|
/>
|
||||||
<Citations sources={sources} />
|
<Citations sources={sources} />
|
||||||
@ -64,46 +63,4 @@ function HistoricalMessage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Citations = ({ sources = [] }) => {
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
if (sources.length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col mt-4 justify-left">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShow(!show)}
|
|
||||||
className="w-fit text-gray-700 dark:text-stone-400 italic text-xs"
|
|
||||||
>
|
|
||||||
{show ? "hide" : "show"} citations{show && "*"}
|
|
||||||
</button>
|
|
||||||
{show && (
|
|
||||||
<>
|
|
||||||
<div className="w-full flex flex-wrap items-center gap-4 mt-1 doc__source">
|
|
||||||
{sources.map((source) => {
|
|
||||||
const { id = null, title, url } = source;
|
|
||||||
const handleClick = () => {
|
|
||||||
if (!url) return false;
|
|
||||||
window.open(url, "_blank");
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={id || v4()}
|
|
||||||
onClick={handleClick}
|
|
||||||
className="italic transition-all duration-300 w-fit bg-gray-400 text-gray-900 py-[1px] hover:text-slate-200 hover:bg-gray-500 hover:dark:text-gray-900 dark:bg-stone-400 dark:hover:bg-stone-300 rounded-full px-2 text-xs leading-tight"
|
|
||||||
>
|
|
||||||
"{HTMLDecode(title)}"
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<p className="w-fit text-gray-700 dark:text-stone-400 text-xs mt-1">
|
|
||||||
*citation may not be relevant to end result.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(HistoricalMessage);
|
export default memo(HistoricalMessage);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef } from "react";
|
||||||
import { AlertTriangle } from "react-feather";
|
import { AlertTriangle } from "react-feather";
|
||||||
import Jazzicon from "../../../../UserIcon";
|
import Jazzicon from "../../../../UserIcon";
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { decode as HTMLDecode } from "he";
|
|
||||||
import renderMarkdown from "../../../../../utils/chat/markdown";
|
import renderMarkdown from "../../../../../utils/chat/markdown";
|
||||||
|
import Citations from "../Citation";
|
||||||
|
|
||||||
function PromptReply({
|
function PromptReply({
|
||||||
uuid,
|
uuid,
|
||||||
@ -69,46 +68,4 @@ function PromptReply({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Citations = ({ sources = [] }) => {
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
if (sources.length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col mt-4 justify-left">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShow(!show)}
|
|
||||||
className="w-fit text-gray-700 dark:text-stone-400 italic text-xs"
|
|
||||||
>
|
|
||||||
{show ? "hide" : "show"} citations{show && "*"}
|
|
||||||
</button>
|
|
||||||
{show && (
|
|
||||||
<>
|
|
||||||
<div className="w-full flex flex-wrap items-center gap-4 mt-1 doc__source">
|
|
||||||
{sources.map((source) => {
|
|
||||||
const { id = null, title, url } = source;
|
|
||||||
const handleClick = () => {
|
|
||||||
if (!url) return false;
|
|
||||||
window.open(url, "_blank");
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={id || v4()}
|
|
||||||
onClick={handleClick}
|
|
||||||
className="italic transition-all duration-300 w-fit bg-gray-400 text-gray-900 py-[1px] hover:text-slate-200 hover:bg-gray-500 hover:dark:text-gray-900 dark:bg-stone-400 dark:hover:bg-stone-300 rounded-full px-2 text-xs leading-tight"
|
|
||||||
>
|
|
||||||
"{HTMLDecode(title)}"
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<p className="w-fit text-gray-700 dark:text-stone-400 text-xs mt-1">
|
|
||||||
*citation may not be relevant to end result.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(PromptReply);
|
export default memo(PromptReply);
|
||||||
|
@ -65,7 +65,7 @@ export default function PromptInput({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowMenu(!showMenu)}
|
onClick={() => setShowMenu(!showMenu)}
|
||||||
type="button"
|
type="button"
|
||||||
className="p-2 text-slate-200 bg-transparent rounded-md hover:bg-gray-50 dark:hover:bg-stone-500"
|
className="p-2 text-slate-500 bg-transparent rounded-md hover:bg-gray-200 dark:hover:bg-stone-500 dark:hover:text-slate-200"
|
||||||
>
|
>
|
||||||
<Menu className="w-4 h-4 md:h-6 md:w-6" />
|
<Menu className="w-4 h-4 md:h-6 md:w-6" />
|
||||||
</button>
|
</button>
|
||||||
@ -93,14 +93,14 @@ export default function PromptInput({
|
|||||||
ref={formRef}
|
ref={formRef}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={buttonDisabled}
|
disabled={buttonDisabled}
|
||||||
className="inline-flex justify-center p-0 md:p-2 rounded-full cursor-pointer text-black-900 dark:text-slate-200 hover:bg-gray-600 dark:hover:bg-stone-500"
|
className="inline-flex justify-center p-0 md:p-2 rounded-full cursor-pointer text-black-900 dark:text-slate-200 hover:bg-gray-200 dark:hover:bg-stone-500 group"
|
||||||
>
|
>
|
||||||
{buttonDisabled ? (
|
{buttonDisabled ? (
|
||||||
<Loader className="w-6 h-6 animate-spin" />
|
<Loader className="w-6 h-6 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="w-6 h-6 rotate-45"
|
className="w-6 h-6 rotate-45 fill-gray-500 dark:fill-slate-500 group-hover:dark:fill-slate-200"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -138,7 +138,7 @@ const Tracking = memo(({ workspaceSlug }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col md:flex-row w-full justify-center items-center gap-2 mb-2 px-4 mx:px-0">
|
<div className="flex flex-col md:flex-row w-full justify-center items-center gap-2 mb-2 px-4 mx:px-0">
|
||||||
<p className="bg-stone-600 text-slate-400 text-xs px-2 rounded-lg font-mono text-center">
|
<p className="bg-gray-200 dark:bg-stone-600 text-gray-800 dark:text-slate-400 text-xs px-2 rounded-lg font-mono text-center">
|
||||||
Chat mode: {chatMode}
|
Chat mode: {chatMode}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-slate-400 text-xs text-center">
|
<p className="text-slate-400 text-xs text-center">
|
||||||
@ -164,13 +164,13 @@ function CommandMenu({ workspace, show, handleClick, hide }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-[-25vh] md:top-[-23vh] min-h-[200px] flex flex-col rounded-lg border border-slate-400 p-2 pt-4 bg-stone-600">
|
<div className="absolute top-[-25vh] md:top-[-23vh] min-h-[200px] flex flex-col rounded-lg border border-slate-400 p-2 pt-4 bg-gray-50 dark:bg-stone-600">
|
||||||
<div className="flex justify-between items-center border-b border-slate-400 px-2 py-1 ">
|
<div className="flex justify-between items-center border-b border-slate-400 px-2 py-1 ">
|
||||||
<p className="text-slate-200">Available Commands</p>
|
<p className="text-gray-800 dark:text-slate-200">Available Commands</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={hide}
|
onClick={hide}
|
||||||
className="p-2 rounded-lg hover:bg-slate-500 rounded-full text-slate-400"
|
className="p-2 rounded-lg hover:bg-gray-200 hover:dark:bg-slate-500 rounded-full text-gray-800 dark:text-slate-400"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -188,10 +188,14 @@ function CommandMenu({ workspace, show, handleClick, hide }) {
|
|||||||
handleClick(cmd);
|
handleClick(cmd);
|
||||||
hide();
|
hide();
|
||||||
}}
|
}}
|
||||||
className="w-full px-4 py-2 flex items-center rounded-lg hover:bg-slate-500 gap-x-1 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2 flex items-center rounded-lg hover:bg-gray-300 hover:dark:bg-slate-500 gap-x-1 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<p className="text-slate-200 font-semibold">{cmd}</p>
|
<p className="text-gray-800 dark:text-slate-200 font-semibold">
|
||||||
<p className="text-slate-400 text-sm">{description}</p>
|
{cmd}
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-800 dark:text-slate-300 text-sm">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -133,6 +133,26 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.fade-up-border {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(220, 221, 223, 10%),
|
||||||
|
rgb(220, 221, 223) 89%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.fade-up-border {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(41, 37, 36, 50%),
|
||||||
|
rgb(41 37 36) 90%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ==============================================
|
* ==============================================
|
||||||
* Dot Falling
|
* Dot Falling
|
||||||
|
@ -5,7 +5,7 @@ export default {
|
|||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'black-900': '#141414',
|
'black-900': '#141414',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
@ -23,7 +23,6 @@ function toChunks(arr, size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function curateSources(sources = []) {
|
function curateSources(sources = []) {
|
||||||
const knownDocs = [];
|
|
||||||
const documents = [];
|
const documents = [];
|
||||||
|
|
||||||
// Sometimes the source may or may not have a metadata property
|
// Sometimes the source may or may not have a metadata property
|
||||||
@ -32,17 +31,12 @@ function curateSources(sources = []) {
|
|||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (source.hasOwnProperty("metadata")) {
|
if (source.hasOwnProperty("metadata")) {
|
||||||
const { metadata = {} } = source;
|
const { metadata = {} } = source;
|
||||||
if (
|
if (Object.keys(metadata).length > 0) {
|
||||||
Object.keys(metadata).length > 0 &&
|
|
||||||
!knownDocs.includes(metadata.title)
|
|
||||||
) {
|
|
||||||
documents.push({ ...metadata });
|
documents.push({ ...metadata });
|
||||||
knownDocs.push(metadata.title);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Object.keys(source).length > 0 && !knownDocs.includes(source.title)) {
|
if (Object.keys(source).length > 0) {
|
||||||
documents.push({ ...source });
|
documents.push({ ...source });
|
||||||
knownDocs.push(source.title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,11 @@ const { chatPrompt } = require("../../chats");
|
|||||||
// Since we roll our own results for prompting we
|
// Since we roll our own results for prompting we
|
||||||
// have to manually curate sources as well.
|
// have to manually curate sources as well.
|
||||||
function curateLanceSources(sources = []) {
|
function curateLanceSources(sources = []) {
|
||||||
const knownDocs = [];
|
|
||||||
const documents = [];
|
const documents = [];
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
const { text: _t, vector: _v, score: _s, ...metadata } = source;
|
const { text, vector: _v, score: _s, ...metadata } = source;
|
||||||
if (
|
if (Object.keys(metadata).length > 0) {
|
||||||
Object.keys(metadata).length > 0 &&
|
documents.push({ ...metadata, text });
|
||||||
!knownDocs.includes(metadata.title)
|
|
||||||
) {
|
|
||||||
documents.push({ ...metadata });
|
|
||||||
knownDocs.push(metadata.title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user