CoolFace
Apppublic

Biomimicry-AI/Nexus

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
plugin.tsx498 linesDownload Raw Back to components
1import { IconButton } from "./button";2import { ErrorBoundary } from "./error";3 4import styles from "./plugin.module.scss";5 6import DownloadIcon from "../icons/download.svg";7import UploadIcon from "../icons/upload.svg";8import EditIcon from "../icons/edit.svg";9import AddIcon from "../icons/add.svg";10import CloseIcon from "../icons/close.svg";11import DeleteIcon from "../icons/delete.svg";12import EyeIcon from "../icons/eye.svg";13import CopyIcon from "../icons/copy.svg";14import LeftIcon from "../icons/left.svg";15 16import { Plugin, usePluginStore } from "../store/plugin";17import {18  ChatMessage,19  createMessage,20  ModelConfig,21  useAppConfig,22  useChatStore,23} from "../store";24import { ROLES } from "../client/api";25import {26  Input,27  List,28  ListItem,29  Modal,30  Popover,31  Select,32  showConfirm,33} from "./ui-lib";34import { Avatar, AvatarPicker } from "./emoji";35import Locale, { AllLangs, ALL_LANG_OPTIONS, Lang } from "../locales";36import { useLocation, useNavigate } from "react-router-dom";37 38import chatStyle from "./chat.module.scss";39import { useEffect, useState } from "react";40import { copyToClipboard, downloadAs, readFromFile } from "../utils";41import { Updater } from "../typing";42import { ModelConfigList } from "./model-config";43import { FileName, Path } from "../constant";44import { BUILTIN_PLUGIN_STORE } from "../plugins";45import { nanoid } from "nanoid";46import { getISOLang, getLang } from "../locales";47 48// export function PluginConfig(props: {49//   plugin: Plugin;50//   updateMask: Updater<Plugin>;51//   extraListItems?: JSX.Element;52//   readonly?: boolean;53//   shouldSyncFromGlobal?: boolean;54// }) {55//   const [showPicker, setShowPicker] = useState(false);56 57//   const updateConfig = (updater: (config: ModelConfig) => void) => {58//     if (props.readonly) return;59 60//     // const config = { ...props.mask.modelConfig };61//     // updater(config);62//     props.updateMask((mask) => {63//       // mask.modelConfig = config;64//       // // if user changed current session mask, it will disable auto sync65//       // mask.syncGlobalConfig = false;66//     });67//   };68 69//   const globalConfig = useAppConfig();70 71//   return (72//     <>73//       <ContextPrompts74//         context={props.mask.context}75//         updateContext={(updater) => {76//           const context = props.mask.context.slice();77//           updater(context);78//           props.updateMask((mask) => (mask.context = context));79//         }}80//       />81 82//       <List>83//         <ListItem title={Locale.Mask.Config.Avatar}>84//           <Popover85//             content={86//               <AvatarPicker87//                 onEmojiClick={(emoji) => {88//                   props.updateMask((mask) => (mask.avatar = emoji));89//                   setShowPicker(false);90//                 }}91//               ></AvatarPicker>92//             }93//             open={showPicker}94//             onClose={() => setShowPicker(false)}95//           >96//             <div97//               onClick={() => setShowPicker(true)}98//               style={{ cursor: "pointer" }}99//             >100//             </div>101//           </Popover>102//         </ListItem>103//         <ListItem title={Locale.Mask.Config.Name}>104//           <input105//             type="text"106//             value={props.mask.name}107//             onInput={(e) =>108//               props.updateMask((mask) => {109//                 mask.name = e.currentTarget.value;110//               })111//             }112//           ></input>113//         </ListItem>114//         <ListItem115//           title={Locale.Mask.Config.HideContext.Title}116//           subTitle={Locale.Mask.Config.HideContext.SubTitle}117//         >118//           <input119//             type="checkbox"120//             checked={props.mask.hideContext}121//             onChange={(e) => {122//               props.updateMask((mask) => {123//                 mask.hideContext = e.currentTarget.checked;124//               });125//             }}126//           ></input>127//         </ListItem>128 129//         {!props.shouldSyncFromGlobal ? (130//           <ListItem131//             title={Locale.Mask.Config.Share.Title}132//             subTitle={Locale.Mask.Config.Share.SubTitle}133//           >134//             <IconButton135//               icon={<CopyIcon />}136//               text={Locale.Mask.Config.Share.Action}137//               onClick={copyMaskLink}138//             />139//           </ListItem>140//         ) : null}141 142//         {props.shouldSyncFromGlobal ? (143//           <ListItem144//             title={Locale.Mask.Config.Sync.Title}145//             subTitle={Locale.Mask.Config.Sync.SubTitle}146//           >147//             <input148//               type="checkbox"149//               checked={props.mask.syncGlobalConfig}150//               onChange={async (e) => {151//                 const checked = e.currentTarget.checked;152//                 if (153//                   checked &&154//                   (await showConfirm(Locale.Mask.Config.Sync.Confirm))155//                 ) {156//                   props.updateMask((mask) => {157//                     mask.syncGlobalConfig = checked;158//                     mask.modelConfig = { ...globalConfig.modelConfig };159//                   });160//                 } else if (!checked) {161//                   props.updateMask((mask) => {162//                     mask.syncGlobalConfig = checked;163//                   });164//                 }165//               }}166//             ></input>167//           </ListItem>168//         ) : null}169//       </List>170 171//       <List>172//         <ModelConfigList173//           modelConfig={{ ...props.mask.modelConfig }}174//           updateConfig={updateConfig}175//         />176//         {props.extraListItems}177//       </List>178//     </>179//   );180// }181 182function ContextPromptItem(props: {183  prompt: ChatMessage;184  update: (prompt: ChatMessage) => void;185  remove: () => void;186}) {187  const [focusingInput, setFocusingInput] = useState(false);188 189  return (190    <div className={chatStyle["context-prompt-row"]}>191      {!focusingInput && (192        <Select193          value={props.prompt.role}194          className={chatStyle["context-role"]}195          onChange={(e) =>196            props.update({197              ...props.prompt,198              role: e.target.value as any,199            })200          }201        >202          {ROLES.map((r) => (203            <option key={r} value={r}>204              {r}205            </option>206          ))}207        </Select>208      )}209      <Input210        value={props.prompt.content}211        type="text"212        className={chatStyle["context-content"]}213        rows={focusingInput ? 5 : 1}214        onFocus={() => setFocusingInput(true)}215        onBlur={() => {216          setFocusingInput(false);217          // If the selection is not removed when the user loses focus, some218          // extensions like "Translate" will always display a floating bar219          window?.getSelection()?.removeAllRanges();220        }}221        onInput={(e) =>222          props.update({223            ...props.prompt,224            content: e.currentTarget.value as any,225          })226        }227      />228      {!focusingInput && (229        <IconButton230          icon={<DeleteIcon />}231          className={chatStyle["context-delete-button"]}232          onClick={() => props.remove()}233          bordered234        />235      )}236    </div>237  );238}239 240export function ContextPrompts(props: {241  context: ChatMessage[];242  updateContext: (updater: (context: ChatMessage[]) => void) => void;243}) {244  const context = props.context;245 246  const addContextPrompt = (prompt: ChatMessage) => {247    props.updateContext((context) => context.push(prompt));248  };249 250  const removeContextPrompt = (i: number) => {251    props.updateContext((context) => context.splice(i, 1));252  };253 254  const updateContextPrompt = (i: number, prompt: ChatMessage) => {255    props.updateContext((context) => (context[i] = prompt));256  };257 258  return (259    <>260      <div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>261        {context.map((c, i) => (262          <ContextPromptItem263            key={i}264            prompt={c}265            update={(prompt) => updateContextPrompt(i, prompt)}266            remove={() => removeContextPrompt(i)}267          />268        ))}269 270        <div className={chatStyle["context-prompt-row"]}>271          <IconButton272            icon={<AddIcon />}273            text={Locale.Context.Add}274            bordered275            className={chatStyle["context-prompt-button"]}276            onClick={() =>277              addContextPrompt(278                createMessage({279                  role: "user",280                  content: "",281                  date: "",282                }),283              )284            }285          />286        </div>287      </div>288    </>289  );290}291 292export function PluginPage() {293  const navigate = useNavigate();294 295  const pluginStore = usePluginStore();296  const chatStore = useChatStore();297 298  const allPlugins = pluginStore299    .getAll()300    .filter(301      (m) => !getLang() || m.lang === (getLang() == "cn" ? getLang() : "en"),302    );303 304  const [searchPlugins, setSearchPlugins] = useState<Plugin[]>([]);305  const [searchText, setSearchText] = useState("");306  const plugins = searchText.length > 0 ? searchPlugins : allPlugins;307 308  // simple search, will refactor later309  const onSearch = (text: string) => {310    setSearchText(text);311    if (text.length > 0) {312      const result = allPlugins.filter((m) => m.name.includes(text));313      setSearchPlugins(result);314    } else {315      setSearchPlugins(allPlugins);316    }317  };318 319  const [editingPluginId, setEditingPluginId] = useState<string | undefined>();320  const editingPlugin =321    pluginStore.get(editingPluginId) ??322    BUILTIN_PLUGIN_STORE.get(editingPluginId);323  const closePluginModal = () => setEditingPluginId(undefined);324 325  const downloadAll = () => {326    downloadAs(JSON.stringify(plugins), FileName.Plugins);327  };328 329  const updatePluginEnableStatus = (id: string, enable: boolean) => {330    console.log(enable);331    if (enable) pluginStore.enable(id);332    else pluginStore.disable(id);333  };334 335  const importFromFile = () => {336    readFromFile().then((content) => {337      try {338        const importPlugins = JSON.parse(content);339        if (Array.isArray(importPlugins)) {340          for (const plugin of importPlugins) {341            if (plugin.name) {342              pluginStore.create(plugin);343            }344          }345          return;346        }347        if (importPlugins.name) {348          pluginStore.create(importPlugins);349        }350      } catch {}351    });352  };353 354  return (355    <ErrorBoundary>356      <div className={styles["plugin-page"]}>357        <div className={styles["plugin-header"]}>358          <IconButton359            icon={<LeftIcon />}360            text={Locale.NewChat.Return}361            onClick={() => navigate(Path.Home)}362          ></IconButton>363        </div>364        <div className="window-header">365          <div className="window-header-title">366            <div className="window-header-main-title">367              {Locale.Plugin.Page.Title}368            </div>369            <div className="window-header-submai-title">370              {Locale.Plugin.Page.SubTitle(allPlugins.length)}371            </div>372          </div>373 374          <div className="window-actions">375            {/* <div className="window-action-button">376              <IconButton377                icon={<DownloadIcon />}378                bordered379                onClick={downloadAll}380              />381            </div>382            <div className="window-action-button">383              <IconButton384                icon={<UploadIcon />}385                bordered386                onClick={() => importFromFile()}387              />388            </div> */}389          </div>390        </div>391 392        <div className={styles["plugin-page-body"]}>393          <div className={styles["plugin-filter"]}>394            <input395              type="text"396              className={styles["search-bar"]}397              placeholder={Locale.Plugin.Page.Search}398              autoFocus399              onInput={(e) => onSearch(e.currentTarget.value)}400            />401 402            {/* <IconButton403              className={styles["mask-create"]}404              icon={<AddIcon />}405              text={Locale.Mask.Page.Create}406              bordered407              onClick={() => {408                const createdMask = pluginStore.create();409                setEditingMaskId(createdMask.id);410              }}411            /> */}412          </div>413 414          <div>415            {plugins.map((m) => (416              <div className={styles["plugin-item"]} key={m.id}>417                <div className={styles["plugin-header"]}>418                  <div className={styles["plugin-title"]}>419                    <div className={styles["plugin-name"]}>{m.name}</div>420                    {/* 描述 */}421                    <div className={styles["plugin-info"] + " one-line"}>422                      {`${m.description}`}423                    </div>424                  </div>425                </div>426                <div className={styles["plugin-actions"]}>427                  <input428                    type="checkbox"429                    checked={m.enable}430                    onChange={(e) => {431                      updatePluginEnableStatus(m.id, e.currentTarget.checked);432                    }}433                  ></input>434                  {/* {m.builtin ? (435                    <IconButton436                      icon={<EyeIcon />}437                      text={Locale.Mask.Item.View}438                      onClick={() => setEditingMaskId(m.id)}439                    />440                  ) : (441                    <IconButton442                      icon={<EditIcon />}443                      text={Locale.Mask.Item.Edit}444                      onClick={() => setEditingMaskId(m.id)}445                    />446                  )}447                  {!m.builtin && (448                    <IconButton449                      icon={<DeleteIcon />}450                      text={Locale.Mask.Item.Delete}451                      onClick={async () => {452                        if (await showConfirm(Locale.Mask.Item.DeleteConfirm)) {453                          maskStore.delete(m.id);454                        }455                      }}456                    />457                  )} */}458                </div>459              </div>460            ))}461          </div>462        </div>463      </div>464 465      {editingPlugin && (466        <div className="modal-mask">467          <Modal468            title={Locale.Plugin.EditModal.Title(editingPlugin?.builtin)}469            onClose={closePluginModal}470            actions={[471              <IconButton472                icon={<DownloadIcon />}473                text={Locale.Plugin.EditModal.Download}474                key="export"475                bordered476                onClick={() =>477                  downloadAs(478                    JSON.stringify(editingPlugin),479                    `${editingPlugin.name}.json`,480                  )481                }482              />,483            ]}484          >485            {/* <PluginConfig486              plugin={editingPlugin}487              updatePlugin={(updater) =>488                pluginStore.update(editingPluginId!, updater)489              }490              readonly={editingPlugin.builtin}491            /> */}492          </Modal>493        </div>494      )}495    </ErrorBoundary>496  );497}498