Skip to content

Commit d9bd4bf

Browse files
committed
feat: Independent Panel (#63, #120)
1 parent ad885ab commit d9bd4bf

File tree

20 files changed

+531
-38
lines changed

20 files changed

+531
-38
lines changed

build.mjs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
3232
import: './src/popup/index.jsx',
3333
dependOn: 'shared',
3434
},
35+
IndependentPanel: {
36+
import: './src/pages/IndependentPanel/index.jsx',
37+
dependOn: 'shared',
38+
},
3539
shared: [
3640
'preact',
3741
'webextension-polyfill',
@@ -238,14 +242,21 @@ async function copyFiles(entryPoints, targetDir) {
238242

239243
async function finishOutput(outputDirSuffix) {
240244
const commonFiles = [
245+
{ src: 'src/logo.png', dst: 'logo.png' },
246+
241247
{ src: 'build/shared.js', dst: 'shared.js' },
248+
{ src: 'build/content-script.css', dst: 'content-script.css' }, // shared
249+
242250
{ src: 'build/content-script.js', dst: 'content-script.js' },
243-
{ src: 'build/content-script.css', dst: 'content-script.css' },
251+
244252
{ src: 'build/background.js', dst: 'background.js' },
253+
245254
{ src: 'build/popup.js', dst: 'popup.js' },
246255
{ src: 'build/popup.css', dst: 'popup.css' },
247256
{ src: 'src/popup/index.html', dst: 'popup.html' },
248-
{ src: 'src/logo.png', dst: 'logo.png' },
257+
258+
{ src: 'build/IndependentPanel.js', dst: 'IndependentPanel.js' },
259+
{ src: 'src/pages/IndependentPanel/index.html', dst: 'IndependentPanel.html' },
249260
]
250261

251262
// chromium

src/_locales/en/main.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,10 @@
8787
"Clear Conversation": "Clear Conversation",
8888
"Retry": "Retry",
8989
"Exceeded maximum context length": "Exceeded maximum context length, please clear the conversation and try again",
90-
"Regenerate the answer after switching model": "Regenerate the answer after switching model"
90+
"Regenerate the answer after switching model": "Regenerate the answer after switching model",
91+
"Pin": "Pin",
92+
"Unpin": "Unpin",
93+
"Delete Conversation": "Delete Conversation",
94+
"Clear conversations": "Clear conversations",
95+
"Settings": "Settings"
9196
}

src/_locales/i18n.mjs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,4 @@ import { resources } from './resources'
44
i18n.init({
55
resources,
66
fallbackLng: 'en',
7-
interpolation: {
8-
escapeValue: false, // not needed for react as it escapes by default
9-
},
107
})

src/_locales/zh-hans/main.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,10 @@
8787
"Clear Conversation": "清理对话",
8888
"Retry": "重试",
8989
"Exceeded maximum context length": "超出最大上下文长度, 请清理对话并重试",
90-
"Regenerate the answer after switching model": "快捷切换模型时自动重新生成回答"
90+
"Regenerate the answer after switching model": "快捷切换模型时自动重新生成回答",
91+
"Pin": "固定侧边",
92+
"Unpin": "收缩侧边",
93+
"Delete Conversation": "删除对话",
94+
"Clear conversations": "清空记录",
95+
"Settings": "设置"
9196
}

src/_locales/zh-hant/main.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,10 @@
8787
"Clear Conversation": "清理對話",
8888
"Retry": "重試",
8989
"Exceeded maximum context length": "超出最大上下文長度, 請清理對話並重試",
90-
"Regenerate the answer after switching model": "快捷切換模型時自動重新生成回答"
90+
"Regenerate the answer after switching model": "快捷切換模型時自動重新生成回答",
91+
"Pin": "固定側邊",
92+
"Unpin": "收縮側邊",
93+
"Delete Conversation": "刪除對話",
94+
"Clear conversations": "清空記錄",
95+
"Settings": "設置"
9196
}

src/background/apis/chatgpt-web.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
6666
port.onDisconnect.addListener(() => {
6767
console.debug('port disconnected')
6868
controller.abort()
69-
deleteConversation(accessToken, session.conversationId)
69+
if (session.autoClean) deleteConversation(accessToken, session.conversationId)
7070
})
7171

7272
const models = await getModels(accessToken).catch(() => {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useTranslation } from 'react-i18next'
2+
import { useEffect, useRef, useState } from 'react'
3+
import PropTypes from 'prop-types'
4+
5+
ConfirmButton.propTypes = {
6+
onConfirm: PropTypes.func.isRequired,
7+
text: PropTypes.string.isRequired,
8+
}
9+
10+
function ConfirmButton({ onConfirm, text }) {
11+
const { t } = useTranslation()
12+
const [waitConfirm, setWaitConfirm] = useState(false)
13+
const confirmRef = useRef(null)
14+
15+
useEffect(() => {
16+
if (waitConfirm) confirmRef.current.focus()
17+
}, [waitConfirm])
18+
19+
return (
20+
<span>
21+
<button
22+
ref={confirmRef}
23+
type="button"
24+
className="normal-button"
25+
style={{
26+
...(waitConfirm ? {} : { display: 'none' }),
27+
}}
28+
onBlur={() => {
29+
setWaitConfirm(false)
30+
}}
31+
onClick={() => {
32+
setWaitConfirm(false)
33+
onConfirm()
34+
}}
35+
>
36+
{t('Confirm')}
37+
</button>
38+
<button
39+
type="button"
40+
className="normal-button"
41+
style={{
42+
...(waitConfirm ? { display: 'none' } : {}),
43+
}}
44+
onClick={() => {
45+
setWaitConfirm(true)
46+
}}
47+
>
48+
{text}
49+
</button>
50+
</span>
51+
)
52+
}
53+
54+
export default ConfirmButton

src/components/ConversationCard/index.jsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ function ConversationCard(props) {
6565
const config = useConfig()
6666

6767
useEffect(() => {
68-
if (props.onUpdate) props.onUpdate()
69-
})
68+
if (props.onUpdate) props.onUpdate(port, session, conversationItemData)
69+
}, [session, conversationItemData])
7070

