diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index 1ce3d2db..24b20e9f 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -49,6 +49,7 @@ Information about release notes of Coco Server is provided here. - style: chat input icons show #515 - refactor: refactoring icon component #514 - refactor: optimizing list styles in markdown content #520 +- feat: add a component for text reading aloud #522 ## 0.4.0 (2025-04-27) diff --git a/src/components/Assistant/Chat.tsx b/src/components/Assistant/Chat.tsx index bd5aeb13..c0c5022d 100644 --- a/src/components/Assistant/Chat.tsx +++ b/src/components/Assistant/Chat.tsx @@ -22,6 +22,7 @@ import ConnectPrompt from "./ConnectPrompt"; import type { Chat } from "./types"; import PrevSuggestion from "@/components/ChatMessage/PrevSuggestion"; import { useAppStore } from "@/stores/appStore"; +// import ReadAloud from "./ReadAloud"; interface ChatAIProps { isSearchActive?: boolean; @@ -378,6 +379,8 @@ const ChatAI = memo( {!activeChat?._id && !visibleStartPage && ( )} + + {/* */} ); } diff --git a/src/components/Assistant/ReadAloud/imgs/back-dark.png b/src/components/Assistant/ReadAloud/imgs/back-dark.png new file mode 100755 index 00000000..c1530df0 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/back-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/back-light.png b/src/components/Assistant/ReadAloud/imgs/back-light.png new file mode 100755 index 00000000..403f1ebf Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/back-light.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/close-dark.png b/src/components/Assistant/ReadAloud/imgs/close-dark.png new file mode 100755 index 00000000..e97c53ce Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/close-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/close-light.png b/src/components/Assistant/ReadAloud/imgs/close-light.png new file mode 100755 index 00000000..487c1e90 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/close-light.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/forward-dark.png b/src/components/Assistant/ReadAloud/imgs/forward-dark.png new file mode 100755 index 00000000..496163ad Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/forward-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/forward-light.png b/src/components/Assistant/ReadAloud/imgs/forward-light.png new file mode 100755 index 00000000..d5b93de3 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/forward-light.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/loading-dark.png b/src/components/Assistant/ReadAloud/imgs/loading-dark.png new file mode 100755 index 00000000..c09b85ac Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/loading-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/loading-light.png b/src/components/Assistant/ReadAloud/imgs/loading-light.png new file mode 100755 index 00000000..42e6d694 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/loading-light.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/pause-dark.png b/src/components/Assistant/ReadAloud/imgs/pause-dark.png new file mode 100755 index 00000000..bbaa1adc Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/pause-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/pause-light.png b/src/components/Assistant/ReadAloud/imgs/pause-light.png new file mode 100755 index 00000000..3b6d52f9 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/pause-light.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/play-dark.png b/src/components/Assistant/ReadAloud/imgs/play-dark.png new file mode 100755 index 00000000..746718c7 Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/play-dark.png differ diff --git a/src/components/Assistant/ReadAloud/imgs/play-light.png b/src/components/Assistant/ReadAloud/imgs/play-light.png new file mode 100755 index 00000000..a5378e9f Binary files /dev/null and b/src/components/Assistant/ReadAloud/imgs/play-light.png differ diff --git a/src/components/Assistant/ReadAloud/index.tsx b/src/components/Assistant/ReadAloud/index.tsx new file mode 100644 index 00000000..8d2ef94e --- /dev/null +++ b/src/components/Assistant/ReadAloud/index.tsx @@ -0,0 +1,134 @@ +import { useThemeStore } from "@/stores/themeStore"; +import loadingLight from "./imgs/loading-light.png"; +import loadingDark from "./imgs/loading-dark.png"; +import playLight from "./imgs/play-light.png"; +import playDark from "./imgs/play-dark.png"; +import pauseLight from "./imgs/pause-light.png"; +import pauseDark from "./imgs/pause-dark.png"; +import backLight from "./imgs/back-light.png"; +import backDark from "./imgs/back-dark.png"; +import forwardLight from "./imgs/forward-light.png"; +import forwardDark from "./imgs/forward-dark.png"; +import closeLight from "./imgs/close-light.png"; +import closeDark from "./imgs/close-dark.png"; +import { useEffect, useMemo, useRef } from "react"; +import { useReactive } from "ahooks"; +import dayjs from "dayjs"; +import durationPlugin from "dayjs/plugin/duration"; + +dayjs.extend(durationPlugin); + +interface State { + loading: boolean; + playing: boolean; + totalDuration: number; + currentDuration: number; +} + +const ReadAloud = () => { + const isDark = useThemeStore((state) => state.isDark); + const state = useReactive({ + loading: false, + playing: true, + totalDuration: 300, + currentDuration: 0, + }); + const timerRef = useRef>(); + + const formatTime = useMemo(() => { + return dayjs.duration(state.currentDuration * 1000).format("mm:ss"); + }, [state.currentDuration]); + + useEffect(() => { + if (state.playing && state.currentDuration >= state.totalDuration) { + state.currentDuration = 0; + } + + changeCurrentDuration(); + }, [state.playing]); + + const changeCurrentDuration = (duration = state.currentDuration) => { + clearTimeout(timerRef.current); + + let nextDuration = duration; + + if (duration < 0) { + nextDuration = 0; + } + + if (duration >= state.totalDuration) { + state.currentDuration = state.totalDuration; + + state.playing = false; + } + + if (!state.playing) return; + + state.currentDuration = nextDuration; + + timerRef.current = setTimeout(() => { + changeCurrentDuration(duration + 1); + }, 1000); + }; + + return ( + + + {state.loading ? ( + + ) : ( + { + state.playing = !state.playing; + }} + > + {state.playing ? ( + + ) : ( + + )} + + )} + + {formatTime} + + + {!state.loading && ( + <> + { + changeCurrentDuration(state.currentDuration - 15); + }} + /> + + { + changeCurrentDuration(state.currentDuration + 15); + }} + /> + > + )} + + + + + ); +}; + +export default ReadAloud;