Bladeren bron

add Plugin page

lloydzhou 1 jaar geleden
bovenliggende
commit
cac99e3908
4 gewijzigde bestanden met toevoegingen van 197 en 0 verwijderingen
  1. 5 0
      app/components/home.tsx
  2. 149 0
      app/components/plugin.tsx
  3. 2 0
      app/constant.ts
  4. 41 0
      app/locales/en.ts

+ 5 - 0
app/components/home.tsx

@@ -59,6 +59,10 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
   loading: () => <Loading noLogo />,
 });
 
+const PluginPage = dynamic(async () => (await import("./plugin")).PluginPage, {
+  loading: () => <Loading noLogo />,
+});
+
 const SearchChat = dynamic(
   async () => (await import("./search-chat")).SearchChatPage,
   {
@@ -181,6 +185,7 @@ function Screen() {
             <Route path={Path.Home} element={<Chat />} />
             <Route path={Path.NewChat} element={<NewChat />} />
             <Route path={Path.Masks} element={<MaskPage />} />
+            <Route path={Path.Plugins} element={<PluginPage />} />
             <Route path={Path.SearchChat} element={<SearchChat />} />
             <Route path={Path.Chat} element={<Chat />} />
             <Route path={Path.Settings} element={<Settings />} />

+ 149 - 0
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<string | undefined>();
+  const editingPlugin = pluginStore.get(editingPluginId);
+  const closePluginModal = () => setEditingPluginId(undefined);
+
+  return (
+    <ErrorBoundary>
+      <div className={styles["mask-page"]}>
+        <div className="window-header">
+          <div className="window-header-title">
+            <div className="window-header-main-title">
+              {Locale.Plugin.Page.Title}
+            </div>
+            <div className="window-header-submai-title">
+              {Locale.Plugin.Page.SubTitle(plugins.length)}
+            </div>
+          </div>
+
+          <div className="window-actions">
+            <div className="window-action-button">
+              <IconButton
+                icon={<CloseIcon />}
+                bordered
+                onClick={() => navigate(-1)}
+              />
+            </div>
+          </div>
+        </div>
+
+        <div className={styles["mask-page-body"]}>
+          <div>
+            {plugins.map((m) => (
+              <div className={styles["mask-item"]} key={m.id}>
+                <div className={styles["mask-header"]}>
+                  <div className={styles["mask-icon"]}></div>
+                  <div className={styles["mask-title"]}>
+                    <div className={styles["mask-name"]}>
+                      {m.title}@<small>{m.version}</small>
+                    </div>
+                    <div className={styles["mask-info"] + " one-line"}>
+                      {`${Locale.Plugin.Item.Info(m.content.length)} / / `}
+                    </div>
+                  </div>
+                </div>
+                <div className={styles["mask-actions"]}>
+                  {m.builtin ? (
+                    <IconButton
+                      icon={<EyeIcon />}
+                      text={Locale.Plugin.Item.View}
+                      onClick={() => setEditingPluginId(m.id)}
+                    />
+                  ) : (
+                    <IconButton
+                      icon={<EditIcon />}
+                      text={Locale.Plugin.Item.Edit}
+                      onClick={() => setEditingPluginId(m.id)}
+                    />
+                  )}
+                  {!m.builtin && (
+                    <IconButton
+                      icon={<DeleteIcon />}
+                      text={Locale.Plugin.Item.Delete}
+                      onClick={async () => {
+                        if (
+                          await showConfirm(Locale.Plugin.Item.DeleteConfirm)
+                        ) {
+                          pluginStore.delete(m.id);
+                        }
+                      }}
+                    />
+                  )}
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      </div>
+
+      {editingPlugin && (
+        <div className="modal-mask">
+          <Modal
+            title={Locale.Plugin.EditModal.Title(editingPlugin?.builtin)}
+            onClose={closePluginModal}
+            actions={[
+              <IconButton
+                icon={<DownloadIcon />}
+                text={Locale.Plugin.EditModal.Download}
+                key="export"
+                bordered
+                onClick={() =>
+                  downloadAs(
+                    JSON.stringify(editingPlugin),
+                    `${editingPlugin.name}.json`,
+                  )
+                }
+              />,
+              <IconButton
+                key="copy"
+                icon={<CopyIcon />}
+                bordered
+                text={Locale.Plugin.EditModal.Clone}
+                onClick={() => {
+                  navigate(Path.Plugins);
+                  pluginStore.create(editingPlugin);
+                  setEditingPluginId(undefined);
+                }}
+              />,
+            ]}
+          >
+            PluginConfig
+          </Modal>
+        </div>
+      )}
+    </ErrorBoundary>
+  );
+}

+ 2 - 0
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 },
 ];

+ 41 - 0
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: {