diff --git a/src/_locales/en/main.json b/src/_locales/en/main.json index 39cd9a1..ed02837 100644 --- a/src/_locales/en/main.json +++ b/src/_locales/en/main.json @@ -84,5 +84,6 @@ "Loading...": "Loading...", "Feedback": "Feedback", "Confirm": "Confirm", - "Clear Conversation": "Clear Conversation" + "Clear Conversation": "Clear Conversation", + "Retry": "Retry" } diff --git a/src/_locales/zh-hans/main.json b/src/_locales/zh-hans/main.json index b82acb0..030e321 100644 --- a/src/_locales/zh-hans/main.json +++ b/src/_locales/zh-hans/main.json @@ -84,5 +84,6 @@ "Loading...": "正在读取...", "Feedback": "反馈", "Confirm": "确认", - "Clear Conversation": "清理对话" + "Clear Conversation": "清理对话", + "Retry": "重试" } diff --git a/src/_locales/zh-hant/main.json b/src/_locales/zh-hant/main.json index 49aea85..42333f7 100644 --- a/src/_locales/zh-hant/main.json +++ b/src/_locales/zh-hant/main.json @@ -84,5 +84,6 @@ "Loading...": "正在讀取...", "Feedback": "反饋", "Confirm": "確認", - "Clear Conversation": "清理對話" + "Clear Conversation": "清理對話", + "Retry": "重試" } diff --git a/src/background/apis/bing-web.mjs b/src/background/apis/bing-web.mjs index 763e7c6..a8239d0 100644 --- a/src/background/apis/bing-web.mjs +++ b/src/background/apis/bing-web.mjs @@ -1,5 +1,6 @@ import BingAIClient from '../clients/BingAIClient' import { getUserConfig } from '../../config/index.mjs' +import { pushRecord } from './shared.mjs' /** * @param {Runtime.Port} port @@ -63,7 +64,7 @@ export async function generateAnswersWithBingWebApi( session.bingWeb.clientId = response.clientId session.bingWeb.invocationId = response.invocationId - session.conversationRecords.push({ question: question, answer: answer }) + pushRecord(session, question, answer) console.debug('conversation history', { content: session.conversationRecords }) port.onMessage.removeListener(stopListener) port.postMessage({ answer: answer, done: true, session: session }) diff --git a/src/background/apis/chatgpt-web.mjs b/src/background/apis/chatgpt-web.mjs index 8dd8631..cfaf840 100644 --- a/src/background/apis/chatgpt-web.mjs +++ b/src/background/apis/chatgpt-web.mjs @@ -3,6 +3,7 @@ import { fetchSSE } from '../../utils/fetch-sse' import { isEmpty } from 'lodash-es' import { chatgptWebModelKeys, getUserConfig, Models } from '../../config/index.mjs' +import { pushRecord } from './shared.mjs' async function request(token, method, path, data) { const apiUrl = (await getUserConfig()).customChatGptWebApiUrl @@ -105,7 +106,7 @@ export async function generateAnswersWithChatgptWebApi(port, question, session, onMessage(message) { console.debug('sse message', message) if (message === '[DONE]') { - session.conversationRecords.push({ question: question, answer: answer }) + pushRecord(session, question, answer) console.debug('conversation history', { content: session.conversationRecords }) port.postMessage({ answer: null, done: true, session: session }) return diff --git a/src/background/apis/custom-api.mjs b/src/background/apis/custom-api.mjs index 7e2e689..18df6e4 100644 --- a/src/background/apis/custom-api.mjs +++ b/src/background/apis/custom-api.mjs @@ -9,6 +9,7 @@ import { getUserConfig, maxResponseTokenLength } from '../../config/index.mjs' import { fetchSSE } from '../../utils/fetch-sse' import { getConversationPairs } from '../../utils/get-conversation-pairs' import { isEmpty } from 'lodash-es' +import { pushRecord } from './shared.mjs' const getCustomApiPromptBase = async () => { return `I am a helpful, creative, clever, and very friendly assistant. I am familiar with various languages in the world.` @@ -59,7 +60,7 @@ export async function generateAnswersWithCustomApi(port, question, session, apiK onMessage(message) { console.debug('sse message', message) if (message === '[DONE]') { - session.conversationRecords.push({ question: question, answer: answer }) + pushRecord(session, question, answer) console.debug('conversation history', { content: session.conversationRecords }) port.postMessage({ answer: null, done: true, session: session }) return diff --git a/src/background/apis/openai-api.mjs b/src/background/apis/openai-api.mjs index 1d2cbff..3079d31 100644 --- a/src/background/apis/openai-api.mjs +++ b/src/background/apis/openai-api.mjs @@ -4,6 +4,7 @@ import { maxResponseTokenLength, Models, getUserConfig } from '../../config/inde import { fetchSSE } from '../../utils/fetch-sse' import { getConversationPairs } from '../../utils/get-conversation-pairs' import { isEmpty } from 'lodash-es' +import { pushRecord } from './shared.mjs' const getChatgptPromptBase = async () => { return `You are a helpful, creative, clever, and very friendly assistant. You are familiar with various languages in the world.` @@ -72,7 +73,7 @@ export async function generateAnswersWithGptCompletionApi( onMessage(message) { console.debug('sse message', message) if (message === '[DONE]') { - session.conversationRecords.push({ question: question, answer: answer }) + pushRecord(session, question, answer) console.debug('conversation history', { content: session.conversationRecords }) port.postMessage({ answer: null, done: true, session: session }) return @@ -148,7 +149,7 @@ export async function generateAnswersWithChatgptApi(port, question, session, api onMessage(message) { console.debug('sse message', message) if (message === '[DONE]') { - session.conversationRecords.push({ question: question, answer: answer }) + pushRecord(session, question, answer) console.debug('conversation history', { content: session.conversationRecords }) port.postMessage({ answer: null, done: true, session: session }) return diff --git a/src/background/apis/shared.mjs b/src/background/apis/shared.mjs new file mode 100644 index 0000000..8519851 --- /dev/null +++ b/src/background/apis/shared.mjs @@ -0,0 +1,8 @@ +export function pushRecord(session, question, answer) { + const recordLength = session.conversationRecords.length + let lastRecord + if (recordLength > 0) lastRecord = session.conversationRecords[recordLength - 1] + + if (session.isRetry && lastRecord && lastRecord.question === question) lastRecord.answer = answer + else session.conversationRecords.push({ question: question, answer: answer }) +} diff --git a/src/components/ConversationCard/index.jsx b/src/components/ConversationCard/index.jsx index 566678a..09a7c0d 100644 --- a/src/components/ConversationCard/index.jsx +++ b/src/components/ConversationCard/index.jsx @@ -4,7 +4,7 @@ import Browser from 'webextension-polyfill' import InputBox from '../InputBox' import ConversationItem from '../ConversationItem' import { createElementAtPosition, initSession, isSafari } from '../../utils' -import { DownloadIcon } from '@primer/octicons-react' +import { DownloadIcon, LinkExternalIcon } from '@primer/octicons-react' import { WindowDesktop, XLg, Pin } from 'react-bootstrap-icons' import FileSaver from 'file-saver' import { render } from 'preact' @@ -94,7 +94,7 @@ function ConversationCard(props) { * @param {'question'|'answer'|'error'} newType * @param {boolean} done */ - const UpdateAnswer = (value, appended, newType, done = false) => { + const updateAnswer = (value, appended, newType, done = false) => { setConversationItemData((old) => { const copy = [...old] const index = copy.findLastIndex((v) => v.type === 'answer') @@ -121,19 +121,20 @@ function ConversationCard(props) { useEffect(() => { const listener = (msg) => { if (msg.answer) { - UpdateAnswer(msg.answer, false, 'answer') + updateAnswer(msg.answer, false, 'answer') } if (msg.session) { + if (msg.done) msg.session = { ...msg.session, isRetry: false } setSession(msg.session) } if (msg.done) { - UpdateAnswer('\n
', true, 'answer', true) + updateAnswer('\n
', true, 'answer', true) setIsReady(true) } if (msg.error) { switch (msg.error) { case 'UNAUTHORIZED': - UpdateAnswer( + updateAnswer( `${t('UNAUTHORIZED')}
${t('Please login at https://chat.openai.com first')}${ isSafari() ? `
${t('Then open https://chat.openai.com/api/auth/session')}` : '' }
${t('And refresh this page or type you question again')}` + @@ -145,7 +146,7 @@ function ConversationCard(props) { ) break case 'CLOUDFLARE': - UpdateAnswer( + updateAnswer( `${t('OpenAI Security Check Required')}
${ isSafari() ? t('Please open https://chat.openai.com/api/auth/session') @@ -159,10 +160,7 @@ function ConversationCard(props) { ) break default: - setConversationItemData([ - ...conversationItemData, - new ConversationItemData('error', msg.error + '\n
'), - ]) + updateAnswer(msg.error + '\n
', false, 'error') break } setIsReady(true) @@ -223,6 +221,18 @@ function ConversationCard(props) { /> )} + {session && session.conversationId && ( + + + + )} { @@ -269,6 +279,27 @@ function ConversationCard(props) { session={session} done={data.done} port={port} + onRetry={ + idx === conversationItemData.length - 1 + ? () => { + updateAnswer( + `

${t('Waiting for response...')}

`, + false, + 'answer', + ) + setIsReady(false) + + const newSession = { ...session, isRetry: true } + setSession(newSession) + try { + port.postMessage({ stop: true }) + port.postMessage({ session: newSession }) + } catch (e) { + updateAnswer(e, false, 'error') + } + } + : null + } /> ))} @@ -283,12 +314,12 @@ function ConversationCard(props) { setConversationItemData([...conversationItemData, newQuestion, newAnswer]) setIsReady(false) - const newSession = { ...session, question } + const newSession = { ...session, question, isRetry: false } setSession(newSession) try { port.postMessage({ session: newSession }) } catch (e) { - UpdateAnswer(e, false, 'error') + updateAnswer(e, false, 'error') } }} /> diff --git a/src/components/ConversationItem/index.jsx b/src/components/ConversationItem/index.jsx index fbb9d00..58ba34e 100644 --- a/src/components/ConversationItem/index.jsx +++ b/src/components/ConversationItem/index.jsx @@ -1,12 +1,11 @@ import { useState } from 'react' -import FeedbackForChatGPTWeb from '../FeedbackForChatGPTWeb' -import { ChevronDownIcon, LinkExternalIcon, XCircleIcon } from '@primer/octicons-react' +import { ChevronDownIcon, XCircleIcon, SyncIcon } from '@primer/octicons-react' import CopyButton from '../CopyButton' import PropTypes from 'prop-types' import MarkdownRender from '../MarkdownRender/markdown.jsx' import { useTranslation } from 'react-i18next' -export function ConversationItem({ type, content, session, done, port }) { +export function ConversationItem({ type, content, session, done, port, onRetry }) { const { t } = useTranslation() const [collapsed, setCollapsed] = useState(false) @@ -59,23 +58,10 @@ export function ConversationItem({ type, content, session, done, port }) { {t('Stop')} )} - {done && session && session.conversationId && ( - - )} - {session && session.conversationId && ( - - - + {onRetry && ( + + + )} {session && content} size={14} />} {!collapsed ? ( @@ -106,6 +92,11 @@ export function ConversationItem({ type, content, session, done, port }) {

{t('Error')}:

+ {onRetry && ( + + + + )} content} size={14} /> {!collapsed ? (