├── .gitignore ├── LICENSE ├── README.md ├── config.ini ├── fetch.py ├── ielts_writing_task2_questions.txt ├── package.json ├── requirements.txt ├── static ├── images │ └── favicon.ico └── styles.css ├── templates ├── answer.html └── index.html └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Compiled Static libraries 11 | *.a 12 | *.lib 13 | 14 | # Executables 15 | *.exe 16 | 17 | # DUB 18 | .dub 19 | docs.json 20 | __dummy.html 21 | docs/ 22 | 23 | # Code coverage 24 | *.lst 25 | 26 | api_key.txt 27 | __pycache__/ 28 | *.pytest_cache/ 29 | *.idea 30 | *.vscode 31 | .DS_Store 32 | **/.DS_Store 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 cool-chicken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift_Ielts 🎓✍️ 2 | 3 | ### 这是一个为雅思备考同学设计的大作文智能批改程序 4 | 5 | ## 项目简介 📚 6 | 7 | Swift_Ielts 是一个基于 Flask 框架的 Web 应用程序,旨在帮助雅思备考的同学进行大作文的智能批改。用户可以提交自己的作文,系统会根据雅思写作评分标准进行批改,并提供详细的反馈和建议。 8 | 9 | ## 功能特性 ✨ 10 | 11 | - **作文批改**:根据雅思写作评分标准对用户提交的作文进行批改,提供评分、反馈、语法错误和建议。 12 | - **参考范文**:提供参考范文,帮助用户更好地理解和改进自己的作文。 13 | - **更多范文**:用户可以获取更多的参考范文,进一步提升写作水平。 14 | 15 | ## 安装与运行 🚀 16 | 17 | ### 环境要求 🛠️ 18 | 19 | - Python 3.8+ 20 | - Flask 21 | - Flask-CORS 22 | - BeautifulSoup4 23 | - Requests 24 | - OpenAI 25 | 26 | ### 安装步骤 📥 27 | 28 | 1. 克隆项目到本地: 29 | ```sh 30 | git clone https://github.com/yourusername/Swift_Ielts.git 31 | cd Swift_Ielts 32 | ``` 33 | 34 | 2. 创建并激活虚拟环境: 35 | ```sh 36 | python -m venv venv 37 | source venv/bin/activate # 对于 Windows 用户,使用 `venv\Scripts\activate` 38 | ``` 39 | 40 | 3. 安装依赖包: 41 | ```sh 42 | pip install -r requirements.txt 43 | ``` 44 | 45 | 4. 设置环境变量: 46 | ```sh 47 | export KIMI_API_KEY="your_actual_api_key_here" 48 | ``` 49 | 申请步骤: 50 | 1. 访问 Moonshot API Keys 51 | 2. 新建 API Key 52 | 3. 将 API Key 复制到 your_actual_api_key_here处 53 | 54 | 5. 运行应用程序: 55 | ```sh 56 | python fetch.py 57 | ``` 58 | 59 | 6. 在浏览器中打开 http://127.0.0.1:5000 访问应用程序。 60 | 61 | ## 使用说明 📖 62 | 63 | 1. 打开应用程序后,选择或输入作文题目。 64 | 2. 在文本框中输入你的作文内容。 65 | 3. 点击“提交批改”按钮,系统会对你的作文进行批改,并显示评分和反馈。 66 | 4. 点击“更多范文”按钮,可以获取更多的参考范文。 67 | 68 | ## 文件结构 🗂️ 69 | 70 | ``` 71 | Swift_Ielts/ 72 | │ 73 | ├── static/ 74 | │ ├── styles.css # 样式文件 75 | │ 76 | ├── templates/ 77 | │ ├── index.html # 主页面 78 | │ ├── answer.html # 作文批改页面 79 | │ 80 | ├── fetch.py # 主应用程序文件 81 | ├── requirements.txt # 依赖包列表 82 | ├── README.md # 项目说明文件 83 | ``` 84 | ## 引用 🔗 85 | 86 | - 本项目使用了 [ieltsliz.com](https://ieltsliz.com) 提取的历年雅思作文题目。 87 | 88 | ## 贡献指南 🤝 89 | 90 | 欢迎对本项目进行贡献!如果你有任何建议或发现了问题,请提交 issue 或 pull request。 91 | 92 | ## 许可证 📄 93 | 94 | 本项目采用 MIT 许可证。详情请参阅 LICENSE 文件。 -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [SETTINGS] 2 | BASE_URL = "https://ieltsliz.com/100-ielts-essay-questions/" # 替换为实际的雅思作文题目URL 3 | STORAGE_PATH = database/questions 4 | 5 | -------------------------------------------------------------------------------- /fetch.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request, render_template 2 | from flask_cors import CORS 3 | from bs4 import BeautifulSoup 4 | import requests 5 | import os 6 | import json 7 | from openai import OpenAI 8 | import re 9 | 10 | app = Flask(__name__, static_folder="static", template_folder="templates") 11 | CORS(app) 12 | 13 | # 预定义可爬取的 URL 列表(确保合法性和安全性) 14 | ALLOWED_URLS = { 15 | 'ieltsliz_communication-and-personality': "https://ieltsliz.com/100-ielts-essay-questions/communication-and-personality/", 16 | 'ieltsliz_business-and-money': 'https://ieltsliz.com/100-ielts-essay-questions/business-and-money/', 17 | 'ieltsliz_crime-and-punishment': 'https://ieltsliz.com/100-ielts-essay-questions/crime-and-punishment/', 18 | 'ieltsliz_environment': 'https://ieltsliz.com/100-ielts-essay-questions/environment', 19 | 'ieltsliz_education': 'https://ieltsliz.com/100-ielts-essay-questions/education/', 20 | 'ieltsliz_health': 'https://ieltsliz.com/100-ielts-essay-questions/health/', 21 | 'ieltsliz_technology': 'https://ieltsliz.com/100-ielts-essay-questions/technology/', 22 | 'ieltsliz_society': 'https://ieltsliz.com/100-ielts-essay-questions/society/', 23 | 'ieltsliz_transport': 'https://ieltsliz.com/100-ielts-essay-questions/transport/', 24 | 'ieltsliz_work': 'https://ieltsliz.com/100-ielts-essay-questions/work/'} 25 | 26 | def crawl_questions(url): 27 | try: 28 | response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}) 29 | soup = BeautifulSoup(response.text, 'html.parser') 30 | content_div = soup.find('div', class_='entry-content') 31 | blockquotes = content_div.find_all('blockquote') if content_div else [] 32 | 33 | questions = [] 34 | for blockquote in blockquotes: 35 | paragraphs = blockquote.find_all('p') 36 | text_parts = [] 37 | for p in paragraphs: 38 | text = p.get_text() 39 | if text: 40 | text_parts.append(text) 41 | # 将段落用换行符连接起来 42 | full_text = '\n'.join(text_parts) 43 | if full_text: 44 | questions.append(full_text) 45 | return questions 46 | except Exception as e: 47 | print(f"An error occurred: {e}") 48 | return None 49 | 50 | 51 | def call_kimi_api(prompt): 52 | try: 53 | client = OpenAI( 54 | api_key = KIMI_API_KEY, 55 | base_url="https://api.moonshot.cn/v1", 56 | ) 57 | response = client.chat.completions.create( 58 | model="moonshot-v1-128k", 59 | messages=[{"role": "user", "content": prompt}], 60 | temperature=0.4 61 | ) 62 | return response 63 | except Exception as e: 64 | raise Exception(f"API调用失败: {str(e)}") 65 | 66 | @app.route('/') 67 | def home(): 68 | return render_template('index.html') 69 | 70 | @app.route('/api/crawl', methods=['POST']) 71 | def handle_crawl(): 72 | data = request.json 73 | url_key = data.get('url_key') 74 | custom_url = data.get('custom_url') 75 | 76 | # 验证 URL 是否合法 77 | if url_key: 78 | url = ALLOWED_URLS.get(url_key) 79 | elif custom_url and 'ieltsliz.com' in custom_url: # 限制自定义域名 80 | url = custom_url 81 | else: 82 | return jsonify({'error': 'Invalid URL'}) 83 | 84 | questions = crawl_questions(url) 85 | if questions is not None: 86 | return jsonify({'questions': questions}) 87 | else: 88 | return jsonify({'error': '爬取失败,请检查URL或稍后重试'}) 89 | 90 | @app.route('/answer') 91 | def answer(): 92 | return render_template('answer.html') 93 | 94 | @app.route('/api/evaluate', methods=['POST']) 95 | def evaluate_essay(): 96 | data = request.json 97 | question = data.get('question') 98 | essay = data.get('essay') 99 | 100 | if not all([question, essay]): 101 | return jsonify({'error': '缺少题目或作文内容'}) 102 | 103 | # 构建评分提示词 104 | prompt = f""" 105 | 请根据英语雅思写作评分标准对以下作文进行严谨批改(至少250词): 106 | - 作文题目:{question} 107 | - 学生作文:{essay} 108 | 109 | 要求返回JSON格式: 110 | {{ 111 | "score": 分数(0-9), 112 | "feedback": "从写作任务回应情况、连贯与衔接、词汇丰富程度、语法准确性、句子结构多样性等方面给出评价", 113 | "errors": ["语法错误1", "语法错误2", ...], 114 | "suggestions": ["哪些句子逻辑不清晰", "用词不当","高级替换","词汇替换" ...], 115 | "reference": "本题参考范文" 116 | }} 117 | """ 118 | 119 | try: 120 | # 调用 Kimi API 121 | result = call_kimi_api(prompt) 122 | feedback_content = result.choices[0].message.content 123 | feedback_content = feedback_content.strip() 124 | feedback_content = re.sub(r'\s+', ' ', feedback_content) 125 | print("Kimi API Response:") 126 | print(feedback_content) 127 | # 解析 Kimi 的回复 128 | feedback_json = json.loads(feedback_content) 129 | print("Kimi API Response (JSON):") 130 | print(feedback_json) 131 | score = feedback_json.get('score') 132 | feedback = feedback_json.get('feedback') 133 | errors = feedback_json.get('errors', []) 134 | suggestions = feedback_json.get('suggestions', []) 135 | reference = feedback_json.get('reference', '') 136 | 137 | return jsonify({ 138 | "score": score, 139 | "feedback": feedback, 140 | "errors": errors, 141 | "suggestions": suggestions, 142 | "reference": reference 143 | }) 144 | 145 | except json.JSONDecodeError: 146 | return jsonify({'error': 'Kimi 返回的数据格式无效'}) 147 | except KeyError as e: 148 | return jsonify({'error': f'解析 Kimi 响应失败: 缺少字段 {str(e)}'}) 149 | except Exception as e: 150 | print(feedback_content) 151 | return jsonify({'error': f'API调用失败: {str(e)}'}) 152 | 153 | @app.route('/api/more_references', methods=['POST']) 154 | def get_more_references(): 155 | print("get_more_references") 156 | data = request.json 157 | question = data.get('question') 158 | 159 | if not question: 160 | return jsonify({'error': '缺少题目'}) 161 | 162 | # 构建获取更多范文的提示词 163 | prompt2 = f""" 164 | 请根据以下雅思作文题目生成一篇新的英文范文(至少250词): 165 | - 作文题目:{question} 166 | 167 | 要求返回严格的JSON格式: 168 | {{ 169 | 'reference2': '本题参考范文' 170 | }} 171 | """ 172 | 173 | try: 174 | # 调用 Kimi API 175 | result2 = call_kimi_api(prompt2) 176 | feedback_content2 = result2.choices[0].message.content 177 | print("Kimi API Response2:") 178 | print(type(feedback_content2)) 179 | # 解析 Kimi 的回复 180 | feedback_content2 = feedback_content2.strip() 181 | feedback_content2 = re.sub(r'\s+', ' ', feedback_content2) 182 | feedback_json2 = json.loads(feedback_content2) 183 | print("Kimi API Response2 (JSON):") 184 | print(type(feedback_json2)) 185 | reference2 = feedback_json2.get('reference2', '') 186 | print("reference2:") 187 | print(reference2) 188 | 189 | return jsonify({ 190 | "reference2": reference2 191 | }) 192 | 193 | except json.JSONDecodeError: 194 | return jsonify({'error': 'Kimi 返回的数据格式无效'}) 195 | except KeyError as e: 196 | return jsonify({'error': f'解析 Kimi 响应失败: 缺少字段 {str(e)}'}) 197 | except Exception as e: 198 | return jsonify({'error': f'API调用失败: {str(e)}'}) 199 | 200 | if __name__ == '__main__': 201 | # 从环境变量中获取 Kimi API Key 202 | KIMI_API_KEY = os.getenv("KIMI_API_KEY") 203 | if not KIMI_API_KEY: 204 | KIMI_API_KEY = input("请输入 Kimi API Key: ") 205 | if not KIMI_API_KEY: 206 | raise ValueError("未提供 Kimi API Key") 207 | 208 | app.run(debug=True) -------------------------------------------------------------------------------- /ielts_writing_task2_questions.txt: -------------------------------------------------------------------------------- 1 | 1. Some people fail in school, but end up being successful in life. 2 | 3 | 2. Some people think that ambition is essential for success. To what extent do you agree? 4 | 5 | 3. Some people enjoy change in life, while other people try to avoid it. What are the advantages and disadvantages of both approaches? 6 | 7 | 4. Some people think that teenagers are happier than adults, but others do not agree. Discuss both sides and give your opinion. 8 | 9 | 5. Some celebrities feel that the media violates their personal space and shares their private life publicly, while others feel it is just part of being famous. 10 | 11 | 6. Discuss the advantages and disadvantages of using technology for communicating. (similar to the question above) 12 | 13 | 7. Many young children have unsupervised access to the internet and are using the internet to socialise with others. This has can lead to a number of dangerous situations which can be threatening for children. 14 | 15 | 8. Some people think that being able to communicate with others online is breaking down geographical barriers and enabling people, who would normally never have the chance to meet, to communicate. 16 | 17 | 9. Some people think that intelligence is innate, while others think that we can improve our intelligence through learning. 18 | 19 | 10. Discuss both sides and give your opinion. (common question) 20 | 21 | 11. Many management interviews are based around assessing not only a person’s level of experience and knowledge but also their personality. 22 | 23 | 12. Some people think that women are generally more peaceful than men as their characters are naturally more nurturing than aggressive. 24 | 25 | 13. Some people think our first impression of someone is important, while others think we should not judge another person so quickly and should take our time to know them better. Discuss both sides and give your opinion. (2022, 2023) 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ielts-essay-evaluation-frontend", 3 | "version": "1.0.0", 4 | "description": "Frontend for IELTS essay evaluation app", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "http-server ./", 8 | "deploy": "gh-pages -d ." 9 | }, 10 | "dependencies": { 11 | "gh-pages": "^4.0.0", 12 | "http-server": "^14.1.1" 13 | }, 14 | "devDependencies": {}, 15 | "keywords": [ 16 | "ielts", 17 | "essay", 18 | "evaluation", 19 | "frontend" 20 | ], 21 | "author": "cool-chicken", 22 | "license": "MIT" 23 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.0 2 | Flask-Cors==4.0.0 3 | beautifulsoup4==4.12.2 4 | requests==2.31.0 -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cool-chicken/Swift_Ielts/b9df7e9df6008a6491f2f689216ab5434a629daf/static/images/favicon.ico -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 20px; 4 | background-color: #f0f2f5; 5 | } 6 | 7 | .container { 8 | max-width: 800px; 9 | margin: 0 auto; 10 | padding: 20px; 11 | background: white; 12 | border-radius: 8px; 13 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 14 | } 15 | 16 | .button-group button { 17 | margin: 5px; 18 | padding: 10px 15px; 19 | background: #4CAF50; 20 | color: white; 21 | border: none; 22 | border-radius: 4px; 23 | cursor: pointer; 24 | } 25 | 26 | .custom-url { 27 | margin: 15px 0; 28 | } 29 | 30 | .custom-url input { 31 | width: 300px; 32 | padding: 8px; 33 | margin-right: 10px; 34 | } 35 | 36 | .result-box { 37 | margin-top: 20px; 38 | padding: 15px; 39 | border: 1px solid #ddd; 40 | border-radius: 4px; 41 | } 42 | 43 | .question-item { 44 | margin: 10px 0; 45 | padding: 10px; 46 | background: #f8f9fa; 47 | border-left: 4px solid #4CAF50; 48 | } 49 | 50 | .error { 51 | color: #ff4444; 52 | margin-top: 10px; 53 | } 54 | 55 | .loading { 56 | display: flex; 57 | align-items: center; /* 垂直居中 */ 58 | justify-content: center; /* 水平居中 */ 59 | gap: 20px; /* 动画和文本之间的间距 */ 60 | padding: 10px; 61 | background: rgba(0, 0, 0, 0); /* 背景颜色 */ 62 | border-radius: 8px; /* 圆角 */ 63 | color: rgb(12, 12, 12); /* 文本颜色 */ 64 | font-size: 16px; /* 文本大小 */ 65 | } 66 | 67 | .scalableBox { 68 | width: 40px; 69 | height: 70px; 70 | } 71 | 72 | .apringBox { 73 | transition: stroke 0.3s; 74 | } 75 | 76 | .apwormOneBox, 77 | .apwormTwoBox { 78 | animation-duration: 3s; 79 | animation-iteration-count: infinite; 80 | } 81 | 82 | .apwormTwoBox { 83 | animation-name: worm2; 84 | visibility: hidden; 85 | } 86 | 87 | .apwormOneBox { 88 | animation-name: worm1; 89 | } 90 | 91 | .loading-text { 92 | font-family: Arial, sans-serif; 93 | font-weight: bold; 94 | } 95 | 96 | @keyframes worm1 { 97 | from { 98 | animation-timing-function: ease-in-out; 99 | stroke-dashoffset: -87.96; 100 | } 101 | 102 | 20% { 103 | animation-timing-function: ease-in; 104 | stroke-dashoffset: 0; 105 | } 106 | 107 | 60% { 108 | stroke-dashoffset: -791.68; 109 | visibility: visible; 110 | } 111 | 112 | 60.1%, 113 | to { 114 | stroke-dashoffset: -791.68; 115 | visibility: hidden; 116 | } 117 | } 118 | 119 | @keyframes worm2 { 120 | from, 121 | 60% { 122 | stroke-dashoffset: -87.96; 123 | visibility: hidden; 124 | } 125 | 126 | 60.1% { 127 | animation-timing-function: cubic-bezier(0, 0, 0.5, 0.75); 128 | stroke-dashoffset: -87.96; 129 | visibility: visible; 130 | } 131 | 132 | 77% { 133 | animation-timing-function: cubic-bezier(0.5, 0.25, 0.5, 0.88); 134 | stroke-dashoffset: -340; 135 | visibility: visible; 136 | } 137 | 138 | to { 139 | stroke-dashoffset: -669.92; 140 | visibility: visible; 141 | } 142 | } 143 | /* 新增以下样式 */ 144 | .question-content { 145 | padding: 15px; 146 | margin: 20px 0; 147 | background: #f8f9fa; 148 | border-radius: 4px; 149 | } 150 | 151 | textarea { 152 | width: 100%; 153 | padding: 12px; 154 | margin: 10px 0; 155 | border: 1px solid #ddd; 156 | border-radius: 4px; 157 | resize: vertical; 158 | } 159 | 160 | .submit-button { 161 | margin: 10px 0; 162 | padding-top: 20px; 163 | background: #2196F3; 164 | color: white; 165 | padding: 12px 24px; 166 | border: none; 167 | border-radius: 10px; 168 | cursor: pointer; 169 | font-size: 16px; 170 | float: right; 171 | } 172 | 173 | .result-area { 174 | margin-top: 65px; 175 | padding: 15px; 176 | border-radius: 4px; 177 | } 178 | 179 | .result-area.success { 180 | background: #e8f5e9; 181 | border-left: 4px solid #4CAF50; 182 | } 183 | 184 | .result-area.error { 185 | background: #ffebee; 186 | border-left: 4px solid #ff4444; 187 | } 188 | /* 如果希望输入框更大 */ 189 | textarea { 190 | font-size: 18px; /* 进一步增大字体 */ 191 | padding: 15px; /* 增加内边距 */ 192 | min-height: 200px; /* 设置最小高度 */ 193 | resize: vertical; /* 设置垂直方向可拉伸 */ 194 | width: 100%; /* 设置宽度为 100% */ 195 | border: 2px solid #a5b29d; /* 设置边框 */ 196 | border-radius: 10px; /* 设置圆角 */ 197 | box-sizing: border-box; /* 设置盒模型 */ 198 | margin: 5px 0px; /* 设置外边距 */ 199 | } 200 | 201 | /* 输入框获得焦点时高亮 */ 202 | textarea:focus { 203 | border-color: #2196F3; 204 | box-shadow: 0 0 5px rgba(33,150,243,0.3); 205 | outline: none; 206 | } 207 | .back-button { 208 | background: #650404b2; 209 | color: white; 210 | padding: 8px 16px; 211 | border: none; 212 | border-radius: 4px; 213 | cursor: pointer; 214 | margin-bottom: 20px; 215 | } 216 | 217 | .back-button:hover { 218 | background:#fa8e5c; 219 | } 220 | 221 | .more-references-button { 222 | padding-top: 10px; 223 | background-color: #f9e4e4; 224 | color: rgb(136, 85, 65); 225 | border: none; 226 | padding: 10px 20px; /* 内边距 */ 227 | text-align: center; /* 文字居中 */ 228 | text-decoration: none; /* 无下划线 */ 229 | display: inline-block; /* 内联块元素 */ 230 | font-size: 16px; /* 字体大小 */ 231 | margin: 10px 2px; /* 外边距 */ 232 | cursor: pointer; /* 鼠标指针 */ 233 | border-radius: 4px; /* 圆角 */ 234 | transition-duration: 0.05s; /* 过渡时间 */ 235 | } 236 | 237 | .more-references-button:hover { 238 | background-color: rgb(245, 195, 195); /* 悬停时背景变为白色 */ 239 | color: #0f0b44ba; /* 悬停时文字变为黑色 */ 240 | } 241 | 242 | /* 其他样式... */ -------------------------------------------------------------------------------- /templates/answer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |