diff --git a/app/components/home.tsx b/app/components/home.tsx index 24e71b9e5..465ad0f1e 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -59,6 +59,10 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, { loading: () => , }); +const PluginPage = dynamic(async () => (await import("./plugin")).PluginPage, { + loading: () => , +}); + const SearchChat = dynamic( async () => (await import("./search-chat")).SearchChatPage, { @@ -181,6 +185,7 @@ function Screen() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/app/components/plugin.tsx b/app/components/plugin.tsx new file mode 100644 index 000000000..769e02f2a --- /dev/null +++ b/app/components/plugin.tsx @@ -0,0 +1,149 @@ +import { IconButton } from "./button"; +import { ErrorBoundary } from "./error"; + +import styles from "./mask.module.scss"; + +import DownloadIcon from "../icons/download.svg"; +import EditIcon from "../icons/edit.svg"; +import AddIcon from "../icons/add.svg"; +import CloseIcon from "../icons/close.svg"; +import DeleteIcon from "../icons/delete.svg"; +import EyeIcon from "../icons/eye.svg"; +import CopyIcon from "../icons/copy.svg"; + +import { Plugin, usePluginStore } from "../store/plugin"; +import { + Input, + List, + ListItem, + Modal, + Popover, + Select, + showConfirm, +} from "./ui-lib"; +import Locale from "../locales"; +import { useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { Path } from "../constant"; +import { nanoid } from "nanoid"; + +export function PluginPage() { + const navigate = useNavigate(); + const pluginStore = usePluginStore(); + const plugins = pluginStore.getAll(); + + const [editingPluginId, setEditingPluginId] = useState(); + const editingPlugin = pluginStore.get(editingPluginId); + const closePluginModal = () => setEditingPluginId(undefined); + + return ( + +
+
+
+
+ {Locale.Plugin.Page.Title} +
+
+ {Locale.Plugin.Page.SubTitle(plugins.length)} +
+
+ +
+
+ } + bordered + onClick={() => navigate(-1)} + /> +
+
+
+ +
+
+ {plugins.map((m) => ( +
+
+
+
+
+ {m.title}@{m.version} +
+
+ {`${Locale.Plugin.Item.Info(m.content.length)} / / `} +
+
+
+
+ {m.builtin ? ( + } + text={Locale.Plugin.Item.View} + onClick={() => setEditingPluginId(m.id)} + /> + ) : ( + } + text={Locale.Plugin.Item.Edit} + onClick={() => setEditingPluginId(m.id)} + /> + )} + {!m.builtin && ( + } + text={Locale.Plugin.Item.Delete} + onClick={async () => { + if ( + await showConfirm(Locale.Plugin.Item.DeleteConfirm) + ) { + pluginStore.delete(m.id); + } + }} + /> + )} +
+
+ ))} +
+
+
+ + {editingPlugin && ( +
+ } + text={Locale.Plugin.EditModal.Download} + key="export" + bordered + onClick={() => + downloadAs( + JSON.stringify(editingPlugin), + `${editingPlugin.name}.json`, + ) + } + />, + } + bordered + text={Locale.Plugin.EditModal.Clone} + onClick={() => { + navigate(Path.Plugins); + pluginStore.create(editingPlugin); + setEditingPluginId(undefined); + }} + />, + ]} + > + PluginConfig + +
+ )} +
+ ); +} diff --git a/app/constant.ts b/app/constant.ts index db365f0fc..0fa14dca5 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -39,6 +39,7 @@ export enum Path { Settings = "/settings", NewChat = "/new-chat", Masks = "/masks", + Plugins = "/plugins", Auth = "/auth", Sd = "/sd", SdNew = "/sd-new", @@ -480,6 +481,7 @@ export const internalAllowedWebDavEndpoints = [ export const DEFAULT_GA_ID = "G-89WN60ZK2E"; export const PLUGINS = [ + { name: "Plugins", path: Path.Plugins }, { name: "Stable Diffusion", path: Path.Sd }, { name: "Search Chat", path: Path.SearchChat }, ]; diff --git a/app/locales/en.ts b/app/locales/en.ts index 77f3a700a..ea098c0f3 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -542,6 +542,47 @@ const en: LocaleType = { View: "View", }, }, + Plugin: { + Name: "Plugin", + Page: { + Title: "Plugins", + SubTitle: (count: number) => `${count} plugins`, + Search: "Search Plugin", + Create: "Create", + }, + Item: { + Info: (count: number) => `${count} plugins`, + Chat: "Chat", + View: "View", + Edit: "Edit", + Delete: "Delete", + DeleteConfirm: "Confirm to delete?", + }, + EditModal: { + Title: (readonly: boolean) => + `Edit Plugin ${readonly ? "(readonly)" : ""}`, + Download: "Download", + Clone: "Clone", + }, + Config: { + Avatar: "Bot Avatar", + Name: "Bot Name", + Sync: { + Title: "Use Global Config", + SubTitle: "Use global config in this chat", + Confirm: "Confirm to override custom config with global config?", + }, + HideContext: { + Title: "Hide Context Prompts", + SubTitle: "Do not show in-context prompts in chat", + }, + Share: { + Title: "Share This Plugin", + SubTitle: "Generate a link to this mask", + Action: "Copy Link", + }, + }, + }, Mask: { Name: "Mask", Page: {