newversison

zhu1ku2 v1.0.3
pzvjlefpx 6 months ago
parent 91e4a0f324
commit 39b286a44f

@ -184,11 +184,6 @@ class FastLLMClient:
stream=False
)
print("==================deepseek response===================")
print(response.choices[0].message.content)
print("================deepseek response end=================")
result = response.model_dump()
return result['choices'][0]['message']['content']

@ -9,6 +9,8 @@ import asyncio
import logging
from typing import Dict, List, Optional, Any
from config import get_config
import re
from typing import Dict, Optional, List, Union
# 配置调试日志
logging.basicConfig(level=logging.DEBUG)
@ -22,6 +24,47 @@ from models import ModelProvider, LLMModel
config = get_config()
def robust_json_parse(result: str) -> Dict:
"""增强的JSON解析函数处理常见格式问题"""
def fix_json(json_str: str) -> str:
"""修复常见JSON格式问题"""
# 1. 修复键名引号问题
json_str = re.sub(r"(\w+)\s*:", r'"\1":', json_str)
# 2. 修复字符串值引号问题
json_str = re.sub(r':\s*([\'"]?)([^\'",]+?)\1([,\]])', r': "\2"\3', json_str)
# 3. 移除尾随逗号
json_str = re.sub(r",\s*([}\]])", r"\1", json_str)
# 4. 修复数组中的字符串引号
json_str = re.sub(r'\[\s*([\'"]?)([^\'"]+?)\1\s*\]', r'["\2"]', json_str)
return json_str
try:
# 尝试直接解析
return json.loads(result)
except json.JSONDecodeError:
try:
# 尝试修复常见问题后解析
fixed = fix_json(result)
return json.loads(fixed)
except json.JSONDecodeError:
try:
# 尝试提取JSON部分
json_match = re.search(r'\{[\s\S]*\}', result)
if json_match:
return json.loads(fix_json(json_match.group()))
except:
pass
# 最终fallback
return {
"strengths": ["内容已提交"],
"issues": ["AI分析格式异常"],
"suggestions": ["建议重新分析"],
"next_steps": ["继续完善内容"],
"raw_response": result
}
class AIService:
"""AI服务类 - 统一上下文处理"""
@ -41,7 +84,7 @@ class AIService:
if user_settings:
return user_settings
return self.default_settings
def _create_llm_model(self, settings: Dict) -> LLMModel:
"""创建LLM模型对象"""
try:
@ -190,11 +233,12 @@ Please analyze the content with American student writing style in mind, focusing
Provide constructive feedback that helps the student write like an American student would, with natural flow and authentic voice.
请用JSON格式回复包含以下字段讲解文字用中文示范文字用英文
请用完整JSON格式回复包含以下字段讲解文字用中文示范文字用英文
- strengths: 优点列表中文讲解
- issues: 问题列表中文讲解
- suggestions: 改进建议列表中文讲解英文示例要体现美国学生写作风格
- next_steps: 下一步建议列表中文讲解"""
- next_steps: 下一步建议列表中文讲解
"""
else:
prompt = f"""作为专业的{ctx_info['subject']}写作指导老师,请分析学生的{stage_name}
@ -210,64 +254,24 @@ Provide constructive feedback that helps the student write like an American stud
- strengths: 优点列表
- issues: 问题列表
- suggestions: 改进建议列表
- next_steps: 下一步建议列表"""
- next_steps: 下一步建议列表
"""
try:
result = quick_generate(
prompt=prompt,
model=model,
max_tokens=600,
max_tokens=1500,
grade=ctx_info['grade'],
subject=ctx_info['subject'],
topic=ctx_info['topic'],
requirement=f"分析{stage_name}阶段的写作内容",
json_mode=True, # 启用JSON模式
temperature=0.3 # 降低温度以提高准确性
json_mode=True,
temperature=0.3
)
'''
logger.debug(f"[AI响应] 原始AI响应: {result[:200]}...")
# 清理AI响应中的markdown代码块标记和think标签
cleaned_result = result.strip()
# 移除<think>标签及其内容
import re
think_pattern = r'<think>.*?</think>'
cleaned_result = re.sub(think_pattern, '', cleaned_result, flags=re.DOTALL)
# 移除markdown代码块标记
if cleaned_result.startswith('```json'):
cleaned_result = cleaned_result[7:] # 移除开头的```json
if cleaned_result.endswith('```'):
cleaned_result = cleaned_result[:-3] # 移除结尾的```
# 查找JSON内容尝试修复不完整的JSON
json_match = re.search(r'\{.*\}', cleaned_result, re.DOTALL)
'''
# 尝试解析JSON响应
try:
return json.loads(result)
except json.JSONDecodeError:
# 如果解析失败尝试提取JSON部分
import re
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
pass
# 如果仍然失败,返回结构化的默认响应
return {
"strengths": ["内容已提交"],
"issues": ["AI分析格式异常"],
"suggestions": ["建议重新分析"],
"next_steps": ["继续完善内容"],
"raw_response": result
}
# 使用增强的JSON解析
return robust_json_parse(result)
except Exception as e:
raise Exception(f"分析内容失败: {str(e)}")
@ -362,6 +366,199 @@ Consider how well the student demonstrates understanding of the topic and expres
except Exception as e:
raise Exception(f"评估文章失败: {str(e)}")
def check_grammar(self, content: str, context: Dict,
user_settings: Optional[Dict] = None) -> Dict:
"""检查语法错误"""
settings = self._get_ai_settings(user_settings)
model = self._create_llm_model(settings)
# 提取上下文信息
ctx_info = self._extract_context_info(context)
# 根据学科制作不同的提示词
if ctx_info['subject'] == '英语':
prompt = f"""As an experienced American English teacher, please conduct a comprehensive grammar check for this {ctx_info['article_type']} written by a {ctx_info['grade']} student.
Topic: {ctx_info['topic']}
Student's Essay:
{content}
Please identify and correct grammatical errors with a focus on:
1. **Grammar Mistakes**: Subject-verb agreement, verb tenses, articles, prepositions
2. **Sentence Structure**: Run-on sentences, sentence fragments, parallel structure
3. **Punctuation**: Commas, periods, quotation marks, apostrophes
4. **Word Usage**: Wrong word choices, awkward phrasing, informal language
5. **Spelling**: Common spelling errors and typos
For each error found, please provide:
- The original text with error highlighted
- Explanation of the error (in Chinese for understanding)
- Corrected version (in proper English)
- Suggestion for improvement
请用完整JSON格式回复包含以下字段
- overall_assessment: 总体评价中文
- total_errors: 总错误数量
- error_categories: 错误分类统计 {{"grammar": 数量, "punctuation": 数量, "word_usage": 数量, "spelling": 数量}}
- errors: 错误列表每个错误包含
- original_text: 原始错误文本
- error_type: 错误类型
- explanation: 错误解释中文
- corrected_text: 修正后的文本
- suggestion: 改进建议中文
- score: 语法得分0-100
- recommendation: 学习建议中文"""
else:
prompt = f"""作为专业的语文老师,请对这篇{ctx_info['article_type']}进行语法检查:
题目{ctx_info['topic']}
学生作文
{content}
请检查以下方面的语法错误
1. **字词错误**错别字用词不当词语搭配错误
2. **句子错误**病句成分残缺搭配不当语序混乱
3. **标点错误**标点符号使用不当缺失或多余
4. **表达错误**表达不清逻辑混乱修辞不当
5. **格式错误**段落格式书写规范问题
对于每个发现的错误请提供
- 包含错误的原始文本
- 错误类型说明
- 错误解释
- 修改后的正确文本
- 改进建议
请用JSON格式回复包含以下字段
- overall_assessment: 总体评价
- total_errors: 总错误数量
- error_categories: 错误分类统计 {{"word": 数量, "sentence": 数量, "punctuation": 数量, "expression": 数量}}
- errors: 错误列表每个错误包含
- original_text: 原始错误文本
- error_type: 错误类型
- explanation: 错误解释
- corrected_text: 修正后的文本
- suggestion: 改进建议
- score: 语法得分0-100
- recommendation: 学习建议"""
try:
result = quick_generate(
prompt=prompt,
model=model,
max_tokens=1800,
grade=ctx_info['grade'],
subject=ctx_info['subject'],
topic=ctx_info['topic'],
requirement="语法检查",
json_mode=True,
temperature=0.1 # 低温度确保准确性
)
try:
grammar_data = json.loads(result)
except json.JSONDecodeError:
# 尝试提取可能的 JSON 子串并解析
import re
json_match = re.search(r'\{[\s\S]*\}', result)
if json_match:
try:
grammar_data = json.loads(json_match.group(0))
except json.JSONDecodeError:
logger.error("[语法检查] 提取到的JSON片段解析失败")
return {
"overall_assessment": "语法检查暂时不可用",
"total_errors": 0,
"error_categories": {},
"errors": [],
"score": 0,
"recommendation": "AI返回的数据无法解析请稍后重试",
"raw_response": result,
"error": "JSON解析失败提取片段后仍失败"
}
else:
logger.error("[语法检查] AI返回内容非JSON且未能提取JSON片段")
return {
"overall_assessment": "语法检查暂时不可用",
"total_errors": 0,
"error_categories": {},
"errors": [],
"score": 0,
"recommendation": "AI返回的数据格式不正确请稍后重试",
"raw_response": result,
"error": "返回非JSON"
}
# --------- END: 更严格的 JSON 解析 ----------
print("===== 语法检查返回数据 =====")
print(grammar_data)
print("==========================")
# 确保返回数据格式正确且包含必要的字段
grammar_data = self._validate_grammar_data(grammar_data, content)
return grammar_data
except Exception as e:
logger.error(f"语法检查失败: {str(e)}")
return {
"overall_assessment": "语法检查暂时不可用",
"total_errors": 0,
"error_categories": {},
"errors": [],
"score": 0,
"recommendation": "请稍后重试或检查网络连接",
"error": str(e)
}
def _validate_grammar_data(self, grammar_data: Dict, content: str) -> Dict:
"""验证和补充语法检查数据"""
# 确保必要字段存在
required_fields = {
'overall_assessment': '文章内容良好,但需要进一步改进语法准确性。',
'total_errors': 0,
'error_categories': {},
'errors': [],
'score': 100,
'recommendation': '建议继续练习,提高语法准确性。'
}
for field, default_value in required_fields.items():
if field not in grammar_data or grammar_data[field] is None:
grammar_data[field] = default_value
# 确保error_categories是字典格式
if not isinstance(grammar_data.get('error_categories'), dict):
grammar_data['error_categories'] = {}
# 确保errors是列表格式
if not isinstance(grammar_data.get('errors'), list):
grammar_data['errors'] = []
# 计算总错误数如果与error_categories不一致
if grammar_data['total_errors'] == 0 and grammar_data['errors']:
grammar_data['total_errors'] = len(grammar_data['errors'])
# 根据错误数量重新计算分数(如果分数不合理)
error_count = grammar_data['total_errors']
content_length = len(content.strip())
if content_length > 0:
# 每100字错误密度
error_density = error_count / max(content_length / 100, 1)
# 如果AI返回的分数不合理根据错误密度重新计算
if grammar_data['score'] > 90 and error_density > 2:
grammar_data['score'] = max(100 - error_density * 10, 0)
elif grammar_data['score'] < 10 and error_density < 1:
grammar_data['score'] = min(100 - error_density * 5, 100)
# 确保分数在合理范围内
grammar_data['score'] = max(0, min(100, grammar_data['score']))
return grammar_data
def generate_suggestions(self, content: str, context: Dict,
suggestion_type: str = "improvement",
user_settings: Optional[Dict] = None) -> List[str]:
@ -555,7 +752,7 @@ Please provide:
result = quick_generate(
prompt=enhanced_prompt,
model=model,
max_tokens=1000,
max_tokens=1800,
grade=ctx_info['grade'],
subject=ctx_info['subject'],
topic=ctx_info['topic'],
@ -972,4 +1169,9 @@ def sync_test_connection(user_settings: Dict) -> bool:
def sync_health_check() -> Dict[str, Any]:
"""同步健康检查"""
return ai_service.health_check()
return ai_service.health_check()
def sync_check_grammar(content: str, context: Dict,
user_settings: Optional[Dict] = None) -> Dict:
"""同步语法检查"""
return ai_service.check_grammar(content, context, user_settings)

@ -73,6 +73,11 @@ class ProjectDAO:
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def save_grammar_check(self, project_id: int, grammar_data: Dict[str, Any]) -> Optional[Dict]:
"""保存语法检查结果"""
grammar_json = json.dumps(grammar_data, ensure_ascii=False)
return ProjectStorage.update_project(project_id, grammar_check=grammar_json)
def create_project(self, user_id: int, title: str, topic: str, article_type: str, subject: str, grade: str = '初中') -> Dict:
"""创建写作项目"""
return ProjectStorage.create_project(

@ -7,7 +7,7 @@ from datetime import datetime
import json
from json_dao import UserDAO, ProjectDAO, with_user_dao, with_project_dao, dict_to_user, dict_to_project, dicts_to_projects
from ai_service import sync_generate_topic, sync_analyze_content, sync_evaluate_article, sync_health_check, sync_test_connection, sync_generate_suggestions, sync_generate_stage_suggestions
from ai_service import sync_generate_topic, sync_analyze_content, sync_evaluate_article, sync_health_check, sync_test_connection, sync_generate_suggestions, sync_generate_stage_suggestions, sync_check_grammar
from scoring_service import ScoringService
from ai_service import AIService
@ -770,6 +770,102 @@ def generate_suggestions():
traceback.print_exc()
return error_response(f"生成建议失败: {str(e)}", 500)
@api_bp.route('/ai/check_grammar', methods=['POST'])
def check_grammar():
"""检查语法错误"""
# 自动设置为已登录状态
if 'user_id' not in session:
session['user_id'] = 1
session['username'] = '267278466@qq.com'
user_id = session.get('user_id')
try:
data = request.get_json()
content = data.get('content')
project_id = data.get('project_id')
if not content:
return error_response("内容不能为空")
# 获取用户信息
@with_user_dao
def _get_user_data(dao):
user = dao.get_user_by_id(user_id)
return user # JSON DAO直接返回字典
user_data = _get_user_data()
if not user_data:
return error_response("用户不存在", 404)
# 获取项目信息
project_data = None
if project_id:
@with_project_dao
def _get_project_data(dao):
proj = dao.get_project_by_id(project_id)
return proj if proj and proj.get('user_id') == user_id else None
project_data = _get_project_data()
# 构建AI上下文 - 优先使用项目的年级和学科信息
if project_data:
# 使用项目的年级和学科信息
context = {
'grade': project_data.get('grade', user_data.get('grade', '')),
'subject': project_data.get('subject', user_data.get('subject', '')),
'content': content,
'topic': project_data.get('topic', ''),
'article_type': project_data.get('article_type', ''),
'title': project_data.get('title', '')
}
else:
# 没有项目信息时使用用户设置
context = {
'grade': user_data.get('grade', ''),
'subject': user_data.get('subject', ''),
'content': content,
'topic': data.get('topic', ''),
'article_type': data.get('article_type', '')
}
# 获取用户AI设置
user_ai_settings = get_user_ai_settings(user_id)
# 检查语法
grammar_result = sync_check_grammar(
content=content,
context=context,
user_settings=user_ai_settings
)
# 保存语法检查结果到项目
if project_id:
@with_project_dao
def _save_grammar_result(dao):
project = dao.get_project_by_id(project_id)
if project and project.get('user_id') == user_id:
# 更新项目的语法检查信息
grammar_data_str = project.get('grammar_check', '{}')
if not grammar_data_str or grammar_data_str.strip() == '':
grammar_data_str = '{}'
grammar_data = json.loads(grammar_data_str)
grammar_data['latest_check'] = grammar_result
grammar_data['last_checked_at'] = datetime.utcnow().isoformat()
# 使用DAO更新项目
dao.save_grammar_check(project_id, grammar_data)
return True
return False
_save_grammar_result()
return success_response(grammar_result)
except Exception as e:
import traceback
traceback.print_exc()
return error_response(f"语法检查失败: {str(e)}", 500)
# AI配置相关API
@api_bp.route('/ai/models/ollama', methods=['GET'])
def get_ollama_models():

@ -413,6 +413,9 @@
<button class="btn btn-outline-primary btn-sm w-100" onclick="getAISuggestions()">
<i class="fas fa-lightbulb me-1"></i> 获取写作建议
</button>
<button class="btn btn-outline-warning btn-sm w-100" onclick="checkGrammar()">
<i class="fas fa-spell-check me-1"></i> 语法检查
</button>
</div>
<div class="ai-feedback" id="aiFeedback">
@ -1170,6 +1173,34 @@
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* 语法检查特定样式 */
.grammar-check-result {
height: auto; /* 高度自适应 */
overflow-y: auto;
}
.error-item {
border-left: 4px solid #dc3545;
transition: all 0.3s ease;
}
.error-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.error-item code {
background: #f8f9fa;
padding: 4px 8px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
border: 1px solid #e9ecef;
}
.recommendation {
border-left: 4px solid #0dcaf0;
}
</style>
{% endblock %}
@ -1618,6 +1649,226 @@ function getStageTitle(stage) {
}
// 显示AI反馈
/**
* 显示AI反馈的优化版本
* @param {Object} feedback - 包含strengths, issues, suggestions等属性的反馈对象
*/
function displayAIFeedback(feedback) {
const feedbackDiv = document.getElementById('aiFeedback');
if (!feedbackDiv) {
console.error("Feedback container not found");
return;
}
// 创建主容器
const container = document.createElement('div');
container.className = 'ai-feedback-container';
// 1. 添加标题部分
const header = createFeedbackSection('AI分析报告', 'bi-robot', 'text-primary');
container.appendChild(header);
// 2. 添加优点部分
if (feedback.strengths?.length) {
container.appendChild(
createListSection('优点', feedback.strengths, 'bi-check-circle', 'text-success', 'list-group-item-success')
);
}
// 3. 添加改进部分
if (feedback.issues?.length) {
container.appendChild(
createListSection('需要改进', feedback.issues, 'bi-exclamation-triangle', 'text-warning', 'list-group-item-warning')
);
}
// 4. 添加建议部分(带手风琴效果)
if (feedback.suggestions?.length) {
const suggestionsSection = document.createElement('div');
suggestionsSection.className = 'feedback-section mb-4';
const heading = createSectionHeading('建议', 'bi-lightbulb', 'text-primary');
suggestionsSection.appendChild(heading);
const accordion = document.createElement('div');
accordion.className = 'accordion';
accordion.id = 'suggestionsAccordion';
feedback.suggestions.forEach((suggestion, index) => {
accordion.appendChild(createSuggestionItem(suggestion, index));
});
suggestionsSection.appendChild(accordion);
container.appendChild(suggestionsSection);
}
// 5. 添加下一步建议
if (feedback.next_steps?.length) {
container.appendChild(
createListSection('下一步建议', feedback.next_steps, 'bi-arrow-right-circle', 'text-info', 'list-group-item-info')
);
}
// 6. 添加原始响应(调试用)
if (feedback.raw_response) {
const rawSection = document.createElement('div');
rawSection.className = 'feedback-section mt-4';
const rawHeader = createSectionHeading('原始响应', 'bi-code', 'text-secondary');
rawSection.appendChild(rawHeader);
const rawPre = document.createElement('pre');
rawPre.className = 'bg-light p-3 rounded';
rawPre.textContent = feedback.raw_response;
rawSection.appendChild(rawPre);
container.appendChild(rawSection);
}
// 清空并添加新内容
feedbackDiv.innerHTML = '';
feedbackDiv.appendChild(container);
// 初始化Bootstrap组件
initBootstrapComponents();
}
// 辅助函数:创建反馈部分标题
function createSectionHeading(text, icon, colorClass) {
const heading = document.createElement('h5');
heading.className = `${colorClass} mb-3 d-flex align-items-center`;
const iconElem = document.createElement('i');
iconElem.className = `bi ${icon} me-2`;
heading.appendChild(iconElem);
heading.appendChild(document.createTextNode(text));
return heading;
}
// 辅助函数:创建列表类型的反馈部分
function createListSection(title, items, icon, colorClass, itemClass) {
const section = document.createElement('div');
section.className = 'feedback-section mb-4';
section.appendChild(createSectionHeading(title, icon, colorClass));
const list = document.createElement('ul');
list.className = 'list-group';
items.forEach(item => {
const li = document.createElement('li');
li.className = `list-group-item ${itemClass} d-flex align-items-center`;
const icon = document.createElement('i');
icon.className = `bi ${icon} me-2`;
li.appendChild(icon);
li.appendChild(document.createTextNode(item));
list.appendChild(li);
});
section.appendChild(list);
return section;
}
// 辅助函数:创建建议项(带手风琴效果)
function createSuggestionItem(suggestion, index) {
const item = document.createElement('div');
item.className = 'accordion-item border-0 mb-2';
// 标题部分
const header = document.createElement('h6');
header.className = 'accordion-header';
header.id = `heading${index}`;
const button = document.createElement('button');
button.className = 'accordion-button collapsed bg-light';
button.type = 'button';
button.setAttribute('data-bs-toggle', 'collapse');
button.setAttribute('data-bs-target', `#collapse${index}`);
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-controls', `collapse${index}`);
button.textContent = suggestion.讲解 || suggestion.suggestion || '建议';
header.appendChild(button);
// 内容部分
const collapse = document.createElement('div');
collapse.id = `collapse${index}`;
collapse.className = 'accordion-collapse collapse';
collapse.setAttribute('aria-labelledby', `heading${index}`);
collapse.setAttribute('data-bs-parent', '#suggestionsAccordion');
const body = document.createElement('div');
body.className = 'accordion-body p-3';
if (suggestion.示例 || suggestion.example) {
const exampleDiv = document.createElement('div');
exampleDiv.className = 'bg-dark text-white p-3 rounded mb-3';
const exampleLabel = document.createElement('small');
exampleLabel.textContent = '示例:';
exampleDiv.appendChild(exampleLabel);
const exampleContent = document.createElement('p');
exampleContent.className = 'mb-0 mt-2';
exampleContent.textContent = suggestion.示例 || suggestion.example;
exampleDiv.appendChild(exampleContent);
body.appendChild(exampleDiv);
}
if (suggestion.讲解 || suggestion.explanation) {
const explanation = document.createElement('p');
explanation.className = 'mb-0';
explanation.textContent = suggestion.讲解 || suggestion.explanation;
body.appendChild(explanation);
}
collapse.appendChild(body);
item.appendChild(header);
item.appendChild(collapse);
return item;
}
// 辅助函数初始化Bootstrap组件
function initBootstrapComponents() {
if (typeof bootstrap !== 'undefined') {
// 初始化所有手风琴
const accordions = document.querySelectorAll('.accordion');
accordions.forEach(acc => new bootstrap.Collapse(acc));
// 添加工具提示
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
}
// 辅助函数:创建反馈部分
function createFeedbackSection(title, icon, colorClass) {
const section = document.createElement('div');
section.className = 'feedback-header mb-4 p-3 rounded-top';
section.style.backgroundColor = '#f8f9fa';
const heading = document.createElement('h4');
heading.className = `${colorClass} mb-0 d-flex align-items-center`;
const iconElem = document.createElement('i');
iconElem.className = `bi ${icon} me-2`;
heading.appendChild(iconElem);
heading.appendChild(document.createTextNode(title));
section.appendChild(heading);
return section;
}
/*
function displayAIFeedback(feedback) {
const feedbackDiv = document.getElementById('aiFeedback');
@ -1664,6 +1915,171 @@ function displayAIFeedback(feedback) {
feedbackDiv.innerHTML = html;
}
*/
// 显示语法检查结果
function displayGrammarResult(grammarData) {
const feedbackDiv = document.getElementById('aiFeedback');
let html = '<div class="grammar-check-result">';
// 总体评估和得分
html += '<div class="grammar-header mb-4 p-3 bg-light rounded">';
html += '<h6 class="text-primary mb-2"><i class="fas fa-chart-bar me-2"></i>语法检查报告</h6>';
// 得分显示
if (grammarData.score !== undefined && grammarData.score !== null) {
const score = parseInt(grammarData.score);
let scoreClass = 'text-danger';
if (score >= 80) scoreClass = 'text-success';
else if (score >= 60) scoreClass = 'text-warning';
html += `<div class="d-flex align-items-center mb-2">
<span class="me-3">语法得分:</span>
<span class="fs-4 fw-bold ${scoreClass}">${score}</span>
<span class="text-muted ms-1">/100</span>
</div>`;
}
// 错误数量
if (grammarData.total_errors !== undefined) {
const errorCount = parseInt(grammarData.total_errors);
html += `<div class="d-flex align-items-center mb-2">
<span class="me-3">发现错误:</span>
<span class="badge ${errorCount > 0 ? 'bg-danger' : 'bg-success'}">${errorCount} 处</span>
</div>`;
}
// 总体评价
if (grammarData.overall_assessment) {
html += `<div class="mt-2">
<small class="text-muted">总体评价:</small>
<div class="fw-bold">${grammarData.overall_assessment}</div>
</div>`;
}
html += '</div>';
// 错误分类统计
if (grammarData.error_categories && Object.keys(grammarData.error_categories).length > 0) {
html += '<div class="error-categories mb-4">';
html += '<h6 class="text-secondary mb-3"><i class="fas fa-tags me-2"></i>错误分类统计</h6>';
html += '<div class="d-flex flex-wrap gap-2">';
Object.entries(grammarData.error_categories).forEach(([category, count]) => {
const countNum = parseInt(count) || 0;
if (countNum > 0) {
html += `<span class="badge bg-warning text-dark">${getErrorCategoryName(category)}: ${countNum}</span>`;
}
});
// 如果没有错误,显示成功标识
if (Object.values(grammarData.error_categories).every(count => (parseInt(count) || 0) === 0)) {
html += `<span class="badge bg-success">恭喜!未发现语法错误</span>`;
}
html += '</div></div>';
}
// 错误详情列表
if (grammarData.errors && grammarData.errors.length > 0) {
html += '<div class="errors-list">';
html += '<h6 class="text-danger mb-3"><i class="fas fa-exclamation-circle me-2"></i>错误详情</h6>';
grammarData.errors.forEach((error, index) => {
html += `<div class="error-item mb-3 p-3 border rounded">`;
html += `<div class="error-header d-flex justify-content-between align-items-center mb-2">`;
html += `<span class="badge bg-danger">错误 ${index + 1}</span>`;
html += `<span class="badge bg-secondary">${getErrorTypeName(error.error_type)}</span>`;
html += `</div>`;
html += `<div class="error-content">`;
// 原始错误文本
if (error.original_text) {
html += `<div class="mb-2">
<strong class="text-danger">原句:</strong>
<code class="ms-2 p-1 bg-danger bg-opacity-10 text-danger">${error.original_text}</code>
</div>`;
}
// 错误解释
if (error.explanation) {
html += `<div class="mb-2">
<strong>解释:</strong>
<span class="ms-2">${error.explanation}</span>
</div>`;
}
// 修正后的文本
if (error.corrected_text) {
html += `<div class="mb-2">
<strong class="text-success">修正:</strong>
<code class="ms-2 p-1 bg-success bg-opacity-10 text-success">${error.corrected_text}</code>
</div>`;
}
// 改进建议
if (error.suggestion) {
html += `<div class="mb-2">
<strong>建议:</strong>
<small class="ms-2 text-muted">${error.suggestion}</small>
</div>`;
}
html += `</div></div>`;
});
html += '</div>';
} else if (grammarData.total_errors === 0 || !grammarData.errors || grammarData.errors.length === 0) {
html += '<div class="text-center text-success py-4">';
html += '<i class="fas fa-check-circle fa-3x mb-3"></i>';
html += '<p class="mb-0">恭喜!未发现语法错误</p>';
html += '</div>';
}
// 学习建议
if (grammarData.recommendation) {
html += '<div class="recommendation mt-4 p-3 bg-info bg-opacity-10 rounded">';
html += '<h6 class="text-info mb-2"><i class="fas fa-graduation-cap me-2"></i>学习建议</h6>';
html += `<p class="mb-0">${grammarData.recommendation}</p>`;
html += '</div>';
}
html += '</div>';
feedbackDiv.innerHTML = html;
// 添加滚动到顶部功能
feedbackDiv.scrollTop = 0;
}
// 错误类型名称映射
function getErrorTypeName(errorType) {
const typeMap = {
'grammar': '语法错误',
'punctuation': '标点错误',
'word_usage': '用词错误',
'spelling': '拼写错误',
'sentence': '句子结构',
'expression': '表达错误',
'word': '字词错误'
};
return typeMap[errorType] || errorType;
}
// 错误分类名称映射
function getErrorCategoryName(category) {
const categoryMap = {
'grammar': '语法错误',
'punctuation': '标点错误',
'word_usage': '用词错误',
'spelling': '拼写错误',
'word': '字词错误',
'sentence': '句子错误',
'expression': '表达错误'
};
return categoryMap[category] || category;
}
// 完成评分
async function evaluateProject() {
@ -2251,5 +2667,216 @@ function insertContinuation(text) {
Utils.showAlert('续写内容已插入', 'success');
}
// 语法检查功能
async function checkGrammar() {
const textarea = document.getElementById(currentStage + 'Content');
const content = textarea.value.trim();
if (!content) {
Utils.showAlert('请先输入要检查的内容', 'warning');
return;
}
// 获取AI设置
const userSettings = Utils.getUserSettings();
if (!userSettings.aiProvider || !userSettings.aiModel) {
Utils.showAlert('请先配置AI设置', 'warning');
return;
}
// 构建AI设置对象
const parsedSettings = {
provider: userSettings.aiProvider,
model: userSettings.aiModel,
base_url: userSettings.aiBaseUrl,
api_key: localStorage.getItem('aiApiKey') || ''
};
showLoading('AI正在检查语法错误...');
try {
// 获取项目信息
const projectResponse = await fetch(`/api/projects/${projectId}`);
let grade = '初中';
let subject = '语文';
if (projectResponse.ok) {
const projectData = await projectResponse.json();
if (projectData.success && projectData.data) {
grade = projectData.data.grade || '初中';
subject = projectData.data.subject || '语文';
}
}
const response = await fetch('/api/ai/check_grammar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
project_id: projectId,
content: content,
grade: grade,
subject: subject,
ai_provider: parsedSettings.provider,
ai_model: parsedSettings.model,
ai_base_url: parsedSettings.base_url,
ai_api_key: parsedSettings.api_key
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
if (result.success) {
displayGrammarResult(result.data);
} else {
Utils.showAlert('语法检查失败:' + result.message, 'error');
}
} catch (error) {
console.error('语法检查错误:', error);
Utils.showAlert('语法检查失败,请检查网络连接', 'error');
} finally {
hideLoading();
}
}
// 显示语法检查结果
function displayGrammarResult(grammarData) {
const feedbackDiv = document.getElementById('aiFeedback');
let html = '<div class="grammar-check-result">';
// 总体评估和得分
html += '<div class="grammar-header mb-4 p-3 bg-light rounded">';
html += '<h6 class="text-primary mb-2"><i class="fas fa-chart-bar me-2"></i>语法检查报告</h6>';
if (grammarData.score !== undefined) {
const score = grammarData.score;
let scoreClass = 'text-danger';
if (score >= 90) scoreClass = 'text-success';
else if (score >= 70) scoreClass = 'text-warning';
html += `<div class="d-flex align-items-center mb-2">
<span class="me-3">语法得分:</span>
<span class="fs-4 fw-bold ${scoreClass}">${score}</span>
<span class="text-muted ms-1">/100</span>
</div>`;
}
if (grammarData.total_errors !== undefined) {
html += `<div class="d-flex align-items-center mb-2">
<span class="me-3">发现错误:</span>
<span class="badge ${grammarData.total_errors > 0 ? 'bg-danger' : 'bg-success'}">${grammarData.total_errors} 处</span>
</div>`;
}
if (grammarData.overall_assessment) {
html += `<div class="mt-2">
<small class="text-muted">总体评价:</small>
<div class="fw-bold">${grammarData.overall_assessment}</div>
</div>`;
}
html += '</div>';
// 错误分类统计
if (grammarData.error_categories && Object.keys(grammarData.error_categories).length > 0) {
html += '<div class="error-categories mb-4">';
html += '<h6 class="text-secondary mb-3"><i class="fas fa-tags me-2"></i>错误分类</h6>';
html += '<div class="d-flex flex-wrap gap-2">';
Object.entries(grammarData.error_categories).forEach(([category, count]) => {
if (count > 0) {
html += `<span class="badge bg-warning text-dark">${getErrorCategoryName(category)}: ${count}</span>`;
}
});
html += '</div></div>';
}
// 错误详情列表
if (grammarData.errors && grammarData.errors.length > 0) {
html += '<div class="errors-list">';
html += '<h6 class="text-danger mb-3"><i class="fas fa-exclamation-circle me-2"></i>错误详情</h6>';
grammarData.errors.forEach((error, index) => {
html += `<div class="error-item mb-3 p-3 border rounded">`;
html += `<div class="error-header d-flex justify-content-between align-items-center mb-2">`;
html += `<span class="badge bg-danger">错误 ${index + 1}</span>`;
html += `<span class="badge bg-secondary">${getErrorTypeName(error.error_type)}</span>`;
html += `</div>`;
html += `<div class="error-content">`;
html += `<div class="mb-2"><strong>原句:</strong><code class="text-danger">${error.original_text || '未提供'}</code></div>`;
if (error.explanation) {
html += `<div class="mb-2"><strong>解释:</strong>${error.explanation}</div>`;
}
if (error.corrected_text) {
html += `<div class="mb-2"><strong>修正:</strong><code class="text-success">${error.corrected_text}</code></div>`;
}
if (error.suggestion) {
html += `<div class="mb-2"><strong>建议:</strong><small class="text-muted">${error.suggestion}</small></div>`;
}
html += `</div></div>`;
});
html += '</div>';
} else {
html += '<div class="text-center text-success py-4">';
html += '<i class="fas fa-check-circle fa-3x mb-3"></i>';
html += '<p class="mb-0">恭喜!未发现语法错误</p>';
html += '</div>';
}
// 学习建议
if (grammarData.recommendation) {
html += '<div class="recommendation mt-4 p-3 bg-info bg-opacity-10 rounded">';
html += '<h6 class="text-info mb-2"><i class="fas fa-graduation-cap me-2"></i>学习建议</h6>';
html += `<p class="mb-0">${grammarData.recommendation}</p>`;
html += '</div>';
}
html += '</div>';
feedbackDiv.innerHTML = html;
}
// 错误类型名称映射
function getErrorTypeName(errorType) {
const typeMap = {
'grammar': '语法错误',
'punctuation': '标点错误',
'word_usage': '用词错误',
'spelling': '拼写错误',
'sentence': '句子结构',
'expression': '表达错误',
'word': '字词错误'
};
return typeMap[errorType] || errorType;
}
// 错误分类名称映射
function getErrorCategoryName(category) {
const categoryMap = {
'grammar': '语法错误',
'punctuation': '标点错误',
'word_usage': '用词错误',
'spelling': '拼写错误',
'word': '字词错误',
'sentence': '句子错误',
'expression': '表达错误'
};
return categoryMap[category] || category;
}
</script>
{% endblock %}

@ -9,14 +9,14 @@
"grade": "大学",
"brainstorm_content": "",
"outline_content": "",
"writing_content": "Generative AI, such as large language models, are becoming increasingly popular. They offers students a powerful tool for complete homework and generating ideas. However, its impact on learning ability are a subject of intense debate.\n\nOn one hand, it is argued that AI hinder the development of critical thinking. When a student rely on AI to write essays, they doesn't engage in the rigorous process of research and analysis their own. This lead to a superficial understanding and a failure to develop their own voice. Furthermore, the convenience of AI means that less effort are put into mastering fundamental skills, such as grammar and structuring arguments.\n\nConversely, supporter of AI argues that it serve as a personalized tutor. It can explaining complex concepts in different ways until the student understands. Used responsibly, AI could helps a student to brainstorm and to organize their thoughts more better than before.\n\nIn conclusion, the effect of generative AI on learning ability depend not on the technology itself, but on how it is been used by the student. The key lies in using it as a supplement for one's own thinking, not as a replacement.",
"ai_feedback": "{\"writing_analysis\": {\"strengths\": [\"文章结构清晰,包含引言、正反论证和结论,符合英语议论文基本框架\", \"能够从正反两个角度分析AI对学习的影响展现了辩证思维能力\", \"结论部分点明核心观点——关键在于如何使用而非技术本身,立意较好\", \"使用了on one hand、conversely、in conclusion等过渡词使文章有一定连贯性\"], \"issues\": [\"语法错误较多,特别是主谓一致问题(如'are becoming'→'is becoming','they offers'→'they offer'\", \"论点发展不够深入,缺乏具体例证和数据支持,论证力度不足\", \"主题句不够突出,段落开头未能清晰表明该段核心论点\", \"用词不够精准学术(如'more better'不符合学术写作规范)\", \"缺乏美国学术写作中典型的thesis statement明确陈述全文论点\"], \"suggestions\": [{\"讲解\": \"在引言段末尾添加明确的总论点陈述\", \"示例\": \"While generative AI presents legitimate concerns about academic integrity, its educational value ultimately depends on how students harness it as a cognitive tool rather than a shortcut.\"}, {\"讲解\": \"改进主题句,使其更具体并预告段落内容\", \"示例\": \"Critics rightly point out that overreliance on AI can undermine the very cognitive processes essential for deep learning, particularly in the domains of critical analysis and original thought.\"}, {\"讲解\": \"增加具体例证使论证更有说服力\", \"示例\": \"For instance, when AI generates entire paragraphs for a history paper, students miss the opportunity to develop their own historical interpretation and analytical voice—skills that distinguish mediocre writing from exceptional scholarship.\"}, {\"讲解\": \"使用更精准的学术词汇和表达\", \"示例\": \"Proponents counter that AI, when used judiciously, functions as an adaptive learning assistant that scaffolds complex tasks and provides immediate, personalized feedback—much like a digital writing center available 24/7.\"}, {\"讲解\": \"加强段落间的逻辑过渡\", \"示例\": \"Despite these valid concerns about dependency, the pedagogical potential of AI cannot be dismissed outright. In fact, when integrated intentionally...\"}], \"next_steps\": [\"系统学习英语主谓一致、时态等基础语法规则\", \"阅读美国大学水平的议论文范文,注意观察其论点陈述方式和论证结构\", \"练习使用具体事例、研究数据或权威引用来支持论点\", \"积累学术写作常用词汇和表达,避免口语化表述\", \"写作时先明确总论点和各分论点,确保整篇文章有清晰的逻辑脉络\"]}}",
"writing_content": "Generative AI, such as large language models, are becoming increasingly popular. They offers students a powerful tool for complete homework and generating ideas. However, its impact on learning ability are a subject of intense debate.\n\nOn one hand, it is argued that AI hinder the development of critical thinking. When a student rely on AI to write essays, they doesn't engage in the rigorous process of research and analysis their own. This lead to a superficial understanding and a failure to develop their own voice. Furthermore, the convenience of AI means that less effort are put into mastering fundamental skills, such as grammar and structuring arguments.",
"ai_feedback": "{\"writing_analysis\": {\"strengths\": [\"文章结构清晰,采用了标准的议论文框架:引入话题-正反论证-总结观点\", \"能够从正反两个角度分析AI对学习的影响体现了辩证思维\", \"结论段明确提出了核心观点,强调使用方式而非技术本身决定影响\", \"使用了适当的学术词汇如'critical thinking''personalized tutor'等\"], \"issues\": [\"语法错误较多,特别是主谓一致问题(如'are becoming''it offers''they doesn't'等)\", \"论点发展不够深入,缺乏具体例证和数据支持\", \"过渡词使用单一,缺乏美国学术写作中常见的递进、转折等复杂过渡\", \"部分表达不够地道,存在中式英语痕迹(如'more better than before'\", \"论点之间的逻辑衔接可以更紧密,缺乏对反论的深入反驳\"], \"suggestions\": [{\"讲解\": \"修正主谓一致等基础语法错误,确保语言准确性\", \"示例\": \"Generative AI, such as large language models, is becoming increasingly popular. It offers students a powerful tool for completing homework and generating ideas. However, its impact on learning ability is a subject of intense debate.\"}, {\"讲解\": \"在主题句后添加具体例证,增强说服力\", \"示例\": \"When students rely exclusively on AI to write essays, they bypass the rigorous process of research and analysis. For instance, instead of wrestling with primary sources to form original arguments, they might simply paraphrase AI-generated content, resulting in a superficial understanding and failure to develop their own academic voice.\"}, {\"讲解\": \"使用更丰富的过渡词和逻辑连接词\", \"示例\": \"While critics emphasize the risks of over-reliance, proponents counter that AI, when used judiciously, can serve as a personalized tutor. Specifically, it can adapt explanations to individual learning styles, breaking down complex concepts until the student achieves genuine comprehension.\"}, {\"讲解\": \"改进结论段,使其更具深度和号召力\", \"示例\": \"Ultimately, the educational impact of generative AI depends less on the technology itself and more on the intentionality behind its use. The key lies in leveraging it as a supplement to—not a replacement for—authentic intellectual engagement, ensuring that technology enhances rather than diminishes our cognitive development.\"}, {\"讲解\": \"使用更地道的学术表达方式\", \"示例\": \"Furthermore, the convenience of AI may reduce the effort students invest in mastering fundamental skills. This could potentially undermine their long-term academic growth, as proficiency in grammar and logical argumentation requires consistent practice and refinement.\"}], \"next_steps\": [\"系统学习英语主谓一致、时态等基础语法规则\", \"阅读美国学生的议论文范文,观察其论证结构和语言风格\", \"练习使用具体的例子、数据或研究结果来支持抽象论点\", \"积累学术写作常用的过渡词和表达方式\", \"完成写作后多次修改,重点关注逻辑流畅性和语言地道性\", \"尝试在正反论证后加入综合分析的段落,展现更高层次的批判性思维\"]}}",
"final_score": 60,
"scores": "{\"brainstorm\": 0, \"outline\": 0, \"writing\": 3, \"highlight\": 3}",
"status": "writing",
"word_count": 974,
"word_count": 558,
"created_at": "2025-10-14T19:14:23.761019",
"updated_at": "2025-10-16T11:42:07.271889",
"updated_at": "2025-10-23T11:42:04.538302",
"completed_at": null
}
]

@ -7,9 +7,9 @@
"subject": "英语",
"ai_provider": "DeepSeek",
"ai_model": "deepseek-chat",
"ai_api_key": "sk-04e8ffc7ce9743aa8c36d17f750adcdb",
"ai_api_key": "sk-94fadde224a44a89ab8d61e3172cdf52",
"ai_base_url": "https://api.deepseek.com",
"created_at": "2025-10-14T19:10:47.461150",
"updated_at": "2025-10-16T10:00:49.805159"
"updated_at": "2025-10-23T10:23:35.691328"
}
]
Loading…
Cancel
Save