refactor: user v1 store (#2047)

This commit is contained in:
boojack 2023-07-29 18:57:09 +08:00 committed by GitHub
parent f5793c142c
commit a6a1898c41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 97 additions and 46 deletions

View File

@ -20,7 +20,7 @@
"i18next": "^21.9.2", "i18next": "^21.9.2",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-react": "^0.105.0", "lucide-react": "^0.263.0",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -42,8 +42,8 @@ dependencies:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
lucide-react: lucide-react:
specifier: ^0.105.0 specifier: ^0.263.0
version: 0.105.0(react@18.2.0) version: 0.263.0(react@18.2.0)
qrcode.react: qrcode.react:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0(react@18.2.0) version: 3.1.0(react@18.2.0)
@ -2428,8 +2428,8 @@ packages:
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
/lucide-react@0.105.0(react@18.2.0): /lucide-react@0.263.0(react@18.2.0):
resolution: {integrity: sha512-iHaIkd4Wq6aNIVrFMXt3If8E/+2lnJd4WlCyntoJNIzZ8nWhdSSHWpsw7XM4rlw2319LZ2t4WLdnM8Z0ECDTOQ==} resolution: {integrity: sha512-F+rHswbbI1xuDZ/OzofiJZJVlBPOIYVVST705cPdRLImJ5aOJNXYaFBPNo3qdUV0iEG/4nZeiUtLSHO2qU2ISw==}
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 react: ^16.5.1 || ^17.0.0 || ^18.0.0
dependencies: dependencies:

View File

@ -8,7 +8,7 @@ import { Link } from "react-router-dom";
import { useFilterStore, useMemoStore, useUserStore } from "@/store/module"; import { useFilterStore, useMemoStore, useUserStore } from "@/store/module";
import { UNKNOWN_ID } from "@/helpers/consts"; import { UNKNOWN_ID } from "@/helpers/consts";
import { getRelativeTimeString } from "@/helpers/datetime"; import { getRelativeTimeString } from "@/helpers/datetime";
import { useMemoCacheStore } from "@/store/zustand"; import { useMemoCacheStore, useUserV1Store } from "@/store/v1";
import { showCommonDialog } from "./Dialog/CommonDialog"; import { showCommonDialog } from "./Dialog/CommonDialog";
import Icon from "./Icon"; import Icon from "./Icon";
import MemoContent from "./MemoContent"; import MemoContent from "./MemoContent";
@ -18,6 +18,7 @@ import showShareMemo from "./ShareMemoDialog";
import showPreviewImageDialog from "./PreviewImageDialog"; import showPreviewImageDialog from "./PreviewImageDialog";
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog"; import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog";
import UserAvatar from "./UserAvatar";
import "@/less/memo.less"; import "@/less/memo.less";
interface Props { interface Props {
@ -36,11 +37,19 @@ const Memo: React.FC<Props> = (props: Props) => {
const userStore = useUserStore(); const userStore = useUserStore();
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const memoCacheStore = useMemoCacheStore(); const memoCacheStore = useMemoCacheStore();
const userV1Store = useUserV1Store();
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.displayTs)); const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.displayTs));
const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]); const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]);
const memoContainerRef = useRef<HTMLDivElement>(null); const memoContainerRef = useRef<HTMLDivElement>(null);
const readonly = userStore.isVisitorMode() || userStore.getCurrentUsername() !== memo.creatorUsername; const readonly = userStore.isVisitorMode() || userStore.getCurrentUsername() !== memo.creatorUsername;
const creator = userV1Store.getUserByUsername(memo.creatorUsername);
// Prepare memo creator.
useEffect(() => {
userV1Store.getOrFetchUserByUsername(memo.creatorUsername);
}, [memo.creatorUsername]);
// Prepare related memos.
useEffect(() => { useEffect(() => {
Promise.allSettled(memo.relationList.map((memoRelation) => memoCacheStore.getOrFetchMemoById(memoRelation.relatedMemoId))).then( Promise.allSettled(memo.relationList.map((memoRelation) => memoCacheStore.getOrFetchMemoById(memoRelation.relatedMemoId))).then(
(results) => { (results) => {
@ -217,14 +226,18 @@ const Memo: React.FC<Props> = (props: Props) => {
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}> <div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}>
<div className="memo-top-wrapper"> <div className="memo-top-wrapper">
<div className="status-text-container"> <div className="status-text-container">
{showCreator && creator && (
<>
<Link className="flex flex-row justify-start items-center" to={`/u/${memo.creatorUsername}`}>
<UserAvatar className="!w-5 !h-auto mr-1" avatarUrl={creator.avatarUrl} />
<span className="text-sm text-gray-600 dark:text-zinc-300">{creator.nickname}</span>
</Link>
<Icon.Dot className="w-4 h-auto text-gray-400 dark:text-zinc-400" />
</>
)}
<Link className="time-text" to={`/m/${memo.id}`} onClick={handleMemoCreatedTimeClick}> <Link className="time-text" to={`/m/${memo.id}`} onClick={handleMemoCreatedTimeClick}>
{createdTimeStr} {createdTimeStr}
</Link> </Link>
{showCreator && (
<Link className="name-text" to={`/u/${memo.creatorUsername}`}>
@{memo.creatorName}
</Link>
)}
</div> </div>
<div className="btns-container space-x-2"> <div className="btns-container space-x-2">
{showVisibility && memo.visibility !== "PRIVATE" && ( {showVisibility && memo.visibility !== "PRIVATE" && (

View File

@ -1,4 +1,4 @@
import { Message } from "@/store/zustand/message"; import { Message } from "@/store/v1/message";
import { marked } from "@/labs/marked"; import { marked } from "@/labs/marked";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import Dropdown from "../kit/Dropdown"; import Dropdown from "../kit/Dropdown";

View File

@ -1,4 +1,4 @@
import { Conversation } from "@/store/zustand/conversation"; import { Conversation } from "@/store/v1/conversation";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
interface ConversationTabProps { interface ConversationTabProps {

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useMemoCacheStore } from "@/store/zustand"; import { useMemoCacheStore } from "@/store/v1";
import Icon from "../Icon"; import Icon from "../Icon";
interface Props { interface Props {

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useMemoCacheStore } from "@/store/zustand"; import { useMemoCacheStore } from "@/store/v1";
import Icon from "./Icon"; import Icon from "./Icon";
interface Props { interface Props {

View File

@ -1,3 +1,5 @@
import classNames from "classnames";
interface Props { interface Props {
avatarUrl?: string; avatarUrl?: string;
className?: string; className?: string;
@ -6,7 +8,7 @@ interface Props {
const UserAvatar = (props: Props) => { const UserAvatar = (props: Props) => {
const { avatarUrl, className } = props; const { avatarUrl, className } = props;
return ( return (
<div className={`${className ?? ""} w-8 h-8 overflow-clip`}> <div className={classNames(`w-8 h-8 overflow-clip`, className)}>
<img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.webp"} alt="" /> <img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.webp"} alt="" />
</div> </div>
); );

View File

@ -1,6 +1,4 @@
import axios from "axios"; import axios from "axios";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import { Message } from "@/store/zustand/message";
export function getSystemStatus() { export function getSystemStatus() {
return axios.get<SystemStatus>("/api/v1/status"); return axios.get<SystemStatus>("/api/v1/status");
@ -139,32 +137,11 @@ export function unpinMemo(memoId: MemoId) {
export function deleteMemo(memoId: MemoId) { export function deleteMemo(memoId: MemoId) {
return axios.delete(`/api/v1/memo/${memoId}`); return axios.delete(`/api/v1/memo/${memoId}`);
} }
export function checkOpenAIEnabled() { export function checkOpenAIEnabled() {
return axios.get<boolean>(`/api/openai/enabled`); return axios.get<boolean>(`/api/openai/enabled`);
} }
export async function chatStreaming(messageList: Array<Message>, onmessage: any, onclose: any) {
await fetchEventSource("/api/v1/openai/chat-streaming", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(messageList),
async onopen() {
// to do nth
},
onmessage(event: any) {
onmessage(event);
},
onclose() {
onclose();
},
onerror(error: any) {
console.log("error", error);
},
});
}
export function getShortcutList(shortcutFind?: ShortcutFind) { export function getShortcutList(shortcutFind?: ShortcutFind) {
const queryList = []; const queryList = [];
if (shortcutFind?.creatorUsername) { if (shortcutFind?.creatorUsername) {

View File

@ -1,12 +1,13 @@
import { Button, Stack } from "@mui/joy"; import { Button, Stack } from "@mui/joy";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import { head } from "lodash-es"; import { head } from "lodash-es";
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";
import * as api from "@/helpers/api"; import * as api from "@/helpers/api";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { useMessageStore } from "@/store/zustand/message"; import { Message, useMessageStore } from "@/store/v1/message";
import { Conversation, useConversationStore } from "@/store/zustand/conversation"; import { Conversation, useConversationStore } from "@/store/v1/conversation";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import { generateUUID } from "@/utils/uuid"; import { generateUUID } from "@/utils/uuid";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
@ -15,6 +16,28 @@ import ChatInput from "@/components/MemoChat/ChatInput";
import ConversationTab from "@/components/MemoChat/ConversationTab"; import ConversationTab from "@/components/MemoChat/ConversationTab";
import Empty from "@/components/Empty"; import Empty from "@/components/Empty";
const chatStreaming = async (messageList: Array<Message>, onmessage: any, onclose: any) => {
await fetchEventSource("/api/v1/openai/chat-streaming", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(messageList),
async onopen() {
// to do nth
},
onmessage(event: any) {
onmessage(event);
},
onclose() {
onclose();
},
onerror(error: any) {
console.log("error", error);
},
});
};
const MemoChat = () => { const MemoChat = () => {
const t = useTranslate(); const t = useTranslate();
const fetchingState = useLoading(false); const fetchingState = useLoading(false);
@ -91,7 +114,7 @@ const MemoChat = () => {
const fetchChatStreaming = async (messageId: string) => { const fetchChatStreaming = async (messageId: string) => {
const messageList = messageStore.getState().messageList; const messageList = messageStore.getState().messageList;
await api.chatStreaming( await chatStreaming(
messageList, messageList,
async (event: any) => { async (event: any) => {
messageStore.updateMessage(messageId, event.data); messageStore.updateMessage(messageId, event.data);

View File

@ -4,7 +4,7 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import { useUserStore } from "./"; import { useUserStore } from "./";
import store, { useAppSelector } from "../"; import store, { useAppSelector } from "../";
import { createMemo, deleteMemo, patchMemo, setIsFetching, upsertMemos } from "../reducer/memo"; import { createMemo, deleteMemo, patchMemo, setIsFetching, upsertMemos } from "../reducer/memo";
import { useMemoCacheStore } from "../zustand/memo"; import { useMemoCacheStore } from "../v1";
export const convertResponseModelMemo = (memo: Memo): Memo => { export const convertResponseModelMemo = (memo: Memo): Memo => {
return { return {

View File

@ -0,0 +1,4 @@
import useMemoCacheStore from "./memo";
import useUserV1Store from "./user";
export { useUserV1Store, useMemoCacheStore };

View File

@ -3,7 +3,7 @@ import { combine } from "zustand/middleware";
import * as api from "@/helpers/api"; import * as api from "@/helpers/api";
import { convertResponseModelMemo } from "../module"; import { convertResponseModelMemo } from "../module";
export const useMemoCacheStore = create( const useMemoCacheStore = create(
combine({ memoById: new Map<MemoId, Memo>() }, (set, get) => ({ combine({ memoById: new Map<MemoId, Memo>() }, (set, get) => ({
getState: () => get(), getState: () => get(),
getOrFetchMemoById: async (memoId: MemoId) => { getOrFetchMemoById: async (memoId: MemoId) => {
@ -39,3 +39,5 @@ export const useMemoCacheStore = create(
}, },
})) }))
); );
export default useMemoCacheStore;

31
web/src/store/v1/user.ts Normal file
View File

@ -0,0 +1,31 @@
import { create } from "zustand";
import * as api from "@/helpers/api";
import { convertResponseModelUser } from "../module";
interface UserV1Store {
userMapByUsername: Record<string, User>;
getOrFetchUserByUsername: (username: string) => Promise<User>;
getUserByUsername: (username: string) => User;
}
const useUserV1Store = create<UserV1Store>()((set, get) => ({
userMapByUsername: {},
getOrFetchUserByUsername: async (username: string) => {
const userMap = get().userMapByUsername;
if (userMap[username]) {
return userMap[username] as User;
}
const { data } = await api.getUserByUsername(username);
const user = convertResponseModelUser(data);
userMap[username] = user;
set(userMap);
return user;
},
getUserByUsername: (username: string) => {
const userMap = get().userMapByUsername;
return userMap[username] as User;
},
}));
export default useUserV1Store;

View File

@ -1 +0,0 @@
export { useMemoCacheStore } from "./memo";