├── .npmrc ├── .eslintignore ├── versions.json ├── imgs ├── logo.png ├── image.png ├── image2.png ├── Pasted image 20240223201340.png ├── Pasted image 20240223201706.png ├── Pasted image 20240223201921.png ├── Pasted image 20240223202150.png ├── Pasted image 20240223202357.png ├── Pasted image 20240223203039.png └── Pasted image 20240223203053.png ├── __pycache__ └── qwen_server.cpython-310.pyc ├── styles.css ├── .editorconfig ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── package.json ├── LICENSE ├── esbuild.config.mjs ├── README.md └── main.ts /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0" 3 | } 4 | -------------------------------------------------------------------------------- /imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/logo.png -------------------------------------------------------------------------------- /imgs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/image.png -------------------------------------------------------------------------------- /imgs/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/image2.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223201340.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223201340.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223201706.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223201706.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223201921.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223201921.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223202150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223202150.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223202357.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223202357.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223203039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223203039.png -------------------------------------------------------------------------------- /imgs/Pasted image 20240223203053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/imgs/Pasted image 20240223203053.png -------------------------------------------------------------------------------- /__pycache__/qwen_server.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravenSanstete/Obsidian-MantouAI/HEAD/__pycache__/qwen_server.cpython-310.pyc -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "mantou-ai", 3 | "name": "MantouAI", 4 | "version": "1.0.0", 5 | "minAppVersion": "0.15.0", 6 | "description": "Work as a personal assistant for translation, writing polish, general Q&A, summarizing, using the power of large language models.", 7 | "author": "Morino Pan", 8 | "authorUrl": "https://ravensanstete.github.io/", 9 | "fundingUrl": "", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mantou-AI", 3 | "version": "1.0.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | }, 24 | "dependencies": { 25 | "gpt-3-encoder": "^1.1.4", 26 | "gpt-tokenizer": "^2.1.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Morino Pan 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 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === "production"); 13 | 14 | const context = await esbuild.context({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["main.ts"], 19 | bundle: true, 20 | external: [ 21 | "obsidian", 22 | "electron", 23 | "@codemirror/autocomplete", 24 | "@codemirror/collab", 25 | "@codemirror/commands", 26 | "@codemirror/language", 27 | "@codemirror/lint", 28 | "@codemirror/search", 29 | "@codemirror/state", 30 | "@codemirror/view", 31 | "@lezer/common", 32 | "@lezer/highlight", 33 | "@lezer/lr", 34 | ...builtins], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | }); 42 | 43 | if (prod) { 44 | await context.rebuild(); 45 | process.exit(0); 46 | } else { 47 | await context.watch(); 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MantouAI—— 让Obsidian变身智能助手 2 |

3 | MantouGPT Logo 4 |

5 |

6 | *MantouAI*是为每个人设计的开放个人助手(生活在Obsidian之中),它能协助办公学习、聊天、(未来:上网、写报告、规划旅行...) 7 |

8 | 9 | 10 | ## 1. 什么是MantouAI🐶? 11 | * MantouAI = **馒头AI**(PS:我们家有一只不会动的柴犬馒头,老婆想让它动起来) 12 | * 一款集成AI大模型的Obsidian插件 13 | * Obsidian + MantouAI = X书助手、X钉魔法棒、NotionAI 14 | * 开源 + 定制(有需要什么功能,请给我issues!) 15 | * ”万事不决、问问馒头“ (Command+Shift+P/Command+P) 16 | 17 | --- 18 | 19 | 20 | ## 2. 功能介绍🥰 (喜欢的话,务必Star⭐哦,拜托了) 21 | --- 22 | ### 2.1 万能提问 23 | * 任意选中文字,向馒头提问吧 24 | * ”万事不决、问问馒头“ 25 |

26 | 27 |

28 | 29 | ### 2.2 内置常见学术功能 30 | * **对任意选中文字**(1000字以内较好):中译英、英译中、中文润色、要点归纳、段落精读 ... 31 |

32 | 33 |

34 | 35 | 36 |

37 | 38 |

39 | 40 | 41 | 42 | ### 2.3 角色扮演 43 | * 在properties中加入键值对”role“,”<角色描述>“(角色描述务必用”“开合,定位用) 44 | * role下方加入随意的key-value一对 45 |

46 | 47 |

48 | 49 | ### 2.4 全文观点提炼 50 |

