diff --git a/web/package.json b/web/package.json index 354e6266..6d92c67f 100644 --- a/web/package.json +++ b/web/package.json @@ -20,7 +20,7 @@ "i18next": "^21.9.2", "i18next-browser-languagedetector": "^7.0.1", "lodash-es": "^4.17.21", - "lucide-react": "^0.105.0", + "lucide-react": "^0.263.0", "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 90fb2a54..61ad60f9 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -42,8 +42,8 @@ dependencies: specifier: ^4.17.21 version: 4.17.21 lucide-react: - specifier: ^0.105.0 - version: 0.105.0(react@18.2.0) + specifier: ^0.263.0 + version: 0.263.0(react@18.2.0) qrcode.react: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) @@ -2428,8 +2428,8 @@ packages: dependencies: yallist: 4.0.0 - /lucide-react@0.105.0(react@18.2.0): - resolution: {integrity: sha512-iHaIkd4Wq6aNIVrFMXt3If8E/+2lnJd4WlCyntoJNIzZ8nWhdSSHWpsw7XM4rlw2319LZ2t4WLdnM8Z0ECDTOQ==} + /lucide-react@0.263.0(react@18.2.0): + resolution: {integrity: sha512-F+rHswbbI1xuDZ/OzofiJZJVlBPOIYVVST705cPdRLImJ5aOJNXYaFBPNo3qdUV0iEG/4nZeiUtLSHO2qU2ISw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 dependencies: diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 753412b5..b859cfd9 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -8,7 +8,7 @@ import { Link } from "react-router-dom"; import { useFilterStore, useMemoStore, useUserStore } from "@/store/module"; import { UNKNOWN_ID } from "@/helpers/consts"; import { getRelativeTimeString } from "@/helpers/datetime"; -import { useMemoCacheStore } from "@/store/zustand"; +import { useMemoCacheStore, useUserV1Store } from "@/store/v1"; import { showCommonDialog } from "./Dialog/CommonDialog"; import Icon from "./Icon"; import MemoContent from "./MemoContent"; @@ -18,6 +18,7 @@ import showShareMemo from "./ShareMemoDialog"; import showPreviewImageDialog from "./PreviewImageDialog"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog"; +import UserAvatar from "./UserAvatar"; import "@/less/memo.less"; interface Props { @@ -36,11 +37,19 @@ const Memo: React.FC = (props: Props) => { const userStore = useUserStore(); const memoStore = useMemoStore(); const memoCacheStore = useMemoCacheStore(); + const userV1Store = useUserV1Store(); const [createdTimeStr, setCreatedTimeStr] = useState(getRelativeTimeString(memo.displayTs)); const [relatedMemoList, setRelatedMemoList] = useState([]); const memoContainerRef = useRef(null); 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(() => { Promise.allSettled(memo.relationList.map((memoRelation) => memoCacheStore.getOrFetchMemoById(memoRelation.relatedMemoId))).then( (results) => { @@ -217,14 +226,18 @@ const Memo: React.FC = (props: Props) => {
+ {showCreator && creator && ( + <> + + + {creator.nickname} + + + + )} {createdTimeStr} - {showCreator && ( - - @{memo.creatorName} - - )}
{showVisibility && memo.visibility !== "PRIVATE" && ( diff --git a/web/src/components/MemoChat/ChatMessage.tsx b/web/src/components/MemoChat/ChatMessage.tsx index 5e199370..cb8032f6 100644 --- a/web/src/components/MemoChat/ChatMessage.tsx +++ b/web/src/components/MemoChat/ChatMessage.tsx @@ -1,4 +1,4 @@ -import { Message } from "@/store/zustand/message"; +import { Message } from "@/store/v1/message"; import { marked } from "@/labs/marked"; import Icon from "@/components/Icon"; import Dropdown from "../kit/Dropdown"; diff --git a/web/src/components/MemoChat/ConversationTab.tsx b/web/src/components/MemoChat/ConversationTab.tsx index c156305a..4ead3749 100644 --- a/web/src/components/MemoChat/ConversationTab.tsx +++ b/web/src/components/MemoChat/ConversationTab.tsx @@ -1,4 +1,4 @@ -import { Conversation } from "@/store/zustand/conversation"; +import { Conversation } from "@/store/v1/conversation"; import Icon from "@/components/Icon"; interface ConversationTabProps { diff --git a/web/src/components/MemoEditor/RelationListView.tsx b/web/src/components/MemoEditor/RelationListView.tsx index b456e499..87b9272b 100644 --- a/web/src/components/MemoEditor/RelationListView.tsx +++ b/web/src/components/MemoEditor/RelationListView.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { useMemoCacheStore } from "@/store/zustand"; +import { useMemoCacheStore } from "@/store/v1"; import Icon from "../Icon"; interface Props { diff --git a/web/src/components/MemoRelationListView.tsx b/web/src/components/MemoRelationListView.tsx index cd25584e..12438277 100644 --- a/web/src/components/MemoRelationListView.tsx +++ b/web/src/components/MemoRelationListView.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { useMemoCacheStore } from "@/store/zustand"; +import { useMemoCacheStore } from "@/store/v1"; import Icon from "./Icon"; interface Props { diff --git a/web/src/components/UserAvatar.tsx b/web/src/components/UserAvatar.tsx index a2f6e2ee..281a7dd3 100644 --- a/web/src/components/UserAvatar.tsx +++ b/web/src/components/UserAvatar.tsx @@ -1,3 +1,5 @@ +import classNames from "classnames"; + interface Props { avatarUrl?: string; className?: string; @@ -6,7 +8,7 @@ interface Props { const UserAvatar = (props: Props) => { const { avatarUrl, className } = props; return ( -
+
); diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index 59c59b93..3dc30552 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -1,6 +1,4 @@ import axios from "axios"; -import { fetchEventSource } from "@microsoft/fetch-event-source"; -import { Message } from "@/store/zustand/message"; export function getSystemStatus() { return axios.get("/api/v1/status"); @@ -139,32 +137,11 @@ export function unpinMemo(memoId: MemoId) { export function deleteMemo(memoId: MemoId) { return axios.delete(`/api/v1/memo/${memoId}`); } + export function checkOpenAIEnabled() { return axios.get(`/api/openai/enabled`); } -export async function chatStreaming(messageList: Array, 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) { const queryList = []; if (shortcutFind?.creatorUsername) { diff --git a/web/src/pages/MemoChat.tsx b/web/src/pages/MemoChat.tsx index dab59aef..0380db73 100644 --- a/web/src/pages/MemoChat.tsx +++ b/web/src/pages/MemoChat.tsx @@ -1,12 +1,13 @@ import { Button, Stack } from "@mui/joy"; +import { fetchEventSource } from "@microsoft/fetch-event-source"; import { head } from "lodash-es"; import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslate } from "@/utils/i18n"; import * as api from "@/helpers/api"; import useLoading from "@/hooks/useLoading"; -import { useMessageStore } from "@/store/zustand/message"; -import { Conversation, useConversationStore } from "@/store/zustand/conversation"; +import { Message, useMessageStore } from "@/store/v1/message"; +import { Conversation, useConversationStore } from "@/store/v1/conversation"; import Icon from "@/components/Icon"; import { generateUUID } from "@/utils/uuid"; import MobileHeader from "@/components/MobileHeader"; @@ -15,6 +16,28 @@ import ChatInput from "@/components/MemoChat/ChatInput"; import ConversationTab from "@/components/MemoChat/ConversationTab"; import Empty from "@/components/Empty"; +const chatStreaming = async (messageList: Array, 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 t = useTranslate(); const fetchingState = useLoading(false); @@ -91,7 +114,7 @@ const MemoChat = () => { const fetchChatStreaming = async (messageId: string) => { const messageList = messageStore.getState().messageList; - await api.chatStreaming( + await chatStreaming( messageList, async (event: any) => { messageStore.updateMessage(messageId, event.data); diff --git a/web/src/store/module/memo.ts b/web/src/store/module/memo.ts index be0c366e..12e97bf7 100644 --- a/web/src/store/module/memo.ts +++ b/web/src/store/module/memo.ts @@ -4,7 +4,7 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import { useUserStore } from "./"; import store, { useAppSelector } from "../"; import { createMemo, deleteMemo, patchMemo, setIsFetching, upsertMemos } from "../reducer/memo"; -import { useMemoCacheStore } from "../zustand/memo"; +import { useMemoCacheStore } from "../v1"; export const convertResponseModelMemo = (memo: Memo): Memo => { return { diff --git a/web/src/store/zustand/conversation.ts b/web/src/store/v1/conversation.ts similarity index 100% rename from web/src/store/zustand/conversation.ts rename to web/src/store/v1/conversation.ts diff --git a/web/src/store/v1/index.ts b/web/src/store/v1/index.ts new file mode 100644 index 00000000..d6a351ee --- /dev/null +++ b/web/src/store/v1/index.ts @@ -0,0 +1,4 @@ +import useMemoCacheStore from "./memo"; +import useUserV1Store from "./user"; + +export { useUserV1Store, useMemoCacheStore }; diff --git a/web/src/store/zustand/memo.ts b/web/src/store/v1/memo.ts similarity index 93% rename from web/src/store/zustand/memo.ts rename to web/src/store/v1/memo.ts index 669d69ac..96c1c89a 100644 --- a/web/src/store/zustand/memo.ts +++ b/web/src/store/v1/memo.ts @@ -3,7 +3,7 @@ import { combine } from "zustand/middleware"; import * as api from "@/helpers/api"; import { convertResponseModelMemo } from "../module"; -export const useMemoCacheStore = create( +const useMemoCacheStore = create( combine({ memoById: new Map() }, (set, get) => ({ getState: () => get(), getOrFetchMemoById: async (memoId: MemoId) => { @@ -39,3 +39,5 @@ export const useMemoCacheStore = create( }, })) ); + +export default useMemoCacheStore; diff --git a/web/src/store/zustand/message.ts b/web/src/store/v1/message.ts similarity index 100% rename from web/src/store/zustand/message.ts rename to web/src/store/v1/message.ts diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts new file mode 100644 index 00000000..86d42d7d --- /dev/null +++ b/web/src/store/v1/user.ts @@ -0,0 +1,31 @@ +import { create } from "zustand"; +import * as api from "@/helpers/api"; +import { convertResponseModelUser } from "../module"; + +interface UserV1Store { + userMapByUsername: Record; + getOrFetchUserByUsername: (username: string) => Promise; + getUserByUsername: (username: string) => User; +} + +const useUserV1Store = create()((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; diff --git a/web/src/store/zustand/index.ts b/web/src/store/zustand/index.ts deleted file mode 100644 index d508549d..00000000 --- a/web/src/store/zustand/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useMemoCacheStore } from "./memo";