diff --git a/package-lock.json b/package-lock.json index 527d9afa..e5ca74e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "chat-gpt-google-extension", "dependencies": { "@geist-ui/core": "^2.3.8", + "@geist-ui/icons": "^1.0.2", "@primer/octicons-react": "^17.9.0", "eventsource-parser": "^0.0.5", "expiry-map": "^2.0.0", @@ -451,6 +452,15 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@geist-ui/icons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@geist-ui/icons/-/icons-1.0.2.tgz", + "integrity": "sha512-Npfa0NW6fQ31qw/+iMPWbs1hAcJ/3FqBjSLYgEfITDqy/3TJFpFKeVyK04AC/hTmYTsdNruVYczqPNcham5FOQ==", + "peerDependencies": { + "@geist-ui/core": ">=1.0.0", + "react": ">=16.13.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -6744,6 +6754,12 @@ "@babel/runtime": "^7.16.7" } }, + "@geist-ui/icons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@geist-ui/icons/-/icons-1.0.2.tgz", + "integrity": "sha512-Npfa0NW6fQ31qw/+iMPWbs1hAcJ/3FqBjSLYgEfITDqy/3TJFpFKeVyK04AC/hTmYTsdNruVYczqPNcham5FOQ==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", diff --git a/package.json b/package.json index a5185c52..9c682d54 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@geist-ui/core": "^2.3.8", + "@geist-ui/icons": "^1.0.2", "@primer/octicons-react": "^17.9.0", "eventsource-parser": "^0.0.5", "expiry-map": "^2.0.0", diff --git a/src/config.ts b/src/config.ts index 160a245d..95f508db 100644 --- a/src/config.ts +++ b/src/config.ts @@ -46,21 +46,12 @@ export interface SitePrompt { prompt: string } -export interface AllPrompt { - default: string - sites: SitePrompt[] -} - -const promptDefaultValue = { - default: Prompt, - sites: [], -} - const userConfigWithDefaultValue = { triggerMode: TriggerMode.Always, theme: Theme.Auto, language: Language.Auto, prompt: Prompt, + promptOverrides: [] as SitePrompt[], } export type UserConfig = typeof userConfigWithDefaultValue diff --git a/src/content-script/ChatGPTCard.tsx b/src/content-script/ChatGPTCard.tsx index 7414c58a..99c9b4a3 100644 --- a/src/content-script/ChatGPTCard.tsx +++ b/src/content-script/ChatGPTCard.tsx @@ -5,6 +5,7 @@ import ChatGPTQuery, { QueryStatus } from './ChatGPTQuery' interface Props { question: string + promptSource: string triggerMode: TriggerMode onStatusChange?: (status: QueryStatus) => void } @@ -13,10 +14,10 @@ function ChatGPTCard(props: Props) { const [triggered, setTriggered] = useState(false) if (props.triggerMode === TriggerMode.Always) { - return + return } if (triggered) { - return + return } return (

