From 4a6ebf089449ebb599f63235ac498982e431f4aa Mon Sep 17 00:00:00 2001 From: IndieKKY Date: Mon, 27 May 2024 10:12:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E9=97=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/biz/Ask.tsx | 72 ++++++++++++++++++++++++++++++ src/biz/Body.tsx | 93 ++++++++++++++------------------------- src/hooks/useTranslate.ts | 22 +++++---- src/redux/envReducer.ts | 49 ++++++++------------- src/typings.d.ts | 11 +++++ 5 files changed, 148 insertions(+), 99 deletions(-) create mode 100644 src/biz/Ask.tsx diff --git a/src/biz/Ask.tsx b/src/biz/Ask.tsx new file mode 100644 index 0000000..9cade4e --- /dev/null +++ b/src/biz/Ask.tsx @@ -0,0 +1,72 @@ +import {AiOutlineCloseCircle, BsDashSquare, BsPlusSquare, FaQuestion} from 'react-icons/all' +import classNames from 'classnames' +import Markdown from '../components/Markdown' +import React, {useCallback} from 'react' +import {delAskInfo, mergeAskInfo, setPage} from '../redux/envReducer' +import {useAppDispatch, useAppSelector} from '../hooks/redux' +import {PAGE_SETTINGS} from '../const' +import toast from 'react-hot-toast' +import useTranslate from '../hooks/useTranslate' + +const Ask = (props: { + ask: AskInfo +}) => { + const {ask} = props + const dispatch = useAppDispatch() + const envData = useAppSelector(state => state.env.envData) + const fontSize = useAppSelector(state => state.env.envData.fontSize) + const segments = useAppSelector(state => state.env.segments) + const {addAskTask} = useTranslate() + + const onRegenerate = useCallback(() => { + const apiKey = envData.aiType === 'gemini'?envData.geminiApiKey:envData.apiKey + if (apiKey) { + if (segments != null && segments.length > 0) { + addAskTask(ask.id, segments[0], ask.question).catch(console.error) + } + } else { + dispatch(setPage(PAGE_SETTINGS)) + toast.error('需要先设置ApiKey!') + } + }, [addAskTask, ask.id, ask.question, dispatch, envData.aiType, envData.apiKey, envData.geminiApiKey, segments]) + + const onAskFold = useCallback(() => { + dispatch(mergeAskInfo({ + id: ask.id, + fold: !ask.fold + })) + }, [ask, dispatch]) + + const onClose = useCallback(() => { + dispatch(delAskInfo(ask.id)) + }, [ask, dispatch]) + + return
+
+
+ {ask.fold + ? : + } +
+ +
+ + 提问 + +
+
+ {!ask.fold && ask.question &&
{ask.question}
} + {!ask.fold && ask.content && +
+ +
} + {!ask.fold && } + {!ask.fold && ask.error &&
{ask.error}
} +
+} + +export default Ask diff --git a/src/biz/Body.tsx b/src/biz/Body.tsx index af8b4c2..4f5007b 100644 --- a/src/biz/Body.tsx +++ b/src/biz/Body.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react' import { - setAskFold, - setAskQuestion, + addAskInfo, + mergeAskInfo, setAutoScroll, setAutoTranslate, setCheckAutoScroll, @@ -16,9 +16,6 @@ import {useAppDispatch, useAppSelector} from '../hooks/redux' import { AiOutlineAim, AiOutlineCloseCircle, - BsDashSquare, - BsPlusSquare, - FaQuestion, FaRegArrowAltCircleDown, IoWarning, MdExpand, @@ -37,10 +34,10 @@ import { } from '../const' import {FaClipboardList} from 'react-icons/fa' import useTranslate from '../hooks/useTranslate' -import {getSummarize} from '../util/biz_util' import {openUrl} from '@kky002/kky-util' -import Markdown from '../components/Markdown' import useKeyService from '../hooks/useKeyService' +import Ask from './Ask' +import {v4} from 'uuid' const Body = () => { const dispatch = useAppDispatch() @@ -57,20 +54,16 @@ const Body = () => { const summarizeEnable = useAppSelector(state => state.env.envData.summarizeEnable) const {addSummarizeTask, addAskTask} = useTranslate() // const infos = useAppSelector(state => state.env.infos) - const askFold = useAppSelector(state => state.env.askFold) - const askQuestion = useAppSelector(state => state.env.askQuestion) - const askContent = useAppSelector(state => state.env.askContent) - const askStatus = useAppSelector(state => state.env.askStatus) - const askError = useAppSelector(state => state.env.askError) const bodyRef = useRef() const curOffsetTop = useAppSelector(state => state.env.curOffsetTop) const checkAutoScroll = useAppSelector(state => state.env.checkAutoScroll) const needScroll = useAppSelector(state => state.env.needScroll) const totalHeight = useAppSelector(state => state.env.totalHeight) const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType) - const title = useAppSelector(state => state.env.title) - const fontSize = useAppSelector(state => state.env.envData.fontSize) + // const title = useAppSelector(state => state.env.title) + // const fontSize = useAppSelector(state => state.env.envData.fontSize) const searchText = useAppSelector(state => state.env.searchText) + const asks = useAppSelector(state => state.env.asks) // const recommendIdx = useMemo(() => random(0, 3), []) const showSearchInput = useMemo(() => { return (segments != null && segments.length > 0) && (envData.searchEnabled ? envData.searchEnabled : (envData.askEnabled ?? ASK_ENABLED_DEFAULT)) @@ -135,14 +128,19 @@ const Body = () => { const onFoldAll = useCallback(() => { dispatch(setFoldAll(!foldAll)) - dispatch(setAskFold(!foldAll)) + for (const ask of asks) { + dispatch(mergeAskInfo({ + id: ask.id, + fold: !foldAll + })) + } for (const segment of segments ?? []) { dispatch(setSegmentFold({ segmentStartIdx: segment.startIdx, fold: !foldAll })) } - }, [dispatch, foldAll, segments]) + }, [asks, dispatch, foldAll, segments]) const toggleAutoTranslateCallback = useCallback(() => { const apiKey = envData.aiType === 'gemini'?envData.geminiApiKey:envData.apiKey @@ -165,14 +163,14 @@ const Body = () => { } }, [autoScroll, dispatch]) - const onCopy = useCallback(() => { - const [success, content] = getSummarize(title, segments, curSummaryType) - if (success) { - navigator.clipboard.writeText(content).then(() => { - toast.success('复制成功') - }).catch(console.error) - } - }, [curSummaryType, segments, title]) + // const onCopy = useCallback(() => { + // const [success, content] = getSummarize(title, segments, curSummaryType) + // if (success) { + // navigator.clipboard.writeText(content).then(() => { + // toast.success('复制成功') + // }).catch(console.error) + // } + // }, [curSummaryType, segments, title]) const onSearchTextChange = useCallback((e: any) => { const searchText = e.target.value @@ -188,8 +186,14 @@ const Body = () => { const apiKey = envData.aiType === 'gemini'?envData.geminiApiKey:envData.apiKey if (apiKey) { if (segments != null && segments.length > 0) { - dispatch(setAskQuestion(searchText)) - addAskTask(segments[0], searchText).catch(console.error) + const id = v4() + addAskTask(id, segments[0], searchText).catch(console.error) + // 添加ask + dispatch(addAskInfo({ + id, + question: searchText, + status: 'pending', + })) } } else { dispatch(setPage(PAGE_SETTINGS)) @@ -198,14 +202,6 @@ const Body = () => { } }, [addAskTask, dispatch, envData.aiType, envData.apiKey, envData.askEnabled, envData.geminiApiKey, searchText, segments]) - const onSetAsk = useCallback(() => { - dispatch(setSearchText(askQuestion??'')) - }, [askQuestion, dispatch]) - - const onAskFold = useCallback(() => { - dispatch(setAskFold(!askFold)) - }, [askFold, dispatch]) - // service useKeyService() @@ -282,33 +278,8 @@ const Body = () => { height: `${totalHeight - HEADER_HEIGHT - TITLE_HEIGHT - (showSearchInput ? SEARCH_BAR_HEIGHT : 0)}px` }} > - {/* ask */} - {(envData.askEnabled ?? ASK_ENABLED_DEFAULT) && (searchText || askQuestion) && -
-
-
- {askFold - ? : - } -
-
- - 提问 - -
-
- {!askFold && askQuestion && -
{askQuestion}
} - {!askFold && askContent && -
- -
} - {!askFold && } - {!askFold && askStatus === 'init' &&
提问举例:这个视频说了什么
} - {!askFold && askError &&
{askError}
} -
} + {/* asks */} + {asks.map(ask => )} {/* segments */} {segments?.map((segment, segmentIdx) => { } }, [dispatch, envData, summarizeLanguage.name, title]) - const addAskTask = useCallback(async (segment: Segment, question: string) => { + const addAskTask = useCallback(async (id: string, segment: Segment, question: string) => { if (segment.text.length >= SUMMARIZE_THRESHOLD) { let prompt: string = envData.prompts?.[PROMPT_TYPE_ASK]??PROMPT_DEFAULTS[PROMPT_TYPE_ASK] // replace params @@ -243,10 +241,14 @@ const useTranslate = () => { // startIdx: segment.startIdx, apiKey: envData.apiKey, geminiApiKey: envData.geminiApiKey, + askId: id, } } console.debug('addAskTask', taskDef) - dispatch(setAskStatus({status: 'pending'})) + dispatch(mergeAskInfo({ + id, + status: 'pending' + })) const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef}) dispatch(addTaskId(task.id)) } @@ -304,9 +306,13 @@ const useTranslate = () => { }) const handleAsk = useMemoizedFn((task: Task, content?: string) => { - dispatch(setAskContent({content})) - dispatch(setAskStatus({status: 'done'})) - dispatch(setAskError({error: task.error})) + dispatch(mergeAskInfo({ + id: task.def.extra.askId, + content, + status: 'done', + error: task.error, + })) + console.debug('setAsk', content, task.error) }) diff --git a/src/redux/envReducer.ts b/src/redux/envReducer.ts index 1b1014a..92d48e7 100644 --- a/src/redux/envReducer.ts +++ b/src/redux/envReducer.ts @@ -1,5 +1,5 @@ import {createSlice, PayloadAction} from '@reduxjs/toolkit' -import {find} from 'lodash-es' +import {find, findIndex} from 'lodash-es' import {getDevData} from '../util/biz_util' import {DEFAULT_SERVER_URL_OPENAI, TOTAL_HEIGHT_DEF} from '../const' @@ -39,11 +39,7 @@ interface EnvState { lastSummarizeTime?: number // ask - askFold?: boolean - askQuestion?: string - askStatus: SummaryStatus - askError?: string - askContent?: string + asks: AskInfo[] /** * 是否输入中(中文) @@ -66,7 +62,6 @@ const initialState: EnvState = { tempData: { curSummaryType: 'overview', }, - askStatus: 'init', totalHeight: TOTAL_HEIGHT_DEF, autoScroll: true, currentTime: import.meta.env.VITE_ENV === 'web-dev' ? 30 : undefined, @@ -80,6 +75,8 @@ const initialState: EnvState = { searchText: '', searchResult: new Set(), + + asks: [], } export const slice = createSlice({ @@ -211,26 +208,20 @@ export const slice = createSlice({ } } }, - setAskFold: (state, action: PayloadAction) => { - state.askFold = action.payload + addAskInfo: (state, action: PayloadAction) => { + state.asks.push(action.payload) }, - setAskQuestion: (state, action: PayloadAction) => { - state.askQuestion = action.payload + delAskInfo: (state, action: PayloadAction) => { + state.asks = state.asks.filter(ask => ask.id !== action.payload) }, - setAskContent: (state, action: PayloadAction<{ - content?: any - }>) => { - state.askContent = action.payload.content - }, - setAskStatus: (state, action: PayloadAction<{ - status: SummaryStatus - }>) => { - state.askStatus = action.payload.status - }, - setAskError: (state, action: PayloadAction<{ - error?: string - }>) => { - state.askError = action.payload.error + mergeAskInfo: (state, action: PayloadAction) => { + const idx = findIndex(state.asks, {id: action.payload.id}) + if (idx >= 0) { + state.asks[idx] = { + ...state.asks[idx], + ...action.payload, + } + } }, setSegmentFold: (state, action: PayloadAction<{ segmentStartIdx: number @@ -303,11 +294,6 @@ export const slice = createSlice({ export const { setUrl, - setAskFold, - setAskQuestion, - setAskStatus, - setAskError, - setAskContent, setTempReady, setTempData, setUploadedTranscript, @@ -346,6 +332,9 @@ export const { setSearchText, setSearchResult, setInputting, + addAskInfo, + delAskInfo, + mergeAskInfo, } = slice.actions export default slice.reducer diff --git a/src/typings.d.ts b/src/typings.d.ts index 0493b81..806d57e 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -105,6 +105,17 @@ interface Summary { content?: any } +interface AskInfo { + id: string + fold?: boolean + question: string + status: SummaryStatus + error?: string + content?: string +} + +type PartialOfAskInfo = Partial + /** * 概览 */