week16 其他一些较难功能的实现

1.模板填充功能的实现

下面是一个整体的感觉

def fill_template_with_ai(self, lesson_data: Dict[str, Any]) -> Dict[str, Any]:
    """
    遍历所有单元格,若内容为字段名,则将其右侧所有合并单元格都写入AI内容,彻底解决合并单元格错位和内容丢失问题。
    """
    try:
        logger.info("开始AI填充自定义Word模板(右侧合并单元格填充)...")
        template_path = os.path.join(os.path.dirname(__file__), '备课教学教案设计.docx')
        doc = Document(template_path)
        tables = doc.tables

        # 需要的字段,顺序与AI输出一致
        fields = [
            "教学课题", "授课教师", "授课课型", "授课时间",
            "知识与技能", "过程与方法", "情感态度与价值观",
            "教学重点", "教学难点", "教学方法", "教学准备",
            "教学过程", "作业设计", "板书设计", "教学反思"
        ]
        logger.info(f"目标字段名: {fields}")

        # 构建AI prompt,要求输出所有字段内容
        prompt = (
            f"请根据如下教案信息,生成详细的教案内容,依次包含如下部分:{fields}。\n"
            f"每个部分用【字段名】单独一行作为标题,下一行紧跟内容。\n"
            f"每个字段内容要详细展开,不少于3句话,包含具体描述、示例或步骤。\n"
            f"不要加粗、不要编号、不要markdown符号、不要'详细说明'子标题。\n"
            f"多项内容用分号或换行分隔。\n"
            f"不要输出任何多余的格式或解释。\n"
            f"对于教学目标部分,分为【知识与技能】【过程与方法】【情感态度与价值观】分别输出,每项内容详细展开。\n"
            f"教案信息:{lesson_data}"
        )
        messages = [
            {"role": "system", "content": "你是专业教案设计AI,输出结构化教案内容,每部分用【字段名】作为标题。"},
            {"role": "user", "content": prompt}
        ]
        response = self.client.chat.completions.create(
            model="deepseek-reasoner",
            messages=messages,
            temperature=0.2,
            max_tokens=4096,
            timeout=60
        )
        ai_raw_content = getattr(response.choices[0].message, 'content', None) if response.choices else None
        logger.info(f"AI接口content字段: {ai_raw_content}")

        # 用正则提取各字段内容
        ai_json = {}
        for field in fields:
            # 允许前后有0~2个星号和空格
            pattern = rf'\*{{0,2}}[ \t]*【{field}】\*{{0,2}}[ \t]*\n*([\s\S]*?)(?=\n*\*{{0,2}}[ \t]*【|$)'
            match = re.search(pattern, ai_raw_content or '', re.MULTILINE)
            if match:
                # 去除每行行首的*、-、-和多余空格
                value = match.group(1)
                value = '\n'.join([re.sub(r'^[\*\--\s]+', '', l) for l in value.splitlines()])
                ai_json[field] = value.strip()
            else:
                ai_json[field] = ''
        logger.info(f"自解析提取内容: {ai_json}")

        # 遍历所有表格和单元格,右侧所有合并单元格精准填充
        debug_map = {}
        for table in tables:
            for row in table.rows:
                for idx, cell in enumerate(row.cells):
                    key = cell.text.replace('\n', '').replace(' ', '').strip()
                    if key in ai_json and ai_json[key]:
                        # 只填充第一个右侧空单元格,防止合并单元格内容丢失
                        for j in range(idx + 1, len(row.cells)):
                            if not row.cells[j].text.strip():
                                row.cells[j].text = ai_json[key]
                                debug_map[f"{key}_col{j}"] = ai_json[key]
                                break  # 只填充一次
        logger.info(f"最终填充字段及内容: {debug_map}")

        # 保存新文档
        file_id = uuid.uuid4().hex[:8]
        topic = lesson_data.get('topic', '教案')
        filename = f"{topic}_AI模板_{file_id}.docx"
        doc_path = os.path.join(self.upload_folder, filename)
        doc.save(doc_path)
        logger.info(f"AI模板教案生成成功:{doc_path}")
        return {
            "file_path": doc_path,
            "filename": filename,
            "status": "success",
            "debug_map": debug_map,
            "fields": fields,
            "ai_json": ai_json,
            "ai_raw_content": ai_raw_content
        }
    except Exception as e:
        logger.error(f"AI模板教案生成失败: {str(e)}", exc_info=True)
        return {
            "error": str(e),
            "status": "error"
        }

