mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: add dev guard for memo chat (#1968)
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { Input } from "@mui/joy";
|
||||
import { Button, Input } from "@mui/joy";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
@ -123,14 +123,14 @@ const CreateTagDialog: React.FC<Props> = (props: Props) => {
|
||||
{shownSuggestTagNameList.length > 0 && (
|
||||
<>
|
||||
<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>
|
||||
<button className="btn-normal ml-2 px-2 py-0 leading-6 font-mono" onClick={handleToggleShowSuggestionTags}>
|
||||
<span className="text-gray-400 mr-2">Tag suggestions</span>
|
||||
<Button size="sm" variant="outlined" onClick={handleToggleShowSuggestionTags}>
|
||||
{showTagSuggestions ? "hide" : "show"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{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) => (
|
||||
<span
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
<button className="btn-normal mt-2 px-2 py-0 leading-6 font-mono" onClick={handleSaveSuggestTagList}>
|
||||
<Button size="sm" onClick={handleSaveSuggestTagList}>
|
||||
Save all
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import classNames from "classnames";
|
||||
import { useEffect } from "react";
|
||||
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 { resolution } from "@/utils/layout";
|
||||
import Icon from "./Icon";
|
||||
@ -12,6 +12,7 @@ import UpgradeVersionView from "./UpgradeVersionBanner";
|
||||
const Header = () => {
|
||||
const t = useTranslate();
|
||||
const location = useLocation();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const layoutStore = useLayoutStore();
|
||||
const showHeader = layoutStore.state.showHeader;
|
||||
@ -111,6 +112,7 @@ const Header = () => {
|
||||
|
||||
{!isVisitorMode && (
|
||||
<>
|
||||
{globalStore.isDev() && (
|
||||
<NavLink
|
||||
to="/memo-chat"
|
||||
id="header-memo-chat"
|
||||
@ -125,6 +127,7 @@ const Header = () => {
|
||||
<Icon.Bot className="mr-3 w-6 h-auto opacity-70" /> {t("memo-chat.title")}
|
||||
</>
|
||||
</NavLink>
|
||||
)}
|
||||
<NavLink
|
||||
to="/archived"
|
||||
id="header-archived"
|
||||
|
@ -11,7 +11,7 @@ interface ConversationTabProps {
|
||||
const ConversationTab = ({ item, selectedConversationId, setSelectedConversationId, closeConversation }: ConversationTabProps) => {
|
||||
return (
|
||||
<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"
|
||||
}`}
|
||||
key={item.messageStorageId}
|
||||
@ -21,7 +21,7 @@ const ConversationTab = ({ item, selectedConversationId, setSelectedConversation
|
||||
>
|
||||
<div className="truncate m-auto">{item.name}</div>
|
||||
<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) => {
|
||||
closeConversation(e);
|
||||
}}
|
||||
|
@ -21,11 +21,11 @@ const ResourceListView = (props: Props) => {
|
||||
return (
|
||||
<div
|
||||
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" />
|
||||
<span className="text-sm max-w-xs truncate font-mono">{resource.filename}</span>
|
||||
<Icon.X className="w-4 h-auto ml-1 hover:opacity-80" onClick={() => handleDeleteResource(resource.id)} />
|
||||
<ResourceIcon resource={resource} className="w-4 h-auto mr-1" />
|
||||
<span className="text-sm max-w-xs truncate">{resource.filename}</span>
|
||||
<Icon.X className="w-4 h-auto ml-1 cursor-pointer hover:opacity-80" onClick={() => handleDeleteResource(resource.id)} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Button } from "@mui/joy";
|
||||
import { isNumber, last, uniq } from "lodash-es";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
@ -422,9 +421,9 @@ const MemoEditor = (props: Props) => {
|
||||
<div className="editor-footer-container">
|
||||
<MemoVisibilitySelector value={state.memoVisibility} onChange={handleMemoVisibilityChange} />
|
||||
<div className="buttons-container">
|
||||
<Button disabled={!allowSave} onClick={handleSaveBtnClick}>
|
||||
<button className="action-btn confirm-btn" disabled={!allowSave} onClick={handleSaveBtnClick}>
|
||||
{t("editor.save")}
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,6 +31,7 @@ const MemoFilter = () => {
|
||||
}}
|
||||
>
|
||||
<Icon.Target className="icon-text" /> {shortcut?.title}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
<div
|
||||
className={"filter-item-container " + (tagQuery ? "" : "!hidden")}
|
||||
@ -39,6 +40,7 @@ const MemoFilter = () => {
|
||||
}}
|
||||
>
|
||||
<Icon.Tag className="icon-text" /> {tagQuery}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
<div
|
||||
className={"filter-item-container " + (memoType ? "" : "!hidden")}
|
||||
@ -48,6 +50,7 @@ const MemoFilter = () => {
|
||||
>
|
||||
<Icon.Box className="icon-text" />{" "}
|
||||
{t(getTextWithMemoType(memoType as MemoSpecType) as Exclude<ReturnType<typeof getTextWithMemoType>, "">)}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
<div
|
||||
className={"filter-item-container " + (visibility ? "" : "!hidden")}
|
||||
@ -56,6 +59,7 @@ const MemoFilter = () => {
|
||||
}}
|
||||
>
|
||||
<Icon.Eye className="icon-text" /> {visibility}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
{duration && duration.from < duration.to ? (
|
||||
<div
|
||||
@ -70,6 +74,7 @@ const MemoFilter = () => {
|
||||
to: getDateString(duration.to),
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
@ -79,6 +84,7 @@ const MemoFilter = () => {
|
||||
}}
|
||||
>
|
||||
<Icon.Search className="icon-text" /> {textQuery}
|
||||
<Icon.X className="w-4 h-auto ml-1 opacity-40" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,18 +1,33 @@
|
||||
import classNames from "classnames";
|
||||
import { getResourceUrl } from "@/utils/resource";
|
||||
import Icon from "./Icon";
|
||||
import SquareDiv from "./kit/SquareDiv";
|
||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
resourceType: string;
|
||||
resource: Resource;
|
||||
}
|
||||
|
||||
const ResourceIcon = (props: Props) => {
|
||||
const { className, resourceType } = props;
|
||||
const { className, resource } = props;
|
||||
|
||||
let ResourceIcon = Icon.FileText;
|
||||
if (resourceType.includes("image")) {
|
||||
ResourceIcon = Icon.Image;
|
||||
if (resource.type.includes("image")) {
|
||||
const url = getResourceUrl(resource);
|
||||
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} />;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Table } from "@mui/joy";
|
||||
import { Button, Input } from "@mui/joy";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
@ -7,7 +7,6 @@ import * as api from "@/helpers/api";
|
||||
import Dropdown from "../kit/Dropdown";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
|
||||
import "@/less/settings/member-section.less";
|
||||
|
||||
interface State {
|
||||
createUserUsername: string;
|
||||
@ -30,7 +29,7 @@ const PreferencesSection = () => {
|
||||
|
||||
const fetchUserList = async () => {
|
||||
const { data } = await api.getUserList();
|
||||
setUserList(data);
|
||||
setUserList(data.sort((a, b) => a.id - b.id));
|
||||
};
|
||||
|
||||
const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -117,52 +116,52 @@ const PreferencesSection = () => {
|
||||
return (
|
||||
<div className="section-container member-section-container">
|
||||
<p className="title-text">{t("setting.member-section.create-a-member")}</p>
|
||||
<div className="create-member-container">
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">{t("common.username")}</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="new-password"
|
||||
placeholder={t("common.username")}
|
||||
value={state.createUserUsername}
|
||||
onChange={handleUsernameInputChange}
|
||||
/>
|
||||
<div className="w-full flex flex-col justify-start items-start gap-2">
|
||||
<div className="flex flex-col justify-start items-start gap-1">
|
||||
<span className="text-sm">{t("common.username")}</span>
|
||||
<Input type="text" placeholder={t("common.username")} value={state.createUserUsername} onChange={handleUsernameInputChange} />
|
||||
</div>
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">{t("common.password")}</span>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
placeholder={t("common.password")}
|
||||
value={state.createUserPassword}
|
||||
onChange={handlePasswordInputChange}
|
||||
/>
|
||||
<div className="flex flex-col justify-start items-start gap-1">
|
||||
<span className="text-sm">{t("common.password")}</span>
|
||||
<Input type="password" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
||||
</div>
|
||||
<div className="btns-container">
|
||||
<button className="btn-normal" onClick={handleCreateUserBtnClick}>
|
||||
{t("common.create")}
|
||||
</button>
|
||||
<Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
|
||||
</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>
|
||||
<Table>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="inline-block min-w-full align-middle">
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>{t("common.username")}</th>
|
||||
<th></th>
|
||||
<th scope="col" className="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
|
||||
ID
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
|
||||
{t("common.username")}
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
|
||||
{t("common.nickname")}
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900">
|
||||
{t("common.email")}
|
||||
</th>
|
||||
<th scope="col" className="relative py-2 pl-3 pr-4"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{userList.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td className="field-text id-text">{user.id}</td>
|
||||
<td className="field-text username-text">{user.username}</td>
|
||||
<td className="flex flex-row justify-end items-center">
|
||||
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900">{user.id}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.username}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.nickname}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.email}</td>
|
||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end">
|
||||
{currentUser?.id === user.id ? (
|
||||
<span className="tip-text">{t("common.yourself")}</span>
|
||||
<span>{t("common.yourself")}</span>
|
||||
) : (
|
||||
<Dropdown
|
||||
actions={
|
||||
@ -204,7 +203,9 @@ const PreferencesSection = () => {
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
102
web/src/components/Settings/OpenAISection.tsx
Normal file
102
web/src/components/Settings/OpenAISection.tsx
Normal 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;
|
@ -1,3 +1,4 @@
|
||||
import { Button, Input } from "@mui/joy";
|
||||
import { useState } from "react";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { toast } from "react-hot-toast";
|
||||
@ -103,9 +104,9 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
{t("setting.system-section.server-name")}
|
||||
<span className="text-sm text-gray-400 ml-1">({t("setting.system-section.customize-server.default")})</span>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
@ -114,17 +115,15 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
<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="flex flex-row justify-start items-center">
|
||||
<button className="btn-normal" onClick={handleRestoreButtonClick}>
|
||||
<Button variant="outlined" onClick={handleRestoreButtonClick}>
|
||||
{t("common.restore")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end items-center">
|
||||
<button className="btn-text" onClick={handleCloseButtonClick}>
|
||||
<Button variant="plain" onClick={handleCloseButtonClick}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button className="btn-primary" onClick={handleSaveButtonClick}>
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</Button>
|
||||
<Button onClick={handleSaveButtonClick}>{t("common.save")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
@layer components {
|
||||
.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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -173,6 +173,7 @@
|
||||
"member": "Member",
|
||||
"member-list": "Member list",
|
||||
"system": "System",
|
||||
"openai": "OpenAI",
|
||||
"storage": "Storage",
|
||||
"sso": "SSO",
|
||||
"account-section": {
|
||||
|
@ -160,13 +160,13 @@ const Auth = () => {
|
||||
{actionBtnLoadingState.isLoading && <Icon.Loader className="w-4 h-auto mr-2 animate-spin dark:text-gray-300" />}
|
||||
{systemStatus?.allowSignUp && (
|
||||
<>
|
||||
<Button variant={"plain"} loading={actionBtnLoadingState.isLoading} onClick={handleSignUpButtonClick}>
|
||||
<Button variant={"plain"} disabled={actionBtnLoadingState.isLoading} onClick={handleSignUpButtonClick}>
|
||||
{t("common.sign-up")}
|
||||
</Button>
|
||||
<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")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -57,7 +57,10 @@ const Explore = () => {
|
||||
})
|
||||
: 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 () => {
|
||||
try {
|
||||
const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, memos.length);
|
||||
|
@ -119,18 +119,16 @@ const MemoChat = () => {
|
||||
};
|
||||
|
||||
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} />
|
||||
<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 flex-row justify-between items-center">
|
||||
<p className="flex flex-row justify-start items-center select-none rounded">
|
||||
<div className="w-full flex">
|
||||
<div className="w-auto flex flex-row justify-start items-center select-none shrink-0 mr-4">
|
||||
<Icon.Bot className="w-5 h-auto mr-1" /> {t("memo-chat.title")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span className="flex flex-row w-full justify-start items-center">
|
||||
<div className="flex space-x-2 max-w-md overflow-scroll">
|
||||
<div className="flex flex-row w-auto justify-start items-center overflow-y-hidden overflow-x-auto">
|
||||
<div className="flex space-x-2 overflow-x-auto">
|
||||
{conversationList.map((item: Conversation) => (
|
||||
<ConversationTab
|
||||
key={item.messageStorageId}
|
||||
@ -146,8 +144,7 @@ const MemoChat = () => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="btn-text px-1 ml-1">
|
||||
<button className="btn-text px-1 ml-1 shrink-0">
|
||||
<Icon.Plus
|
||||
className="w-4 h-auto"
|
||||
onClick={() => {
|
||||
@ -155,7 +152,7 @@ const MemoChat = () => {
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dialog-content-container w-full">
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { useState } from "react";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { useUserStore } from "@/store/module";
|
||||
import { useGlobalStore, useUserStore } from "@/store/module";
|
||||
import Icon from "@/components/Icon";
|
||||
import BetaBadge from "@/components/BetaBadge";
|
||||
import MyAccountSection from "@/components/Settings/MyAccountSection";
|
||||
import PreferencesSection from "@/components/Settings/PreferencesSection";
|
||||
import MemberSection from "@/components/Settings/MemberSection";
|
||||
import SystemSection from "@/components/Settings/SystemSection";
|
||||
import OpenAISection from "@/components/Settings/OpenAISection";
|
||||
import StorageSection from "@/components/Settings/StorageSection";
|
||||
import SSOSection from "@/components/Settings/SSOSection";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
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 {
|
||||
selectedSection: SettingSection;
|
||||
@ -21,6 +22,7 @@ interface State {
|
||||
|
||||
const Setting = () => {
|
||||
const t = useTranslate();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
const [state, setState] = useState<State>({
|
||||
@ -37,8 +39,12 @@ const Setting = () => {
|
||||
const getSettingSectionList = () => {
|
||||
let settingList: SettingSection[] = ["my-account", "preference"];
|
||||
if (isHost) {
|
||||
if (globalStore.isDev()) {
|
||||
settingList = settingList.concat(["member", "system", "openai", "storage", "sso"]);
|
||||
} else {
|
||||
settingList = settingList.concat(["member", "system", "storage", "sso"]);
|
||||
}
|
||||
}
|
||||
return settingList;
|
||||
};
|
||||
|
||||
@ -78,6 +84,14 @@ const Setting = () => {
|
||||
>
|
||||
<Icon.Settings2 className="w-4 h-auto mr-2 opacity-80" /> {t("setting.system")}
|
||||
</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
|
||||
onClick={() => handleSectionSelectorItemClick("storage")}
|
||||
className={`section-item ${state.selectedSection === "storage" ? "selected" : ""}`}
|
||||
@ -114,6 +128,8 @@ const Setting = () => {
|
||||
<MemberSection />
|
||||
) : state.selectedSection === "system" ? (
|
||||
<SystemSection />
|
||||
) : state.selectedSection === "openai" ? (
|
||||
<OpenAISection />
|
||||
) : state.selectedSection === "storage" ? (
|
||||
<StorageSection />
|
||||
) : state.selectedSection === "sso" ? (
|
||||
|
Reference in New Issue
Block a user