7171
useEffect(() => {
7272
bodyRef.current.scrollTop = bodyRef.current.scrollHeight
@@ -201,6 +201,7 @@ function ConversationCard(props) {
201201
title={t('Close the Window')}
202202
size={16}
203203
onClick={() => {
204+
port.disconnect()
204205
if (props.onClose) props.onClose()
205206
}}
206207
/>
@@ -217,7 +218,7 @@ function ConversationCard(props) {
217218
<img src={logo} style="user-select:none;width:20px;height:20px;" />
218219
)}
219220
<select
220-
style={{ width: windowSize[0] * 0.05 + 'px' }}
221+
style={props.notClampSize ? {} : { width: windowSize[0] * 0.05 + 'px' }}
221222
className="normal-button"
222223
required
223224
onChange={(e) => {
@@ -275,6 +276,7 @@ function ConversationCard(props) {
275276
)}
276277
<DeleteButton
277278
size={16}
279+
text={t('Clear Conversation')}
278280
onConfirm={() => {
279281
port.postMessage({ stop: true })
280282
Browser.runtime.sendMessage({
@@ -309,7 +311,11 @@ function ConversationCard(props) {
309311
<div
310312
ref={bodyRef}
311313
className="markdown-body"
312-
style={{ maxHeight: windowSize[1] * 0.75 + 'px' }}
314+
style={
315+
props.notClampSize
316+
? { flexGrow: 1 }
317+
: { maxHeight: windowSize[1] * 0.75 + 'px', resize: 'vertical' }
318+
}
313319
>
314320
{conversationItemData.map((data, idx) => (
315321
<ConversationItem
@@ -356,6 +362,7 @@ ConversationCard.propTypes = {
356362
onClose: PropTypes.func,
357363
dockable: PropTypes.bool,
358364
onDock: PropTypes.func,
365+
notClampSize: PropTypes.bool,
359366
}
360367

361368
export default memo(ConversationCard)

src/components/DecisionCard/index.jsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,10 @@ function DecisionCard(props) {
8080
return <ConversationCard session={props.session} question={question} />
8181
}
8282
return (
83-
<p
84-
className="gpt-inner manual-btn icon-and-text"
85-
onClick={() => setTriggered(true)}
86-
>
87-
<SearchIcon size="small" /> {t('Ask ChatGPT')}
83+
<p className="gpt-inner manual-btn" onClick={() => setTriggered(true)}>
84+
<span className="icon-and-text">
85+
<SearchIcon size="small" /> {t('Ask ChatGPT')}
86+
</span>
8887
</p>
8988
)
9089
case 'questionMark':
@@ -95,18 +94,19 @@ function DecisionCard(props) {
9594
return <ConversationCard session={props.session} question={question} />
9695
}
9796
return (
98-
<p
99-
className="gpt-inner manual-btn icon-and-text"
100-
onClick={() => setTriggered(true)}
101-
>
102-
<SearchIcon size="small" /> {t('Ask ChatGPT')}
97+
<p className="gpt-inner manual-btn" onClick={() => setTriggered(true)}>
98+
<span className="icon-and-text">
99+
<SearchIcon size="small" /> {t('Ask ChatGPT')}
100+
</span>
103101
</p>
104102
)
105103
}
106104
else
107105
return (
108-
<p className="gpt-inner icon-and-text">
109-
<LightBulbIcon size="small" /> {t('No Input Found')}
106+
<p className="gpt-inner">
107+
<span className="icon-and-text">
108+
<LightBulbIcon size="small" /> {t('No Input Found')}
109+
</span>
110110
</p>
111111
)
112112
})()}

src/components/DeleteButton/index.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { TrashIcon } from '@primer/octicons-react'
66
DeleteButton.propTypes = {
77
onConfirm: PropTypes.func.isRequired,
88
size: PropTypes.number.isRequired,
9+
text: PropTypes.string.isRequired,
910
}
1011

11-
function DeleteButton({ onConfirm, size }) {
12+
function DeleteButton({ onConfirm, size, text }) {
1213
const { t } = useTranslation()
1314
const [waitConfirm, setWaitConfirm] = useState(false)
1415
const confirmRef = useRef(null)
@@ -38,7 +39,7 @@ function DeleteButton({ onConfirm, size }) {
3839
{t('Confirm')}
3940
</button>
4041
<span
41-
title={t('Clear Conversation')}
42+
title={text}
4243
className="gpt-util-icon"
4344
style={waitConfirm ? { display: 'none' } : {}}
4445
onClick={() => {

0 commit comments

Comments
 (0)