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 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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -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,6 +112,7 @@ const Header = () => {
|
|||||||
|
|
||||||
{!isVisitorMode && (
|
{!isVisitorMode && (
|
||||||
<>
|
<>
|
||||||
|
{globalStore.isDev() && (
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/memo-chat"
|
to="/memo-chat"
|
||||||
id="header-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")}
|
<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"
|
||||||
|
@ -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);
|
||||||
}}
|
}}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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,52 +116,52 @@ 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">
|
||||||
|
<div className="inline-block min-w-full align-middle">
|
||||||
|
<table className="min-w-full divide-y divide-gray-300">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th scope="col" className="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
|
||||||
<th>{t("common.username")}</th>
|
ID
|
||||||
<th></th>
|
</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody className="divide-y divide-gray-200">
|
||||||
{userList.map((user) => (
|
{userList.map((user) => (
|
||||||
<tr key={user.id}>
|
<tr key={user.id}>
|
||||||
<td className="field-text id-text">{user.id}</td>
|
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900">{user.id}</td>
|
||||||
<td className="field-text username-text">{user.username}</td>
|
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.username}</td>
|
||||||
<td className="flex flex-row justify-end items-center">
|
<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 ? (
|
{currentUser?.id === user.id ? (
|
||||||
<span className="tip-text">{t("common.yourself")}</span>
|
<span>{t("common.yourself")}</span>
|
||||||
) : (
|
) : (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
actions={
|
actions={
|
||||||
@ -204,7 +203,9 @@ const PreferencesSection = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</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 { 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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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": "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": {
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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">
|
||||||
|
@ -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,8 +39,12 @@ const Setting = () => {
|
|||||||
const getSettingSectionList = () => {
|
const getSettingSectionList = () => {
|
||||||
let settingList: SettingSection[] = ["my-account", "preference"];
|
let settingList: SettingSection[] = ["my-account", "preference"];
|
||||||
if (isHost) {
|
if (isHost) {
|
||||||
|
if (globalStore.isDev()) {
|
||||||
|
settingList = settingList.concat(["member", "system", "openai", "storage", "sso"]);
|
||||||
|
} else {
|
||||||
settingList = settingList.concat(["member", "system", "storage", "sso"]);
|
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" ? (
|
||||||
|
Reference in New Issue
Block a user