chore: add dev guard for memo chat (#1968)

This commit is contained in:
boojack
2023-07-16 13:02:52 +08:00
committed by GitHub
parent 032509ddba
commit 220cba84ae
17 changed files with 284 additions and 163 deletions

View File

@ -1,4 +1,4 @@
import { Input } from "@mui/joy"; import { Button, Input } from "@mui/joy";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
@ -123,14 +123,14 @@ const CreateTagDialog: React.FC<Props> = (props: Props) => {
{shownSuggestTagNameList.length > 0 && ( {shownSuggestTagNameList.length > 0 && (
<> <>
<div className="mt-4 mb-1 text-sm w-full flex flex-row justify-start items-center"> <div className="mt-4 mb-1 text-sm w-full flex flex-row justify-start items-center">
<span className="text-gray-400">Tag suggestions</span> <span className="text-gray-400 mr-2">Tag suggestions</span>
<button className="btn-normal ml-2 px-2 py-0 leading-6 font-mono" onClick={handleToggleShowSuggestionTags}> <Button size="sm" variant="outlined" onClick={handleToggleShowSuggestionTags}>
{showTagSuggestions ? "hide" : "show"} {showTagSuggestions ? "hide" : "show"}
</button> </Button>
</div> </div>
{showTagSuggestions && ( {showTagSuggestions && (
<> <>
<div className="w-full flex flex-row justify-start items-start flex-wrap"> <div className="w-full flex flex-row justify-start items-start flex-wrap mb-2">
{shownSuggestTagNameList.map((tag) => ( {shownSuggestTagNameList.map((tag) => (
<span <span
className="max-w-[120px] text-sm mr-2 mt-1 font-mono cursor-pointer truncate dark:text-gray-300 hover:opacity-60" className="max-w-[120px] text-sm mr-2 mt-1 font-mono cursor-pointer truncate dark:text-gray-300 hover:opacity-60"
@ -141,9 +141,9 @@ const CreateTagDialog: React.FC<Props> = (props: Props) => {
</span> </span>
))} ))}
</div> </div>
<button className="btn-normal mt-2 px-2 py-0 leading-6 font-mono" onClick={handleSaveSuggestTagList}> <Button size="sm" onClick={handleSaveSuggestTagList}>
Save all Save all
</button> </Button>
</> </>
)} )}
</> </>

View File

@ -1,7 +1,7 @@
import classNames from "classnames"; import classNames from "classnames";
import { useEffect } from "react"; import { useEffect } from "react";
import { NavLink, useLocation } from "react-router-dom"; import { NavLink, useLocation } from "react-router-dom";
import { useLayoutStore, useUserStore } from "@/store/module"; import { useGlobalStore, useLayoutStore, useUserStore } from "@/store/module";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { resolution } from "@/utils/layout"; import { resolution } from "@/utils/layout";
import Icon from "./Icon"; import Icon from "./Icon";
@ -12,6 +12,7 @@ import UpgradeVersionView from "./UpgradeVersionBanner";
const Header = () => { const Header = () => {
const t = useTranslate(); const t = useTranslate();
const location = useLocation(); const location = useLocation();
const globalStore = useGlobalStore();
const userStore = useUserStore(); const userStore = useUserStore();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const showHeader = layoutStore.state.showHeader; const showHeader = layoutStore.state.showHeader;
@ -111,20 +112,22 @@ const Header = () => {
{!isVisitorMode && ( {!isVisitorMode && (
<> <>
<NavLink {globalStore.isDev() && (
to="/memo-chat" <NavLink
id="header-memo-chat" to="/memo-chat"
className={({ isActive }) => id="header-memo-chat"
classNames( className={({ isActive }) =>
"px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", classNames(
isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700",
) isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent"
} )
> }
<> >
<Icon.Bot className="mr-3 w-6 h-auto opacity-70" /> {t("memo-chat.title")} <>
</> <Icon.Bot className="mr-3 w-6 h-auto opacity-70" /> {t("memo-chat.title")}
</NavLink> </>
</NavLink>
)}
<NavLink <NavLink
to="/archived" to="/archived"
id="header-archived" id="header-archived"

View File

@ -11,7 +11,7 @@ interface ConversationTabProps {
const ConversationTab = ({ item, selectedConversationId, setSelectedConversationId, closeConversation }: ConversationTabProps) => { const ConversationTab = ({ item, selectedConversationId, setSelectedConversationId, closeConversation }: ConversationTabProps) => {
return ( return (
<div <div
className={`flex rounded-lg h-8 px-3 border dark:border-zinc-600 ${ className={`flex rounded-lg h-8 px-3 cursor-pointer border dark:border-zinc-600 ${
selectedConversationId === item.messageStorageId ? "bg-white dark:bg-zinc-700" : "bg-gray-200 dark:bg-zinc-800 opacity-60" selectedConversationId === item.messageStorageId ? "bg-white dark:bg-zinc-700" : "bg-gray-200 dark:bg-zinc-800 opacity-60"
}`} }`}
key={item.messageStorageId} key={item.messageStorageId}
@ -21,7 +21,7 @@ const ConversationTab = ({ item, selectedConversationId, setSelectedConversation
> >
<div className="truncate m-auto">{item.name}</div> <div className="truncate m-auto">{item.name}</div>
<Icon.X <Icon.X
className="w-4 h-auto m-auto cursor-pointer" className="ml-1 w-4 h-auto m-auto cursor-pointer opacity-60"
onClick={(e: any) => { onClick={(e: any) => {
closeConversation(e); closeConversation(e);
}} }}

View File

@ -21,11 +21,11 @@ const ResourceListView = (props: Props) => {
return ( return (
<div <div
key={resource.id} key={resource.id}
className="max-w-full flex flex-row justify-start items-center flex-nowrap bg-gray-100 dark:bg-zinc-800 hover:opacity-80 px-2 py-1 rounded cursor-pointer text-gray-500" className="max-w-full flex flex-row justify-start items-center flex-nowrap bg-gray-100 dark:bg-zinc-800 px-2 py-1 rounded text-gray-500"
> >
<ResourceIcon resourceType={resource.type} className="w-4 h-auto mr-1" /> <ResourceIcon resource={resource} className="w-4 h-auto mr-1" />
<span className="text-sm max-w-xs truncate font-mono">{resource.filename}</span> <span className="text-sm max-w-xs truncate">{resource.filename}</span>
<Icon.X className="w-4 h-auto ml-1 hover:opacity-80" onClick={() => handleDeleteResource(resource.id)} /> <Icon.X className="w-4 h-auto ml-1 cursor-pointer hover:opacity-80" onClick={() => handleDeleteResource(resource.id)} />
</div> </div>
); );
})} })}

View File

@ -1,4 +1,3 @@
import { Button } from "@mui/joy";
import { isNumber, last, uniq } from "lodash-es"; import { isNumber, last, uniq } from "lodash-es";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@ -422,9 +421,9 @@ const MemoEditor = (props: Props) => {
<div className="editor-footer-container"> <div className="editor-footer-container">
<MemoVisibilitySelector value={state.memoVisibility} onChange={handleMemoVisibilityChange} /> <MemoVisibilitySelector value={state.memoVisibility} onChange={handleMemoVisibilityChange} />
<div className="buttons-container"> <div className="buttons-container">
<Button disabled={!allowSave} onClick={handleSaveBtnClick}> <button className="action-btn confirm-btn" disabled={!allowSave} onClick={handleSaveBtnClick}>
{t("editor.save")} {t("editor.save")}
</Button> </button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -31,6 +31,7 @@ const MemoFilter = () => {
}} }}
> >
<Icon.Target className="icon-text" /> {shortcut?.title} <Icon.Target className="icon-text" /> {shortcut?.title}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
<div <div
className={"filter-item-container " + (tagQuery ? "" : "!hidden")} className={"filter-item-container " + (tagQuery ? "" : "!hidden")}
@ -39,6 +40,7 @@ const MemoFilter = () => {
}} }}
> >
<Icon.Tag className="icon-text" /> {tagQuery} <Icon.Tag className="icon-text" /> {tagQuery}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
<div <div
className={"filter-item-container " + (memoType ? "" : "!hidden")} className={"filter-item-container " + (memoType ? "" : "!hidden")}
@ -48,6 +50,7 @@ const MemoFilter = () => {
> >
<Icon.Box className="icon-text" />{" "} <Icon.Box className="icon-text" />{" "}
{t(getTextWithMemoType(memoType as MemoSpecType) as Exclude<ReturnType<typeof getTextWithMemoType>, "">)} {t(getTextWithMemoType(memoType as MemoSpecType) as Exclude<ReturnType<typeof getTextWithMemoType>, "">)}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
<div <div
className={"filter-item-container " + (visibility ? "" : "!hidden")} className={"filter-item-container " + (visibility ? "" : "!hidden")}
@ -56,6 +59,7 @@ const MemoFilter = () => {
}} }}
> >
<Icon.Eye className="icon-text" /> {visibility} <Icon.Eye className="icon-text" /> {visibility}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
{duration && duration.from < duration.to ? ( {duration && duration.from < duration.to ? (
<div <div
@ -70,6 +74,7 @@ const MemoFilter = () => {
to: getDateString(duration.to), to: getDateString(duration.to),
interpolation: { escapeValue: false }, interpolation: { escapeValue: false },
})} })}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
) : null} ) : null}
<div <div
@ -79,6 +84,7 @@ const MemoFilter = () => {
}} }}
> >
<Icon.Search className="icon-text" /> {textQuery} <Icon.Search className="icon-text" /> {textQuery}
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
</div> </div>
</div> </div>
); );

