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
+
+
+ )}
+
+ close()}>
+ Cancel
+
+ {
+ if (!validateInput()) {
+ return
+ }
+ onSave({ site, prompt })
+ .then(() => {
+ setToast({ text: 'New Prompt saved', type: 'success' })
+ close()
+ })
+ .catch(() => {
+ setToast({ text: 'Failed to save prompt', type: 'error' })
+ })
+ }}
+ >
+ Save
+
+
+ )
+}
+
+export default AddNewPromptModal
diff --git a/src/options/App.tsx b/src/options/App.tsx
index 14fb9620..03ab1c32 100644
--- a/src/options/App.tsx
+++ b/src/options/App.tsx
@@ -1,19 +1,12 @@
-import {
- Button,
- CssBaseline,
- GeistProvider,
- Radio,
- Text,
- Textarea,
- Toggle,
- useToasts,
-} from '@geist-ui/core'
+import { Button, CssBaseline, GeistProvider, Radio, Text, Toggle, useToasts } from '@geist-ui/core'
+import { Plus } from '@geist-ui/icons'
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
import '../base.css'
import {
getUserConfig,
Language,
Prompt,
+ SitePrompt,
Theme,
TriggerMode,
TRIGGER_MODE_TEXT,
@@ -21,12 +14,17 @@ import {
} from '../config'
import logo from '../logo.png'
import { detectSystemColorScheme, getExtensionVersion } from '../utils'
+import AddNewPromptModal from './AddNewPromptModal'
+import PromptCard from './PromptCard'
import ProviderSelect from './ProviderSelect'
function OptionsPage(props: { theme: Theme; onThemeChange: (theme: Theme) => void }) {
const [triggerMode, setTriggerMode] = useState(TriggerMode.Always)
const [language, setLanguage] = useState(Language.Auto)
const [prompt, setPrompt] = useState(Prompt)
+ const [promptOverrides, setPromptOverrides] = useState([])
+ const [modalVisible, setModalVisible] = useState(false)
+
const { setToast } = useToasts()
useEffect(() => {
@@ -34,9 +32,14 @@ function OptionsPage(props: { theme: Theme; onThemeChange: (theme: Theme) => voi
setTriggerMode(config.triggerMode)
setLanguage(config.language)
setPrompt(config.prompt)
+ setPromptOverrides(config.promptOverrides)
})
}, [])
+ const closeModalHandler = useCallback(() => {
+ setModalVisible(false)
+ }, [])
+
const onTriggerModeChange = useCallback(
(mode: TriggerMode) => {
setTriggerMode(mode)
@@ -55,15 +58,6 @@ function OptionsPage(props: { theme: Theme; onThemeChange: (theme: Theme) => voi
[props, setToast],
)
- const onPromptChange = useCallback(
- (prompt: string) => {
- setPrompt(prompt)
- updateUserConfig({ prompt })
- setToast({ text: 'Prompt changes saved', type: 'success' })
- },
- [props, setToast],
- )
-
const onLanguageChange = useCallback(
(language: Language) => {
updateUserConfig({ language })
@@ -96,23 +90,64 @@ function OptionsPage(props: { theme: Theme; onThemeChange: (theme: Theme) => voi
-
+
Options
Prompt
-
-