mirror of
https://github.com/wassname/chatGPTBox.git
synced 2026-07-15 11:22:23 +08:00
This commit is contained in:
@@ -38,7 +38,10 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
'@primer/octicons-react',
|
||||
'react-bootstrap-icons',
|
||||
'countries-list',
|
||||
'i18next',
|
||||
'react-i18next',
|
||||
'./src/utils',
|
||||
'./src/_locales/i18n',
|
||||
],
|
||||
},
|
||||
output: {
|
||||
|
||||
Generated
+48
-3
@@ -15,6 +15,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"gpt-3-encoder": "^1.1.4",
|
||||
"i18next": "^22.4.13",
|
||||
"katex": "^0.16.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parse5": "^6.0.1",
|
||||
@@ -24,6 +25,7 @@
|
||||
"react-bootstrap-icons": "^1.10.2",
|
||||
"react-dom": "npm:@preact/compat@^17.1.2",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-tabs": "^4.2.1",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
@@ -1666,7 +1668,6 @@
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
@@ -4766,6 +4767,14 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
@@ -4798,6 +4807,14 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "22.4.13",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-22.4.13.tgz",
|
||||
"integrity": "sha512-GX7flMHRRqQA0I1yGLmaZ4Hwt1JfLqagk8QPDPZsqekbKtXsuIngSVWM/s3SLgNkrEXjA+0sMGNuOEkkmyqmWg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.6"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -7576,6 +7593,27 @@
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-12.2.0.tgz",
|
||||
"integrity": "sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 19.0.0",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -7701,8 +7739,7 @@
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.1",
|
||||
@@ -8900,6 +8937,14 @@
|
||||
"unist-util-stringify-position": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"gpt-3-encoder": "^1.1.4",
|
||||
"i18next": "^22.4.13",
|
||||
"katex": "^0.16.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parse5": "^6.0.1",
|
||||
@@ -37,6 +38,7 @@
|
||||
"react-bootstrap-icons": "^1.10.2",
|
||||
"react-dom": "npm:@preact/compat@^17.1.2",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-tabs": "^4.2.1",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"General": "General",
|
||||
"Selection Tools": "Selection Tools",
|
||||
"Sites": "Sites",
|
||||
"Advanced": "Advanced",
|
||||
"Donate": "Donate",
|
||||
"Triggers": "Triggers",
|
||||
"Theme": "Theme",
|
||||
"API Mode": "API Mode",
|
||||
"Get": "Get",
|
||||
"Balance": "Balance",
|
||||
"Preferred Language": "Preferred Language",
|
||||
"Insert ChatGPT at the top of search results": "Insert ChatGPT at the top of search results",
|
||||
"Lock scrollbar while answering": "Lock scrollbar while answering",
|
||||
"Current Version": "Current Version",
|
||||
"Latest": "Latest",
|
||||
"Help | Changelog ": "Help | Changelog ",
|
||||
"Custom ChatGPT Web API Url": "Custom ChatGPT Web API Url",
|
||||
"Custom ChatGPT Web API Path": "Custom ChatGPT Web API Path",
|
||||
"Custom OpenAI API Url": "Custom OpenAI API Url",
|
||||
"Custom Site Regex:": "Custom Site Regex:",
|
||||
"Exclusively use Custom Site Regex for website matching,": "Exclusively use Custom Site Regex for website matching,",
|
||||
"ignoring built-in rules": "ignoring built-in rules",
|
||||
"Input Query:": "Input Query:",
|
||||
"Append Query:": "Append Query:",
|
||||
"Prepend Query:": "Prepend Query:",
|
||||
"Wechat Pay": "Wechat Pay",
|
||||
"Type your question here\nEnter to send, shift + enter to break line": "Type your question here\nEnter to send, shift + enter to break line",
|
||||
"Wait for the answer to finish and then continue here": "Wait for the answer to finish and then continue here",
|
||||
"Ask ChatGPT": "Ask ChatGPT",
|
||||
"No Input Found": "No Input Found",
|
||||
"You:": "You:",
|
||||
"Collapse": "Collapse",
|
||||
"Expand": "Expand",
|
||||
"Stop": "Stop",
|
||||
"Continue on official website": "Continue on official website",
|
||||
"Error:": "Error:",
|
||||
"Copy": "Copy",
|
||||
"Question:": "Question:",
|
||||
"Answer:": "Answer:",
|
||||
"Waiting for response...": "Waiting for response...",
|
||||
"Close the Window": "Close the Window",
|
||||
"Pin the Window": "Pin the Window",
|
||||
"Float the Window": "Float the Window",
|
||||
"Save Conversation": "Save Conversation",
|
||||
"UNAUTHORIZED": "UNAUTHORIZED",
|
||||
"Please login at https://chat.openai.com first": "Please login at https://chat.openai.com first",
|
||||
"Then open https://chat.openai.com/api/auth/session": "Then open https://chat.openai.com/api/auth/session",
|
||||
"And refresh this page or type you question again": "And refresh this page or type you question again",
|
||||
"Consider creating an api key at https://platform.openai.com/account/api-keys": "Consider creating an api key at https://platform.openai.com/account/api-keys",
|
||||
"OpenAI Security Check Required": "OpenAI Security Check Required",
|
||||
"Please open https://chat.openai.com/api/auth/session": "Please open https://chat.openai.com/api/auth/session",
|
||||
"Please open https://chat.openai.com": "Please open https://chat.openai.com",
|
||||
"New Chat": "New Chat",
|
||||
"Summarize Page": "Summarize Page",
|
||||
"Translate": "Translate",
|
||||
"Translate (Bidirectional)": "Translate (Bidirectional)",
|
||||
"Summary": "Summary",
|
||||
"Polish": "Polish",
|
||||
"Sentiment Analysis": "Sentiment Analysis",
|
||||
"Divide Paragraphs": "Divide Paragraphs",
|
||||
"Code Explain": "Code Explain",
|
||||
"Ask": "Ask",
|
||||
"Always": "Always",
|
||||
"Manually": "Manually",
|
||||
"When query ends with question mark (?)": "When query ends with question mark (?)",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark",
|
||||
"Auto": "Auto",
|
||||
"ChatGPT (Web)": "ChatGPT (Web)",
|
||||
"ChatGPT (Web, GPT-4)": "ChatGPT (Web, GPT-4)",
|
||||
"Bing (Web, GPT-4)": "Bing (Web, GPT-4)",
|
||||
"ChatGPT (GPT-3.5-turbo)": "ChatGPT (GPT-3.5-turbo)",
|
||||
"ChatGPT (GPT-4-8k)": "ChatGPT (GPT-4-8k)",
|
||||
"ChatGPT (GPT-4-32k)": "ChatGPT (GPT-4-32k)",
|
||||
"GPT-3.5": "GPT-3.5",
|
||||
"Custom Model": "Custom Model",
|
||||
"Balanced": "Balanced",
|
||||
"Creative": "Creative",
|
||||
"Precise": "Precise",
|
||||
"Fast": "Fast",
|
||||
"API Key": "API Key",
|
||||
"Model Name": "Model Name",
|
||||
"Custom Model API Url": "Custom Model API Url",
|
||||
"Loading...": "Loading...",
|
||||
"Feedback": "Feedback"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
import { resources } from './resources'
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import en from './en/main.json'
|
||||
import zhHans from './zh-hans/main.json'
|
||||
import zhHant from './zh-hant/main.json'
|
||||
|
||||
export const resources = {
|
||||
en: {
|
||||
translation: en,
|
||||
},
|
||||
zh: {
|
||||
translation: zhHans,
|
||||
},
|
||||
zhHant: {
|
||||
translation: zhHant,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"General": "常规",
|
||||
"Selection Tools": "选择浮动工具",
|
||||
"Sites": "站点适配",
|
||||
"Advanced": "高级",
|
||||
"Donate": "打赏",
|
||||
"Triggers": "触发方式",
|
||||
"Theme": "主题",
|
||||
"API Mode": "API模式",
|
||||
"Get": "获取",
|
||||
"Balance": "余额",
|
||||
"Preferred Language": "语言偏好",
|
||||
"Insert ChatGPT at the top of search results": "将对话卡片插入到搜索结果顶部",
|
||||
"Lock scrollbar while answering": "回答时锁定滚动条",
|
||||
"Current Version": "当前版本",
|
||||
"Latest": "最新",
|
||||
"Help | Changelog ": "帮助 | 更新日志 ",
|
||||
"Custom ChatGPT Web API Url": "自定义的ChatGPT网页API地址",
|
||||
"Custom ChatGPT Web API Path": "自定义的ChatGPT网页API路径",
|
||||
"Custom OpenAI API Url": "自定义的OpenAI API地址",
|
||||
"Custom Site Regex:": "自定义站点正则匹配:",
|
||||
"Exclusively use Custom Site Regex for website matching,": "只使用自定义站点正则匹配,",
|
||||
"ignoring built-in rules": "忽略内置站点规则",
|
||||
"Input Query:": "输入的查询选择器:",
|
||||
"Append Query:": "挂载到末尾的查询选择器:",
|
||||
"Prepend Query:": "插入到开头的查询选择器:",
|
||||
"Wechat Pay": "微信打赏",
|
||||
"Type your question here\nEnter to send, shift + enter to break line": "在此输入你的问题\n回车 发送, shift+回车 换行",
|
||||
"Wait for the answer to finish and then continue here": "等待回答完成, 然后在此继续",
|
||||
"Ask ChatGPT": "询问ChatGPT",
|
||||
"No Input Found": "无输入",
|
||||
"You:": "你:",
|
||||
"Collapse": "折叠",
|
||||
"Expand": "展开",
|
||||
"Stop": "停止",
|
||||
"Continue on official website": "在官网继续",
|
||||
"Error:": "错误:",
|
||||
"Copy": "复制",
|
||||
"Question:": "问题:",
|
||||
"Answer:": "回答:",
|
||||
"Waiting for response...": "等待响应...",
|
||||
"Close the Window": "关闭窗口",
|
||||
"Pin the Window": "固定窗口",
|
||||
"Float the Window": "浮出/分裂窗口",
|
||||
"Save Conversation": "保存对话",
|
||||
"UNAUTHORIZED": "未授权",
|
||||
"Please login at https://chat.openai.com first": "请先登录 https://chat.openai.com",
|
||||
"Then open https://chat.openai.com/api/auth/session": "然后打开 https://chat.openai.com/api/auth/session",
|
||||
"And refresh this page or type you question again": "之后刷新页面或重新输入你的问题",
|
||||
"Consider creating an api key at https://platform.openai.com/account/api-keys": "考虑在 https://platform.openai.com/account/api-keys 创建一个API Key",
|
||||
"OpenAI Security Check Required": "需要通过OpenAI的安全检查",
|
||||
"Please open https://chat.openai.com/api/auth/session": "请打开 https://chat.openai.com/api/auth/session",
|
||||
"Please open https://chat.openai.com": "请打开 https://chat.openai.com",
|
||||
"New Chat": "新建聊天",
|
||||
"Summarize Page": "总结本页",
|
||||
"Translate": "翻译",
|
||||
"Translate (Bidirectional)": "双向翻译",
|
||||
"Summary": "总结",
|
||||
"Polish": "润色",
|
||||
"Sentiment Analysis": "情感分析",
|
||||
"Divide Paragraphs": "段落划分",
|
||||
"Code Explain": "代码解释",
|
||||
"Ask": "询问",
|
||||
"Always": "自动触发",
|
||||
"Manually": "手动触发",
|
||||
"When query ends with question mark (?)": "问题以问号结尾时触发",
|
||||
"Light": "浅色",
|
||||
"Dark": "深色",
|
||||
"Auto": "自动",
|
||||
"ChatGPT (Web)": "ChatGPT (网页版)",
|
||||
"ChatGPT (Web, GPT-4)": "ChatGPT (网页版, GPT-4)",
|
||||
"Bing (Web, GPT-4)": "Bing (网页版, GPT-4)",
|
||||
"ChatGPT (GPT-3.5-turbo)": "ChatGPT (GPT-3.5-turbo)",
|
||||
"ChatGPT (GPT-4-8k)": "ChatGPT (GPT-4-8k)",
|
||||
"ChatGPT (GPT-4-32k)": "ChatGPT (GPT-4-32k)",
|
||||
"GPT-3.5": "GPT-3.5",
|
||||
"Custom Model": "自定义模型",
|
||||
"Balanced": "平衡",
|
||||
"Creative": "有创造力",
|
||||
"Precise": "精确",
|
||||
"Fast": "快速",
|
||||
"API Key": "API Key",
|
||||
"Model Name": "模型名",
|
||||
"Custom Model API Url": "自定义模型的API地址",
|
||||
"Loading...": "正在读取...",
|
||||
"Feedback": "反馈"
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"General": "常規",
|
||||
"Selection Tools": "選擇浮動工具",
|
||||
"Sites": "站點適配",
|
||||
"Advanced": "高級",
|
||||
"Donate": "打賞",
|
||||
"Triggers": "觸發方式",
|
||||
"Theme": "主題",
|
||||
"API Mode": "API模式",
|
||||
"Get": "獲取",
|
||||
"Balance": "余額",
|
||||
"Preferred Language": "語言偏好",
|
||||
"Insert ChatGPT at the top of search results": "將對話卡片插入到搜索結果頂部",
|
||||
"Lock scrollbar while answering": "回答時鎖定滾動條",
|
||||
"Current Version": "當前版本",
|
||||
"Latest": "最新",
|
||||
"Help | Changelog ": "幫助 | 更新日誌 ",
|
||||
"Custom ChatGPT Web API Url": "自定義的ChatGPT網頁API地址",
|
||||
"Custom ChatGPT Web API Path": "自定義的ChatGPT網頁API路徑",
|
||||
"Custom OpenAI API Url": "自定義的OpenAI API地址",
|
||||
"Custom Site Regex:": "自定義站點正則匹配:",
|
||||
"Exclusively use Custom Site Regex for website matching,": "只使用自定義站點正則匹配,",
|
||||
"ignoring built-in rules": "忽略內置站點規則",
|
||||
"Input Query:": "輸入的查詢選擇器:",
|
||||
"Append Query:": "掛載到末尾的查詢選擇器:",
|
||||
"Prepend Query:": "插入到開頭的查詢選擇器:",
|
||||
"Wechat Pay": "微信打賞",
|
||||
"Type your question here\nEnter to send, shift + enter to break line": "在此輸入你的問題\n回車 發送, shift+回車 換行",
|
||||
"Wait for the answer to finish and then continue here": "等待回答完成, 然後在此繼續",
|
||||
"Ask ChatGPT": "詢問ChatGPT",
|
||||
"No Input Found": "無輸入",
|
||||
"You:": "你:",
|
||||
"Collapse": "折疊",
|
||||
"Expand": "展開",
|
||||
"Stop": "停止",
|
||||
"Continue on official website": "在官網繼續",
|
||||
"Error:": "錯誤:",
|
||||
"Copy": "復製",
|
||||
"Question:": "問題:",
|
||||
"Answer:": "回答:",
|
||||
"Waiting for response...": "等待響應...",
|
||||
"Close the Window": "關閉窗口",
|
||||
"Pin the Window": "固定窗口",
|
||||
"Float the Window": "浮出/分裂窗口",
|
||||
"Save Conversation": "保存對話",
|
||||
"UNAUTHORIZED": "未授權",
|
||||
"Please login at https://chat.openai.com first": "請先登錄 https://chat.openai.com",
|
||||
"Then open https://chat.openai.com/api/auth/session": "然後打開 https://chat.openai.com/api/auth/session",
|
||||
"And refresh this page or type you question again": "之後刷新頁面或重新輸入你的問題",
|
||||
"Consider creating an api key at https://platform.openai.com/account/api-keys": "考慮在 https://platform.openai.com/account/api-keys 創建一個API Key",
|
||||
"OpenAI Security Check Required": "需要通過OpenAI的安全檢查",
|
||||
"Please open https://chat.openai.com/api/auth/session": "請打開 https://chat.openai.com/api/auth/session",
|
||||
"Please open https://chat.openai.com": "請打開 https://chat.openai.com",
|
||||
"New Chat": "新建聊天",
|
||||
"Summarize Page": "總結本頁",
|
||||
"Translate": "翻譯",
|
||||
"Translate (Bidirectional)": "雙向翻譯",
|
||||
"Summary": "總結",
|
||||
"Polish": "潤色",
|
||||
"Sentiment Analysis": "情感分析",
|
||||
"Divide Paragraphs": "段落劃分",
|
||||
"Code Explain": "代碼解釋",
|
||||
"Ask": "詢問",
|
||||
"Always": "自動觸發",
|
||||
"Manually": "手動觸發",
|
||||
"When query ends with question mark (?)": "問題以問號結尾時觸發",
|
||||
"Light": "淺色",
|
||||
"Dark": "深色",
|
||||
"Auto": "自動",
|
||||
"ChatGPT (Web)": "ChatGPT (網頁版)",
|
||||
"ChatGPT (Web, GPT-4)": "ChatGPT (網頁版, GPT-4)",
|
||||
"Bing (Web, GPT-4)": "Bing (網頁版, GPT-4)",
|
||||
"ChatGPT (GPT-3.5-turbo)": "ChatGPT (GPT-3.5-turbo)",
|
||||
"ChatGPT (GPT-4-8k)": "ChatGPT (GPT-4-8k)",
|
||||
"ChatGPT (GPT-4-32k)": "ChatGPT (GPT-4-32k)",
|
||||
"GPT-3.5": "GPT-3.5",
|
||||
"Custom Model": "自定義模型",
|
||||
"Balanced": "平衡",
|
||||
"Creative": "有創造力",
|
||||
"Precise": "精確",
|
||||
"Fast": "快速",
|
||||
"API Key": "API Key",
|
||||
"Model Name": "模型名",
|
||||
"Custom Model API Url": "自定義模型的API地址",
|
||||
"Loading...": "正在讀取...",
|
||||
"Feedback": "反饋"
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
chatgptWebModelKeys,
|
||||
customApiModelKeys,
|
||||
defaultConfig,
|
||||
getPreferredLanguageKey,
|
||||
getUserConfig,
|
||||
gptApiModelKeys,
|
||||
isUsingCustomModel,
|
||||
@@ -20,6 +21,8 @@ import {
|
||||
} from '../config/index.mjs'
|
||||
import { isSafari } from '../utils/is-safari'
|
||||
import { config as menuConfig } from '../content-script/menu-tools'
|
||||
import { t, changeLanguage } from 'i18next'
|
||||
import '../_locales/i18n'
|
||||
|
||||
const KEY_ACCESS_TOKEN = 'accessToken'
|
||||
const cache = new ExpiryMap(10 * 1000)
|
||||
@@ -140,7 +143,10 @@ Browser.commands.onCommand.addListener(async (command) => {
|
||||
})
|
||||
|
||||
function refreshMenu() {
|
||||
Browser.contextMenus.removeAll().then(() => {
|
||||
Browser.contextMenus.removeAll().then(async () => {
|
||||
await getPreferredLanguageKey().then((lang) => {
|
||||
changeLanguage(lang)
|
||||
})
|
||||
const menuId = 'ChatGPTBox-Menu'
|
||||
Browser.contextMenus.create({
|
||||
id: menuId,
|
||||
@@ -152,7 +158,7 @@ function refreshMenu() {
|
||||
Browser.contextMenus.create({
|
||||
id: menuId + k,
|
||||
parentId: menuId,
|
||||
title: v.label,
|
||||
title: t(v.label),
|
||||
contexts: ['all'],
|
||||
})
|
||||
}
|
||||
@@ -168,7 +174,7 @@ function refreshMenu() {
|
||||
Browser.contextMenus.create({
|
||||
id: menuId + key,
|
||||
parentId: menuId,
|
||||
title: desc,
|
||||
title: t(desc),
|
||||
contexts: ['selection'],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { render } from 'preact'
|
||||
import FloatingToolbar from '../FloatingToolbar'
|
||||
import { useClampWindowSize } from '../../hooks/use-clamp-window-size'
|
||||
import { defaultConfig, getUserConfig } from '../../config/index.mjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logo = Browser.runtime.getURL('logo.png')
|
||||
|
||||
@@ -29,6 +30,7 @@ class ConversationItemData extends Object {
|
||||
}
|
||||
|
||||
function ConversationCard(props) {
|
||||
const { t } = useTranslation()
|
||||
const [isReady, setIsReady] = useState(!props.question)
|
||||
const [port, setPort] = useState(() => Browser.runtime.connect())
|
||||
const [session, setSession] = useState(props.session)
|
||||
@@ -44,7 +46,7 @@ function ConversationCard(props) {
|
||||
return [
|
||||
new ConversationItemData(
|
||||
'answer',
|
||||
'<p class="gpt-loading">Waiting for response...</p>',
|
||||
`<p class="gpt-loading">${t(`Waiting for response...`)}</p>`,
|
||||
),
|
||||
]
|
||||
else return []
|
||||
@@ -131,20 +133,26 @@ function ConversationCard(props) {
|
||||
switch (msg.error) {
|
||||
case 'UNAUTHORIZED':
|
||||
UpdateAnswer(
|
||||
`UNAUTHORIZED<br>Please login at https://chat.openai.com first${
|
||||
isSafari() ? '<br>Then open https://chat.openai.com/api/auth/session' : ''
|
||||
}<br>And refresh this page or type you question again` +
|
||||
`<br><br>Consider creating an api key at https://platform.openai.com/account/api-keys<hr>`,
|
||||
`${t('UNAUTHORIZED')}<br>${t('Please login at https://chat.openai.com first')}${
|
||||
isSafari() ? `<br>${t('Then open https://chat.openai.com/api/auth/session')}` : ''
|
||||
}<br>${t('And refresh this page or type you question again')}` +
|
||||
`<br><br>${t(
|
||||
'Consider creating an api key at https://platform.openai.com/account/api-keys',
|
||||
)}<hr>`,
|
||||
false,
|
||||
'error',
|
||||
)
|
||||
break
|
||||
case 'CLOUDFLARE':
|
||||
UpdateAnswer(
|
||||
`OpenAI Security Check Required<br>Please open ${
|
||||
isSafari() ? 'https://chat.openai.com/api/auth/session' : 'https://chat.openai.com'
|
||||
}<br>And refresh this page or type you question again` +
|
||||
`<br><br>Consider creating an api key at https://platform.openai.com/account/api-keys<hr>`,
|
||||
`${t('OpenAI Security Check Required')}<br>${
|
||||
isSafari()
|
||||
? t('Please open https://chat.openai.com/api/auth/session')
|
||||
: t('Please open https://chat.openai.com')
|
||||
}<br>${t('And refresh this page or type you question again')}` +
|
||||
`<br><br>${t(
|
||||
'Consider creating an api key at https://platform.openai.com/account/api-keys',
|
||||
)}<hr>`,
|
||||
false,
|
||||
'error',
|
||||
)
|
||||
@@ -172,7 +180,7 @@ function ConversationCard(props) {
|
||||
<XLg
|
||||
className="gpt-util-icon"
|
||||
style="margin:5px 15px 0px;"
|
||||
title="Close the Window"
|
||||
title={t('Close the Window')}
|
||||
size={16}
|
||||
onClick={() => {
|
||||
if (props.onClose) props.onClose()
|
||||
@@ -182,7 +190,7 @@ function ConversationCard(props) {
|
||||
<Pin
|
||||
className="gpt-util-icon"
|
||||
style="margin:5px 15px 0px;"
|
||||
title="Pin the Window"
|
||||
title={t('Pin the Window')}
|
||||
size={16}
|
||||
onClick={() => {
|
||||
if (props.onDock) props.onDock()
|
||||
@@ -196,7 +204,7 @@ function ConversationCard(props) {
|
||||
) : (
|
||||
<WindowDesktop
|
||||
className="gpt-util-icon"
|
||||
title="Float the Window"
|
||||
title={t('Float the Window')}
|
||||
size={16}
|
||||
onClick={() => {
|
||||
const position = { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 }
|
||||
@@ -216,7 +224,7 @@ function ConversationCard(props) {
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
title="Save Conversation"
|
||||
title={t('Save Conversation')}
|
||||
className="gpt-util-icon"
|
||||
style="margin:15px;"
|
||||
onClick={() => {
|
||||
@@ -254,7 +262,7 @@ function ConversationCard(props) {
|
||||
const newQuestion = new ConversationItemData('question', question + '\n<hr/>')
|
||||
const newAnswer = new ConversationItemData(
|
||||
'answer',
|
||||
'<p class="gpt-loading">Waiting for response...</p>',
|
||||
`<p class="gpt-loading">${t('Waiting for response...')}</p>`,
|
||||
)
|
||||
setConversationItemData([...conversationItemData, newQuestion, newAnswer])
|
||||
setIsReady(false)
|
||||
|
||||
@@ -4,8 +4,10 @@ import { ChevronDownIcon, LinkExternalIcon, XCircleIcon } from '@primer/octicons
|
||||
import CopyButton from '../CopyButton'
|
||||
import PropTypes from 'prop-types'
|
||||
import MarkdownRender from '../MarkdownRender/markdown.jsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function ConversationItem({ type, content, session, done, port }) {
|
||||
const { t } = useTranslation()
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
switch (type) {
|
||||
@@ -13,15 +15,23 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
return (
|
||||
<div className={type} dir="auto">
|
||||
<div className="gpt-header">
|
||||
<p>You:</p>
|
||||
<p>{t('You:')}</p>
|
||||
<div style="display: flex; gap: 15px;">
|
||||
<CopyButton contentFn={() => content} size={14} />
|
||||
{!collapsed ? (
|
||||
<span title="Collapse" className="gpt-util-icon" onClick={() => setCollapsed(true)}>
|
||||
<span
|
||||
title={t('Collapse')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(true)}
|
||||
>
|
||||
<XCircleIcon size={14} />
|
||||
</span>
|
||||
) : (
|
||||
<span title="Expand" className="gpt-util-icon" onClick={() => setCollapsed(false)}>
|
||||
<span
|
||||
title={t('Expand')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(false)}
|
||||
>
|
||||
<ChevronDownIcon size={14} />
|
||||
</span>
|
||||
)}
|
||||
@@ -35,7 +45,7 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
<div className={type} dir="auto">
|
||||
<div className="gpt-header">
|
||||
<p style="white-space: nowrap;">
|
||||
{session && session.aiName ? `${session.aiName}:` : 'Loading...'}
|
||||
{session && session.aiName ? `${t(session.aiName)}:` : t('Loading...')}
|
||||
</p>
|
||||
<div style="display: flex; gap: 15px; align-items: center; white-space: nowrap;">
|
||||
{!done && (
|
||||
@@ -46,7 +56,7 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
port.postMessage({ stop: true })
|
||||
}}
|
||||
>
|
||||
Stop
|
||||
{t('Stop')}
|
||||
</button>
|
||||
)}
|
||||
{done && session && session.conversationId && (
|
||||
@@ -57,7 +67,7 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
)}
|
||||
{session && session.conversationId && (
|
||||
<a
|
||||
title="Continue on official website"
|
||||
title={t('Continue on official website')}
|
||||
href={'https://chat.openai.com/chat/' + session.conversationId}
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
@@ -69,11 +79,19 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
)}
|
||||
{session && <CopyButton contentFn={() => content} size={14} />}
|
||||
{!collapsed ? (
|
||||
<span title="Collapse" className="gpt-util-icon" onClick={() => setCollapsed(true)}>
|
||||
<span
|
||||
title={t('Collapse')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(true)}
|
||||
>
|
||||
<XCircleIcon size={14} />
|
||||
</span>
|
||||
) : (
|
||||
<span title="Expand" className="gpt-util-icon" onClick={() => setCollapsed(false)}>
|
||||
<span
|
||||
title={t('Expand')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(false)}
|
||||
>
|
||||
<ChevronDownIcon size={14} />
|
||||
</span>
|
||||
)}
|
||||
@@ -86,15 +104,23 @@ export function ConversationItem({ type, content, session, done, port }) {
|
||||
return (
|
||||
<div className={type} dir="auto">
|
||||
<div className="gpt-header">
|
||||
<p>Error:</p>
|
||||
<p>{t('Error:')}</p>
|
||||
<div style="display: flex; gap: 15px;">
|
||||
<CopyButton contentFn={() => content} size={14} />
|
||||
{!collapsed ? (
|
||||
<span title="Collapse" className="gpt-util-icon" onClick={() => setCollapsed(true)}>
|
||||
<span
|
||||
title={t('Collapse')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(true)}
|
||||
>
|
||||
<XCircleIcon size={14} />
|
||||
</span>
|
||||
) : (
|
||||
<span title="Expand" className="gpt-util-icon" onClick={() => setCollapsed(false)}>
|
||||
<span
|
||||
title={t('Expand')}
|
||||
className="gpt-util-icon"
|
||||
onClick={() => setCollapsed(false)}
|
||||
>
|
||||
<ChevronDownIcon size={14} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { CheckIcon, CopyIcon } from '@primer/octicons-react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
CopyButton.propTypes = {
|
||||
contentFn: PropTypes.func.isRequired,
|
||||
@@ -9,6 +10,7 @@ CopyButton.propTypes = {
|
||||
}
|
||||
|
||||
function CopyButton({ className, contentFn, size }) {
|
||||
const { t } = useTranslation()
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const onClick = () => {
|
||||
@@ -23,7 +25,11 @@ function CopyButton({ className, contentFn, size }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<span title="Copy" className={`gpt-util-icon ${className ? className : ''}`} onClick={onClick}>
|
||||
<span
|
||||
title={t('Copy')}
|
||||
className={`gpt-util-icon ${className ? className : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{copied ? <CheckIcon size={size} /> : <CopyIcon size={size} />}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -5,8 +5,10 @@ import ConversationCard from '../ConversationCard'
|
||||
import { defaultConfig, getUserConfig } from '../../config/index.mjs'
|
||||
import Browser from 'webextension-polyfill'
|
||||
import { getPossibleElementByQuerySelector, endsWithQuestionMark } from '../../utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
function DecisionCard(props) {
|
||||
const { t } = useTranslation()
|
||||
const [triggered, setTriggered] = useState(false)
|
||||
const [config, setConfig] = useState(defaultConfig)
|
||||
const [render, setRender] = useState(false)
|
||||
@@ -103,7 +105,7 @@ function DecisionCard(props) {
|
||||
className="gpt-inner manual-btn icon-and-text"
|
||||
onClick={() => setTriggered(true)}
|
||||
>
|
||||
<SearchIcon size="small" /> Ask ChatGPT
|
||||
<SearchIcon size="small" /> {t('Ask ChatGPT')}
|
||||
</p>
|
||||
)
|
||||
case 'questionMark':
|
||||
@@ -118,14 +120,14 @@ function DecisionCard(props) {
|
||||
className="gpt-inner manual-btn icon-and-text"
|
||||
onClick={() => setTriggered(true)}
|
||||
>
|
||||
<SearchIcon size="small" /> Ask ChatGPT
|
||||
<SearchIcon size="small" /> {t('Ask ChatGPT')}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
else
|
||||
return (
|
||||
<p className="gpt-inner icon-and-text">
|
||||
<LightBulbIcon size="small" /> No Input Found
|
||||
<LightBulbIcon size="small" /> {t('No Input Found')}
|
||||
</p>
|
||||
)
|
||||
})()}
|
||||
|
||||
@@ -2,8 +2,10 @@ import PropTypes from 'prop-types'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { ThumbsupIcon, ThumbsdownIcon } from '@primer/octicons-react'
|
||||
import Browser from 'webextension-polyfill'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const FeedbackForChatGPTWeb = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const [action, setAction] = useState(null)
|
||||
|
||||
const clickThumbsUp = useCallback(async () => {
|
||||
@@ -39,7 +41,7 @@ const FeedbackForChatGPTWeb = (props) => {
|
||||
}, [props, action])
|
||||
|
||||
return (
|
||||
<div title="Feedback" className="gpt-feedback">
|
||||
<div title={t('Feedback')} className="gpt-feedback">
|
||||
<span
|
||||
onClick={clickThumbsUp}
|
||||
className={action === 'thumbsUp' ? 'gpt-feedback-selected gpt-util-icon' : 'gpt-util-icon'}
|
||||
|
||||
@@ -7,10 +7,12 @@ import { config as toolsConfig } from '../../content-script/selection-tools'
|
||||
import { getClientPosition, isMobile, setElementPositionInViewport } from '../../utils'
|
||||
import Draggable from 'react-draggable'
|
||||
import { useClampWindowSize } from '../../hooks/use-clamp-window-size'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logo = Browser.runtime.getURL('logo.png')
|
||||
|
||||
function FloatingToolbar(props) {
|
||||
const { t } = useTranslation()
|
||||
const [selection, setSelection] = useState(props.selection)
|
||||
const [prompt, setPrompt] = useState(props.prompt)
|
||||
const [triggered, setTriggered] = useState(props.triggered)
|
||||
@@ -127,7 +129,7 @@ function FloatingToolbar(props) {
|
||||
cloneElement(toolConfig.icon, {
|
||||
size: 20,
|
||||
className: 'chatgptbox-selection-toolbar-button',
|
||||
title: toolConfig.label,
|
||||
title: t(toolConfig.label),
|
||||
onClick: async () => {
|
||||
const p = getClientPosition(props.container)
|
||||
props.container.style.position = 'fixed'
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { updateRefHeight } from '../../utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function InputBox({ onSubmit, enabled }) {
|
||||
const { t } = useTranslation()
|
||||
const [value, setValue] = useState('')
|
||||
const inputRef = useRef(null)
|
||||
|
||||
@@ -28,8 +30,8 @@ export function InputBox({ onSubmit, enabled }) {
|
||||
className="interact-input"
|
||||
placeholder={
|
||||
enabled
|
||||
? 'Type your question here\nEnter to send, shift + enter to break line'
|
||||
: 'Wait for the answer to finish and then continue here'
|
||||
? t('Type your question here\nEnter to send, shift + enter to break line')
|
||||
: t('Wait for the answer to finish and then continue here')
|
||||
}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
|
||||
+14
-2
@@ -63,7 +63,7 @@ export const defaultConfig = {
|
||||
apiKey: '',
|
||||
/** @type {keyof ModelMode}*/
|
||||
modelMode: 'balanced',
|
||||
preferredLanguage: navigator.language.substring(0, 2),
|
||||
preferredLanguage: getNavigatorLanguage(),
|
||||
insertAtTop: isMobile(),
|
||||
lockWhenAnswer: false,
|
||||
customModelApiUrl: 'http://localhost:8000/chat/completions',
|
||||
@@ -98,7 +98,7 @@ export const defaultConfig = {
|
||||
|
||||
// unchangeable
|
||||
|
||||
userLanguage: navigator.language.substring(0, 2),
|
||||
userLanguage: getNavigatorLanguage(),
|
||||
selectionTools: [
|
||||
'translate',
|
||||
'translateBidi',
|
||||
@@ -132,6 +132,12 @@ export const defaultConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
export function getNavigatorLanguage() {
|
||||
const l = navigator.language.toLowerCase()
|
||||
if (['zh-hk', 'zh-mo', 'zh-tw', 'zh-cht', 'zh-hant'].includes(l)) return 'zhHant'
|
||||
return navigator.language.substring(0, 2)
|
||||
}
|
||||
|
||||
export function isUsingApiKey(config) {
|
||||
return (
|
||||
gptApiModelKeys.includes(config.modelName) || chatgptApiModelKeys.includes(config.modelName)
|
||||
@@ -146,6 +152,12 @@ export function isUsingCustomModel(config) {
|
||||
return customApiModelKeys.includes(config.modelName)
|
||||
}
|
||||
|
||||
export async function getPreferredLanguageKey() {
|
||||
const config = await getUserConfig()
|
||||
if (config.preferredLanguage === 'auto') return config.userLanguage
|
||||
return config.preferredLanguage
|
||||
}
|
||||
|
||||
/**
|
||||
* get user config from local storage
|
||||
* @returns {Promise<UserConfig>}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { languages } from 'countries-list'
|
||||
import { defaultConfig, getUserConfig } from './index.mjs'
|
||||
|
||||
export const languageList = { auto: { name: 'Auto', native: 'Auto' }, ...languages }
|
||||
languageList.zh.name = 'Chinese (Simplified)'
|
||||
languageList.zh.native = '简体中文'
|
||||
languageList.zhHant = { ...languageList.zh }
|
||||
languageList.zhHant.name = 'Chinese (Traditional)'
|
||||
languageList.zhHant.native = '正體中文'
|
||||
|
||||
export async function getUserLanguage() {
|
||||
return languageList[defaultConfig.userLanguage].name
|
||||
|
||||
@@ -5,7 +5,12 @@ import DecisionCard from '../components/DecisionCard'
|
||||
import { config as siteConfig } from './site-adapters'
|
||||
import { config as toolsConfig } from './selection-tools'
|
||||
import { config as menuConfig } from './menu-tools'
|
||||
import { clearOldAccessToken, getUserConfig, setAccessToken } from '../config/index.mjs'
|
||||
import {
|
||||
clearOldAccessToken,
|
||||
getPreferredLanguageKey,
|
||||
getUserConfig,
|
||||
setAccessToken,
|
||||
} from '../config/index.mjs'
|
||||
import {
|
||||
createElementAtPosition,
|
||||
cropText,
|
||||
@@ -17,6 +22,8 @@ import {
|
||||
import FloatingToolbar from '../components/FloatingToolbar'
|
||||
import Browser from 'webextension-polyfill'
|
||||
import { getPreferredLanguage } from '../config/language.mjs'
|
||||
import '../_locales/i18n'
|
||||
import { changeLanguage } from 'i18next'
|
||||
|
||||
/**
|
||||
* @param {SiteConfig} siteConfig
|
||||
@@ -287,6 +294,17 @@ let userConfig
|
||||
|
||||
async function run() {
|
||||
userConfig = await getUserConfig()
|
||||
await getPreferredLanguageKey().then((lang) => {
|
||||
changeLanguage(lang)
|
||||
})
|
||||
Browser.runtime.onMessage.addListener(async (message) => {
|
||||
console.log(message)
|
||||
if (message.type === 'CHANGE_LANG') {
|
||||
const data = message.data
|
||||
changeLanguage(data.lang)
|
||||
}
|
||||
})
|
||||
|
||||
if (isSafari()) await prepareForSafari()
|
||||
prepareForSelectionTools()
|
||||
prepareForSelectionToolsTouch()
|
||||
|
||||
+71
-35
@@ -2,6 +2,7 @@ import '@picocss/pico'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
defaultConfig,
|
||||
getPreferredLanguageKey,
|
||||
getUserConfig,
|
||||
isUsingApiKey,
|
||||
isUsingCustomModel,
|
||||
@@ -24,8 +25,10 @@ import bugmeacoffee from './donation/bugmeacoffee.png'
|
||||
import { useWindowTheme } from '../hooks/use-window-theme.mjs'
|
||||
import { languageList } from '../config/language.mjs'
|
||||
import { isFirefox, isSafari } from '../utils/index.mjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
function GeneralPart({ config, updateConfig }) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const [balance, setBalance] = useState(null)
|
||||
|
||||
const getBalance = async () => {
|
||||
@@ -41,7 +44,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
<legend>Triggers</legend>
|
||||
<legend>{t('Triggers')}</legend>
|
||||
<select
|
||||
required
|
||||
onChange={(e) => {
|
||||
@@ -52,14 +55,14 @@ function GeneralPart({ config, updateConfig }) {
|
||||
{Object.entries(TriggerMode).map(([key, desc]) => {
|
||||
return (
|
||||
<option value={key} key={key} selected={key === config.triggerMode}>
|
||||
{desc}
|
||||
{t(desc)}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<legend>Theme</legend>
|
||||
<legend>{t('Theme')}</legend>
|
||||
<select
|
||||
required
|
||||
onChange={(e) => {
|
||||
@@ -70,14 +73,14 @@ function GeneralPart({ config, updateConfig }) {
|
||||
{Object.entries(ThemeMode).map(([key, desc]) => {
|
||||
return (
|
||||
<option value={key} key={key} selected={key === config.themeMode}>
|
||||
{desc}
|
||||
{t(desc)}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<legend>API Mode</legend>
|
||||
<legend>{t('API Mode')}</legend>
|
||||
<span style="display: flex; gap: 15px;">
|
||||
<select
|
||||
style={
|
||||
@@ -94,7 +97,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
{Object.entries(Models).map(([key, model]) => {
|
||||
return (
|
||||
<option value={key} key={key} selected={key === config.modelName}>
|
||||
{model.desc}
|
||||
{t(model.desc)}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
@@ -111,7 +114,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
{Object.entries(ModelMode).map(([key, desc]) => {
|
||||
return (
|
||||
<option value={key} key={key} selected={key === config.modelMode}>
|
||||
{desc}
|
||||
{t(desc)}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
@@ -123,7 +126,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
<input
|
||||
type="password"
|
||||
value={config.apiKey}
|
||||
placeholder="API Key"
|
||||
placeholder={t('API Key')}
|
||||
onChange={(e) => {
|
||||
const apiKey = e.target.value
|
||||
updateConfig({ apiKey: apiKey })
|
||||
@@ -135,7 +138,9 @@ function GeneralPart({ config, updateConfig }) {
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
<button type="button">Get</button>
|
||||
<button style="white-space: nowrap;" type="button">
|
||||
{t('Get')}
|
||||
</button>
|
||||
</a>
|
||||
) : balance ? (
|
||||
<button type="button" onClick={getBalance}>
|
||||
@@ -143,7 +148,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={getBalance}>
|
||||
Balance
|
||||
{t('Balance')}
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
@@ -153,7 +158,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
<input
|
||||
type="text"
|
||||
value={config.customModelName}
|
||||
placeholder="Model Name"
|
||||
placeholder={t('Model Name')}
|
||||
onChange={(e) => {
|
||||
const customModelName = e.target.value
|
||||
updateConfig({ customModelName: customModelName })
|
||||
@@ -166,7 +171,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
<input
|
||||
type="text"
|
||||
value={config.customModelApiUrl}
|
||||
placeholder="Custom Model API Url"
|
||||
placeholder={t('Custom Model API Url')}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
updateConfig({ customModelApiUrl: value })
|
||||
@@ -175,13 +180,31 @@ function GeneralPart({ config, updateConfig }) {
|
||||
)}
|
||||
</label>
|
||||
<label>
|
||||
<legend>Preferred Language</legend>
|
||||
<legend>{t('Preferred Language')}</legend>
|
||||
<span style="display: flex; gap: 15px;">
|
||||
<select
|
||||
required
|
||||
onChange={(e) => {
|
||||
const preferredLanguageKey = e.target.value
|
||||
updateConfig({ preferredLanguage: preferredLanguageKey })
|
||||
|
||||
let lang
|
||||
if (preferredLanguageKey === 'auto') lang = config.userLanguage
|
||||
else lang = preferredLanguageKey
|
||||
i18n.changeLanguage(lang)
|
||||
|
||||
Browser.tabs.query({}).then((tabs) => {
|
||||
tabs.forEach((tab) => {
|
||||
Browser.tabs
|
||||
.sendMessage(tab.id, {
|
||||
type: 'CHANGE_LANG',
|
||||
data: {
|
||||
lang,
|
||||
},
|
||||
})
|
||||
.catch(() => ({}))
|
||||
})
|
||||
})
|
||||
}}
|
||||
>
|
||||
{Object.entries(languageList).map(([k, v]) => {
|
||||
@@ -203,7 +226,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
updateConfig({ insertAtTop: checked })
|
||||
}}
|
||||
/>
|
||||
Insert ChatGPT at the top of search results
|
||||
{t('Insert ChatGPT at the top of search results')}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
@@ -214,7 +237,7 @@ function GeneralPart({ config, updateConfig }) {
|
||||
updateConfig({ lockWhenAnswer: checked })
|
||||
}}
|
||||
/>
|
||||
Lock scrollbar while answering
|
||||
{t('Lock scrollbar while answering')}
|
||||
</label>
|
||||
<br />
|
||||
</>
|
||||
@@ -227,10 +250,12 @@ GeneralPart.propTypes = {
|
||||
}
|
||||
|
||||
function AdvancedPart({ config, updateConfig }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
Custom ChatGPT Web API Url
|
||||
{t('Custom ChatGPT Web API Url')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.customChatGptWebApiUrl}
|
||||
@@ -241,7 +266,7 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Custom ChatGPT Web API Path
|
||||
{t('Custom ChatGPT Web API Path')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.customChatGptWebApiPath}
|
||||
@@ -252,7 +277,7 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Custom OpenAI API Url
|
||||
{t('Custom OpenAI API Url')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.customOpenAiApiUrl}
|
||||
@@ -263,7 +288,7 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Custom Site Regex:
|
||||
{t('Custom Site Regex:')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.siteRegex}
|
||||
@@ -282,12 +307,13 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
updateConfig({ userSiteRegexOnly: checked })
|
||||
}}
|
||||
/>
|
||||
Exclusively use Custom Site Regex for website matching,{isFirefox() ? <br /> : ' '}ignoring
|
||||
built-in rules
|
||||
{`${t('Exclusively use Custom Site Regex for website matching,')}${
|
||||
isFirefox() ? <br /> : ' '
|
||||
}${t('ignoring built-in rules')}`}
|
||||
</label>
|
||||
<br />
|
||||
<label>
|
||||
Input Query:
|
||||
{t('Input Query:')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.inputQuery}
|
||||
@@ -298,7 +324,7 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Append Query:
|
||||
{t('Append Query:')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.appendQuery}
|
||||
@@ -309,7 +335,7 @@ function AdvancedPart({ config, updateConfig }) {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Prepend Query:
|
||||
{t('Prepend Query:')}
|
||||
<input
|
||||
type="text"
|
||||
value={config.prependQuery}
|
||||
@@ -329,6 +355,8 @@ AdvancedPart.propTypes = {
|
||||
}
|
||||
|
||||
function SelectionTools({ config, updateConfig }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
{config.selectionTools.map((key) => (
|
||||
@@ -343,7 +371,7 @@ function SelectionTools({ config, updateConfig }) {
|
||||
updateConfig({ activeSelectionTools })
|
||||
}}
|
||||
/>
|
||||
{toolsConfig[key].label}
|
||||
{t(toolsConfig[key].label)}
|
||||
</label>
|
||||
))}
|
||||
</>
|
||||
@@ -383,6 +411,8 @@ SiteAdapters.propTypes = {
|
||||
}
|
||||
|
||||
function Donation() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div style="display:flex;flex-direction:column;align-items:center;">
|
||||
<a
|
||||
@@ -394,7 +424,7 @@ function Donation() {
|
||||
</a>
|
||||
<br />
|
||||
<>
|
||||
Wechat Pay
|
||||
{t('Wechat Pay')}
|
||||
<img alt="wechatpay" src={wechatpay} />
|
||||
</>
|
||||
</div>
|
||||
@@ -403,15 +433,17 @@ function Donation() {
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function Footer({ currentVersion, latestVersion }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="footer">
|
||||
<div>
|
||||
Current Version: {currentVersion}{' '}
|
||||
{`${t('Current Version')}: ${currentVersion} `}
|
||||
{currentVersion === latestVersion ? (
|
||||
'(Latest)'
|
||||
`(${t('Latest')})`
|
||||
) : (
|
||||
<>
|
||||
(Latest:{' '}
|
||||
({`${t('Latest')}: `}
|
||||
<a
|
||||
href={'https://github.com/josStorer/chatGPTBox/releases/tag/v' + latestVersion}
|
||||
target="_blank"
|
||||
@@ -429,7 +461,7 @@ function Footer({ currentVersion, latestVersion }) {
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
<span>Help | Changelog </span>
|
||||
<span>{t('Help | Changelog ')}</span>
|
||||
<MarkGithubIcon />
|
||||
</a>
|
||||
</div>
|
||||
@@ -438,6 +470,7 @@ function Footer({ currentVersion, latestVersion }) {
|
||||
}
|
||||
|
||||
function Popup() {
|
||||
const { t, i18n } = useTranslation()
|
||||
const [config, setConfig] = useState(defaultConfig)
|
||||
const [currentVersion, setCurrentVersion] = useState('')
|
||||
const [latestVersion, setLatestVersion] = useState('')
|
||||
@@ -449,6 +482,9 @@ function Popup() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getPreferredLanguageKey().then((lang) => {
|
||||
i18n.changeLanguage(lang)
|
||||
})
|
||||
getUserConfig().then((config) => {
|
||||
setConfig(config)
|
||||
setCurrentVersion(Browser.runtime.getManifest().version.replace('v', ''))
|
||||
@@ -469,11 +505,11 @@ function Popup() {
|
||||
<form style="width:100%;">
|
||||
<Tabs selectedTabClassName="popup-tab--selected">
|
||||
<TabList>
|
||||
<Tab className="popup-tab">General</Tab>
|
||||
<Tab className="popup-tab">Selection Tools</Tab>
|
||||
<Tab className="popup-tab">Sites</Tab>
|
||||
<Tab className="popup-tab">Advanced</Tab>
|
||||
{isSafari() ? null : <Tab className="popup-tab">Donate</Tab>}
|
||||
<Tab className="popup-tab">{t('General')}</Tab>
|
||||
<Tab className="popup-tab">{t('Selection Tools')}</Tab>
|
||||
<Tab className="popup-tab">{t('Sites')}</Tab>
|
||||
<Tab className="popup-tab">{t('Advanced')}</Tab>
|
||||
{isSafari() ? null : <Tab className="popup-tab">{t('Donate')}</Tab>}
|
||||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render } from 'preact'
|
||||
import Popup from './Popup'
|
||||
import '../_locales/i18n'
|
||||
|
||||
render(<Popup />, document.getElementById('app'))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function isFirefox() {
|
||||
return navigator.userAgent.includes('Firefox')
|
||||
return navigator.userAgent.toLowerCase().includes('firefox')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user