setTriggered(true)}> diff --git a/src/content-script/ChatGPTContainer.tsx b/src/content-script/ChatGPTContainer.tsx index 8d27ad30..390215dc 100644 --- a/src/content-script/ChatGPTContainer.tsx +++ b/src/content-script/ChatGPTContainer.tsx @@ -7,6 +7,7 @@ import { QueryStatus } from './ChatGPTQuery' interface Props { question: string + promptSource: string triggerMode: TriggerMode } @@ -22,6 +23,7 @@ function ChatGPTContainer(props: Props) {

diff --git a/src/content-script/ChatGPTQuery.tsx b/src/content-script/ChatGPTQuery.tsx index 3cdf0fb7..dd064a43 100644 --- a/src/content-script/ChatGPTQuery.tsx +++ b/src/content-script/ChatGPTQuery.tsx @@ -13,6 +13,7 @@ export type QueryStatus = 'success' | 'error' | undefined interface Props { question: string + promptSource: string onStatusChange?: (status: QueryStatus) => void } @@ -85,6 +86,7 @@ function ChatGPTQuery(props: Props) { + {`"${props.promptSource}" prompt is used`} , + , container, ) } @@ -58,7 +62,13 @@ async function run() { console.log('Body: ' + bodyInnerText) const userConfig = await getUserConfig() - mount(userConfig.prompt + bodyInnerText, siteConfig) + const found = userConfig.promptOverrides.find( + (override) => new URL(override.site).hostname === location.hostname, + ) + const question = found?.prompt ?? userConfig.prompt + const promptSource = found?.site ?? 'default' + + mount(question + bodyInnerText, promptSource, siteConfig) } } } diff --git a/src/content-script/utils.ts b/src/content-script/utils.ts index 8f727cf6..b4aa95b4 100644 --- a/src/content-script/utils.ts +++ b/src/content-script/utils.ts @@ -32,3 +32,13 @@ export async function shouldShowRatingTip() { await Browser.storage.local.set({ ratingTipShowTimes: ratingTipShowTimes + 1 }) return ratingTipShowTimes >= 2 } + +export function isValidHttpUrl(string: string) { + let url + try { + url = new URL(string) + } catch (_) { + return false + } + return url.protocol === 'http:' || url.protocol === 'https:' +} diff --git a/src/options/AddNewPromptModal.tsx b/src/options/AddNewPromptModal.tsx new file mode 100644 index 00000000..e938329a --- /dev/null +++ b/src/options/AddNewPromptModal.tsx @@ -0,0 +1,93 @@ +import { Input, Modal, Text, Textarea, useToasts } from '@geist-ui/core' +import { useState } from 'preact/hooks' +import { isValidHttpUrl } from '../content-script/utils' + +function AddNewPromptModal(props: { + visible: boolean + onClose: () => void + onSave: (newOverride: { site: string; prompt: string }) => Promise +}) { + const { visible, onClose, onSave } = props + const [site, setSite] = useState('') + const [siteError, setSiteError] = useState(false) + const [prompt, setPrompt] = useState('') + const [promptError, setPromptError] = useState(false) + const { setToast } = useToasts() + + function validateInput() { + const isSiteValid = isValidHttpUrl(site) + setSiteError(!isSiteValid) + if (!isSiteValid) { + return false + } + const isPromptValid = prompt.trim().length > 0 + setPromptError(!isPromptValid) + return isPromptValid + } + + function close() { + setSite('') + setSiteError(false) + setPrompt('') + setPromptError(false) + onClose() + } + + return ( + + Add New Prompt + + setSite(e.target.value)} + > + {siteError && ( + + Site is not valid + + )} + + {promptError && ( +
+ + Prompt cannot be empty + +
+ )} + - - + { + const newOverride: SitePrompt = { + site, + prompt, + } + const newOverrides = promptOverrides.concat([newOverride]) + setPromptOverrides(newOverrides) + return updateUserConfig({ promptOverrides: newOverrides }) + }} + /> + + Trigger Mode diff --git a/src/options/PromptCard.tsx b/src/options/PromptCard.tsx new file mode 100644 index 00000000..93fea457 --- /dev/null +++ b/src/options/PromptCard.tsx @@ -0,0 +1,88 @@ +import { Button, Card, Divider, Grid, Text, Textarea, useToasts } from '@geist-ui/core' +import Trash2 from '@geist-ui/icons/trash2' +import { useCallback, useState } from 'preact/hooks' + +function PromptCard(props: { + header: string + prompt: string + onSave: (newPrompt: string) => Promise + onDismiss?: () => Promise +}) { + const { header, prompt, onSave, onDismiss } = props + const [value, setValue] = useState(prompt) + const { setToast } = useToasts() + + const onClickSave = useCallback( + (prompt: string) => { + setValue(prompt) + onSave(prompt) + .then(() => { + setToast({ text: 'Prompt changes saved', type: 'success' }) + }) + .catch(() => { + setToast({ text: 'Failed to save prompt', type: 'error' }) + }) + }, + [onSave, setToast], + ) + + return ( + + + + + + {header} + + + {onDismiss && ( + + + + + ) +} + +export default PromptCard