feat: to select table directly in the sidebar (#106)

This commit is contained in:
CorrectRoadH
2023-05-31 15:47:32 +08:00
committed by GitHub
parent 00022e6bb7
commit 933fb6f280
11 changed files with 366 additions and 262 deletions

View File

@ -7,12 +7,11 @@ import useLoading from "@/hooks/useLoading";
import Select from "./kit/Select";
import Icon from "./Icon";
import DarkModeSwitch from "./DarkModeSwitch";
import ConversationList from "./Sidebar/ConversationList";
import ConnectionList from "./Sidebar/ConnectionList";
import QuotaView from "./QuotaView";
import { hasFeature } from "../utils";
import MultipleSelect from "./kit/MultipleSelect";
import { countTextTokens, hasFeature } from "../utils";
import SettingAvatarIcon from "./SettingAvatarIcon";
import Checkbox from "./kit/Checkbox";
interface State {}
@ -34,6 +33,12 @@ const ConnectionSidebar = () => {
conversationStore.getConversationById(conversationStore.currentConversationId)?.selectedSchemaName || "";
const tableSchemaLoadingState = useLoading();
const currentConversation = conversationStore.getConversationById(conversationStore.currentConversationId);
const [totalToken, setTotalToken] = useState<number>(0);
useEffect(() => {
updateHasSchemaProperty(
currentConnectionCtx?.connection.engineType === Engine.PostgreSQL || currentConnectionCtx?.connection.engineType === Engine.MSSQL
);
}, [currentConnectionCtx?.connection]);
useEffect(() => {
updateHasSchemaProperty(
@ -60,6 +65,16 @@ const ConnectionSidebar = () => {
};
}, []);
useEffect(() => {
// update total token
const totalToken = selectedTablesName.reduce((totalToken, tableName) => {
const table = tableList.find((table) => table.name === tableName);
// because old cache didn't have token, So the value may is undefined.
return totalToken + (table?.token || countTextTokens(table?.structure || ""));
}, 0);
setTotalToken(totalToken);
}, [selectedTablesName, tableList]);
useEffect(() => {
if (currentConnectionCtx?.connection) {
setIsRequestingDatabase(true);
@ -134,16 +149,12 @@ const ConnectionSidebar = () => {
}
};
const handleTableNameSelect = async (selectedTablesName: string[]) => {
conversationStore.updateSelectedTablesName(selectedTablesName);
};
const handleAllSelect = async () => {
conversationStore.updateSelectedTablesName(tableList.map((table) => table.name));
};
const handleEmptySelect = async () => {
conversationStore.updateSelectedTablesName([]);
const handleTableCheckboxChange = async (tableName: string, value: boolean) => {
if (value) {
conversationStore.updateSelectedTablesName([...selectedTablesName, tableName]);
} else {
conversationStore.updateSelectedTablesName(selectedTablesName.filter((name) => name !== tableName));
}
};
const handleSchemaNameSelect = async (schemaName: string) => {
@ -211,42 +222,31 @@ const ConnectionSidebar = () => {
)}
{currentConnectionCtx &&
(tableSchemaLoadingState.isLoading ? (
<div className="w-full h-12 flex flex-row justify-start items-center px-4 sticky top-0 border z-1 mb-4 mt-2 rounded-lg text-sm text-gray-600 dark:text-gray-400">
<div className="w-full h-12 flex flex-row justify-start items-center px-4 sticky top-0 z-1 mb-4 mt-2 rounded-lg text-sm text-gray-600 dark:text-gray-400">
<Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" /> {t("common.loading")}
</div>
) : (
tableList.length > 0 && (
<div className="w-full sticky top-0 z-1 my-4">
<MultipleSelect
className="w-full px-4 py-3 !text-base"
value={selectedTablesName}
itemList={tableList.map((table) => {
return {
label: table.name === "" ? t("connection.all-tables") : table.name,
value: table.name,
};
})}
onValueChange={(tableName) => handleTableNameSelect(tableName)}
placeholder={(selectedTablesName.length ? selectedTablesName.join(",") : t("connection.all-tables")) || ""}
>
<button
className="whitespace-nowrap rounded w-full bg-indigo-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={(e) => {
selectedTablesName.length ? handleEmptySelect() : handleAllSelect();
// The Button area is a option that have select event. So must to stop Propagation
e.stopPropagation();
}}
tableList.length > 0 &&
tableList.map((table) => {
return (
<div key={table.name}>
<Checkbox
value={selectedTablesName.includes(table.name)}
label={table.name}
onValueChange={handleTableCheckboxChange}
>
{selectedTablesName.length ? t("connection.empty-select") : t("connection.select-all")}
</button>
</MultipleSelect>
</div>
)
<div className="text-black dark:text-gray-300">{table.token || countTextTokens(table.structure)}</div>
</Checkbox>
</div>
);
})
))}
{/* TODO(steven): remove this after we finish left sidebar */}
<ConversationList />
</div>
<div className="sticky bottom-0 w-full flex flex-col justify-center bg-gray-100 dark:bg-zinc-700 backdrop-blur bg-opacity-60 pb-4 py-2">
<div className="text-black dark:text-gray-300">
{t("connection.total-token")} {totalToken}
</div>
{!settingStore.setting.openAIApiConfig?.key && hasFeature("quota") && (
<div className="mb-4">
<QuotaView />

View File

@ -0,0 +1,34 @@
import * as CheckboxUI from "@radix-ui/react-checkbox";
import { CheckIcon, DividerHorizontalIcon } from "@radix-ui/react-icons";
import { ReactNode } from "react";
interface CheckboxProps {
value: boolean;
label: string;
onValueChange: (tableName: string, value: boolean) => void;
}
const Checkbox = (props: CheckboxProps & { children?: ReactNode }) => {
const { value, label, onValueChange, children } = props;
return (
<form>
<div className="flex justify-between items-center px-3 py-2">
<CheckboxUI.Root
checked={value}
onCheckedChange={(value: boolean) => onValueChange(label, value)}
className="bg-white w-5 h-5 shrink-0 cursor-pointer rounded-sm flex border border-gray-300 hover:border-black m-auto"
id={label}
>
<CheckboxUI.Indicator className="m-auto text-black">
<CheckIcon />
</CheckboxUI.Indicator>
</CheckboxUI.Root>
<label className="Label grow m-auto px-3 py-2 cursor-pointer truncate text-black dark:text-gray-300" htmlFor={label}>
{label}
</label>
{children}
</div>
</form>
);
};
export default Checkbox;

View File

@ -1,59 +0,0 @@
import { Listbox, Transition } from "@headlessui/react";
import { Fragment, ReactNode, useState } from "react";
import * as SelectUI from "@radix-ui/react-select";
import Icon from "../Icon";
interface Props<T = any> {
value: T[];
itemList: {
value: T;
label: string;
}[];
className?: string;
placeholder?: string;
selectedPlaceholder?: string;
onValueChange: (value: T) => void;
}
const MultipleSelect = (props: Props & { children?: ReactNode }) => {
const { itemList, value, placeholder, className, onValueChange, children } = props;
return (
<Listbox value={value} onChange={onValueChange} multiple>
<Listbox.Button
className={`${
className || ""
} flex flex-row justify-between items-center text-sm whitespace-nowrap dark:text-gray-300 bg-white dark:bg-zinc-700 border dark:border-zinc-800 px-3 py-2 rounded-lg`}
>
<div className="truncate">{placeholder}</div>
<SelectUI.Icon className="ml-1 w-5 h-auto shrink-0">
<Icon.BiChevronDown className="w-full h-auto opacity-60" />
</SelectUI.Icon>
</Listbox.Button>
<Transition as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0">
<Listbox.Options className="absolute border rounded-lg drop-shadow-lg dark:border-zinc-800 p-1 mt-1 max-h-80 overflow-y-auto scrollbar-hide w-full overflow-auto bg-white dark:bg-zinc-700 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{children && (
<Listbox.Option className="px-2 py-2" key="button" value="button">
{children}
</Listbox.Option>
)}
{itemList.map((item) => (
<Listbox.Option
className="w-full px-3 py-2 whitespace-nowrap truncate text-ellipsis overflow-x-hidden text-sm rounded-lg flex flex-row justify-between items-center cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-zinc-800"
key={item.value}
value={item.value}
>
<div className="truncate">{item.label}</div>
{(value.find((v: string) => v === item.value) ? true : false) ? (
<span className="w-5 h-auto">
<Icon.BiCheck className="w-full h-auto" aria-hidden="true" />
</span>
) : null}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</Listbox>
);
};
export default MultipleSelect;