下面来仔细讲解一下,给予特殊的prompt

 需要的字段,顺序与AI输出一致
        fields = [
            "教学课题", "授课教师", "授课课型", "授课时间",
            "知识与技能", "过程与方法", "情感态度与价值观",
            "教学重点", "教学难点", "教学方法", "教学准备",
            "教学过程", "作业设计", "板书设计", "教学反思"
        ]
        logger.info(f"目标字段名: {fields}")
 # 构建AI prompt,要求输出所有字段内容
        prompt = (
            f"请根据如下教案信息,生成详细的教案内容,依次包含如下部分:{fields}。\n"
            f"每个部分用【字段名】单独一行作为标题,下一行紧跟内容。\n"
            f"每个字段内容要详细展开,不少于3句话,包含具体描述、示例或步骤。\n"
            f"不要加粗、不要编号、不要markdown符号、不要'详细说明'子标题。\n"
            f"多项内容用分号或换行分隔。\n"
            f"不要输出任何多余的格式或解释。\n"
            f"对于教学目标部分,分为【知识与技能】【过程与方法】【情感态度与价值观】分别输出,每项内容详细展开。\n"
            f"教案信息:{lesson_data}"
        )

正则表达式等来处理文法,有点梦回编译原理与web数据开发

 # 构建AI prompt,要求输出所有字段内容
        prompt = (
            f"请根据如下教案信息,生成详细的教案内容,依次包含如下部分:{fields}。\n"
            f"每个部分用【字段名】单独一行作为标题,下一行紧跟内容。\n"
            f"每个字段内容要详细展开,不少于3句话,包含具体描述、示例或步骤。\n"
            f"不要加粗、不要编号、不要markdown符号、不要'详细说明'子标题。\n"
            f"多项内容用分号或换行分隔。\n"
            f"不要输出任何多余的格式或解释。\n"
            f"对于教学目标部分,分为【知识与技能】【过程与方法】【情感态度与价值观】分别输出,每项内容详细展开。\n"
            f"教案信息:{lesson_data}"
        )

使用正则表达式等,对模板进行填充,是一个需要非常仔细细心的过程。

2.Markdown转word的实现

pandoc可以完成多种文件类型的相互转换,支持的文件格式参见官方网站:Pandoc - index。本博客参考了官方文档以及社区讨论,将介绍如何利用pandoc将markdown转换为word文档。

def generate_lesson_plan_word(self, lesson_data: Dict[str, Any], plain_text: bool = False) -> dict:
    """生成Word格式的教案,支持纯文本和富格式两种模式"""
    try:
        logger.info("开始生成Word格式教案...")
        filename = f"lesson_plan_{uuid.uuid4().hex[:8]}.docx"
        file_path = os.path.join(self.upload_folder, filename)
        doc = Document()
        self._setup_document_styles(doc)

        # 根据plain_text参数选择prompt和处理方式
        enhanced_data = self._enhance_with_deepseek(lesson_data, force_plaintext=plain_text)
        ai_content = enhanced_data.get('ai_enhanced_content')
        if not ai_content:
            error_msg = enhanced_data.get('ai_error', 'AI生成的内容为空,请检查输入内容或稍后重试')
            logger.error(error_msg)
            raise ValueError(error_msg)

        if plain_text:
            # 纯文本模式,直接分段写入
            for para in ai_content.split('\n'):
                if para.strip():
                    doc.add_paragraph(para.strip())
        elif lesson_data.get('isRichText'):
            # 富文本模式,使用pandoc将markdown转换为Word文档
            import subprocess
            import tempfile
            with tempfile.NamedTemporaryFile(suffix='.md', delete=False) as temp_md:
                temp_md.write(ai_content.encode('utf-8'))
                temp_md_path = temp_md.name
            try:
                subprocess.run(['pandoc', temp_md_path, '-o', file_path, '--reference-doc=template.docx'], check=True)
                logger.info(f"使用pandoc将markdown转换为Word文档成功:{file_path}")
            except subprocess.CalledProcessError as e:
                logger.error(f"pandoc转换失败: {str(e)}")
                raise
            finally:
                os.unlink(temp_md_path)
        else:
            # 其它情况,原有简单清洗
            lines = ai_content.split('\n')
            for line in lines:
                clean_line = re.sub(r'^[#>*\-\s]+', '', line).strip()
                if clean_line:
                    doc.add_paragraph(clean_line)

        doc.save(file_path)
        logger.info(f"Word文档已保存到: {file_path}")
        return {"filename": filename, "file_path": file_path, "enhanced_content": enhanced_data}
    except Exception as e:
        logger.error(f"生成Word文档失败: {str(e)}")
        raise
  1. 富文本模式:当isRichText为True时,系统会使用pandoc将生成的markdown内容转换为Word文档。
  2. 临时文件:系统会创建一个临时的markdown文件,将AI生成的内容写入该文件。
  3. pandoc转换:调用pandoc命令将markdown文件转换为Word文档,并指定参考文档为template.docx。
  4. 错误处理:如果pandoc转换失败,系统会记录错误并抛出异常。
  5. 清理临时文件:转换完成后,系统会删除临时markdown文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值