51 | 52 |

53 | 54 |

55 | 56 |

57 | 58 | --- 59 | ## 3. 开始使用吧👐! 60 | ### 3.1 开箱即用 61 | 1. Releases下载MantouAI-v0.1.zip 62 | 2. 放到Obsidian Vault的plugins文件夹,解压缩 63 | 3. 重新打开Obsidian,加载MantouAI 64 |

65 | 66 |

67 | 68 | 4. 根据[教程](https://help.aliyun.com/zh/dashscope/developer-reference/api-details),免费申请阿里Dashscope账号和api key 69 | 5. 配置你的key 70 |

71 | 72 |

73 | 74 | ### 3.2 面向开发者 75 | 1. git clone this repository 76 | 2. npm install esbuild 77 | 3. 目录下执行 npm run dev (持续编译) 78 | 4. 开始魔改 main.ts 吧 79 | 80 | 81 | ## 4. 开发路线图 🗺︎ 82 | - [ ] 本地化大模型(数据隐私): 83 | - [ ] Gemma-2b-it(魔改ing,正在接入) 84 | - [ ] 个性化助手 85 | - [ ] 与当前文档对话 86 | - [ ] 与Obsidian知识库对话 87 | - [ ] 连接Obsidian其他插件,自主Agent 88 | - [ ] ... 89 | 90 | ## 5. 注意事项⚠︎ 91 | * 当前版本接入通义大模型,需联网(过于敏感的数据请勿使用) 92 | * 请遵守负责任使用AI的原则,任何滥用与本代码库无关 93 | * ... 94 | 95 | ## 6. 为什么开发MantouAI?All-in-Obsidian! ❓ 96 | * A: 使用网页版大模型各种复制文档,好麻烦... 97 | * B: 写一个webui界面,大家还得命令行开启,不如obsidian插件自然... 98 | * 你觉得呢? 99 | 100 | ## 7. 搭配以下插件食用更加 101 | * Better Command Palette 102 | * Marp Slides (馒头精读内容,直接生成PPT啦!) 103 | * ... (等你发现 104 | 105 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, Menu, requestUrl, normalizePath} from 'obsidian'; 2 | // Remember to rename these classes and interfaces! 3 | 4 | import { 5 | encode, 6 | decode, 7 | } from 'gpt-tokenizer' 8 | 9 | function extractRoleValue(text: string): string { 10 | const roleRegex = /Role:([\s\S]+?)\n[\s\S]+?---/g; 11 | const matches = roleRegex.exec(text); 12 | 13 | if (matches && matches.length > 1) { 14 | const roleValue = matches[1].trim(); 15 | return roleValue; 16 | } 17 | 18 | return ''; 19 | } 20 | 21 | async function payload(url:string, data:any, apiKey:string){ 22 | const headers = { 23 | Authorization: `Bearer ${apiKey}`, 24 | "Content-Type": "application/json" 25 | }; 26 | 27 | try { 28 | const response = await requestUrl({url:url, method:'POST', headers:headers, body:JSON.stringify(data)}); 29 | 30 | let out = JSON.parse(response.text) 31 | return out['output']['text'] 32 | } catch (error) { 33 | console.error('Error:', error); 34 | new Notice(`馒头:U•ェ•*U 请求错误: 代号${error}`) 35 | // throw error; 36 | return '[请求错误]' 37 | } 38 | 39 | }; 40 | 41 | function create_newline(editor: Editor){ 42 | // const curserStart = editor.getCursor("from"); 43 | const curserEnd = editor.getCursor("to"); 44 | const line = editor.getLine(curserEnd.line); 45 | editor.setLine(curserEnd.line, (line + "\n")); 46 | editor.setCursor({ 47 | line: curserEnd.line+1, 48 | ch:0 49 | }); 50 | } 51 | 52 | 53 | 54 | interface MantouAIPluginSettings { 55 | api_key: string; 56 | } 57 | 58 | const DEFAULT_SETTINGS: MantouAIPluginSettings = { 59 | api_key: '' 60 | } 61 | 62 | function operation_on_selection(editor: Editor, sys: string, user_prefix: string, user_suffix:string, api_key:string, post_fn:any=(x:string)=>`\n---\n${x}\n`, selection_fn=(x:string)=>(x)){ 63 | const selection = editor.getSelection(); 64 | 65 | editor.replaceSelection(selection_fn(selection)) 66 | create_newline(editor) 67 | const data = { 68 | 'system_prompt': sys, 69 | 'user_prompt': user_prefix + selection + user_suffix 70 | }; 71 | 72 | let notice = new Notice('馒头:U•ェ•*U努力思考中...', 0) 73 | chat(data['user_prompt'], data['system_prompt'], api_key) 74 | .then((result) => { 75 | // update the editor 76 | result = post_fn(result) 77 | notice.setMessage('馒头:U•ェ•*U完成啦!') 78 | editor.replaceRange(result, editor.getCursor()); 79 | notice.hide() 80 | }).catch((error) => { 81 | console.error('Error:', error) 82 | notice.hide() 83 | }); 84 | } 85 | 86 | 87 | 88 | 89 | 90 | function splitTextOnTokens(text: string, tokensPerChunk: number): string[] { 91 | const splits: string[] = []; 92 | const inputIds = encode(text); 93 | let startIdx = 0; 94 | let curIdx = Math.min(startIdx + tokensPerChunk, inputIds.length); 95 | let chunkIds = inputIds.slice(startIdx, curIdx); 96 | 97 | while (startIdx < inputIds.length) { 98 | splits.push(decode(chunkIds)); 99 | 100 | if (curIdx === inputIds.length) { 101 | break; 102 | } 103 | 104 | startIdx += tokensPerChunk; 105 | curIdx = Math.min(startIdx + tokensPerChunk, inputIds.length); 106 | chunkIds = inputIds.slice(startIdx, curIdx); 107 | } 108 | return splits; 109 | } 110 | 111 | const GENERATION_URL = 112 | "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"; 113 | 114 | async function chat(user_prompt:string, system_prompt:string="You are a helpful assistant.", api_key:string){ 115 | const requestData = { 116 | model: "qwen-max", 117 | input: { 118 | //prompt: user_prompt, 119 | messages: [ 120 | { 121 | "role": "system", 122 | "content": system_prompt.replace('"', "'") 123 | }, 124 | { 125 | "role": "user", 126 | "content": user_prompt.replace('"', "'") 127 | }] 128 | } 129 | }; 130 | return payload(GENERATION_URL, requestData, api_key) 131 | 132 | } 133 | 134 | const URL = 'http://127.0.0.1:5200/query'; 135 | const SUMMARY_SYS = 'You are a reading assistant who are good at reading and summarizing key points from text.' 136 | 137 | const SUMMARY_USER_PREFIX = '对以下内容进行总结归纳,从中提取关键论点,并明确、具体地提取对应的支撑论据(包括实验数据、相关文献、理论结果等),保留原文中markdown图片链接(![text](url)):' 138 | 139 | const SUMMARY_USER_SUFFIX = `\n以Markdown格式输出 140 | ## 关键论点: [论点表述] 141 | - [支撑论据] 142 | - [支撑论据] 143 | - [支撑论据] 144 | - ... 145 | 146 | --- 147 | 148 | ## 关键论点:[论点表述] 149 | - [支撑论据] 150 | - [支撑论据] 151 | - [支撑论据] 152 | - ... 153 | 154 | --- 155 | ...` 156 | const META_ROLE = `You are an AI assistant whose name is MantouAI. 157 | - MantouAI is a conversational language model that is developed by Fudan University. It is designed to be helpful, honest, and harmless. 158 | - Its responses must not be vague, accusatory, rude, controversial, off-topic, or defensive. 159 | - MantouAI can understand and communicate fluently in the language chosen by the user such as English and 中文. MantouAI can perform any language-based tasks. 160 | - MantouAI is an expert at reading, understanding and writing. 161 | - It can provide additional relevant details to answer in-depth and comprehensively covering mutiple aspects. 162 | - All the equations should be output in MathJAX format. 163 | - It apologizes and accepts the user's suggestion if the user corrects the incorrect answer generated by MantouAI. 164 | ` 165 | 166 | async function summarize_chunk(chunk:string, api_key:string){ 167 | const data = { 168 | 'system_prompt': SUMMARY_SYS, 169 | 'user_prompt': SUMMARY_USER_PREFIX + chunk + SUMMARY_USER_SUFFIX 170 | }; 171 | 172 | return chat(data['user_prompt'], data['system_prompt'], api_key) 173 | } 174 | 175 | function addGreaterThanSign(text: string): string { 176 | const lines = text.split('\n'); 177 | const modifiedLines = lines.map(line => `> ${line}`); 178 | return modifiedLines.join('\n'); 179 | } 180 | 181 | export default class MantouAIPlugin extends Plugin { 182 | settings: MantouAIPluginSettings; 183 | // util codes 184 | async appendFile(filePath: string, note: string) { 185 | let existingContent = await this.app.vault.adapter.read(filePath); 186 | if(existingContent.length > 0) { 187 | existingContent = existingContent + '\r\r'; 188 | } 189 | await this.app.vault.adapter.write(filePath, existingContent + note); 190 | } 191 | 192 | async saveToFile(filePath: string, mdString: string) { 193 | const fileExists = await this.app.vault.adapter.exists(filePath); 194 | if (fileExists) { 195 | await this.appendFile(filePath, mdString); 196 | } else { 197 | await this.app.vault.create(filePath, mdString); 198 | } 199 | } 200 | 201 | async onload() { 202 | await this.loadSettings(); 203 | // This creates an icon in the left ribbon. 204 | this.addRibbonIcon("paw-print", "Open menu", (event) => { 205 | const menu = new Menu(); 206 | 207 | menu.addItem((item) => 208 | item 209 | .setTitle("全文摘要") 210 | .setIcon("paste") 211 | .onClick(async () => { 212 | let file = this.app.workspace.getActiveFile(); 213 | if(file === null) return; 214 | if(file.extension !== 'md') return; 215 | 216 | const file_name = '[摘要] ' + file.name; 217 | let folder:string = file.parent.path 218 | let summary_list:string[] = [] 219 | this.app.vault.read(file) 220 | .then(async (text: string) => { 221 | let splits = splitTextOnTokens(text, 1000) 222 | 223 | for (let index = 0; index < splits.length; index++){ 224 | let summary:string = await summarize_chunk(splits[index], this.settings.api_key) 225 | summary_list.push(summary) 226 | await this.saveToFile(normalizePath(`${folder}/${file_name}`), summary); 227 | 228 | new Notice(`馒头:U•ェ•*U努力阅读中 (${index+1}/${splits.length})`) 229 | } 230 | 231 | }) 232 | .catch((err: any) => { 233 | console.error(err); 234 | }); 235 | 236 | await this.app.workspace.openLinkText(`${folder}/${file_name}`, '', true); 237 | }) 238 | ); 239 | menu.showAtMouseEvent(event); 240 | }); 241 | 242 | // TODO: 英译中 243 | this.addCommand({ 244 | id: "translate_en", 245 | name: "🐶英译中", 246 | editorCallback: (editor: Editor) => operation_on_selection( 247 | editor, 248 | META_ROLE, 249 | '作为经验丰富的翻译,充分考虑中文的语法、清晰、简洁和整体可读性,必要时,你可以修改整个句子的顺序以确保翻译后的段落符合中文的语言习惯,任务是把给定的学术文章段落翻译成中文。你需要翻译的文本如下:', 250 | '', this.settings.api_key) 251 | } 252 | ) 253 | 254 | 255 | 256 | // TODO: 中译英 257 | this.addCommand({ 258 | id: "translate_zh", 259 | name: "🐶中译英", 260 | editorCallback: (editor: Editor) => operation_on_selection( 261 | editor, 262 | META_ROLE, 263 | '作为英文学术论文写作专家,对用户给出的学术文章段落进行翻译为英文,提高语法、清晰度和整体可读性,尽量使用被动语态,更像美国native writer一些,写作风格尽量精简,提高文章的学术性。以下是需要翻译的学术论文节选:', 264 | '', this.settings.api_key) 265 | } 266 | ) 267 | 268 | // TODO: 中译英 269 | this.addCommand({ 270 | id: "question_for_mantou", 271 | name: "🐶向馒头提问", 272 | editorCallback: (editor: Editor) => { 273 | let editorView = this.app.workspace.getActiveViewOfType(MarkdownView); 274 | let role = META_ROLE 275 | 276 | if (!editorView) { 277 | return 278 | }else{ 279 | const markdownText = editor.getValue(); 280 | let temp_role = extractRoleValue(markdownText) 281 | if(temp_role.length != 0){ 282 | role = temp_role 283 | } 284 | } 285 | 286 | operation_on_selection( 287 | editor, 288 | role, 289 | '', 290 | '', this.settings.api_key, 291 | (x:string)=> { 292 | x = x.replace( /\[/gi, "$$$").replace( /\]/gi, "$$$").replace( /\(/gi, "$").replace( /\)/gi, "$").replace("\$", "$"); 293 | x = addGreaterThanSign(x) 294 | x = x 295 | return x 296 | }, 297 | (x:string)=>{ 298 | x = `> [!NOTE] ${x}` 299 | return x 300 | }); 301 | } 302 | } 303 | ) 304 | 305 | this.addCommand({ 306 | id: "summarize_general", 307 | name: "🐶要点归纳", 308 | editorCallback: (editor: Editor) => operation_on_selection( 309 | editor, 310 | META_ROLE, 311 | '以Markdown要点的形式总结以下内容:', 312 | '', this.settings.api_key) 313 | } 314 | ) 315 | 316 | // TODO: 摘要 317 | this.addCommand({ 318 | id: "summarize_ppt", 319 | name: "🐶段落精读(PPT)", 320 | editorCallback: (editor: Editor) => operation_on_selection( 321 | editor, 322 | META_ROLE, 323 | '对以下内容进行总结归纳,从中提取关键论点,并明确、具体地提取对应的支撑论据(包括实验数据、相关文献、理论结果等):', 324 | `\n以Markdown格式输出 325 | ## 关键论点1: [论点表述] 326 | - [支撑论据1] 327 | - [支撑论据2] 328 | - [支撑论据3] 329 | - ... 330 | 331 | --- 332 | 333 | ## 关键论点2:[论点表述] 334 | - [支撑论据1] 335 | - [支撑论据2] 336 | - [支撑论据3] 337 | - ... 338 | 339 | --- 340 | ...`, this.settings.api_key) 341 | } 342 | ) 343 | 344 | // TODO: 英文润色 345 | this.addCommand({ 346 | id: "polish_en", 347 | name: "🐶英文润色", 348 | editorCallback: (editor: Editor) => operation_on_selection( 349 | editor, 350 | 'You are a helpful assistant who are good at academic English.', 351 | "I'm a researcher working in artificial intelligence. I want you to act as an English translator, spelling corrector, and improver. Please polish my text to meet the academic standard in my research area, your goal is to improve the spelling, grammar, clarity, concision, and overall readability. When necessary, rewrite the whole sentence. Furthermore, list all modifications and explain the reasons to do so in a markdown table. Now please polish the following text:", 352 | '', this.settings.api_key) 353 | } 354 | ) 355 | 356 | this.addCommand({ 357 | id: "polish_cn", 358 | name: "🐶中文润色", 359 | editorCallback: (editor: Editor) => operation_on_selection( 360 | editor, 361 | 'You are a helpful assistant who are good at writing', 362 | "请充分理解下面文本的含义,重新表述,要求用词严谨、正确、简洁精炼,不得擅自修改其中的专业术语,表述符合中文表达习惯和中文语序,且符合学术写作要求:", 363 | '', this.settings.api_key) 364 | } 365 | ) 366 | 367 | // This adds a settings tab so the user can configure various aspects of the plugin 368 | this.addSettingTab(new MantouAISettingTab(this.app, this)); 369 | } 370 | 371 | onunload() { 372 | 373 | } 374 | 375 | async loadSettings() { 376 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 377 | } 378 | 379 | async saveSettings() { 380 | await this.saveData(this.settings); 381 | } 382 | } 383 | 384 | 385 | class MantouAISettingTab extends PluginSettingTab { 386 | plugin: MantouAIPlugin; 387 | 388 | constructor(app: App, plugin: MantouAIPlugin) { 389 | super(app, plugin); 390 | this.plugin = plugin; 391 | } 392 | 393 | display(): void { 394 | const {containerEl} = this; 395 | 396 | containerEl.empty(); 397 | 398 | new Setting(containerEl) 399 | .setName('Dashscope API Key') 400 | .addText(text => text 401 | .setPlaceholder('APIKEY: sk-xxxxxx') 402 | .setValue(this.plugin.settings.api_key) 403 | .onChange(async (value) => { 404 | this.plugin.settings.api_key = value; 405 | await this.plugin.saveSettings(); 406 | })); 407 | } 408 | } 409 | --------------------------------------------------------------------------------