View File

@ -1,18 +1,33 @@
import classNames from "classnames";
import { getResourceUrl } from "@/utils/resource";
import Icon from "./Icon"; import Icon from "./Icon";
import SquareDiv from "./kit/SquareDiv";
import showPreviewImageDialog from "./PreviewImageDialog";
interface Props { interface Props {
className: string; className: string;
resourceType: string; resource: Resource;
} }
const ResourceIcon = (props: Props) => { const ResourceIcon = (props: Props) => {
const { className, resourceType } = props; const { className, resource } = props;
let ResourceIcon = Icon.FileText; if (resource.type.includes("image")) {
if (resourceType.includes("image")) { const url = getResourceUrl(resource);
ResourceIcon = Icon.Image; return (
<SquareDiv key={resource.id} className={classNames("cursor-pointer rounded hover:shadow", className)}>
<img
className="min-h-full min-w-full w-auto h-auto rounded"
src={resource.externalLink ? url : url + "?thumbnail=1"}
onClick={() => showPreviewImageDialog([url], 0)}
decoding="async"
loading="lazy"
/>
</SquareDiv>
);
} }
const ResourceIcon = Icon.FileText;
return <ResourceIcon className={className} />; return <ResourceIcon className={className} />;
}; };

View File

@ -1,4 +1,4 @@
import { Table } from "@mui/joy"; import { Button, Input } from "@mui/joy";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
@ -7,7 +7,6 @@ import * as api from "@/helpers/api";
import Dropdown from "../kit/Dropdown"; import Dropdown from "../kit/Dropdown";
import { showCommonDialog } from "../Dialog/CommonDialog"; import { showCommonDialog } from "../Dialog/CommonDialog";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
import "@/less/settings/member-section.less";
interface State { interface State {
createUserUsername: string; createUserUsername: string;
@ -30,7 +29,7 @@ const PreferencesSection = () => {
const fetchUserList = async () => { const fetchUserList = async () => {
const { data } = await api.getUserList(); const { data } = await api.getUserList();
setUserList(data); setUserList(data.sort((a, b) => a.id - b.id));
}; };
const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -117,94 +116,96 @@ const PreferencesSection = () => {
return ( return (
<div className="section-container member-section-container"> <div className="section-container member-section-container">
<p className="title-text">{t("setting.member-section.create-a-member")}</p> <p className="title-text">{t("setting.member-section.create-a-member")}</p>
<div className="create-member-container"> <div className="w-full flex flex-col justify-start items-start gap-2">
<div className="input-form-container"> <div className="flex flex-col justify-start items-start gap-1">
<span className="field-text">{t("common.username")}</span> <span className="text-sm">{t("common.username")}</span>
<input <Input type="text" placeholder={t("common.username")} value={state.createUserUsername} onChange={handleUsernameInputChange} />
type="text"
autoComplete="new-password"
placeholder={t("common.username")}
value={state.createUserUsername}
onChange={handleUsernameInputChange}
/>
</div> </div>
<div className="input-form-container"> <div className="flex flex-col justify-start items-start gap-1">
<span className="field-text">{t("common.password")}</span> <span className="text-sm">{t("common.password")}</span>
<input <Input type="password" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
type="password"
autoComplete="new-password"
placeholder={t("common.password")}
value={state.createUserPassword}
onChange={handlePasswordInputChange}
/>
</div> </div>
<div className="btns-container"> <div className="btns-container">
<button className="btn-normal" onClick={handleCreateUserBtnClick}> <Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
{t("common.create")}
</button>
</div> </div>
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center mt-6">
<div className="title-text">{t("setting.member-list")}</div> <div className="title-text">{t("setting.member-list")}</div>
</div> </div>
<Table> <div className="w-full overflow-x-auto">
<thead> <div className="inline-block min-w-full align-middle">
<tr> <table className="min-w-full divide-y divide-gray-300">
<th>ID</th> <thead>
<th>{t("common.username")}</th> <tr>
<th></th> <th scope="col" className="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
</tr> ID
</thead> </th>
<tbody> <th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
{userList.map((user) => ( {t("common.username")}
<tr key={user.id}> </th>
<td className="field-text id-text">{user.id}</td> <th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
<td className="field-text username-text">{user.username}</td> {t("common.nickname")}
<td className="flex flex-row justify-end items-center"> </th>
{currentUser?.id === user.id ? ( <th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
<span className="tip-text">{t("common.yourself")}</span> {t("common.email")}
) : ( </th>
<Dropdown <th scope="col" className="relative py-2 pl-3 pr-4"></th>
actions={ </tr>
<> </thead>
<button <tbody className="divide-y divide-gray-200">
className="w-full text-left text-sm whitespace-nowrap leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600" {userList.map((user) => (
onClick={() => handleChangePasswordClick(user)} <tr key={user.id}>
> <td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900">{user.id}</td>
{t("setting.account-section.change-password")} <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.username}</td>
</button> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.nickname}</td>
{user.rowStatus === "NORMAL" ? ( <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.email}</td>
<button <td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end">
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600" {currentUser?.id === user.id ? (
onClick={() => handleArchiveUserClick(user)} <span>{t("common.yourself")}</span>
> ) : (
{t("setting.member-section.archive-member")} <Dropdown
</button> actions={
) : (
<> <>
<button <button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600" className="w-full text-left text-sm whitespace-nowrap leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleRestoreUserClick(user)} onClick={() => handleChangePasswordClick(user)}
> >
{t("common.restore")} {t("setting.account-section.change-password")}
</button>
<button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded text-red-600 hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleDeleteUserClick(user)}
>
{t("setting.member-section.delete-member")}
</button> </button>
{user.rowStatus === "NORMAL" ? (
<button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleArchiveUserClick(user)}
>
{t("setting.member-section.archive-member")}
</button>
) : (
<>
<button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleRestoreUserClick(user)}
>
{t("common.restore")}
</button>
<button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded text-red-600 hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleDeleteUserClick(user)}
>
{t("setting.member-section.delete-member")}
</button>
</>
)}
</> </>
)} }
</> />
} )}
/> </td>
)} </tr>
</td> ))}
</tr> </tbody>
))} </table>
</tbody> </div>
</Table> </div>
</div> </div>
); );
}; };

