Skip to content

Commit

Permalink
feat: float/split staticCard
Browse files Browse the repository at this point in the history
  • Loading branch information
josStorer committed Mar 14, 2023
1 parent 316d257 commit e180ddf
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 23 deletions.
100 changes: 84 additions & 16 deletions src/components/ConversationCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,75 @@ import PropTypes from 'prop-types'
import Browser from 'webextension-polyfill'
import InputBox from '../InputBox'
import ConversationItem from '../ConversationItem'
import { initSession, isSafari } from '../../utils'
import { createElementAtPosition, initSession, isSafari } from '../../utils'
import { DownloadIcon } from '@primer/octicons-react'
import { WindowDesktop, XLg } from 'react-bootstrap-icons'
import FileSaver from 'file-saver'
import { render } from 'preact'
import FloatingToolbar from '../FloatingToolbar'

const logo = Browser.runtime.getURL('logo.png')

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

function ConversationCard(props) {
const [isReady, setIsReady] = useState(!props.question)
const [port, setPort] = useState(() => Browser.runtime.connect())
const [session, setSession] = useState(props.session)
/**
* @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())
const [session, setSession] = useState(props.session)
const [conversationItemData, setConversationItemData] = useState(
(() => {
if (props.session.conversationRecords.length === 0)
if (props.question)
return [
new ConversationItemData(
'answer',
'<p class="gpt-loading">Waiting for response...</p>',
),
]
else return []
else {
const ret = []
for (const record of props.session.conversationRecords) {
ret.push(
new ConversationItemData('question', record.question + '\n<hr/>', props.session, true),
)
ret.push(
new ConversationItemData('answer', record.answer + '\n<hr/>', props.session, true),
)
}
return ret
}
})(),
)

useEffect(() => {
if (props.onUpdate) props.onUpdate()
})

useEffect(() => {
// when the page is responsive, session may accumulate redundant data and needs to be cleared after remounting and before making a new request
const newSession = initSession({ question: props.question })
setSession(newSession)
port.postMessage({ session: newSession })
if (props.question) {
const newSession = initSession({ question: props.question })
setSession(newSession)
port.postMessage({ session: newSession })
}
}, [props.question]) // usually only triggered once

/**
Expand Down Expand Up @@ -128,8 +157,45 @@ function ConversationCard(props) {
return (
<div className="gpt-inner">
<div className="gpt-header">
<img src={logo} width="20" height="20" style="margin:5px 15px 0px;" />
{props.showDragbar && <div className="dragbar" />}
{!props.closeable ? (
<img src={logo} width="20" height="20" style="margin:5px 15px 0px;user-select:none;" />
) : (
<XLg
className="gpt-util-icon"
style="margin:5px 15px 0px;"
title="Close the Window"
size={16}
onClick={() => {
if (props.onClose) props.onClose()
}}
/>
)}
{props.draggable ? (
<div className="dragbar" />
) : (
<WindowDesktop
className="gpt-util-icon"
title="Float the Window"
size={16}
onClick={() => {
const position = { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 }
const toolbarContainer = createElementAtPosition(position.x, position.y)
toolbarContainer.className = 'toolbar-container-not-queryable'
render(
<FloatingToolbar
session={session}
selection=""
position={position}
container={toolbarContainer}
closeable={true}
triggered={true}
onClose={() => toolbarContainer.remove()}
/>,
toolbarContainer,
)
}}
/>
)}
<span
title="Save Conversation"
className="gpt-util-icon"
Expand Down Expand Up @@ -186,7 +252,9 @@ ConversationCard.propTypes = {
session: PropTypes.object.isRequired,
question: PropTypes.string.isRequired,
onUpdate: PropTypes.func,
showDragbar: PropTypes.bool,
draggable: PropTypes.bool,
closeable: PropTypes.bool,
onClose: PropTypes.func,
}

export default memo(ConversationCard)
17 changes: 11 additions & 6 deletions src/components/FloatingToolbar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import ConversationCard from '../ConversationCard'
import PropTypes from 'prop-types'
import { defaultConfig, getUserConfig } from '../../config.mjs'
import { config as toolsConfig } from '../../content-script/selection-tools'
import { initSession, setElementPositionInViewport } from '../../utils'
import { setElementPositionInViewport } from '../../utils'
import Draggable from 'react-draggable'

const logo = Browser.runtime.getURL('logo.png')

function FloatingToolbar(props) {
const [prompt, setPrompt] = useState('')
const [triggered, setTriggered] = useState(false)
const [triggered, setTriggered] = useState(props.triggered)
const [config, setConfig] = useState(defaultConfig)
const [render, setRender] = useState(false)
const [position, setPosition] = useState(props.position)
const [virtualPosition, setVirtualPosition] = useState({ x: 0, y: 0 })
const [session] = useState(initSession())

useEffect(() => {
getUserConfig()
Expand Down Expand Up @@ -72,9 +71,11 @@ function FloatingToolbar(props) {
<div className="gpt-selection-window">
<div className="chat-gpt-container">
<ConversationCard
session={session}
session={props.session}
question={prompt}
showDragbar={true}
draggable={true}
closeable={props.closeable}
onClose={props.onClose}
onUpdate={() => {
updatePosition()
}}
Expand Down Expand Up @@ -105,7 +106,7 @@ function FloatingToolbar(props) {
return (
<div data-theme={config.themeMode}>
<div className="gpt-selection-toolbar">
<img src={logo} width="24" height="24" />
<img src={logo} width="24" height="24" style="user-select:none;" />
{tools}
</div>
</div>
Expand All @@ -114,9 +115,13 @@ function FloatingToolbar(props) {
}

FloatingToolbar.propTypes = {
session: PropTypes.object.isRequired,
selection: PropTypes.string.isRequired,
position: PropTypes.object.isRequired,
container: PropTypes.object.isRequired,
triggered: PropTypes.bool,
closeable: PropTypes.bool,
onClose: PropTypes.func,
}

export default FloatingToolbar
1 change: 1 addition & 0 deletions src/content-script/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ async function prepareForSelectionTools() {
toolbarContainer.className = 'toolbar-container'
render(
<FloatingToolbar
session={initSession()}
selection={selection}
position={position}
container={toolbarContainer}
Expand Down
2 changes: 1 addition & 1 deletion src/content-script/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
}

.gpt-selection-toolbar-button {
margin-left: 5px;
margin-left: 2px;
padding: 2px;
border-radius: 30px;
background-color: #ffffff;
Expand Down

0 comments on commit e180ddf

Please sign in to comment.