= ({ search, setSearch, placeholder, inputProps }) => {
return (
-
+
{cloneElement(Icons.Search, {
className: "w-4 h-4 absolute right-2 top-1/2 -translate-y-1/2 stroke-gray-500 dark:stroke-neutral-500 cursor-pointer transition-all hover:scale-110 rounded-full group-hover/search-input:opacity-10",
})}
diff --git a/frontend/src/components/sidebar/sidebar.tsx b/frontend/src/components/sidebar/sidebar.tsx
index b2d8170..4045b26 100644
--- a/frontend/src/components/sidebar/sidebar.tsx
+++ b/frontend/src/components/sidebar/sidebar.tsx
@@ -7,7 +7,7 @@ import { useDispatch } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { InternalRoutes, PublicRoutes } from "../../config/routes";
-import { DatabaseType, useGetDatabaseQuery, useGetSchemaQuery, useLoginMutation, useLoginWithProfileMutation } from "../../generated/graphql";
+import { DatabaseType, useGetAiModelsQuery, useGetDatabaseQuery, useGetSchemaQuery, useLoginMutation, useLoginWithProfileMutation } from "../../generated/graphql";
import { AuthActions, LocalLoginProfile } from "../../store/auth";
import { DatabaseActions } from "../../store/database";
import { notify } from "../../store/function";
@@ -151,6 +151,7 @@ export const Sidebar: FC = () => {
const pathname = useLocation().pathname;
const current = useAppSelector(state => state.auth.current);
const profiles = useAppSelector(state => state.auth.profiles);
+ const { data: aiModels } = useGetAiModelsQuery();
const { data: availableDatabases, loading: availableDatabasesLoading } = useGetDatabaseQuery({
variables: {
type: current?.Type as DatabaseType,
@@ -266,6 +267,14 @@ export const Sidebar: FC = () => {
path: InternalRoutes.Graph.path,
},
];
+
+ if (!isNoSQL(current.Type) && aiModels?.AIModel != null && aiModels.AIModel.length > 0) {
+ routes.unshift({
+ title: "Chat",
+ icon: Icons.Chat,
+ path: InternalRoutes.Chat.path,
+ });
+ }
if (!DATABASES_THAT_DONT_SUPPORT_SCRATCH_PAD.includes(current.Type as DatabaseType)) {
routes.push({
title: "Scratchpad",
@@ -274,7 +283,7 @@ export const Sidebar: FC = () => {
});
}
return routes;
- }, [current]);
+ }, [aiModels?.AIModel, current]);
const handleCollapseToggle = useCallback(() => {
setCollapsed(c => !c);
diff --git a/frontend/src/components/table.tsx b/frontend/src/components/table.tsx
index 045a2bb..60f8ae4 100644
--- a/frontend/src/components/table.tsx
+++ b/frontend/src/components/table.tsx
@@ -1,18 +1,20 @@
import classNames from "classnames";
import { AnimatePresence, motion } from "framer-motion";
+import { clone, isString, values } from "lodash";
import { CSSProperties, FC, KeyboardEvent, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Cell, Row, useBlockLayout, useTable } from 'react-table';
import { FixedSizeList, ListChildComponentProps } from "react-window";
import { twMerge } from "tailwind-merge";
+import { notify } from "../store/function";
import { isMarkdown, isNumeric, isValidJSON } from "../utils/functions";
import { ActionButton, AnimatedButton } from "./button";
import { Portal } from "./common";
import { CodeEditor } from "./editor";
import { useExportToCSV, useLongPress } from "./hooks";
import { Icons } from "./icons";
-import { SearchInput } from "./search";
+import { CheckBoxInput } from "./input";
import { Loading } from "./loading";
-import { clone, values } from "lodash";
+import { SearchInput } from "./search";
type IPaginationProps = {
pageCount: number;
@@ -85,12 +87,14 @@ const Pagination: FC = ({ pageCount, currentPage, onPageChange
};
type ITDataProps = {
- cell: Cell>;
- onCellUpdate?: (row: Cell>) => Promise;
+ cell: Cell>;
+ onCellUpdate?: (row: Cell>) => Promise;
disableEdit?: boolean;
+ checked?: boolean;
+ onRowCheck?: (value: boolean) => void;
}
-const TData: FC = ({ cell, onCellUpdate, disableEdit }) => {
+const TData: FC = ({ cell, onCellUpdate, checked, onRowCheck, disableEdit }) => {
const [changed, setChanged] = useState(false);
const [editedData, setEditedData] = useState(cell.value);
const [editable, setEditable] = useState(false);
@@ -99,6 +103,7 @@ const TData: FC = ({ cell, onCellUpdate, disableEdit }) => {
const cellRef = useRef(null);
const [copied, setCopied] = useState(false);
const [updating, setUpdating] = useState(false);
+ const [escapeAttempted, setEscapeAttempted] = useState(false);
const handleChange = useCallback((value: string) => {
setEditedData(value);
@@ -161,7 +166,25 @@ const TData: FC = ({ cell, onCellUpdate, disableEdit }) => {
});
}, [cell, editedData, onCellUpdate]);
+ const handleEditorEscapeButton = useCallback((e: KeyboardEvent) => {
+ if (e.key === "Escape" && !changed) {
+ handleCancel();
+ } else if (e.key === "Escape" && changed) {
+ if (escapeAttempted) {
+ setEscapeAttempted(false);
+ handleCancel();
+ } else {
+ setEscapeAttempted(true);
+ notify("You have unsaved changes, please save or cancel. Pressing Escape again will close without saving.", "warning");
+ setTimeout(() => setEscapeAttempted(false), 2000); // reset it in case
+ }
+ }
+ }, [changed, handleCancel, escapeAttempted]);
+
const language = useMemo(() => {
+ if (editedData == null) {
+ return;
+ }
if (isValidJSON(editedData)) {
return "json";
}
@@ -188,8 +211,21 @@ const TData: FC = ({ cell, onCellUpdate, disableEdit }) => {
className={classNames("w-full h-full p-2 leading-tight focus:outline-none focus:shadow-outline appearance-none transition-all duration-300 border-solid border-gray-200 dark:border-white/5 overflow-hidden whitespace-nowrap select-none text-gray-600 dark:text-neutral-300", {
"group-even/row:bg-gray-100 hover:bg-gray-300 group-even/row:hover:bg-gray-300 dark:group-even/row:bg-white/10 dark:group-odd/row:bg-white/5 dark:group-even/row:hover:bg-white/15 dark:group-odd/row:hover:bg-white/15": !editable,
"bg-transparent": editable,
- })}
- {...longPressProps}>{editedData}
+ })}>
+
+
+
+
+ {editedData}
+
+