├── .gitignore ├── LICENSE ├── README.md ├── build.mjs ├── googlechatgpt-current-chrome.zip ├── package-lock.json ├── package.json ├── src ├── _locales │ ├── de │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── ko │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ └── zh_CN │ │ └── messages.json ├── assets │ └── icons │ │ ├── icon128.png │ │ ├── icon16.png │ │ └── icon48.png ├── background │ └── bg.ts ├── components │ ├── dropdown.tsx │ ├── errorMessage.tsx │ ├── footer.tsx │ ├── navBar.tsx │ ├── promptEditor.tsx │ ├── socialIconButton.tsx │ ├── toolbar.tsx │ └── tooltipWrapper.tsx ├── content-scripts │ ├── api.ts │ └── mainUI.tsx ├── declaration.d.ts ├── manifest.json ├── manifest.v2.json ├── options │ ├── options.html │ └── options.tsx ├── style │ └── base.css └── util │ ├── createShadowRoot.ts │ ├── elementFinder.ts │ ├── icons.tsx │ ├── localization.ts │ ├── localizedStrings.json │ ├── promptManager.ts │ ├── regionOptions.json │ ├── timePeriodOptions.json │ └── userConfig.ts ├── tailwind.config.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build/* 3 | !build/webchatgpt*.zip 4 | /*.log 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 qunash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [link-chrome]: https://chrome.google.com/webstore/detail/googlechatgpt-chatgpt-wit/mdonbhpnpdajiekihkjeneenjhmeipam?hl=en&authuser=0 'Chrome Web Store' 2 | 3 | [Chrome][link-chrome] 4 | # GoogleChatGPT 5 | google_chatgpt 6 | ss 7 | 8 | 9 | 10 | GoogleChatGPT is an innovative and user-friendly chrome extension that combines the power of Google web search with the knowledge of ChatGPT. The extension provides up-to-date information on a variety of topics, making it the perfect tool for anyone seeking quick and accurate information. 11 | 12 | Inspired by qunash/chatgpt-advanced, GoogleChatGPT offers a unique and interactive experience, allowing users to engage in a dialogue with the extension to find the information they need. This dialogue format makes it possible for GoogleChatGPT to answer follow-up questions, admit its mistakes, challenge incorrect premises, and reject inappropriate requests. 13 | 14 | To get started with GoogleChatGPT, simply install the extension in your Chrome browser and start searching. Whether you're looking for information on a specific topic, need help with a project, or just want to learn something new, GoogleChatGPT has got you covered. 15 | 16 | So why wait? Try GoogleChatGPT today and experience the power of combined knowledge! 17 | 18 | ## Key Features 19 | * Combines the power of Google web search with the knowledge of ChatGPT 20 | * Provides up-to-date information on a variety of topics 21 | * Easy to use, simply install and start searching 22 | * Inspired by qunash/chatgpt-advanced 23 | 24 | 25 | ## Chrome Installation 26 | 1. Download prebuilt chrome zip file: `googlechatgpt-current-chrome.zip` 27 | 2. Unzip the file. 28 | 3. Open `chrome://extensions` in Chrome. 29 | 4. Enable developer mode (top right corner). 30 | 5. Click on `Load unpacked` and select the unzipped folder. 31 | 6. Go to [ChatGPT](https://chat.openai.com/chat/) and enjoy! 32 | 33 | ## Build from source 34 | 35 | 1. `git clone https://github.com/hunkim/chatgpt-with-google.git` 36 | 2. `npm install` 37 | 3. `npm run build-prod` 38 | 4. Open `chrome://extensions` in Chrome. 39 | 5. Enable developer mode (top right corner). 40 | 6. Click on `Load unpacked` and select the `build` folder. 41 | 42 | 43 | ## Contributing 44 | 45 | Contributions are welcome! Please submit pull requests. 46 | 47 | ## Credit 48 | This project is forked from qunash/chatgpt-advanced. 49 | -------------------------------------------------------------------------------- /build.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import archiver from "archiver"; 3 | import fs from "fs-extra"; 4 | import tailwindcss from "tailwindcss"; 5 | import autoprefixer from "autoprefixer"; 6 | import postcssPlugin from "esbuild-style-plugin"; 7 | import copyStaticFilesPlugin from "esbuild-copy-files-plugin"; 8 | import path from 'path'; 9 | 10 | 11 | const buildDir = "build"; 12 | const minify = process.argv.includes("--minify"); 13 | 14 | async function createBuildDir() { 15 | if(!fs.existsSync(buildDir)) { 16 | fs.mkdirSync(buildDir); 17 | } 18 | } 19 | 20 | async function cleanBuildDir() { 21 | const entries = await fs.readdir(buildDir); 22 | for (const entry of entries) { 23 | if (path.extname(entry) === ".zip") continue; 24 | await fs.remove(`${buildDir}/${entry}`); 25 | } 26 | } 27 | 28 | async function runEsbuild() { 29 | await esbuild.build({ 30 | entryPoints: [ 31 | "src/content-scripts/mainUI.tsx", 32 | "src/background/bg.ts", 33 | "src/options/options.tsx", 34 | ], 35 | outdir: buildDir, 36 | bundle: true, 37 | minify: minify, 38 | treeShaking: true, 39 | define: { 40 | "process.env.NODE_ENV": '"production"', 41 | }, 42 | jsxFactory: "h", 43 | jsxFragment: "Fragment", 44 | jsx: "automatic", 45 | loader: { 46 | ".png": "dataurl", 47 | }, 48 | plugins: [ 49 | postcssPlugin({ 50 | postcss: { 51 | plugins: [tailwindcss, autoprefixer], 52 | }, 53 | }), 54 | copyStaticFilesPlugin({ 55 | source: ["src/manifest.json", "src/assets/"], 56 | target: buildDir, 57 | copyWithFolder: false, 58 | }), 59 | copyStaticFilesPlugin({ 60 | source: ["src/options/options.html"], 61 | target: buildDir + "/options", 62 | copyWithFolder: false, 63 | }), 64 | copyStaticFilesPlugin({ 65 | source: ["src/_locales/"], 66 | target: buildDir, 67 | copyWithFolder: true, 68 | }), 69 | ], 70 | }); 71 | } 72 | 73 | async function zipExtensionForBrowser(browser) { 74 | const manifest = await fs.readJson(`${buildDir}/manifest.json`); 75 | const version = manifest.version; 76 | let archiveName = `build/googlechatgpt-${version}-${browser}.zip`; 77 | 78 | const archive = archiver("zip", { zlib: { level: 9 } }); 79 | const stream = fs.createWriteStream(archiveName); 80 | 81 | archive.pipe(stream); 82 | 83 | await addFilesToZip(archive, browser); 84 | 85 | console.log(`Creating ${archiveName}…`); 86 | archive.finalize(); 87 | } 88 | 89 | async function addFilesToZip(archive, browser) { 90 | const entries = await fs.readdir("build"); 91 | for (const entry of entries) { 92 | const entryStat = await fs.stat(`build/${entry}`); 93 | 94 | if (entryStat.isDirectory()) { 95 | archive.directory(`build/${entry}`, entry); 96 | } else { 97 | if (path.extname(entry) === ".zip") continue; 98 | if (entry === "manifest.json") continue; 99 | archive.file(`build/${entry}`, { name: entry }); 100 | } 101 | } 102 | if (browser === "firefox") { 103 | archive.file("src/manifest.v2.json", { name: "manifest.json" }); 104 | } else if (browser === "chrome") { 105 | archive.file("build/manifest.json", { name: "manifest.json" }); 106 | } 107 | } 108 | 109 | async function build() { 110 | await createBuildDir(); 111 | await cleanBuildDir(); 112 | await runEsbuild(); 113 | 114 | const createZips = process.argv.includes("--create-zips"); 115 | if (createZips) { 116 | try { 117 | await zipExtensionForBrowser("chrome"); 118 | await zipExtensionForBrowser("firefox"); 119 | } catch (error) { 120 | console.error(error); 121 | } 122 | } 123 | 124 | console.log("Build complete"); 125 | } 126 | 127 | build(); 128 | -------------------------------------------------------------------------------- /googlechatgpt-current-chrome.zip: -------------------------------------------------------------------------------- 1 | build/googlechatgpt-2023.02.04-chrome.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "webchatgpt", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "build-dev": "node build.mjs", 8 | "build-prod": "node build.mjs --create-zips", 9 | "build-prod-min": "node build.mjs --create-zips --minify", 10 | "watch": "chokidar src -c \"npm run build-dev\"" 11 | }, 12 | "eslintConfig": { 13 | "parser": "@typescript-eslint/parser", 14 | "extends": [ 15 | "preact", 16 | "plugin:@typescript-eslint/recommended" 17 | ], 18 | "ignorePatterns": [ 19 | "build/" 20 | ] 21 | }, 22 | "dependencies": { 23 | "lodash-es": "^4.17.21", 24 | "preact": "^10.10.0", 25 | "uuid": "^9.0.0" 26 | }, 27 | "devDependencies": { 28 | "@types/lodash-es": "^4.17.6", 29 | "@types/uuid": "^9.0.0", 30 | "@types/webextension-polyfill": "^0.10.0", 31 | "@typescript-eslint/eslint-plugin": "^5.30.6", 32 | "@typescript-eslint/parser": "^5.30.6", 33 | "archiver": "^5.3.1", 34 | "autoprefixer": "^10.4.13", 35 | "chokidar-cli": "^3.0.0", 36 | "daisyui": "^2.47.0", 37 | "esbuild": "^0.16.17", 38 | "esbuild-copy-files-plugin": "^1.1.0", 39 | "esbuild-style-plugin": "^1.6.1", 40 | "eslint": "^8.20.0", 41 | "eslint-config-preact": "^1.3.0", 42 | "fs-extra": "^11.1.0", 43 | "preact-cli": "^3.4.0", 44 | "tailwindcss": "^3.2.4", 45 | "typescript": "^4.5.2", 46 | "webextension-polyfill": "^0.10.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "GoogleChatGPT: ChatGPT mit Internetzugang" 4 | }, 5 | "appDesc": { 6 | "message": "Erweitern Sie Ihre ChatGPT-Prompts mit relevanten Ergebnissen aus dem Web." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "GoogleChatGPT: ChatGPT with Google" 4 | }, 5 | "appDesc": { 6 | "message": "Augment your ChatGPT prompts with relevant results from Google." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT: ChatGPT con acceso a internet" 4 | }, 5 | "appDesc": { 6 | "message": "Aumente sus prompts ChatGPT con resultados relevantes de la web." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT: ChatGPT avec accès à Internet" 4 | }, 5 | "appDesc": { 6 | "message": "Augmentez vos prompts ChatGPT avec des résultats pertinents provenant du Web." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT: ChatGPT con accesso a Internet" 4 | }, 5 | "appDesc": { 6 | "message": "Migliora i tuoi prompt di ChatGPT con risultati pertinenti dal web." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT: インターネットにアクセスできる ChatGPT" 4 | }, 5 | "appDesc": { 6 | "message": "Webから関連する結果を使用して、ChatGPTのプロンプトを拡張します。" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "GoogleChatGPT: ChatGPT와 구글을 함께 사용" 4 | }, 5 | "appDesc": { 6 | "message": "웹에서 관련 결과를 사용하여 ChatGPT 프롬프트를 향상시킵니다." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT: ChatGPT com acesso à internet" 4 | }, 5 | "appDesc": { 6 | "message": "Aumente seus prompts do ChatGPT com resultados relevantes da web." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "WebChatGPT:可访问互联网的 ChatGPT" 4 | }, 5 | "appDesc": { 6 | "message": "使用来自网络的相关结果增强您的 ChatGPT 提示。" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunkim/chatgpt-with-google/9485748cebecd43c49229c93ae2b7b0387aaa2a7/src/assets/icons/icon128.png -------------------------------------------------------------------------------- /src/assets/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunkim/chatgpt-with-google/9485748cebecd43c49229c93ae2b7b0387aaa2a7/src/assets/icons/icon16.png -------------------------------------------------------------------------------- /src/assets/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunkim/chatgpt-with-google/9485748cebecd43c49229c93ae2b7b0387aaa2a7/src/assets/icons/icon48.png -------------------------------------------------------------------------------- /src/background/bg.ts: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | 3 | 4 | const manifest_version = Browser.runtime.getManifest().manifest_version 5 | 6 | 7 | Browser.runtime.onInstalled.addListener(async () => openChatGPTWebpage()) 8 | 9 | function openChatGPTWebpage() { 10 | Browser.tabs.create({ 11 | url: "https://chat.openai.com/chat", 12 | }) 13 | } 14 | 15 | // open chatgpt webpage when extension icon is clicked 16 | if (manifest_version == 2) { 17 | Browser.browserAction.onClicked.addListener(openChatGPTWebpage) 18 | } else { 19 | Browser.action.onClicked.addListener(openChatGPTWebpage) 20 | } 21 | 22 | 23 | Browser.runtime.onMessage.addListener((request) => { 24 | if (request === "show_options") { 25 | Browser.runtime.openOptionsPage() 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /src/components/dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { h, JSX } from "preact" 2 | 3 | function Dropdown(props: { 4 | value: string | number 5 | onChange: (e: any) => void 6 | options: Array<{ value: any; label: string }> 7 | onClick?: (e: any) => void 8 | }): JSX.Element { 9 | 10 | return ( 11 | 20 | ) 21 | } 22 | 23 | export default Dropdown 24 | -------------------------------------------------------------------------------- /src/components/errorMessage.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import { useEffect, useState } from 'preact/hooks' 3 | 4 | 5 | function ErrorMessage({ message }) { 6 | const [show, setShow] = useState(true) 7 | 8 | useEffect(() => { 9 | const timer = setTimeout(() => { 10 | setShow(false) 11 | }, 10000) 12 | return () => clearTimeout(timer) 13 | }, []) 14 | 15 | return show && ( 16 | //
17 |
18 | An error occurred
19 | {message}

20 | Check the console for more details. (Ctrl+Shift+J) 21 |
22 | ) 23 | } 24 | 25 | export default ErrorMessage 26 | -------------------------------------------------------------------------------- /src/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact' 2 | import Browser from 'webextension-polyfill' 3 | 4 | function Footer() { 5 | const extension_version = Browser.runtime.getManifest().version 6 | 7 | return ( 8 |
9 | 10 | GoogleChatGPT extension v.{extension_version} 11 | . 12 | If you like the extension, please consider supporting GoogleChatGPT. 13 |
14 | ) 15 | } 16 | 17 | export default Footer 18 | -------------------------------------------------------------------------------- /src/components/navBar.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import { useCallback, useEffect, useState } from 'preact/hooks' 3 | import { icons } from 'src/util/icons' 4 | import { getTranslation, Languages, localizationKeys } from 'src/util/localization' 5 | import Browser from 'webextension-polyfill' 6 | import IconButton from './socialIconButton' 7 | import TooltipWrapper from './tooltipWrapper' 8 | 9 | 10 | const NavBar = ( 11 | props: { 12 | language: string, 13 | onLanguageChange: (language: string) => void, 14 | } 15 | ) => { 16 | 17 | const version = Browser.runtime.getManifest().version 18 | 19 | return (
20 |
21 | 22 | GoogleChatGPT 23 | {version} 24 |
25 |
26 |
27 | 28 |
29 |
) 30 | } 31 | 32 | // 33 | // 34 | // 35 | export default NavBar 36 | -------------------------------------------------------------------------------- /src/components/promptEditor.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import { useState, useEffect, useRef, useLayoutEffect } from 'preact/hooks' 3 | import { getTranslation, localizationKeys } from 'src/util/localization' 4 | import { deletePrompt, getDefaultPrompt, getSavedPrompts, Prompt, savePrompt } from 'src/util/promptManager' 5 | import TooltipWrapper from './tooltipWrapper' 6 | 7 | const PromptEditor = ( 8 | props: { 9 | language: string 10 | } 11 | ) => { 12 | const [savedPrompts, setSavedPrompts] = useState([]) 13 | const [prompt, setPrompt] = useState(getDefaultPrompt()) 14 | const [hasWebResultsPlaceholder, setHasWebResultsPlaceholder] = useState(false) 15 | const [hasQueryPlaceholder, setHasQueryPlaceholder] = useState(false) 16 | const [deleteBtnText, setDeleteBtnText] = useState("delete") 17 | 18 | const [showErrors, setShowErrors] = useState(false) 19 | const [nameError, setNameError] = useState(false) 20 | const [textError, setTextError] = useState(false) 21 | const [webResultsError, setWebResultsError] = useState(false) 22 | const [queryError, setQueryError] = useState(false) 23 | 24 | useLayoutEffect(() => { 25 | updateSavedPrompts() 26 | }, []) 27 | 28 | const updateSavedPrompts = async () => { 29 | let prompts = await getSavedPrompts() 30 | setSavedPrompts(prompts) 31 | if (prompt.uuid === 'default') { 32 | setPrompt(prompts[0]) 33 | } 34 | } 35 | 36 | useEffect(() => { 37 | updateSavedPrompts() 38 | }, [props.language]) 39 | 40 | useEffect(() => { 41 | updatePlaceholderButtons(prompt.text) 42 | }, [prompt]) 43 | 44 | useEffect(() => { 45 | setNameError(prompt.name.trim() === '') 46 | setTextError(prompt.text.trim() === '') 47 | setWebResultsError(!prompt.text.includes('{web_results}')) 48 | setQueryError(!prompt.text.includes('{query}')) 49 | }, [prompt]) 50 | 51 | async function updateList() { 52 | getSavedPrompts().then(sp => { 53 | setSavedPrompts(sp) 54 | }) 55 | } 56 | 57 | const handleSelect = (prompt: Prompt) => { 58 | setShowErrors(false) 59 | setPrompt(prompt) 60 | setDeleteBtnText("delete") 61 | } 62 | 63 | 64 | const handleAdd = () => { 65 | setShowErrors(false) 66 | setPrompt({ name: '', text: '' }) 67 | setDeleteBtnText("delete") 68 | if (nameInputRef.current) { 69 | nameInputRef.current.focus() 70 | } 71 | } 72 | 73 | const handleSave = async () => { 74 | setShowErrors(true) 75 | if (nameError || textError || webResultsError || queryError) { 76 | return 77 | } 78 | 79 | await savePrompt(prompt) 80 | await updateList() 81 | } 82 | 83 | const handleDeleteBtnClick = () => { 84 | if (deleteBtnText === "delete") { 85 | setDeleteBtnText("check"); 86 | } else { 87 | handleDelete(); 88 | } 89 | } 90 | 91 | const handleDelete = async () => { 92 | await deletePrompt(prompt) 93 | updateList() 94 | handleAdd() 95 | } 96 | 97 | 98 | const nameInputRef = useRef(null) 99 | const textareaRef = useRef(null) 100 | 101 | const handleInsertText = (text: string) => { 102 | if (textareaRef.current) { 103 | const start = textareaRef.current.selectionStart 104 | const end = textareaRef.current.selectionEnd 105 | const currentText = textareaRef.current.value 106 | const newText = currentText.substring(0, start) + text + currentText.substring(end, currentText.length) 107 | textareaRef.current.setSelectionRange(start + text.length, start + text.length) 108 | textareaRef.current.focus() 109 | 110 | setPrompt({ ...prompt, text: newText }) 111 | } 112 | } 113 | 114 | const handleTextareaChange = (e: Event) => { 115 | let text = (e.target as HTMLTextAreaElement).value 116 | if (text !== prompt.text) { 117 | setTextError(false) 118 | setPrompt({ ...prompt, text: text }) 119 | } 120 | } 121 | 122 | const updatePlaceholderButtons = (text: string) => { 123 | setHasWebResultsPlaceholder(text.includes("{web_results}")) 124 | setHasQueryPlaceholder(text.includes("{query}")) 125 | } 126 | 127 | const actionToolbar = ( 128 |
131 |
132 | 133 | 144 | 145 | 146 | 157 | 158 | 159 | 165 | 166 |
167 | 168 | 174 |
175 | ) 176 | 177 | const PromptList = ( 178 |
179 | 193 |
194 | ) 195 | 196 | const nameInput = ( 197 | { 205 | setNameError(false) 206 | setPrompt({ ...prompt, name: (e.target as HTMLInputElement).value }) 207 | }} 208 | disabled={prompt.uuid === 'default' || prompt.uuid === 'default_en'} 209 | /> 210 | ) 211 | 212 | const btnDelete = ( 213 | 223 | ) 224 | 225 | const textArea = ( 226 |