From bb0a4b05316697395e9495544bdf5a868d717c44 Mon Sep 17 00:00:00 2001 From: yonathan suarez <47557263+yond1994@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:22:03 +0200 Subject: [PATCH 1/5] Add API Endpoint for Deleting Documents in system (#1623) * fix: "remove document system api" * fix: "update text system system " --------- Co-authored-by: Timothy Carambat --- server/endpoints/api/system/index.js | 63 ++++++++++++++++++++++ server/swagger/openapi.json | 78 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/server/endpoints/api/system/index.js b/server/endpoints/api/system/index.js index c8e5e06c..da941bd8 100644 --- a/server/endpoints/api/system/index.js +++ b/server/endpoints/api/system/index.js @@ -206,6 +206,69 @@ 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 }; diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index ed6f1533..adc843f3 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -2241,6 +2241,84 @@ } } } + }, + "/v1/system/remove-documents": { + "delete": { + "tags": [ + "System Settings" + ], + "description": "Permanently remove documents from the system.", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "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": { + "schemas": { + "InvalidAPIKey": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Invalid API Key" + } + } + } + } } }, "components": { From 3434bf4686901721b109b7d67b771ef2957f264c Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Fri, 7 Jun 2024 10:09:35 -0700 Subject: [PATCH 2/5] linting --- server/endpoints/api/system/index.js | 5 ++++- server/swagger/openapi.json | 15 +-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/server/endpoints/api/system/index.js b/server/endpoints/api/system/index.js index da941bd8..7fdb92cc 100644 --- a/server/endpoints/api/system/index.js +++ b/server/endpoints/api/system/index.js @@ -262,7 +262,10 @@ function apiSystemEndpoints(app) { 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(); + response + .status(200) + .json({ success: true, message: "Documents removed successfully" }) + .end(); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index adc843f3..230f0ce6 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -2251,7 +2251,7 @@ "parameters": [], "responses": { "200": { - "description": "OK", + "description": "Documents removed successfully.", "content": { "application/json": { "schema": { @@ -2306,19 +2306,6 @@ } } } - }, - "components": { - "schemas": { - "InvalidAPIKey": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Invalid API Key" - } - } - } - } } }, "components": { From 48b7d54b529e2dd3f57bd39b129eefded95f746d Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Fri, 7 Jun 2024 12:03:48 -0700 Subject: [PATCH 3/5] [FIX] Delete SQL agent connection bug fix (#1635) fix update agent settings button not appearing when deleting sql connection --- .../AgentConfig/SQLConnectorSelection/DBConnection.jsx | 3 ++- .../AgentConfig/SQLConnectorSelection/index.jsx | 2 ++ frontend/src/pages/WorkspaceSettings/AgentConfig/index.jsx | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/DBConnection.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/DBConnection.jsx index 7a58da45..b2ff33bf 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/DBConnection.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/DBConnection.jsx @@ -9,7 +9,7 @@ export const DB_LOGOS = { "sql-server": MSSQLLogo, }; -export default function DBConnection({ connection, onRemove }) { +export default function DBConnection({ connection, onRemove, setHasChanges }) { const { database_id, engine } = connection; function removeConfirmation() { if ( @@ -20,6 +20,7 @@ export default function DBConnection({ connection, onRemove }) { return false; } onRemove(database_id); + setHasChanges(true); } return ( diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx index 9feb4b8b..848d44ed 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx @@ -9,6 +9,7 @@ export default function AgentSQLConnectorSelection({ settings, toggleSkill, enabled = false, + setHasChanges, }) { const { isOpen, openModal, closeModal } = useModal(); const [connections, setConnections] = useState( @@ -72,6 +73,7 @@ export default function AgentSQLConnectorSelection({ }) ); }} + setHasChanges={setHasChanges} /> ))} + +
+
+
+
+ + setFolderName(e.target.value)} + /> +
+ {error &&

Error: {error}

} +
+
+
+ + +
+
+ + + ); +} diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx index d479a6cc..c7794a3f 100644 --- a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx +++ b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx @@ -3,11 +3,16 @@ import PreLoader from "@/components/Preloader"; import { memo, useEffect, useState } from "react"; import FolderRow from "./FolderRow"; 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 showToast from "@/utils/toast"; import FolderSelectionPopup from "./FolderSelectionPopup"; 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({ files, @@ -24,9 +29,13 @@ function Directory({ loadingMessage, }) { const [amountSelected, setAmountSelected] = useState(0); - const [newFolderName, setNewFolderName] = useState(""); - const [showNewFolderInput, setShowNewFolderInput] = useState(false); const [showFolderSelection, setShowFolderSelection] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const { + isOpen: isFolderModalOpen, + openModal: openFolderModal, + closeModal: closeFolderModal, + } = useModal(); useEffect(() => { setAmountSelected(Object.keys(selectedItems).length); @@ -121,32 +130,6 @@ function Directory({ 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 toMove = []; for (const itemId of Object.keys(selectedItems)) { @@ -183,40 +166,39 @@ function Directory({ setLoading(false); }; + const handleSearch = debounce((e) => { + const searchValue = e.target.value; + setSearchTerm(searchValue); + }, 500); + + const filteredFiles = filterFileSearchResults(files, searchTerm); return (

My Documents

- {showNewFolderInput ? ( -
- setNewFolderName(e.target.value)} - className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-md p-2.5 w-[150px] h-[32px]" - /> -
- -
+
+ + +
+ - )} +
@@ -234,8 +216,8 @@ function Directory({ {loadingMessage}

- ) : files.items ? ( - files.items.map( + ) : filteredFiles.length > 0 ? ( + filteredFiles.map( (item, index) => item.type === "folder" && ( )}
+
+ + + +
); } diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/utils.js b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/utils.js new file mode 100644 index 00000000..1bea2615 --- /dev/null +++ b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/utils.js @@ -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; +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 35159b3f..f3fa95ea 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -742,3 +742,7 @@ does not extend the close button beyond the viewport. */ opacity: 0; } } + +.search-input::-webkit-search-cancel-button { + filter: grayscale(100%) invert(1) brightness(100) opacity(0.5); +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 93bdc088..d5bdc0d6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2260,6 +2260,11 @@ jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" 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: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"