View File

@ -0,0 +1,102 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslate } from "@/utils/i18n";
import { Button, Input } from "@mui/joy";
import { useGlobalStore } from "@/store/module";
import * as api from "@/helpers/api";
import LearnMore from "../LearnMore";
import "@/less/settings/system-section.less";
interface OpenAIConfig {
key: string;
host: string;
}
const OpenAISection = () => {
const t = useTranslate();
const globalStore = useGlobalStore();
const [openAIConfig, setOpenAIConfig] = useState<OpenAIConfig>({
key: "",
host: "",
});
useEffect(() => {
globalStore.fetchSystemStatus();
}, []);
useEffect(() => {
api.getSystemSetting().then(({ data: systemSettings }) => {
const openAIConfigSetting = systemSettings.find((setting) => setting.name === "openai-config");
if (openAIConfigSetting) {
setOpenAIConfig(JSON.parse(openAIConfigSetting.value));
}
});
}, []);
const handleOpenAIConfigKeyChanged = (value: string) => {
setOpenAIConfig({
...openAIConfig,
key: value,
});
};
const handleOpenAIConfigHostChanged = (value: string) => {
setOpenAIConfig({
...openAIConfig,
host: value,
});
};
const handleSaveOpenAIConfig = async () => {
try {
await api.upsertSystemSetting({
name: "openai-config",
value: JSON.stringify(openAIConfig),
});
} catch (error) {
console.error(error);
return;
}
toast.success("OpenAI Config updated");
};
return (
<div className="section-container system-section-container">
<p className="title-text">{t("setting.openai")}</p>
<div className="form-label">
<div className="flex flex-row items-center">
<span className="text-sm mr-1">{t("setting.system-section.openai-api-key")}</span>
<LearnMore title={t("setting.system-section.openai-api-key-description")} url="https://platform.openai.com/account/api-keys" />
</div>
</div>
<Input
className="w-full"
sx={{
fontFamily: "monospace",
fontSize: "14px",
}}
placeholder={t("setting.system-section.openai-api-key-placeholder")}
value={openAIConfig.key}
onChange={(event) => handleOpenAIConfigKeyChanged(event.target.value)}
/>
<div className="form-label mt-2">
<span className="normal-text">{t("setting.system-section.openai-api-host")}</span>
</div>
<Input
className="w-full"
sx={{
fontFamily: "monospace",
fontSize: "14px",
}}
placeholder={t("setting.system-section.openai-api-host-placeholder")}
value={openAIConfig.host}
onChange={(event) => handleOpenAIConfigHostChanged(event.target.value)}
/>
<div className="mt-4">
<Button onClick={handleSaveOpenAIConfig}>{t("common.save")}</Button>
</div>
</div>
);
};
export default OpenAISection;

