├── .gitignore
├── icons
└── icon.png
├── src
├── content
│ ├── question
│ │ ├── handlers
│ │ │ ├── answerFormatter.js
│ │ │ ├── questionToImage.js
│ │ │ ├── questionHandlers.js
│ │ │ └── autoFill.js
│ │ └── questionExtractor.js
│ ├── utils
│ │ └── notification.js
│ ├── ai
│ │ ├── tongyi-content.js
│ │ ├── doubao-content.js
│ │ ├── deepseek-content.js
│ │ ├── gemini-content.js
│ │ ├── chatglm-content.js
│ │ ├── chatgpt-content.js
│ │ ├── xinghuo-content.js
│ │ ├── yiyan-content.js
│ │ ├── debugPanel.js
│ │ └── kimi-content.js
│ └── main.js
├── config
│ └── config.js
└── popup
│ ├── popup.js
│ └── popup.html
├── LICENSE
├── README.md
├── manifest.json
└── test
├── test.html
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # 忽略 release 目录及其内容
2 | release/
3 | testhtml/
--------------------------------------------------------------------------------
/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Trashwbin/MultiAI-Answer-cx/HEAD/icons/icon.png
--------------------------------------------------------------------------------
/src/content/question/handlers/answerFormatter.js:
--------------------------------------------------------------------------------
1 | // 格式化答案和解析
2 | function formatAnswerWithAnalysis(answer) {
3 | // 初始化结果对象
4 | const result = {
5 | answer: '',
6 | analysis: ''
7 | };
8 |
9 | // 如果没有答案,直接返回空结果
10 | if (!answer || typeof answer !== 'string') {
11 | return result;
12 | }
13 |
14 | // 提取答案和解析
15 | // 首先尝试匹配"答案:"后面的内容
16 | const answerMatch = answer.match(/答案[::]\s*([\s\S]+?)(?=\s*解析[::]|$)/);
17 | const analysisMatch = answer.match(/解析[::]\s*([\s\S]+)$/);
18 |
19 | // 设置答案
20 | if (answerMatch) {
21 | result.answer = answerMatch[1].trim();
22 | } else {
23 | // 如果没有找到"答案:"标记,尝试直接提取第一部分内容
24 | const firstPart = answer.split(/\s*解析[::]/)[0];
25 | result.answer = firstPart.trim();
26 | }
27 |
28 | // 设置解析
29 | if (analysisMatch) {
30 | result.analysis = analysisMatch[1].trim();
31 | }
32 |
33 | return result;
34 | }
35 |
36 | // 导出函数
37 | window.formatAnswerWithAnalysis = formatAnswerWithAnalysis;
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Trashwbin
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MultiAI Answer
2 |
3 |
67 | `;
68 |
69 | // 添加单选框切换事件
70 | const radios = container.querySelectorAll('input[type="radio"]');
71 | const customInput = container.querySelector('.custom-option-input');
72 | radios.forEach(radio => {
73 | radio.addEventListener('change', () => {
74 | customInput.disabled = radio.value !== 'other';
75 | if (radio.value === 'other') {
76 | customInput.focus();
77 | }
78 | });
79 | });
80 | }
81 |
82 | return container;
83 | }
84 |
85 | getAnswer() {
86 | const container = document.querySelector(`.question-row-${this.questionNum} .final-answer`);
87 | if (!container) return '';
88 |
89 | if (this.isMultiple) {
90 | const input = container.querySelector('.multiple-choice-input input');
91 | // 直接返回排序后的大写字母
92 | return input ? input.value.replace(/[^a-zA-Z]/g, '').toUpperCase().split('').sort().join('') : '';
93 | } else {
94 | const radioChecked = container.querySelector('input[type="radio"]:checked');
95 | if (!radioChecked) return '';
96 |
97 | if (radioChecked.value === 'other') {
98 | const customInput = container.querySelector('.custom-option-input');
99 | return customInput.value.trim();
100 | }
101 |
102 | return radioChecked.value;
103 | }
104 | }
105 | }
106 |
107 | // 填空题处理器
108 | class BlankHandler extends QuestionHandler {
109 | createEditor() {
110 | const container = document.createElement('div');
111 | container.className = 'editable-final-answer';
112 | container.style.userSelect = 'text';
113 |
114 | // 解析现有答案
115 | const answers = [];
116 | if (this.answer) {
117 | // 匹配所有的"第X空:答案"格式
118 | const matches = this.answer.split('\n').filter(line => line.trim());
119 | matches.forEach(match => {
120 | const parts = match.match(/第(\d+)空[::](.+)/);
121 | if (parts) {
122 | const [_, num, content] = parts;
123 | const index = parseInt(num) - 1;
124 | while (answers.length <= index) {
125 | answers.push('');
126 | }
127 | answers[index] = content.trim();
128 | }
129 | });
130 | }
131 |
132 | // 如果没有答案,至少创建一个空白输入框
133 | if (answers.length === 0) {
134 | answers.push('');
135 | }
136 |
137 | // 创建填空输入框
138 | container.innerHTML = `
139 |
477 |
478 |
479 |
484 |
485 |
486 |
MultiAI Answer v1.0.3
487 |
510 |
511 |
515 |
519 |
520 |
531 |
532 |
533 |
534 |
535 |
543 |
544 |
545 | -
546 | 总开关
547 | 点击右上角按钮可以快速开启/关闭插件,所有功能将同时被启用或禁用
548 |
549 | -
550 | 解除限制功能
551 |
552 | - 解除粘贴限制:允许在答题区域自由粘贴文本
553 | - 题目复制按钮:在每道题目旁添加快捷复制按钮
554 | - 解除选择限制:允许选择和复制页面任何内容
555 |
556 |
557 | -
558 | 题目列表功能
559 |
560 | - 支持查看当前试卷的所有题目
561 | - 可以批量选择多个题目
562 | - 提供题目预览和快速复制
563 | - 显示题目类型和填空数量
564 |
565 |
566 | -
567 | AI 答案功能
568 |
569 | - 支持多个主流 AI 模型(ChatGPT、Gemini等)
570 | - 可自定义 AI 权重和启用状态
571 | - 提供多种解答模式(简洁、详细等)
572 | - 支持查看完整答案和解析
573 | - 可以一键发送到所有已启用的 AI
574 |
575 |
576 | -
577 | 运行模式
578 |
579 | - 稳定模式:逐个发送题目,等待响应
580 | - 快速模式:并行发送所有题目
581 | - 可在 AI 配置界面切换模式
582 |
583 |
584 | -
585 | 注意事项
586 |
587 | - 切换功能开关后需要刷新页面
588 | - 考试页面需要先跳转到整卷预览
589 | - 使用 AI 答案前请先选择题目
590 | - 答案仅供参考,请自行判断准确性
591 | - 请合理使用功能,遵守相关规定
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | // AI 配置
2 | const AI_CONFIG = {
3 | kimi: {
4 | name: 'Kimi',
5 | color: '#FF6B6B',
6 | url: 'https://www.kimi.com/'
7 | },
8 | deepseek: {
9 | name: 'DeepSeek',
10 | color: '#4ECDC4',
11 | url: 'https://chat.deepseek.com/'
12 | },
13 | tongyi: {
14 | name: '通义千问',
15 | color: '#45B7D1',
16 | url: 'https://tongyi.aliyun.com/'
17 | },
18 | chatglm: {
19 | name: '智谱清言',
20 | color: '#2454FF',
21 | url: 'https://chatglm.cn/'
22 | },
23 | doubao: {
24 | name: '豆包',
25 | color: '#FF6A00',
26 | url: 'https://www.doubao.com/'
27 | },
28 | yiyan: {
29 | name: '文心一言',
30 | color: '#4B5CC4',
31 | url: 'https://yiyan.baidu.com/'
32 | },
33 | xinghuo: {
34 | name: '讯飞星火',
35 | color: '#1890FF',
36 | url: 'https://xinghuo.xfyun.cn/desk'
37 | },
38 | chatgpt: {
39 | name: 'ChatGPT',
40 | color: '#10A37F',
41 | url: 'https://chatgpt.com/'
42 | },
43 | gemini: {
44 | name: 'Gemini',
45 | color: '#1A73E8',
46 | url: 'https://gemini.google.com/'
47 | }
48 | };
49 |
50 | // 存储 AI 标签页 ID
51 | const aiTabs = {};
52 | let currentTabId = null;
53 |
54 | // 运行模式
55 | let runningMode = 'stable'; // 'stable' 或 'fast'
56 | let pendingResponses = new Set(); // 用于跟踪待响应的AI
57 |
58 | // 初始化 UI
59 | function initUI() {
60 | const aiSelection = document.getElementById('aiSelection');
61 | const aiResults = document.getElementById('aiResults');
62 |
63 | // 初始化模式选择
64 | document.querySelectorAll('input[name="mode"]').forEach(radio => {
65 | radio.addEventListener('change', (e) => {
66 | runningMode = e.target.value;
67 | });
68 | });
69 |
70 | // 创建 AI 选择框
71 | Object.entries(AI_CONFIG).forEach(([aiType, config]) => {
72 | const checkbox = document.createElement('label');
73 | checkbox.className = 'ai-checkbox';
74 | checkbox.innerHTML = `
75 |