Compare commits

...

3 Commits

@ -191,7 +191,7 @@ A: 系统设计注重引导而非替代1AI提供建议而非直接答案
## 联系我们
### 开发团队
- **技术支持**267278466@qq.com
- **技术支持**
## 开源协议

@ -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,42 +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
)
# 尝试解析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)}")
@ -340,6 +366,360 @@ 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 vocabulary_upgrade(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 writing instructor, please analyze this {ctx_info['article_type']} and identify basic vocabulary that can be upgraded to more sophisticated alternatives.
Topic: {ctx_info['topic']}
Student's Content:
{content}
Please identify 5-8 basic words or phrases that could be replaced with more advanced vocabulary appropriate for {ctx_info['grade']} level American academic writing.
For each vocabulary item, provide:
1. The original basic word/phrase
2. 2-3 more sophisticated alternatives with explanations
3. Example sentences showing proper usage
4. Contextual guidance on when to use each alternative
Focus on vocabulary that would make the writing sound more like a native American student's work.
请用完整JSON格式回复包含以下字段讲解文字用中文示范文字用英文
- overall_assessment: 总体词汇水平评估中文
- total_suggestions: 总建议数量
- vocabulary_suggestions: 词汇建议列表每个建议包含
- original_word: 原始词汇
- alternatives: 替代词汇列表每个包含
- word: 高阶词汇
- meaning: 词汇含义解释中文
- usage_example: 使用示例英文
- explanation: 升级理由中文
- difficulty_level: 难度等级初级/中级/高级
- learning_tips: 学习建议列表中文
- next_steps: 后续学习步骤中文"""
else:
prompt = f"""作为专业的{ctx_info['subject']}写作指导老师,请分析这篇{ctx_info['article_type']}并识别可以升级的基础词汇。
题目{ctx_info['topic']}
学生作文
{content}
请识别5-8个可以升级的基础词汇或短语为每个词汇提供更高级的替代方案
对于每个词汇项目请提供
1. 原始基础词汇
2. 2-3个更高级的替代词汇及解释
3. 使用示例句子
4. 使用场景指导
请用JSON格式回复包含以下字段
- overall_assessment: 总体词汇水平评估
- total_suggestions: 总建议数量
- vocabulary_suggestions: 词汇建议列表每个建议包含
- original_word: 原始词汇
- alternatives: 替代词汇列表每个包含
- word: 高阶词汇
- meaning: 词汇含义解释
- usage_example: 使用示例
- explanation: 升级理由
- difficulty_level: 难度等级初级/中级/高级
- learning_tips: 学习建议列表
- next_steps: 后续学习步骤"""
try:
result = quick_generate(
prompt=prompt,
model=model,
max_tokens=1200,
grade=ctx_info['grade'],
subject=ctx_info['subject'],
topic=ctx_info['topic'],
requirement="词汇升级分析",
json_mode=True,
temperature=0.3
)
print("===== 词汇升级返回数据 =====")
print(result)
print("==========================")
try:
vocabulary_data = json.loads(result)
except json.JSONDecodeError:
# 尝试提取JSON部分
import re
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if json_match:
try:
vocabulary_data = json.loads(json_match.group(0))
except json.JSONDecodeError:
logger.error("[词汇升级] 提取到的JSON片段解析失败")
return self._get_default_vocabulary_response(content)
else:
logger.error("[词汇升级] AI返回内容非JSON且未能提取JSON片段")
return self._get_default_vocabulary_response(content)
# 验证和补充词汇升级数据
vocabulary_data = self._validate_vocabulary_data(vocabulary_data, content)
return vocabulary_data
except Exception as e:
logger.error(f"词汇升级失败: {str(e)}")
return self._get_default_vocabulary_response(content, str(e))
def _get_default_vocabulary_response(self, content: str, error_msg: str = "") -> Dict:
"""获取默认的词汇升级响应"""
return {
"overall_assessment": "词汇升级功能暂时不可用" + (f"{error_msg}" if error_msg else ""),
"total_suggestions": 0,
"vocabulary_suggestions": [],
"learning_tips": ["请检查网络连接后重试", "建议先保存作品再尝试词汇升级"],
"next_steps": ["继续积累词汇量", "多阅读优秀范文"],
"error": error_msg or "未知错误"
}
def _validate_vocabulary_data(self, vocabulary_data: Dict, content: str) -> Dict:
"""验证和补充词汇升级数据"""
# 确保必要字段存在
required_fields = {
'overall_assessment': '词汇使用基本正确,有提升空间。',
'total_suggestions': 0,
'vocabulary_suggestions': [],
'learning_tips': ['多阅读优秀作品,积累词汇'],
'next_steps': ['定期复习升级的词汇']
}
for field, default_value in required_fields.items():
if field not in vocabulary_data or vocabulary_data[field] is None:
vocabulary_data[field] = default_value
# 确保vocabulary_suggestions是列表格式
if not isinstance(vocabulary_data.get('vocabulary_suggestions'), list):
vocabulary_data['vocabulary_suggestions'] = []
# 计算总建议数如果与vocabulary_suggestions不一致
if vocabulary_data['total_suggestions'] == 0 and vocabulary_data['vocabulary_suggestions']:
vocabulary_data['total_suggestions'] = len(vocabulary_data['vocabulary_suggestions'])
# 确保每个建议都有必要的字段
for suggestion in vocabulary_data['vocabulary_suggestions']:
if 'original_word' not in suggestion:
suggestion['original_word'] = '未知词汇'
if 'alternatives' not in suggestion or not isinstance(suggestion.get('alternatives'), list):
suggestion['alternatives'] = []
if 'explanation' not in suggestion:
suggestion['explanation'] = '可以升级为更高级的词汇'
if 'difficulty_level' not in suggestion:
suggestion['difficulty_level'] = '中级'
return vocabulary_data
def generate_suggestions(self, content: str, context: Dict,
suggestion_type: str = "improvement",
user_settings: Optional[Dict] = None) -> List[str]:
@ -533,7 +913,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'],
@ -950,4 +1330,14 @@ 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)
def sync_vocabulary_upgrade(content: str, context: Dict,
user_settings: Optional[Dict] = None) -> Dict:
"""同步词汇升级"""
return ai_service.vocabulary_upgrade(content, context, user_settings)

@ -72,6 +72,20 @@ class ProjectDAO:
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def update_project(self, project_id: int, **kwargs) -> Optional[Dict]:
"""更新项目信息"""
return ProjectStorage.update_project(project_id, **kwargs)
def save_vocabulary_upgrade(self, project_id: int, vocabulary_data: Dict[str, Any]) -> Optional[Dict]:
"""保存词汇升级结果"""
vocabulary_json = json.dumps(vocabulary_data, ensure_ascii=False)
return ProjectStorage.update_project(project_id, vocabulary_upgrade=vocabulary_json)
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:
"""创建写作项目"""

@ -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,sync_vocabulary_upgrade
from scoring_service import ScoringService
from ai_service import AIService
@ -770,6 +770,198 @@ def generate_suggestions():
traceback.print_exc()
return error_response(f"生成建议失败: {str(e)}", 500)
@api_bp.route('/ai/vocabulary_upgrade', methods=['POST'])
def vocabulary_upgrade():
"""词汇升级"""
# 自动设置为已登录状态
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)
# 词汇升级
vocabulary_result = sync_vocabulary_upgrade(
content=content,
context=context,
user_settings=user_ai_settings
)
# 保存词汇升级结果到项目
if project_id:
@with_project_dao
def _save_vocabulary_result(dao):
project = dao.get_project_by_id(project_id)
if project and project.get('user_id') == user_id:
# 更新项目的词汇升级信息
vocab_data_str = project.get('vocabulary_upgrade', '{}')
if not vocab_data_str or vocab_data_str.strip() == '':
vocab_data_str = '{}'
vocab_data = json.loads(vocab_data_str)
vocab_data['latest_upgrade'] = vocabulary_result
vocab_data['last_upgraded_at'] = datetime.utcnow().isoformat()
# 使用DAO更新项目 - 需要先添加这个字段到项目存储
dao.save_vocabulary_upgrade(project_id, vocab_data)
return True
return False
_save_vocabulary_result()
return success_response(vocabulary_result)
except Exception as e:
import traceback
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,12 @@
<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>
<button class="btn btn-outline-info btn-sm w-100" onclick="vocabularyUpgrade()">
<i class="fas fa-language me-1"></i> 词汇升级
</button>
</div>
<div class="ai-feedback" id="aiFeedback">
@ -463,6 +469,21 @@
display: flex;
flex-direction: column;
}
.ai-assistant {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
position: sticky;
top: 20px;
height: auto; /* 改为自动高度 */
min-height: 400px; /* 适当的最小高度 */
display: flex;
flex-direction: column;
}
@media (max-width: 1200px) {
.writing-content.writing-flex {
flex-direction: column;
@ -616,6 +637,7 @@
padding: 6px 12px;
}
/*
.ai-assistant {
background: white;
border-radius: 10px;
@ -628,7 +650,7 @@
display: flex;
flex-direction: column;
}
*/
.ai-header {
margin-bottom: 15px;
text-align: center;
@ -1154,6 +1176,75 @@
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;
}
/* 词汇升级特定样式 */
.vocabulary-upgrade-result {
height: auto;
overflow-y: auto;
}
.suggestion-item {
border-left: 4px solid #28a745;
transition: all 0.3s ease;
}
.suggestion-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.alternative-item {
border: 1px solid #dee2e6;
transition: all 0.3s ease;
}
.alternative-item:hover {
border-color: #28a745;
background-color: #f8fff9 !important;
}
.assessment-content {
line-height: 1.6;
color: #495057;
}
.usage code {
background: transparent;
color: #f8f9fa;
border: none;
font-family: 'Courier New', monospace;
}
.learning-tips li, .next-steps li {
border-radius: 6px;
}
</style>
{% endblock %}
@ -1446,6 +1537,213 @@ async function getAISuggestions() {
}
}
// 词汇升级功能
async function vocabularyUpgrade() {
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/vocabulary_upgrade', {
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) {
displayVocabularyResult(result.data);
} else {
Utils.showAlert('词汇升级失败:' + result.message, 'error');
}
} catch (error) {
console.error('词汇升级错误:', error);
Utils.showAlert('词汇升级失败,请检查网络连接', 'error');
} finally {
hideLoading();
}
}
// 显示词汇升级结果
function displayVocabularyResult(vocabularyData) {
const feedbackDiv = document.getElementById('aiFeedback');
let html = '<div class="vocabulary-upgrade-result">';
// 总体评估
html += '<div class="vocabulary-header mb-4 p-3 bg-light rounded">';
html += '<h6 class="text-primary mb-2"><i class="fas fa-language me-2"></i>词汇升级报告</h6>';
if (vocabularyData.overall_assessment) {
html += `<div class="mb-3">
<div class="fw-bold text-secondary mb-1">总体评估:</div>
<div class="assessment-content">${vocabularyData.overall_assessment}</div>
</div>`;
}
if (vocabularyData.total_suggestions !== undefined) {
const suggestionCount = parseInt(vocabularyData.total_suggestions);
html += `<div class="d-flex align-items-center mb-2">
<span class="me-3">升级建议:</span>
<span class="badge ${suggestionCount > 0 ? 'bg-success' : 'bg-secondary'}">${suggestionCount} 个词汇可升级</span>
</div>`;
}
html += '</div>';
// 词汇建议列表
if (vocabularyData.vocabulary_suggestions && vocabularyData.vocabulary_suggestions.length > 0) {
html += '<div class="vocabulary-suggestions mb-4">';
html += '<h6 class="text-success mb-3"><i class="fas fa-lightbulb me-2"></i>词汇升级建议</h6>';
vocabularyData.vocabulary_suggestions.forEach((suggestion, index) => {
html += `<div class="suggestion-item mb-4 p-3 border rounded">`;
html += `<div class="suggestion-header d-flex justify-content-between align-items-center mb-3">`;
html += `<div>
<span class="badge bg-primary me-2">建议 ${index + 1}</span>
<span class="fw-bold text-danger">${suggestion.original_word || '未知词汇'}</span>
<span class="text-muted ms-2"></span>
</div>`;
html += `<span class="badge bg-secondary">${suggestion.difficulty_level || '中级'}</span>`;
html += `</div>`;
if (suggestion.explanation) {
html += `<div class="explanation mb-3">
<div class="text-muted small">升级理由:</div>
<div class="fw-bold">${suggestion.explanation}</div>
</div>`;
}
// 替代词汇
if (suggestion.alternatives && suggestion.alternatives.length > 0) {
html += '<div class="alternatives">';
html += '<div class="text-success fw-bold mb-2">推荐替代词汇:</div>';
suggestion.alternatives.forEach((alternative, altIndex) => {
html += `<div class="alternative-item mb-3 p-2 bg-light rounded">`;
html += `<div class="d-flex align-items-center mb-2">
<span class="badge bg-success me-2">${altIndex + 1}</span>
<span class="fw-bold text-primary">${alternative.word || '未知词汇'}</span>
</div>`;
if (alternative.meaning) {
html += `<div class="meaning mb-2">
<div class="text-muted small">含义:</div>
<div>${alternative.meaning}</div>
</div>`;
}
if (alternative.usage_example) {
html += `<div class="usage">
<div class="text-muted small">使用示例:</div>
<div class="bg-dark text-white p-2 rounded mt-1">
<code>${alternative.usage_example}</code>
</div>
</div>`;
}
html += `</div>`;
});
html += '</div>';
}
html += `</div>`;
});
html += '</div>';
} else {
html += '<div class="text-center text-muted py-4">';
html += '<i class="fas fa-check-circle fa-3x mb-3 text-success"></i>';
html += '<p class="mb-0">恭喜!当前词汇使用恰当,暂无升级建议</p>';
html += '</div>';
}
// 学习建议
if (vocabularyData.learning_tips && vocabularyData.learning_tips.length > 0) {
html += '<div class="learning-tips mb-4">';
html += '<h6 class="text-info mb-3"><i class="fas fa-graduation-cap me-2"></i>学习建议</h6>';
html += '<ul class="list-unstyled">';
vocabularyData.learning_tips.forEach(tip => {
html += `<li class="mb-2 p-2 bg-info bg-opacity-10 rounded">
<i class="fas fa-chevron-right text-info me-2"></i>${tip}
</li>`;
});
html += '</ul></div>';
}
// 后续步骤
if (vocabularyData.next_steps && vocabularyData.next_steps.length > 0) {
html += '<div class="next-steps">';
html += '<h6 class="text-warning mb-3"><i class="fas fa-road me-2"></i>后续步骤</h6>';
html += '<ul class="list-unstyled">';
vocabularyData.next_steps.forEach(step => {
html += `<li class="mb-2 p-2 bg-warning bg-opacity-10 rounded">
<i class="fas fa-flag text-warning me-2"></i>${step}
</li>`;
});
html += '</ul></div>';
}
html += '</div>';
feedbackDiv.innerHTML = html;
// 添加滚动到顶部功能
feedbackDiv.scrollTop = 0;
}
// 显示AI建议
function displayAISuggestions(suggestions) {
const feedbackDiv = document.getElementById('aiFeedback');
@ -1602,6 +1900,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');
@ -1648,6 +2166,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() {
@ -2235,5 +2918,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,34 @@
"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_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对学习的潜在负面影响特别是批判性思维和基础技能方面显示出对主题的基本理解\", \"尝试从不同角度展开讨论,体现了初步的论证意识\", \"使用了一些学术写作的基本元素,如'On one hand'这样的过渡词\"], \"issues\": [\"缺乏明确的中心论点(thesis statement),文章方向不清晰\", \"段落缺乏有力的主题句(topic sentence),论证结构松散\", \"语法错误较多,特别是主谓一致问题(如'are'代替'is''doesn't'代替'don't')\", \"论证深度不足,缺乏具体例证和深入分析\", \"语言表达不够地道,有些表达显得生硬或不自然\"], \"suggestions\": [{\"讲解\": \"在开头段落加入清晰的中心论点\", \"示例\": \"While generative AI offers unprecedented convenience for students, its overreliance ultimately undermines the very cognitive skills essential for meaningful learning—critical analysis, original thought, and academic integrity.\"}, {\"讲解\": \"为每个主体段落添加有力的主题句\", \"示例\": \"Perhaps the most significant concern is how AI dependency stunts the growth of critical thinking abilities that form the bedrock of genuine education.\"}, {\"讲解\": \"使用更自然的过渡词和表达方式\", \"示例\": \"Moreover, the instant gratification provided by AI tools creates a dangerous disincentive for developing foundational writing competencies.\"}, {\"讲解\": \"添加具体例证增强说服力\", \"示例\": \"For instance, when a student uses AI to generate a history paper instead of wrestling with primary sources, they miss the opportunity to develop their own historical interpretation and analytical voice—skills that simply cannot be outsourced to an algorithm.\"}, {\"讲解\": \"修正语法错误,使用更地道的表达\", \"示例\": \"This reliance leads to superficial understanding and prevents students from developing their unique academic voice, ultimately creating a generation of passive consumers rather than active creators of knowledge.\"}], \"next_steps\": [\"阅读几篇优秀的美国大学生议论文范文,注意观察其论点陈述和段落结构\", \"练习写作清晰的主题句,确保每个段落都有明确的中心思想\", \"重点复习英语主谓一致和时态等基础语法规则\", \"在论证中添加具体事例、数据或引用,增强说服力\", \"多使用美国学术写作中常见的过渡词和表达方式,如'furthermore', 'conversely', 'in light of this'等\"]}}",
"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-16T09:13:12.104510",
"updated_at": "2025-11-02T16:19:18.601309",
"completed_at": null
},
{
"id": 2,
"user_id": 1,
"title": "写作项目 - 2025年11月02日 16:35",
"topic": "The Role of Failure in Personal Growth (2024 National)",
"article_type": "议论文",
"subject": "英语",
"grade": "高中",
"brainstorm_content": "",
"outline_content": "",
"writing_content": "",
"ai_feedback": "",
"final_score": 0,
"scores": "",
"status": "writing",
"word_count": 0,
"created_at": "2025-11-02T16:36:39.799848",
"updated_at": "2025-11-02T16:48:04.387646",
"completed_at": null
}
]

@ -3,13 +3,13 @@
"id": 1,
"username": "267278466@qq.com",
"password_hash": "07d0a715a160482331a556b514bee739$fe094c2d3e40fad4e83c6b06aaa95976c68e325bd899a003e7a2928edfe0fce8",
"grade": "大学",
"grade": "高中",
"subject": "英语",
"ai_provider": "",
"ai_model": "",
"ai_api_key": "",
"ai_base_url": "",
"ai_provider": "DeepSeek",
"ai_model": "deepseek-chat",
"ai_api_key": "sk-4fa62c38b3f44e1da5741c553ebe0344",
"ai_base_url": "https://api.deepseek.com",
"created_at": "2025-10-14T19:10:47.461150",
"updated_at": "2025-10-16T09:10:05.436879"
"updated_at": "2025-11-02T16:36:09.442564"
}
]
Loading…
Cancel
Save