View File

@ -1,3 +1,4 @@
import { Button, Input } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@ -103,9 +104,9 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
{t("setting.system-section.server-name")} {t("setting.system-section.server-name")}
<span className="text-sm text-gray-400 ml-1">({t("setting.system-section.customize-server.default")})</span> <span className="text-sm text-gray-400 ml-1">({t("setting.system-section.customize-server.default")})</span>
</p> </p>
<input type="text" className="input-text" value={state.name} onChange={handleNameChanged} /> <Input className="w-full" type="text" value={state.name} onChange={handleNameChanged} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p> <p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
<input type="text" className="input-text" value={state.logoUrl} onChange={handleLogoUrlChanged} /> <Input className="w-full" type="text" value={state.logoUrl} onChange={handleLogoUrlChanged} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.description")}</p> <p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.description")}</p>
<Textarea minRows="2" maxRows="4" className="!input-text" value={state.description} onChange={handleDescriptionChanged} /> <Textarea minRows="2" maxRows="4" className="!input-text" value={state.description} onChange={handleDescriptionChanged} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.locale")}</p> <p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.locale")}</p>
@ -114,17 +115,15 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
<AppearanceSelect className="!w-full" value={state.appearance} onChange={handleAppearanceSelectChange} /> <AppearanceSelect className="!w-full" value={state.appearance} onChange={handleAppearanceSelectChange} />
<div className="mt-4 w-full flex flex-row justify-between items-center space-x-2"> <div className="mt-4 w-full flex flex-row justify-between items-center space-x-2">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
<button className="btn-normal" onClick={handleRestoreButtonClick}> <Button variant="outlined" onClick={handleRestoreButtonClick}>
{t("common.restore")} {t("common.restore")}
</button> </Button>
</div> </div>
<div className="flex flex-row justify-end items-center"> <div className="flex flex-row justify-end items-center">
<button className="btn-text" onClick={handleCloseButtonClick}> <Button variant="plain" onClick={handleCloseButtonClick}>
{t("common.cancel")} {t("common.cancel")}
</button> </Button>
<button className="btn-primary" onClick={handleSaveButtonClick}> <Button onClick={handleSaveButtonClick}>{t("common.save")}</Button>
{t("common.save")}
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,7 +21,7 @@
@layer components { @layer components {
.btn-normal { .btn-normal {
@apply select-none flex flex-row justify-center items-center border dark:border-zinc-700 cursor-pointer px-3 text-sm leading-8 rounded-md hover:opacity-80 hover:shadow disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:shadow-none; @apply select-none flex flex-row justify-center items-center border dark:border-zinc-800 cursor-pointer px-3 text-sm leading-8 rounded-md hover:opacity-80 hover:shadow disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:shadow-none;
} }
.btn-primary { .btn-primary {

View File

@ -1,21 +0,0 @@
.member-section-container {
> .create-member-container {
@apply w-full flex flex-col justify-start items-start;
> .input-form-container {
@apply w-full mb-3 flex flex-row justify-start items-center;
> .field-text {
@apply text-sm text-gray-600 dark:text-gray-400 w-20 text-right pr-2;
}
> input {
@apply border dark:border-zinc-700 rounded text-sm leading-7 shadow-inner px-2 dark:bg-zinc-800;
}
}
> .btns-container {
@apply w-full mb-6 pl-20 flex flex-row justify-start items-center;
}
}
}

View File

@ -173,6 +173,7 @@
"member": "Member", "member": "Member",
"member-list": "Member list", "member-list": "Member list",
"system": "System", "system": "System",
"openai": "OpenAI",
"storage": "Storage", "storage": "Storage",
"sso": "SSO", "sso": "SSO",
"account-section": { "account-section": {

View File

@ -160,13 +160,13 @@ const Auth = () => {
{actionBtnLoadingState.isLoading && <Icon.Loader className="w-4 h-auto mr-2 animate-spin dark:text-gray-300" />} {actionBtnLoadingState.isLoading && <Icon.Loader className="w-4 h-auto mr-2 animate-spin dark:text-gray-300" />}
{systemStatus?.allowSignUp && ( {systemStatus?.allowSignUp && (
<> <>
<Button variant={"plain"} loading={actionBtnLoadingState.isLoading} onClick={handleSignUpButtonClick}> <Button variant={"plain"} disabled={actionBtnLoadingState.isLoading} onClick={handleSignUpButtonClick}>
{t("common.sign-up")} {t("common.sign-up")}
</Button> </Button>
<span className="mr-2 font-mono text-gray-200">/</span> <span className="mr-2 font-mono text-gray-200">/</span>
</> </>
)} )}
<Button type="submit" loading={actionBtnLoadingState.isLoading} onClick={handleSignInButtonClick}> <Button type="submit" disabled={actionBtnLoadingState.isLoading} onClick={handleSignInButtonClick}>
{t("common.sign-in")} {t("common.sign-in")}
</Button> </Button>
</div> </div>

View File

@ -57,7 +57,10 @@ const Explore = () => {
}) })
: memos; : memos;
const sortedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL" && m.visibility !== "PRIVATE"); const sortedMemos = shownMemos
.filter((m) => m.rowStatus === "NORMAL" && m.visibility !== "PRIVATE")
.sort((mi, mj) => mj.displayTs - mi.displayTs);
const handleFetchMoreClick = async () => { const handleFetchMoreClick = async () => {
try { try {
const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, memos.length); const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, memos.length);

View File

@ -119,18 +119,16 @@ const MemoChat = () => {
}; };
return ( return (
<section className="w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800"> <section className="w-full max-w-3xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
<MobileHeader showSearch={false} /> <MobileHeader showSearch={false} />
<div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300"> <div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300">
<div className="flex space-x-2"> <div className="w-full flex">
<div className="w-full flex flex-row justify-between items-center"> <div className="w-auto flex flex-row justify-start items-center select-none shrink-0 mr-4">
<p className="flex flex-row justify-start items-center select-none rounded"> <Icon.Bot className="w-5 h-auto mr-1" /> {t("memo-chat.title")}
<Icon.Bot className="w-5 h-auto mr-1" /> {t("memo-chat.title")}
</p>
</div> </div>
<span className="flex flex-row w-full justify-start items-center"> <div className="flex flex-row w-auto justify-start items-center overflow-y-hidden overflow-x-auto">
<div className="flex space-x-2 max-w-md overflow-scroll"> <div className="flex space-x-2 overflow-x-auto">
{conversationList.map((item: Conversation) => ( {conversationList.map((item: Conversation) => (
<ConversationTab <ConversationTab
key={item.messageStorageId} key={item.messageStorageId}
@ -146,8 +144,7 @@ const MemoChat = () => {
/> />
))} ))}
</div> </div>
<button className="btn-text px-1 ml-1 shrink-0">
<button className="btn-text px-1 ml-1">
<Icon.Plus <Icon.Plus
className="w-4 h-auto" className="w-4 h-auto"
onClick={() => { onClick={() => {
@ -155,7 +152,7 @@ const MemoChat = () => {
}} }}
/> />
</button> </button>
</span> </div>
</div> </div>
<div className="dialog-content-container w-full"> <div className="dialog-content-container w-full">

View File

@ -1,19 +1,20 @@
import { Option, Select } from "@mui/joy"; import { Option, Select } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module"; import { useGlobalStore, useUserStore } from "@/store/module";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import BetaBadge from "@/components/BetaBadge"; import BetaBadge from "@/components/BetaBadge";
import MyAccountSection from "@/components/Settings/MyAccountSection"; import MyAccountSection from "@/components/Settings/MyAccountSection";
import PreferencesSection from "@/components/Settings/PreferencesSection"; import PreferencesSection from "@/components/Settings/PreferencesSection";
import MemberSection from "@/components/Settings/MemberSection"; import MemberSection from "@/components/Settings/MemberSection";
import SystemSection from "@/components/Settings/SystemSection"; import SystemSection from "@/components/Settings/SystemSection";
import OpenAISection from "@/components/Settings/OpenAISection";
import StorageSection from "@/components/Settings/StorageSection"; import StorageSection from "@/components/Settings/StorageSection";
import SSOSection from "@/components/Settings/SSOSection"; import SSOSection from "@/components/Settings/SSOSection";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import "@/less/setting.less"; import "@/less/setting.less";
type SettingSection = "my-account" | "preference" | "member" | "system" | "storage" | "sso"; type SettingSection = "my-account" | "preference" | "member" | "system" | "openai" | "storage" | "sso";
interface State { interface State {
selectedSection: SettingSection; selectedSection: SettingSection;
@ -21,6 +22,7 @@ interface State {
const Setting = () => { const Setting = () => {
const t = useTranslate(); const t = useTranslate();
const globalStore = useGlobalStore();
const userStore = useUserStore(); const userStore = useUserStore();
const user = userStore.state.user; const user = userStore.state.user;
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
@ -37,7 +39,11 @@ const Setting = () => {
const getSettingSectionList = () => { const getSettingSectionList = () => {
let settingList: SettingSection[] = ["my-account", "preference"]; let settingList: SettingSection[] = ["my-account", "preference"];
if (isHost) { if (isHost) {
settingList = settingList.concat(["member", "system", "storage", "sso"]); if (globalStore.isDev()) {
settingList = settingList.concat(["member", "system", "openai", "storage", "sso"]);
} else {
settingList = settingList.concat(["member", "system", "storage", "sso"]);
}
} }
return settingList; return settingList;
}; };
@ -78,6 +84,14 @@ const Setting = () => {
> >
<Icon.Settings2 className="w-4 h-auto mr-2 opacity-80" /> {t("setting.system")} <Icon.Settings2 className="w-4 h-auto mr-2 opacity-80" /> {t("setting.system")}
</span> </span>
{globalStore.isDev() && (
<span
onClick={() => handleSectionSelectorItemClick("openai")}
className={`section-item ${state.selectedSection === "openai" ? "selected" : ""}`}
>
<Icon.Bot className="w-4 h-auto mr-2 opacity-80" /> {t("setting.openai")} <BetaBadge />
</span>
)}
<span <span
onClick={() => handleSectionSelectorItemClick("storage")} onClick={() => handleSectionSelectorItemClick("storage")}
className={`section-item ${state.selectedSection === "storage" ? "selected" : ""}`} className={`section-item ${state.selectedSection === "storage" ? "selected" : ""}`}
@ -114,6 +128,8 @@ const Setting = () => {
<MemberSection /> <MemberSection />
) : state.selectedSection === "system" ? ( ) : state.selectedSection === "system" ? (
<SystemSection /> <SystemSection />
) : state.selectedSection === "openai" ? (
<OpenAISection />
) : state.selectedSection === "storage" ? ( ) : state.selectedSection === "storage" ? (
<StorageSection /> <StorageSection />
) : state.selectedSection === "sso" ? ( ) : state.selectedSection === "sso" ? (