add Plugin page

This commit is contained in:
lloydzhou 2024-08-30 13:02:03 +08:00
parent 571ce11e53
commit cac99e3908
4 changed files with 197 additions and 0 deletions

View File

@ -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
app/components/plugin.tsx Normal file
View File

@ -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>
);
}

View File

@ -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 },
];

View File

@ -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: {