mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-11 01:10:11 +01:00
Compare commits
6 Commits
1b8386b079
...
3c98d15c6a
Author | SHA1 | Date | |
---|---|---|---|
|
3c98d15c6a | ||
|
13da9cb396 | ||
|
41c176c746 | ||
|
48b7d54b52 | ||
|
3434bf4686 | ||
|
bb0a4b0531 |
@ -19,6 +19,7 @@
|
|||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
|
"js-levenshtein": "^1.1.6",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
@ -63,4 +64,4 @@
|
|||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"vite": "^4.3.0"
|
"vite": "^4.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { X } from "@phosphor-icons/react";
|
||||||
|
import Document from "@/models/document";
|
||||||
|
|
||||||
|
export default function NewFolderModal({ closeModal, files, setFiles }) {
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [folderName, setFolderName] = useState("");
|
||||||
|
|
||||||
|
const handleCreate = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
if (folderName.trim() !== "") {
|
||||||
|
const newFolder = {
|
||||||
|
name: folderName,
|
||||||
|
type: "folder",
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
const { success } = await Document.createFolder(folderName);
|
||||||
|
if (success) {
|
||||||
|
setFiles({
|
||||||
|
...files,
|
||||||
|
items: [...files.items, newFolder],
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
setError("Failed to create folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full max-w-xl max-h-full">
|
||||||
|
<div className="relative bg-main-gradient rounded-lg shadow">
|
||||||
|
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
|
||||||
|
<h3 className="text-xl font-semibold text-white">
|
||||||
|
Create New Folder
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||||
|
data-modal-hide="staticModal"
|
||||||
|
>
|
||||||
|
<X className="text-gray-300 text-lg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleCreate}>
|
||||||
|
<div className="p-6 space-y-6 flex h-full w-full">
|
||||||
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="folderName"
|
||||||
|
className="block mb-2 text-sm font-medium text-white"
|
||||||
|
>
|
||||||
|
Folder Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="folderName"
|
||||||
|
type="text"
|
||||||
|
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||||
|
placeholder="Enter folder name"
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
value={folderName}
|
||||||
|
onChange={(e) => setFolderName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
|
||||||
|
>
|
||||||
|
Create Folder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,11 +3,16 @@ import PreLoader from "@/components/Preloader";
|
|||||||
import { memo, useEffect, useState } from "react";
|
import { memo, useEffect, useState } from "react";
|
||||||
import FolderRow from "./FolderRow";
|
import FolderRow from "./FolderRow";
|
||||||
import System from "@/models/system";
|
import System from "@/models/system";
|
||||||
import { Plus, Trash } from "@phosphor-icons/react";
|
import { MagnifyingGlass, Plus, Trash } from "@phosphor-icons/react";
|
||||||
import Document from "@/models/document";
|
import Document from "@/models/document";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
import FolderSelectionPopup from "./FolderSelectionPopup";
|
import FolderSelectionPopup from "./FolderSelectionPopup";
|
||||||
import MoveToFolderIcon from "./MoveToFolderIcon";
|
import MoveToFolderIcon from "./MoveToFolderIcon";
|
||||||
|
import { useModal } from "@/hooks/useModal";
|
||||||
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
|
import NewFolderModal from "./NewFolderModal";
|
||||||
|
import debounce from "lodash.debounce";
|
||||||
|
import { filterFileSearchResults } from "./utils";
|
||||||
|
|
||||||
function Directory({
|
function Directory({
|
||||||
files,
|
files,
|
||||||
@ -24,9 +29,13 @@ function Directory({
|
|||||||
loadingMessage,
|
loadingMessage,
|
||||||
}) {
|
}) {
|
||||||
const [amountSelected, setAmountSelected] = useState(0);
|
const [amountSelected, setAmountSelected] = useState(0);
|
||||||
const [newFolderName, setNewFolderName] = useState("");
|
|
||||||
const [showNewFolderInput, setShowNewFolderInput] = useState(false);
|
|
||||||
const [showFolderSelection, setShowFolderSelection] = useState(false);
|
const [showFolderSelection, setShowFolderSelection] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const {
|
||||||
|
isOpen: isFolderModalOpen,
|
||||||
|
openModal: openFolderModal,
|
||||||
|
closeModal: closeFolderModal,
|
||||||
|
} = useModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAmountSelected(Object.keys(selectedItems).length);
|
setAmountSelected(Object.keys(selectedItems).length);
|
||||||
@ -121,32 +130,6 @@ function Directory({
|
|||||||
return !!selectedItems[id];
|
return !!selectedItems[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNewFolder = () => {
|
|
||||||
setShowNewFolderInput(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmNewFolder = async () => {
|
|
||||||
if (newFolderName.trim() !== "") {
|
|
||||||
const newFolder = {
|
|
||||||
name: newFolderName,
|
|
||||||
type: "folder",
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// If folder failed to create - silently fail.
|
|
||||||
const { success } = await Document.createFolder(newFolderName);
|
|
||||||
if (success) {
|
|
||||||
setFiles({
|
|
||||||
...files,
|
|
||||||
items: [...files.items, newFolder],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewFolderName("");
|
|
||||||
setShowNewFolderInput(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveToFolder = async (folder) => {
|
const moveToFolder = async (folder) => {
|
||||||
const toMove = [];
|
const toMove = [];
|
||||||
for (const itemId of Object.keys(selectedItems)) {
|
for (const itemId of Object.keys(selectedItems)) {
|
||||||
@ -183,40 +166,39 @@ function Directory({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearch = debounce((e) => {
|
||||||
|
const searchValue = e.target.value;
|
||||||
|
setSearchTerm(searchValue);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const filteredFiles = filterFileSearchResults(files, searchTerm);
|
||||||
return (
|
return (
|
||||||
<div className="px-8 pb-8">
|
<div className="px-8 pb-8">
|
||||||
<div className="flex flex-col gap-y-6">
|
<div className="flex flex-col gap-y-6">
|
||||||
<div className="flex items-center justify-between w-[560px] px-5 relative">
|
<div className="flex items-center justify-between w-[560px] px-5 relative">
|
||||||
<h3 className="text-white text-base font-bold">My Documents</h3>
|
<h3 className="text-white text-base font-bold">My Documents</h3>
|
||||||
{showNewFolderInput ? (
|
<div className="relative">
|
||||||
<div className="flex items-center gap-x-2 z-50">
|
<input
|
||||||
<input
|
type="search"
|
||||||
type="text"
|
placeholder="Search for document"
|
||||||
placeholder="Folder name"
|
onChange={handleSearch}
|
||||||
value={newFolderName}
|
className="search-input bg-zinc-900 text-white placeholder-white/40 text-sm rounded-lg pl-9 pr-2.5 py-2 w-[250px] h-[32px]"
|
||||||
onChange={(e) => setNewFolderName(e.target.value)}
|
/>
|
||||||
className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-md p-2.5 w-[150px] h-[32px]"
|
<MagnifyingGlass
|
||||||
/>
|
size={14}
|
||||||
<div className="flex gap-x-2">
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white"
|
||||||
<button
|
weight="bold"
|
||||||
onClick={confirmNewFolder}
|
/>
|
||||||
className="text-sky-400 rounded-md text-sm font-bold hover:text-sky-500"
|
</div>
|
||||||
>
|
<button
|
||||||
Create
|
className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60"
|
||||||
</button>
|
onClick={openFolderModal}
|
||||||
</div>
|
>
|
||||||
|
<Plus size={18} weight="bold" color="#D3D4D4" />
|
||||||
|
<div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
|
||||||
|
New Folder
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</button>
|
||||||
<button
|
|
||||||
className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60"
|
|
||||||
onClick={createNewFolder}
|
|
||||||
>
|
|
||||||
<Plus size={18} weight="bold" color="#D3D4D4" />
|
|
||||||
<div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
|
|
||||||
New Folder
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
|
<div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
|
||||||
@ -234,8 +216,8 @@ function Directory({
|
|||||||
{loadingMessage}
|
{loadingMessage}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : files.items ? (
|
) : filteredFiles.length > 0 ? (
|
||||||
files.items.map(
|
filteredFiles.map(
|
||||||
(item, index) =>
|
(item, index) =>
|
||||||
item.type === "folder" && (
|
item.type === "folder" && (
|
||||||
<FolderRow
|
<FolderRow
|
||||||
@ -302,6 +284,7 @@ function Directory({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UploadFile
|
<UploadFile
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
fetchKeys={fetchKeys}
|
fetchKeys={fetchKeys}
|
||||||
@ -309,6 +292,14 @@ function Directory({
|
|||||||
setLoadingMessage={setLoadingMessage}
|
setLoadingMessage={setLoadingMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ModalWrapper isOpen={isFolderModalOpen}>
|
||||||
|
<NewFolderModal
|
||||||
|
closeModal={closeFolderModal}
|
||||||
|
files={files}
|
||||||
|
setFiles={setFiles}
|
||||||
|
/>
|
||||||
|
</ModalWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import strDistance from "js-levenshtein";
|
||||||
|
|
||||||
|
const LEVENSHTEIN_MIN = 8;
|
||||||
|
|
||||||
|
// Regular expression pattern to match the v4 UUID and the ending .json
|
||||||
|
const uuidPattern =
|
||||||
|
/-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/;
|
||||||
|
const jsonPattern = /\.json$/;
|
||||||
|
|
||||||
|
// Function to strip UUID v4 and JSON from file names as that will impact search results.
|
||||||
|
const stripUuidAndJsonFromString = (input = "") => {
|
||||||
|
return input
|
||||||
|
?.replace(uuidPattern, "") // remove v4 uuid
|
||||||
|
?.replace(jsonPattern, "") // remove trailing .json
|
||||||
|
?.replace("-", " "); // turn slugged names into spaces
|
||||||
|
};
|
||||||
|
|
||||||
|
export function filterFileSearchResults(files = [], searchTerm = "") {
|
||||||
|
if (!searchTerm) return files?.items || [];
|
||||||
|
|
||||||
|
const searchResult = [];
|
||||||
|
for (const folder of files?.items) {
|
||||||
|
// If folder is a good match then add all its children
|
||||||
|
if (strDistance(folder.name, searchTerm) <= LEVENSHTEIN_MIN) {
|
||||||
|
searchResult.push(folder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check children for good results
|
||||||
|
const fileSearchResults = [];
|
||||||
|
for (const file of folder?.items) {
|
||||||
|
if (
|
||||||
|
strDistance(stripUuidAndJsonFromString(file.name), searchTerm) <=
|
||||||
|
LEVENSHTEIN_MIN
|
||||||
|
) {
|
||||||
|
fileSearchResults.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileSearchResults.length > 0) {
|
||||||
|
searchResult.push({
|
||||||
|
...folder,
|
||||||
|
items: fileSearchResults,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchResult;
|
||||||
|
}
|
@ -27,7 +27,6 @@ export default function ThreadItem({
|
|||||||
const { slug } = useParams();
|
const { slug } = useParams();
|
||||||
const optionsContainer = useRef(null);
|
const optionsContainer = useRef(null);
|
||||||
const [showOptions, setShowOptions] = useState(false);
|
const [showOptions, setShowOptions] = useState(false);
|
||||||
const [name, setName] = useState(thread.name);
|
|
||||||
const linkTo = !thread.slug
|
const linkTo = !thread.slug
|
||||||
? paths.workspace.chat(slug)
|
? paths.workspace.chat(slug)
|
||||||
: paths.workspace.thread(slug, thread.slug);
|
: paths.workspace.thread(slug, thread.slug);
|
||||||
@ -97,7 +96,7 @@ export default function ThreadItem({
|
|||||||
isActive ? "font-medium text-white" : "text-slate-400"
|
isActive ? "font-medium text-white" : "text-slate-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{truncate(name, 25)}
|
{truncate(thread.name, 25)}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@ -133,7 +132,6 @@ export default function ThreadItem({
|
|||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
onRename={setName}
|
|
||||||
close={() => setShowOptions(false)}
|
close={() => setShowOptions(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -144,14 +142,7 @@ export default function ThreadItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OptionsMenu({
|
function OptionsMenu({ containerRef, workspace, thread, onRemove, close }) {
|
||||||
containerRef,
|
|
||||||
workspace,
|
|
||||||
thread,
|
|
||||||
onRename,
|
|
||||||
onRemove,
|
|
||||||
close,
|
|
||||||
}) {
|
|
||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
|
|
||||||
// Ref menu options
|
// Ref menu options
|
||||||
@ -208,7 +199,7 @@ function OptionsMenu({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRename(name);
|
thread.name = name;
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,26 @@ export default function ThreadContainer({ workspace }) {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [ctrlPressed, setCtrlPressed] = useState(false);
|
const [ctrlPressed, setCtrlPressed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const chatHandler = (event) => {
|
||||||
|
const { threadSlug, newName } = event.detail;
|
||||||
|
setThreads((prevThreads) =>
|
||||||
|
prevThreads.map((thread) => {
|
||||||
|
if (thread.slug === threadSlug) {
|
||||||
|
return { ...thread, name: newName };
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("renameThread", chatHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("renameThread", chatHandler);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchThreads() {
|
async function fetchThreads() {
|
||||||
if (!workspace.slug) return;
|
if (!workspace.slug) return;
|
||||||
|
@ -12,6 +12,7 @@ import handleSocketResponse, {
|
|||||||
AGENT_SESSION_END,
|
AGENT_SESSION_END,
|
||||||
AGENT_SESSION_START,
|
AGENT_SESSION_START,
|
||||||
} from "@/utils/chat/agent";
|
} from "@/utils/chat/agent";
|
||||||
|
import truncate from "truncate";
|
||||||
|
|
||||||
export default function ChatContainer({ workspace, knownHistory = [] }) {
|
export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||||
const { threadSlug = null } = useParams();
|
const { threadSlug = null } = useParams();
|
||||||
@ -39,6 +40,18 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!message || message === "") return false;
|
if (!message || message === "") return false;
|
||||||
|
|
||||||
|
// If first message and it is a thread
|
||||||
|
// and message is not blank/whitespace,
|
||||||
|
// then send event to rename the thread
|
||||||
|
if (threadSlug && chatHistory.length === 0 && message.trim().length > 0) {
|
||||||
|
const truncatedName = truncate(message, 22);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("renameThread", {
|
||||||
|
detail: { threadSlug, newName: truncatedName },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const prevChatHistory = [
|
const prevChatHistory = [
|
||||||
...chatHistory,
|
...chatHistory,
|
||||||
{ content: message, role: "user" },
|
{ content: message, role: "user" },
|
||||||
|
@ -742,3 +742,7 @@ does not extend the close button beyond the viewport. */
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-input::-webkit-search-cancel-button {
|
||||||
|
filter: grayscale(100%) invert(1) brightness(100) opacity(0.5);
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ export const DB_LOGOS = {
|
|||||||
"sql-server": MSSQLLogo,
|
"sql-server": MSSQLLogo,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DBConnection({ connection, onRemove }) {
|
export default function DBConnection({ connection, onRemove, setHasChanges }) {
|
||||||
const { database_id, engine } = connection;
|
const { database_id, engine } = connection;
|
||||||
function removeConfirmation() {
|
function removeConfirmation() {
|
||||||
if (
|
if (
|
||||||
@ -20,6 +20,7 @@ export default function DBConnection({ connection, onRemove }) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
onRemove(database_id);
|
onRemove(database_id);
|
||||||
|
setHasChanges(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -9,6 +9,7 @@ export default function AgentSQLConnectorSelection({
|
|||||||
settings,
|
settings,
|
||||||
toggleSkill,
|
toggleSkill,
|
||||||
enabled = false,
|
enabled = false,
|
||||||
|
setHasChanges,
|
||||||
}) {
|
}) {
|
||||||
const { isOpen, openModal, closeModal } = useModal();
|
const { isOpen, openModal, closeModal } = useModal();
|
||||||
const [connections, setConnections] = useState(
|
const [connections, setConnections] = useState(
|
||||||
@ -72,6 +73,7 @@ export default function AgentSQLConnectorSelection({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
setHasChanges={setHasChanges}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
|
@ -100,6 +100,7 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
|
|||||||
skills={agentSkills}
|
skills={agentSkills}
|
||||||
toggleAgentSkill={toggleAgentSkill}
|
toggleAgentSkill={toggleAgentSkill}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
setHasChanges={setHasChanges}
|
||||||
/>
|
/>
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<button
|
<button
|
||||||
@ -143,7 +144,12 @@ function LoadingSkeleton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
|
function AvailableAgentSkills({
|
||||||
|
skills,
|
||||||
|
settings,
|
||||||
|
toggleAgentSkill,
|
||||||
|
setHasChanges,
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col mb-8">
|
<div className="flex flex-col mb-8">
|
||||||
@ -211,6 +217,7 @@ function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
|
|||||||
settings={settings}
|
settings={settings}
|
||||||
toggleSkill={toggleAgentSkill}
|
toggleSkill={toggleAgentSkill}
|
||||||
enabled={skills.includes("sql-agent")}
|
enabled={skills.includes("sql-agent")}
|
||||||
|
setHasChanges={setHasChanges}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2260,6 +2260,11 @@ jiti@^1.19.1:
|
|||||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
|
||||||
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
|
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
|
||||||
|
|
||||||
|
js-levenshtein@^1.1.6:
|
||||||
|
version "1.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
|
||||||
|
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
@ -206,6 +206,72 @@ function apiSystemEndpoints(app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
app.delete(
|
||||||
|
"/v1/system/remove-documents",
|
||||||
|
[validApiKey],
|
||||||
|
async (request, response) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['System Settings']
|
||||||
|
#swagger.description = 'Permanently remove documents from the system.'
|
||||||
|
#swagger.requestBody = {
|
||||||
|
description: 'Array of document names to be removed permanently.',
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
names: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
example: [
|
||||||
|
"custom-documents/file.txt-fc4beeeb-e436-454d-8bb4-e5b8979cb48f.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
description: 'Documents removed successfully.',
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
example: {
|
||||||
|
success: true,
|
||||||
|
message: 'Documents removed successfully'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#swagger.responses[403] = {
|
||||||
|
description: 'Forbidden',
|
||||||
|
schema: {
|
||||||
|
"$ref": "#/definitions/InvalidAPIKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#swagger.responses[500] = {
|
||||||
|
description: 'Internal Server Error'
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
const { names } = reqBody(request);
|
||||||
|
for await (const name of names) await purgeDocument(name);
|
||||||
|
response
|
||||||
|
.status(200)
|
||||||
|
.json({ success: true, message: "Documents removed successfully" })
|
||||||
|
.end();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message, e);
|
||||||
|
response.sendStatus(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { apiSystemEndpoints };
|
module.exports = { apiSystemEndpoints };
|
||||||
|
@ -15,6 +15,8 @@ const {
|
|||||||
validWorkspaceSlug,
|
validWorkspaceSlug,
|
||||||
} = require("../utils/middleware/validWorkspace");
|
} = require("../utils/middleware/validWorkspace");
|
||||||
const { writeResponseChunk } = require("../utils/helpers/chat/responses");
|
const { writeResponseChunk } = require("../utils/helpers/chat/responses");
|
||||||
|
const { WorkspaceThread } = require("../models/workspaceThread");
|
||||||
|
const truncate = require("truncate");
|
||||||
|
|
||||||
function chatEndpoints(app) {
|
function chatEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
@ -196,6 +198,14 @@ function chatEndpoints(app) {
|
|||||||
user,
|
user,
|
||||||
thread
|
thread
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await WorkspaceThread.autoRenameThread({
|
||||||
|
thread,
|
||||||
|
workspace,
|
||||||
|
user,
|
||||||
|
newName: truncate(message, 22),
|
||||||
|
});
|
||||||
|
|
||||||
await Telemetry.sendTelemetry("sent_chat", {
|
await Telemetry.sendTelemetry("sent_chat", {
|
||||||
multiUserMode: multiUserMode(response),
|
multiUserMode: multiUserMode(response),
|
||||||
LLMSelection: process.env.LLM_PROVIDER || "openai",
|
LLMSelection: process.env.LLM_PROVIDER || "openai",
|
||||||
|
@ -8,7 +8,7 @@ const WorkspaceThread = {
|
|||||||
try {
|
try {
|
||||||
const thread = await prisma.workspace_threads.create({
|
const thread = await prisma.workspace_threads.create({
|
||||||
data: {
|
data: {
|
||||||
name: "New thread",
|
name: "Thread",
|
||||||
slug: uuidv4(),
|
slug: uuidv4(),
|
||||||
user_id: userId ? Number(userId) : null,
|
user_id: userId ? Number(userId) : null,
|
||||||
workspace_id: workspace.id,
|
workspace_id: workspace.id,
|
||||||
@ -84,6 +84,25 @@ const WorkspaceThread = {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Will fire on first message (included or not) for a thread and rename the thread with the newName prop.
|
||||||
|
autoRenameThread: async function ({
|
||||||
|
workspace = null,
|
||||||
|
thread = null,
|
||||||
|
user = null,
|
||||||
|
newName = null,
|
||||||
|
}) {
|
||||||
|
if (!workspace || !thread || !newName) return false;
|
||||||
|
const { WorkspaceChats } = require("./workspaceChats");
|
||||||
|
const chatCount = await WorkspaceChats.count({
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
user_id: user?.id || null,
|
||||||
|
thread_id: thread.id,
|
||||||
|
});
|
||||||
|
if (chatCount !== 1) return false;
|
||||||
|
await this.update(thread, { name: newName });
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { WorkspaceThread };
|
module.exports = { WorkspaceThread };
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"swagger-autogen": "^2.23.5",
|
"swagger-autogen": "^2.23.5",
|
||||||
"swagger-ui-express": "^5.0.0",
|
"swagger-ui-express": "^5.0.0",
|
||||||
|
"truncate": "^3.0.0",
|
||||||
"url-pattern": "^1.0.3",
|
"url-pattern": "^1.0.3",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"uuid-apikey": "^1.5.3",
|
"uuid-apikey": "^1.5.3",
|
||||||
|
@ -2241,6 +2241,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/v1/system/remove-documents": {
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"System Settings"
|
||||||
|
],
|
||||||
|
"description": "Permanently remove documents from the system.",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Documents removed successfully.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"success": true,
|
||||||
|
"message": "Documents removed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/InvalidAPIKey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/xml": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/InvalidAPIKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Array of document names to be removed permanently.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"names": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": [
|
||||||
|
"custom-documents/file.txt-fc4beeeb-e436-454d-8bb4-e5b8979cb48f.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
@ -6211,6 +6211,11 @@ triple-beam@^1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
|
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
|
||||||
integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==
|
integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==
|
||||||
|
|
||||||
|
truncate@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/truncate/-/truncate-3.0.0.tgz#7dbe19e2f72c614e36b79bab00fbfbeb1cbaf078"
|
||||||
|
integrity sha512-C+0Xojw7wZPl6MDq5UjMTuxZvBPK04mtdFet7k+GSZPINcvLZFCXg+15kWIL4wAqDB7CksIsKiRLbQ1wa7rKdw==
|
||||||
|
|
||||||
tslib@^2.2.0, tslib@^2.4.0, tslib@^2.5.3, tslib@^2.6.2:
|
tslib@^2.2.0, tslib@^2.4.0, tslib@^2.5.3, tslib@^2.6.2:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||||
|
Loading…
Reference in New Issue
Block a user