├── docs ├── anki.png ├── config.png └── select.png ├── public ├── unload.png ├── index.html ├── manifest.json └── background.js ├── tailwind.config.js ├── src ├── index.js ├── index.css └── app.js ├── .gitignore ├── README.md ├── README-en.md └── package.json /docs/anki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mggger/chatgpt-anki-chrome-extension/HEAD/docs/anki.png -------------------------------------------------------------------------------- /docs/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mggger/chatgpt-anki-chrome-extension/HEAD/docs/config.png -------------------------------------------------------------------------------- /docs/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mggger/chatgpt-anki-chrome-extension/HEAD/docs/select.png -------------------------------------------------------------------------------- /public/unload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mggger/chatgpt-anki-chrome-extension/HEAD/public/unload.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Anki card with chatgpt 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | module.exports = { 4 | content: [ 5 | "./src/**/*.{js,jsx,ts,tsx}", 6 | 'node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}' 7 | ], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [] 12 | } 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import AnkiPage from './app'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @layer base { 4 | html { 5 | @apply text-neutral-800; 6 | } 7 | html.dark { 8 | @apply text-neutral-50; 9 | @apply bg-neutral-800; 10 | } 11 | } 12 | 13 | @tailwind components; 14 | @tailwind utilities; 15 | 16 | 17 | body { 18 | width: 500px; 19 | height: 200px; 20 | overflow: hidden; 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .idea/ 26 | .idea/* 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anki card createor 2 | 3 | 4 | [English Docs](./README-en.md) 5 | 6 | **通过chatgpt创建anki card** 7 | 8 | 这个项目的出发点: 9 | 10 | 1. 在网上冲浪的时候,有价值的碎片信息,收集一下,用chatgpt整理成anki卡片 11 | 2. 考证 / 面试背八股文 12 | 13 | 14 | 15 | ## 如何使用 16 | 17 | 1. 安装chrome extension, 通过zip包安装: [下载地址](https://github.com/mggger/chatgpt-anki-chrome-extension/files/11913090/v0.1.zip) 18 | 19 | 2. 配置相关配置, 并保存 20 | 21 | xx 22 | 23 | 3. 当遇到感兴趣知识点的时候,选中文本, 通过chatgpt总结成anki卡片 24 | 25 | xx 26 | 27 | 4. 完成后,可以在anki里面开始背诵了 28 | 29 | ![xx](./docs/anki.png) 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Anki card creator", 4 | "version": "1.0", 5 | "description": "Create Anki cards from selected text", 6 | "permissions": ["tabs", "contextMenus", "storage", "notifications", "activeTab"], 7 | "background": { 8 | "service_worker": "background.js" 9 | }, 10 | "options_ui": { 11 | "page": "index.html", 12 | "open_in_tab": true 13 | }, 14 | "icons": { 15 | "48": "unload.png" 16 | }, 17 | "action": { 18 | "default_popup": "index.html", 19 | "default_title": "anki extractor", 20 | "default_icon": { 21 | "16": "unload.png", 22 | "32": "unload.png", 23 | "48": "unload.png", 24 | "128": "unload.png" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # Anki Card Creator 2 | 3 | Create Anki cards using ChatGPT 4 | 5 | This project originates from: 6 | 7 | 1. When surfing online, collect valuable fragmented information and use ChatGPT to organize it into Anki cards. 8 | 2. Studying for exams/certifications or memorizing standard answers for interviews. 9 | 10 | ## How to Use 11 | 12 | 1. Install the Chrome extension through the zip package: [Download Link](https://github.com/mggger/chatgpt-anki-chrome-extension/files/11913090/v0.1.zip) 13 | 14 | 2. Set up the relevant configurations and save them. 15 | 16 | ![Configuration Image](./docs/config.png) 17 | 18 | 3. When you come across points of interest, select the text and use ChatGPT to summarize it into an Anki card. 19 | 20 | ![Selection Image](./docs/select.png) 21 | 22 | 4. Once done, you can start revising it in Anki. 23 | 24 | ![Anki Image](./docs/anki.png) 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-docs-anki", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "autoprefixer": "^10.4.14", 10 | "install": "^0.13.0", 11 | "postcss": "^8.4.24", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-icons": "^4.9.0", 15 | "react-scripts": "5.0.1", 16 | "tailwindcss": "^3.3.2", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onInstalled.addListener(() => { 2 | chrome.contextMenus.create({ 3 | id: "add-to-anki", 4 | title: "Create Anki card", 5 | contexts: ["selection"], 6 | }); 7 | }); 8 | 9 | chrome.contextMenus.onClicked.addListener(handleContextMenu); 10 | 11 | function handleContextMenu(info) { 12 | if (info.menuItemId === "add-to-anki") { 13 | const text = "请将下列描述提取成anki卡片的形式, 描述: " + info.selectionText + "\n并且以[{\"front\": \"xxx\", \"back\": \"xxx\"}]格式返回"; 14 | getGPT3Result(text).then((result) => { 15 | saveAnki(result); 16 | }); 17 | } 18 | } 19 | 20 | async function getGPT3Result(text) { 21 | const config = await getAnkiConfig(); 22 | const GPT_API_KEY = config.openAIKey; 23 | 24 | const response = await fetch("https://api.openai.com/v1/chat/completions", { 25 | method: "POST", 26 | headers: { 27 | "Content-Type": "application/json", 28 | Authorization: "Bearer " + GPT_API_KEY, 29 | }, 30 | body: JSON.stringify({ 31 | model: "gpt-3.5-turbo", 32 | messages: [{content: text, role: "user"}] 33 | }) 34 | }); 35 | const data = await response.json(); 36 | const ret = data.choices[0].message.content.trim(); 37 | return ret; 38 | } 39 | 40 | async function saveAnki(result) { 41 | const resultArray = JSON.parse(result); 42 | 43 | const today = new Date(); 44 | const year = today.getFullYear(); 45 | const month = ('0' + (today.getMonth() + 1)).slice(-2); // Month is zero-indexed 46 | const day = ('0' + today.getDate()).slice(-2); 47 | const today_time_str = `${year}-${month}-${day}`; 48 | 49 | const config = await getAnkiConfig(); 50 | const ANKI_DESK_NAME = config.ankiDeskName; 51 | const ANKI_SERVER_ADDRESS = config.ankiServerAddress; 52 | 53 | const requestData = { 54 | action: "addNote", 55 | version: 6, 56 | params: { 57 | note: { 58 | deckName: ANKI_DESK_NAME, 59 | modelName: "Basic", 60 | fields: { 61 | Front: "", 62 | Back: "" 63 | }, 64 | options: { 65 | allowDuplicate: false, 66 | duplicateScope: "deck", 67 | duplicateScopeOptions: { 68 | deckName: "Default", 69 | checkChildren: false, 70 | checkAllModels: false 71 | } 72 | }, 73 | tags: [today_time_str], 74 | } 75 | } 76 | }; 77 | 78 | for (let i = 0; i < resultArray.length; i++) { 79 | const currentResult = resultArray[i]; 80 | requestData.params.note.fields.Front = currentResult.front; 81 | requestData.params.note.fields.Back = currentResult.back; 82 | 83 | fetch(ANKI_SERVER_ADDRESS, { 84 | method: "POST", 85 | modelName: "Basic", 86 | headers: { 87 | "Content-Type": "application/json", 88 | }, 89 | body: JSON.stringify(requestData), 90 | }) 91 | .then((response) => { 92 | if (response.ok) { 93 | console.log("Note added to Anki!"); 94 | showNotification("add anki success"); 95 | } else { 96 | console.error("Failed to add note to Anki:", response); 97 | showNotification("add anki failed"); 98 | } 99 | }) 100 | .catch((error) => { 101 | console.error("Failed to add note to Anki:", error); 102 | }); 103 | } 104 | } 105 | 106 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 107 | if (message.type === 'SAVE_ANKI_CONFIG') { 108 | chrome.storage.local.set({ankiConfig: message.config}); 109 | console.log("set anki config success"); 110 | } 111 | }); 112 | 113 | function getAnkiConfig() { 114 | return new Promise((resolve, reject) => { 115 | chrome.storage.local.get("ankiConfig", (result) => { 116 | if (result.ankiConfig) { 117 | resolve(result.ankiConfig); 118 | } else { 119 | resolve({ 120 | openAIKey: "your_openai_key", 121 | ankiDeskName: "your_anki_desk_name", 122 | ankiServerAddress: "your_anki_server_address", 123 | }); 124 | } 125 | }); 126 | }); 127 | } 128 | 129 | function showNotification(message) { 130 | chrome.notifications.create({ 131 | type: "basic", 132 | title: "Anki Card Created", 133 | message: message, 134 | iconUrl: "unload.png", 135 | }); 136 | } 137 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /*global chrome*/ 2 | import React, {useState, useEffect} from 'react'; 3 | import {FaEye, FaEyeSlash} from 'react-icons/fa'; 4 | 5 | 6 | const AnkiPage = () => { 7 | const [openAIKey, setOpenAIKey] = useState(''); 8 | const [showOpenAIKey, setShowOpenAIKey] = useState(false); 9 | const [ankiServerAddress, setAnkiServerAddress] = useState(''); 10 | const [ankiDeskName, setAnkiDeskName] = useState(''); 11 | const [reload, setReload] = useState(false); // New state variable 12 | 13 | 14 | const handleOpenAIKeyChange = (e) => { 15 | setOpenAIKey(e.target.value); 16 | }; 17 | 18 | const toggleShowOpenAIKey = () => { 19 | setShowOpenAIKey((prevState) => !prevState); 20 | }; 21 | 22 | const handleAnkiServerAddressChange = (e) => { 23 | setAnkiServerAddress(e.target.value); 24 | }; 25 | 26 | const handleAnkiDeskNameChange = (e) => { 27 | setAnkiDeskName(e.target.value); 28 | }; 29 | 30 | useEffect(() => { 31 | const config = JSON.parse(localStorage.getItem("ankiConfig")); 32 | if (config) { 33 | const {openAIKey, ankiServerAddress, ankiDeskName} = config; 34 | setOpenAIKey(openAIKey); 35 | setAnkiServerAddress(ankiServerAddress); 36 | setAnkiDeskName(ankiDeskName); 37 | } 38 | }, [reload]); 39 | 40 | const handleSave = () => { 41 | const config = { 42 | openAIKey: openAIKey, 43 | ankiServerAddress: ankiServerAddress, 44 | ankiDeskName: ankiDeskName 45 | }; 46 | 47 | localStorage.setItem("ankiConfig", JSON.stringify(config)); 48 | setReload(!reload); // Toggle the reload state variable to trigger useEffect 49 | 50 | if (chrome && chrome.runtime && chrome.runtime.sendMessage) { 51 | chrome.runtime.sendMessage({type: 'SAVE_ANKI_CONFIG', config: config}); 52 | } 53 | 54 | alert("save success"); 55 | }; 56 | 57 | return ( 58 |
59 | 60 |

Configuration

61 | 62 |
63 |
64 | 65 |
66 |
67 | 74 | 84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 | 98 |
99 | 100 |
101 |
102 | 103 |
104 | 111 |
112 | 113 | 119 |
120 | ); 121 | 122 | }; 123 | 124 | export default AnkiPage; 125 | --------------------------------------------------------------------------------