Skip to content

Commit

Permalink
refactor: adjust directory structure
Browse files Browse the repository at this point in the history
  • Loading branch information
josStorer committed Mar 9, 2023
1 parent ebb65e1 commit 4c8ae1f
Show file tree
Hide file tree
Showing 25 changed files with 380 additions and 350 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scripts/verify-search-engine-configs.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JSDOM } from 'jsdom'
import { config } from '../../../src/content-script/search-engine-configs.mjs'
import { config } from '../../../src/content-script/site-adapters'
import fetch, { Headers } from 'node-fetch'

const urls = {
Expand Down
15 changes: 11 additions & 4 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ async function runWebpack(isWithoutKatex, callback) {
}),
...(isWithoutKatex
? [
new webpack.NormalModuleReplacementPlugin(
/markdown\.jsx/,
'./markdown-without-katex.jsx',
),
new webpack.NormalModuleReplacementPlugin(/markdown\.jsx/, (result) => {
if (result.request) {
result.request = result.request.replace(
'markdown.jsx',
'markdown-without-katex.jsx',
)
}
}),
]
: []),
],
Expand All @@ -82,6 +86,9 @@ async function runWebpack(isWithoutKatex, callback) {
{
test: /\.m?jsx?$/,
exclude: /(node_modules)/,
resolve: {
fullySpecified: false,
},
use: [
{
loader: 'babel-loader',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// web version

import { fetchSSE } from './fetch-sse.mjs'
import { fetchSSE } from '../../utils'
import { isEmpty } from 'lodash-es'
import { chatgptWebModelKeys, Models } from '../config.js'
import { chatgptWebModelKeys, Models } from '../../config'

async function request(token, method, path, data) {
const response = await fetch(`https://chat.openai.com/backend-api${path}`, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// api version

import { Models } from '../config.js'
import { fetchSSE } from './fetch-sse.mjs'
import { Models } from '../../config'
import { fetchSSE, getConversationPairs } from '../../utils'
import { isEmpty } from 'lodash-es'
import { getChatPairs } from '../utils.mjs'

const chatgptPromptBase =
`You are a helpful, creative, clever, and very friendly assistant.` +
Expand Down Expand Up @@ -37,7 +36,9 @@ export async function generateAnswersWithGptCompletionApi(
})

const prompt =
gptPromptBase + getChatPairs(session.conversationRecords, false) + `Human:${question}\nAI:`
gptPromptBase +
getConversationPairs(session.conversationRecords, false) +
`Human:${question}\nAI:`

let answer = ''
await fetchSSE('https://api.openai.com/v1/completions', {
Expand Down Expand Up @@ -97,7 +98,7 @@ export async function generateAnswersWithChatgptApi(port, question, session, api
controller.abort()
})

const prompt = getChatPairs(session.conversationRecords, true)
const prompt = getConversationPairs(session.conversationRecords, true)
prompt.unshift({ role: 'system', content: chatgptPromptBase })
prompt.push({ role: 'user', content: question })

Expand Down
16 changes: 8 additions & 8 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { v4 as uuidv4 } from 'uuid'
import Browser from 'webextension-polyfill'
import { generateAnswersWithChatgptWebApi, sendMessageFeedback } from './chatgpt-web.mjs'
import ExpiryMap from 'expiry-map'
import { generateAnswersWithChatgptWebApi, sendMessageFeedback } from './apis/chatgpt-web'
import {
generateAnswersWithChatgptApi,
generateAnswersWithGptCompletionApi,
} from './apis/openai-api'
import {
chatgptApiModelKeys,
chatgptWebModelKeys,
getUserConfig,
gptApiModelKeys,
isUsingApiKey,
} from '../config.js'
import {
generateAnswersWithChatgptApi,
generateAnswersWithGptCompletionApi,
} from './openai-api.mjs'
import ExpiryMap from 'expiry-map'
import { isSafari } from '../utils.mjs'
} from '../config'
import { isSafari } from '../utils'

const KEY_ACCESS_TOKEN = 'accessToken'
const cache = new ExpiryMap(10 * 1000)
Expand Down
155 changes: 155 additions & 0 deletions src/components/ConversationCardForSearch/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { memo, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import Browser from 'webextension-polyfill'
import InputBox from '../InputBox'
import ConversationItem from '../ConversationItem'
import { isSafari } from '../../utils'

class ConversationItemData extends Object {
/**
* @param {'question'|'answer'|'error'} type
* @param {string} content
*/
constructor(type, content) {
super()
this.type = type
this.content = content
this.session = null
this.done = false
}
}

function ConversationCardForSearch(props) {
/**
* @type {[ConversationItemData[], (conversationItemData: ConversationItemData[]) => void]}
*/
const [conversationItemData, setConversationItemData] = useState([
new ConversationItemData('answer', '<p class="gpt-loading">Waiting for response...</p>'),
])
const [isReady, setIsReady] = useState(false)
const [port, setPort] = useState(() => Browser.runtime.connect())

useEffect(() => {
window.session.question = props.question
port.postMessage({ session: window.session })
}, [props.question]) // usually only triggered once

/**
* @param {string} value
* @param {boolean} appended
* @param {'question'|'answer'|'error'} newType
* @param {boolean} done
*/
const UpdateAnswer = (value, appended, newType, done = false) => {
setConversationItemData((old) => {
const copy = [...old]
const index = copy.findLastIndex((v) => v.type === 'answer')
if (index === -1) return copy
copy[index] = new ConversationItemData(
newType,
appended ? copy[index].content + value : value,
)
copy[index].session = { ...window.session }
copy[index].done = done
return copy
})
}

useEffect(() => {
const listener = () => {
setPort(Browser.runtime.connect())
}
port.onDisconnect.addListener(listener)
return () => {
port.onDisconnect.removeListener(listener)
}
}, [port])
useEffect(() => {
const listener = (msg) => {
if (msg.answer) {
UpdateAnswer(msg.answer, false, 'answer')
}
if (msg.session) {
window.session = msg.session
}
if (msg.done) {
UpdateAnswer('\n<hr>', true, 'answer', true)
setIsReady(true)
}
if (msg.error) {
switch (msg.error) {
case 'UNAUTHORIZED':
UpdateAnswer(
`UNAUTHORIZED<br>Please login at https://chat.openai.com first${
isSafari() ? '<br>Then open https://chat.openai.com/api/auth/session' : ''
}<br>And refresh this page or type you question again`,
false,
'error',
)
break
case 'CLOUDFLARE':
UpdateAnswer(
`OpenAI Security Check Required<br>Please open ${
isSafari() ? 'https://chat.openai.com/api/auth/session' : 'https://chat.openai.com'
}<br>And refresh this page or type you question again`,
false,
'error',
)
break
default:
setConversationItemData([
...conversationItemData,
new ConversationItemData('error', msg.error + '\n<hr>'),
])
break
}
setIsReady(true)
}
}
port.onMessage.addListener(listener)
return () => {
port.onMessage.removeListener(listener)
}
}, [conversationItemData])

return (
<div className="gpt-inner">
<div className="markdown-body">
{conversationItemData.map((data, idx) => (
<ConversationItem
content={data.content}
key={idx}
type={data.type}
session={data.session}
done={data.done}
/>
))}
</div>
<InputBox
enabled={isReady}
onSubmit={(question) => {
const newQuestion = new ConversationItemData('question', '**You:**\n' + question)
const newAnswer = new ConversationItemData(
'answer',
'<p class="gpt-loading">Waiting for response...</p>',
)
setConversationItemData([...conversationItemData, newQuestion, newAnswer])
setIsReady(false)

window.session.question = question
try {
port.postMessage({ session: window.session })
} catch (e) {
UpdateAnswer(e, false, 'error')
}
}}
/>
</div>
)
}

ConversationCardForSearch.propTypes = {
question: PropTypes.string.isRequired,
}

export default memo(ConversationCardForSearch)
59 changes: 59 additions & 0 deletions src/components/ConversationItem/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useState } from 'react'
import FeedbackForChatGPTWeb from '../FeedbackForChatGPTWeb'
import { ChevronDownIcon, LinkExternalIcon, XCircleIcon } from '@primer/octicons-react'
import CopyButton from '../CopyButton'
import PropTypes from 'prop-types'
import MarkdownRender from '../MarkdownRender/markdown.jsx'

export function ConversationItem({ type, content, session, done }) {
const [collapsed, setCollapsed] = useState(false)

return (
<div className={type} dir="auto">
{type === 'answer' && (
<div className="gpt-header">
<p>{session ? 'ChatGPT:' : 'Loading...'}</p>
<div style="display: flex; gap: 15px;">
{done && !session.useApiKey && (
<FeedbackForChatGPTWeb
messageId={session.messageId}
conversationId={session.conversationId}
/>
)}
{session && session.conversationId && !session.useApiKey && (
<a
title="Continue on official website"
href={'https://chat.openai.com/chat/' + session.conversationId}
target="_blank"
rel="nofollow noopener noreferrer"
style="color: inherit;"
>
<LinkExternalIcon size={14} />
</a>
)}
{session && <CopyButton contentFn={() => content} size={14} />}
{!collapsed ? (
<span title="Collapse" className="gpt-util-icon" onClick={() => setCollapsed(true)}>
<XCircleIcon size={14} />
</span>
) : (
<span title="Expand" className="gpt-util-icon" onClick={() => setCollapsed(false)}>
<ChevronDownIcon size={14} />
</span>
)}
</div>
</div>
)}
{!collapsed && <MarkdownRender>{content}</MarkdownRender>}
</div>
)
}

ConversationItem.propTypes = {
type: PropTypes.oneOf(['question', 'answer', 'error']).isRequired,
content: PropTypes.string.isRequired,
session: PropTypes.object.isRequired,
done: PropTypes.bool.isRequired,
}

export default ConversationItem
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { LightBulbIcon, SearchIcon } from '@primer/octicons-react'
import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import ChatGPTQuery from './ChatGPTQuery'
import { getPossibleElementByQuerySelector, endsWithQuestionMark } from '../utils.mjs'
import { defaultConfig, getUserConfig } from '../config'
import ConversationCardForSearch from '../ConversationCardForSearch'
import { defaultConfig, getUserConfig } from '../../config'
import Browser from 'webextension-polyfill'
import { getPossibleElementByQuerySelector, endsWithQuestionMark } from '../../utils'

function ChatGPTCard(props) {
function DecisionCardForSearch(props) {
const [triggered, setTriggered] = useState(false)
const [config, setConfig] = useState(defaultConfig)
const [render, setRender] = useState(false)
Expand Down Expand Up @@ -92,10 +92,10 @@ function ChatGPTCard(props) {
if (question)
switch (config.triggerMode) {
case 'always':
return <ChatGPTQuery question={question} />
return <ConversationCardForSearch question={question} />
case 'manually':
if (triggered) {
return <ChatGPTQuery question={question} />
return <ConversationCardForSearch question={question} />
}
return (
<p
Expand All @@ -107,7 +107,7 @@ function ChatGPTCard(props) {
)
case 'questionMark':
if (endsWithQuestionMark(question.trim())) {
return <ChatGPTQuery question={question} />
return <ConversationCardForSearch question={question} />
}
return (
<p className="gpt-inner icon-and-text">
Expand All @@ -129,10 +129,10 @@ function ChatGPTCard(props) {
)
}

ChatGPTCard.propTypes = {
DecisionCardForSearch.propTypes = {
question: PropTypes.string.isRequired,
siteConfig: PropTypes.object.isRequired,
container: PropTypes.object.isRequired,
}

export default ChatGPTCard
export default DecisionCardForSearch
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { memo, useCallback, useState } from 'react'
import { ThumbsupIcon, ThumbsdownIcon } from '@primer/octicons-react'
import Browser from 'webextension-polyfill'

const ChatGPTFeedback = (props) => {
const FeedbackForChatGPTWeb = (props) => {
const [action, setAction] = useState(null)

const clickThumbsUp = useCallback(async () => {
Expand Down Expand Up @@ -56,9 +56,9 @@ const ChatGPTFeedback = (props) => {
)
}

ChatGPTFeedback.propTypes = {
FeedbackForChatGPTWeb.propTypes = {
messageId: PropTypes.string.isRequired,
conversationId: PropTypes.string.isRequired,
}

export default memo(ChatGPTFeedback)
export default memo(FeedbackForChatGPTWeb)
Loading

0 comments on commit 4c8ae1f

Please sign in to comment.