import json import re import html as html_lib import httpx import logging import time import base64 from config import get_settings settings = get_settings() logger = logging.getLogger(__name__) class AIService: # Shared circuit-breaker timestamp: all instances share one breaker so a # single AI outage trips the breaker globally across every router. _ai_unavailable_until: float = 0.0 def __init__(self): self.api_key = settings.AI_API_KEY self.api_base = settings.AI_API_BASE.rstrip("/") self.model = settings.AI_MODEL self.client = httpx.AsyncClient( base_url=self.api_base, headers={"Authorization": f"Bearer {self.api_key}"}, timeout=httpx.Timeout(connect=10.0, read=120.0, write=15.0, pool=10.0), ) async def _chat(self, messages: list[dict], temperature: float = 0.7, max_tokens: int = 8192) -> str: if time.monotonic() < AIService._ai_unavailable_until: logger.warning("AI provider skipped (circuit breaker active for %.0fs more)", AIService._ai_unavailable_until - time.monotonic()) raise RuntimeError("AI_PROVIDER_UNAVAILABLE") try: response = await self.client.post( "/chat/completions", json={ "model": self.model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, # glm-5.x is a reasoning model: without disabling # thinking it burns the entire token budget on # chain-of-thought and returns EMPTY content # (finish_reason=length), forcing every feature to # fall back to static templates. Disable for real output. "enable_thinking": False, "thinking": {"type": "disabled"}, }, ) response.raise_for_status() except httpx.ConnectError as exc: # Connection-level failure: server likely down. Short breaker so we retry sooner. logger.error("AI provider connect failed: %s: %s", type(exc).__name__, exc) AIService._ai_unavailable_until = time.monotonic() + 20 raise RuntimeError("AI_PROVIDER_CONNECT_ERROR") from exc except httpx.TimeoutException as exc: # Timeout (connect/read): server slow or unreachable. Short breaker. logger.error("AI provider timeout: %s: %s", type(exc).__name__, exc) AIService._ai_unavailable_until = time.monotonic() + 20 raise RuntimeError("AI_PROVIDER_TIMEOUT") from exc except httpx.HTTPStatusError as exc: # HTTP error (4xx/5xx): server is up but rejected the request. logger.error("AI provider HTTP %s: %s", exc.response.status_code, str(exc.response.text)[:200]) breaker = 30 if exc.response.status_code >= 500 else 120 AIService._ai_unavailable_until = time.monotonic() + breaker raise RuntimeError("AI_PROVIDER_HTTP_ERROR") from exc except Exception as exc: logger.error("AI provider request failed: %s: %s", type(exc).__name__, exc) AIService._ai_unavailable_until = time.monotonic() + 30 raise RuntimeError("AI_PROVIDER_UNAVAILABLE") from exc data = response.json() content = data["choices"][0]["message"]["content"] or "" # Reasoning models may return empty content if thinking consumed the token budget if not content.strip(): logger.warning("AI provider returned empty content (finish=%s, model=%s) - reasoning budget likely exhausted", data["choices"][0].get("finish_reason","?"), self.model) raise RuntimeError("AI_PROVIDER_EMPTY_RESPONSE") finish_reason = data["choices"][0].get("finish_reason", "") if finish_reason == "length": content = content + '..."}' return content async def _chat_json(self, messages: list[dict], temperature: float = 0.3, max_tokens: int = 8192) -> dict: content = await self._chat(messages, temperature, max_tokens) return self._extract_json(content) async def ocr_image(self, image_bytes: bytes, filename: str = "") -> str: """Extract text from an image using the AI model's vision capability.""" if not self.api_key or self.api_key == "change-me-in-production": raise RuntimeError("AI_PROVIDER_UNAVAILABLE") if time.monotonic() < AIService._ai_unavailable_until: raise RuntimeError("AI_PROVIDER_UNAVAILABLE") b64 = base64.b64encode(image_bytes).decode("ascii") ext = "png" lower_name = (filename or "").lower() if lower_name.endswith((".jpg", ".jpeg")): ext = "jpeg" elif lower_name.endswith(".webp"): ext = "webp" elif lower_name.endswith(".gif"): ext = "gif" messages = [ { "role": "user", "content": [ { "type": "text", "text": ( "请仔细识别这张图片中的所有文字内容,包括题目、公式、表格和标注。" "完整提取文字,保持原有结构和层次。" "如果是题目,请保留题号和选项格式。" "数学公式用可读文本表示(例如 a² + b² = c²)。" "只输出识别到的文字内容,不要添加解释或说明。" "如果图片中没有文字或无法识别,请回复:[无法识别文字内容]" ), }, { "type": "image_url", "image_url": {"url": f"data:image/{ext};base64,{b64}"}, }, ], } ] try: response = await self.client.post( "/chat/completions", json={ "model": self.model, "messages": messages, "temperature": 0.1, "max_tokens": 4096, "enable_thinking": False, "thinking": {"type": "disabled"}, }, timeout=httpx.Timeout(connect=10.0, read=90.0, write=15.0, pool=10.0), ) response.raise_for_status() except Exception as exc: logger.error("Vision OCR request failed: %s", exc) AIService._ai_unavailable_until = time.monotonic() + 60 raise RuntimeError("AI_PROVIDER_UNAVAILABLE") from exc data = response.json() return data["choices"][0]["message"]["content"].strip() def _extract_json(self, content: str) -> dict: content = content.strip() # Strip markdown code fences import re as _re content = _re.sub(r'^```\w*\n?', '', content) content = _re.sub(r'\n?```$', '', content) content = content.strip() first_brace = content.find('{') if first_brace == -1: return {"raw_content": content} last_brace = content.rfind('}') if last_brace <= first_brace: return {"raw_content": content} # Try the full span first candidate = content[first_brace:last_brace + 1] try: result = json.loads(candidate) if isinstance(result, dict): return result except json.JSONDecodeError: pass # Try auto-fixing truncated JSON by closing open structures fixed = self._try_fix_truncated_json(candidate) if fixed: return fixed # Try replacing HTML double quotes with single quotes inside the html field fixed2 = _re.sub(r'(style|onclick|onchange)=\\?"([^"]*?)\\?"', r"\1='\2'", candidate) if fixed2 != candidate: try: result = json.loads(fixed2) if isinstance(result, dict): return result except json.JSONDecodeError: pass # Find all '}' positions from end to start pos = last_brace while pos > first_brace: if content[pos] == '}': try: result = json.loads(content[first_brace:pos + 1]) if isinstance(result, dict): return result except json.JSONDecodeError: pass pos -= 1 # Last resort: try to extract html field via regex html_match = _re.search(r'"html"\s*:\s*"((?:[^"\\]|\\.)*)"', content, _re.DOTALL) if html_match: try: html_val = json.loads('"' + html_match.group(1) + '"') return {"title": "动画", "description": "", "html": html_val} except: pass # All attempts failed - save debug info try: with open("debug_json_fail.txt", "w", encoding="utf-8") as f: f.write(f"Content length: {len(content)}\n") f.write(f"First 500 chars:\n{content[:500]}\n\n") f.write(f"Last 500 chars:\n{content[-500:]}\n\n") f.write(f"Full content:\n{content}\n") except: pass return {"raw_content": content} def _try_fix_truncated_json(self, candidate: str) -> dict | None: """Try to fix truncated JSON by closing open strings/brackets.""" import re as _re # Count brackets opens = candidate.count('{') - candidate.count('}') opens_bracket = candidate.count('[') - candidate.count(']') # Check if we're inside a string in_string = False escape = False for ch in candidate: if escape: escape = False continue if ch == '\\': escape = True continue if ch == '"': in_string = not in_string fixed = candidate if in_string: fixed += '' # Close open string if inside one if in_string: fixed += '"' # Close open brackets for _ in range(max(0, opens_bracket)): fixed += ']' for _ in range(max(0, opens)): fixed += '}' try: result = json.loads(fixed) if isinstance(result, dict): # If html field exists, this is usable if 'html' in result: return result except json.JSONDecodeError: pass # Try simpler fix: just close the outermost object if in_string: # Find last complete key-value pair before truncation # Try: add closing quote, skip to end of object for suffix in ['"}', '"}]}', '"}]}', '"]}', '"}' ]: try: result = json.loads(candidate + suffix) if isinstance(result, dict): return result except json.JSONDecodeError: continue return None async def chat_teaching(self, history: list[dict], user_message: str, subject: str = "", grade: str = "") -> str: """General-purpose conversational teaching assistant (multi-turn).""" context_bits = [] if subject: context_bits.append(f"任教学科:{subject}") if grade: context_bits.append(f"教学学段/年级:{grade}") context_line = (";".join(context_bits) + "。") if context_bits else "" system_prompt = ( "你是“智教助手”,一位经验丰富、耐心细致的 K12 全学科教学顾问与备课搭档。" "你的任务是帮助一线教师解决真实的教学问题:备课思路、知识点讲解、活动设计、" "课堂提问、学情诊断、家校沟通、教法选择等。\n" "回答要求:\n" "1. 用简体中文回答,语气专业、亲切、可落地,避免空话套话。\n" "2. 结构清晰,善用标题、要点、表格和示例,必要时分步骤说明。\n" "3. 紧扣中国课程标准和一线课堂实际,给出可直接使用的内容(如完整提问、活动脚本、讲解话术)。\n" "4. 遇到模糊请求时主动追问关键信息(年级、学科、目标、时长)再给出方案。\n" "5. 对学生的易错点、认知难点要有针对性提醒;涉及数值/事实要准确。\n" f"{context_line}" ) messages: list[dict] = [{"role": "system", "content": system_prompt}] for turn in (history or []): role = turn.get("role") content = (turn.get("content") or "").strip() if role in {"user", "assistant"} and content: messages.append({"role": role, "content": content}) messages.append({"role": "user", "content": user_message}) try: return await self._chat(messages, temperature=0.6, max_tokens=8192) except RuntimeError: return self._fallback_chat(history or [], user_message, subject, grade) async def generate_mindmap(self, topic: str, subject: str = "综合", grade: str = "") -> dict: """生成知识思维导图,返回层级节点结构。""" topic = self._repair_text(topic) grade_hint = f",适用学段:{grade}" if grade else "" system_prompt = f"""你是一位资深学科教学专家,擅长梳理知识结构。根据教师给出的主题,生成一份层次清晰、内容专业的知识思维导图。 要求: - 围绕主题梳理 4 到 6 个一级分支(核心知识维度) - 每个一级分支下展开 2 到 4 个二级子节点 - 节点标题用精炼短语(4 到 14 字),不要长句 - 内容要符合学科规范与教学逻辑,覆盖该主题的核心知识脉络(学科:{subject}{grade_hint}) - 如果主题偏小众,也尽量给出合理、可教学的结构 直接输出JSON(不要markdown代码块,不要其他文字): {{"title":"主题名","nodes":[{{"title":"一级分支标题","color":"#00a870","children":[{{"title":"二级子节点标题","children":[{{"title":"可展开的细节要点"}}]}}]}}]}}""" try: result = await self._chat_json([ {"role": "system", "content": system_prompt}, {"role": "user", "content": topic}, ], temperature=0.5, max_tokens=8192) return self._normalize_mindmap_result(result, topic, subject) except RuntimeError: return self._fallback_mindmap(topic, subject) def _normalize_mindmap_result(self, result: dict, topic: str, subject: str) -> dict: topic = self._repair_text(topic) nodes = result.get("nodes") if not isinstance(nodes, list) or not nodes: return self._fallback_mindmap(topic, subject) palette = ["#00a870", "#2563eb", "#d97706", "#db2777", "#7c3aed", "#0891b2", "#4f46e5", "#ea580c"] clean: list = [] for i, branch in enumerate(nodes): if not isinstance(branch, dict) or not str(branch.get("title", "")).strip(): continue color = branch.get("color") or palette[i % len(palette)] children = self._collect_mindmap_children(branch.get("children")) if not children: continue clean.append({"title": str(branch["title"]).strip()[:30], "color": color, "children": children}) if len(clean) < 2: return self._fallback_mindmap(topic, subject) return {"title": str(result.get("title") or topic)[:80], "nodes": clean[:8]} def _collect_mindmap_children(self, raw) -> list: if not isinstance(raw, list): return [] out: list = [] for item in raw: if isinstance(item, dict) and str(item.get("title", "")).strip(): title = str(item["title"]).strip()[:30] sub = self._collect_mindmap_children(item.get("children")) node = {"title": title} if sub: node["children"] = sub out.append(node) elif isinstance(item, str) and item.strip(): out.append({"title": item.strip()[:30]}) return out[:6] def _fallback_chat(self, history: list, user_message: str, subject: str = "", grade: str = "") -> str: """AI 离线时的教学顾问兜底:按意图匹配给出可落地、结构化的教学建议。""" subject = (subject or "").strip() grade = (grade or "").strip() msg = self._repair_text(user_message).strip() ctx_bits = [] if subject: ctx_bits.append(subject) if grade: ctx_bits.append(grade) ctx = ("(" + "·".join(ctx_bits) + ")") if ctx_bits else "" offline_note = ( "\n\n— 当前为离线知识模式(AI 云端未连通),以上为基于教学经验的通用建议;" "如需针对你具体班级、教材版本的个性化方案,请在 AI 服务恢复后再次提问。" ) if any(k in msg for k in ["你好", "您好", "在吗", "谢谢", "感谢", "辛苦"]) and len(msg) <= 12: hi = "你好!我是智教助手,你的教学顾问与备课搭档。" + (f"已记录你的任教学段{ctx}。" if ctx else "") return ( hi + "\n\n我可以帮你:备课与教学设计、知识点讲解思路、课堂活动与提问设计、" "学情诊断与分层教学、家校沟通、复习备考、课堂管理、学生动机激发等。\n\n" "直接告诉我你想解决的教学问题,例如:\n" "- \"如何讲清分数乘法的算理?\"\n" "- \"设计一节《草船借箭》的导入环节\"\n" "- \"班上两极分化严重怎么办?\"\n" "- \"家长群里如何回复投诉?\"" + offline_note ) subj_resp = self._subject_advice(msg, subject, grade) if subj_resp: return subj_resp + offline_note if any(k in msg for k in ["备课", "教学设计", "教案", "教学环节", "教学目标", "怎么设计", "如何设计", "设计一节", "设计这节", "课时方案"]): return self._chat_lesson_prep(ctx) + offline_note if any(k in msg for k in ["怎么讲", "如何讲", "怎么解释", "如何解释", "讲清楚", "讲不清", "学生不懂", "听不懂", "理解不了", "突破难点", "重难点", "算理", "为什么这样", "原理"]): return self._chat_explain(ctx) + offline_note if any(k in msg for k in ["导入", "开场", "引入", "情境", "激发兴趣", "吸引"]): return self._chat_intro(ctx) + offline_note if any(k in msg for k in ["课堂活动", "互动活动", "小组活动", "合作学习", "游戏", "动手", "探究", "角色扮演", "情境表演"]): return self._chat_activity(ctx) + offline_note if any(k in msg for k in ["提问", "追问", "问题设计", "提问技巧", "课堂提问"]): return self._chat_questioning(ctx) + offline_note if any(k in msg for k in ["纪律", "管纪律", "课堂管理", "课堂常规", "调皮", "捣乱", "走神", "注意力", "不专心", "讲话", "吵闹", "坐不住"]): return self._chat_management(ctx) + offline_note if any(k in msg for k in ["学困生", "后进生", "学困", "待优生", "跟不上", "两极分化", "差距大", "分层", "差异化", "因材施教", "培优补差"]): return self._chat_differentiation(ctx) + offline_note if any(k in msg for k in ["家长", "家校", "家访", "家长会", "家长群", "沟通家长", "投诉"]): return self._chat_parents(ctx) + offline_note if any(k in msg for k in ["作业", "布置作业", "评价", "批改", "反馈", "形成性评价", "作业量", "作业设计"]): return self._chat_assessment(ctx) + offline_note if any(k in msg for k in ["不想学", "没兴趣", "没动力", "厌学", "积极性", "不想听", "敷衍", "内驱力", "学习动机"]): return self._chat_motivation(ctx) + offline_note if any(k in msg for k in ["复习", "备考", "期末", "期中", "考试", "复习课", "冲刺", "考点", "应试"]): return self._chat_review(ctx) + offline_note if any(k in msg for k in ["新教师", "新手", "刚入职", "实习", "第一次上课", "新老师", "刚上班"]): return self._chat_newteacher(ctx) + offline_note return self._chat_general(msg, ctx) + offline_note def _subject_advice(self, msg: str, subject: str, grade: str) -> str: """识别学科相关提问,返回学科特色建议;无匹配返回空串。""" is_math = any(k in msg for k in ["数学", "算理", "算式", "方程", "函数", "几何", "分数", "小数", "乘法", "除法", "加减法", "应用题"]) is_chinese = any(k in msg for k in ["语文", "课文", "识字", "写字", "阅读理解", "作文", "古诗词", "古诗", "文言文", "拼音", "草船借箭", "荷塘月色"]) is_english = any(k in msg for k in ["英语", "单词", "语法", "口语", "听力", "时态", "从句", "because"]) is_physics = any(k in msg for k in ["物理", "力学", "电学", "电路", "牛顿", "压强", "浮力", "光学"]) if is_math: return ( "【数学教学建议】\n\n" "一、算理优于算法\n" "数学核心是让学生懂\"为什么\",而非记\"怎么做\"。讲新运算先用实物/画图/数轴把算理可视化(如分数乘法用面积模型),再给法则。\n\n" "二、三讲三练结构\n" "- 讲例题(边讲边写思维过程)→ 练模仿题\n" "- 讲变式(改一个条件/数据)→ 练变式题\n" "- 讲错题(学生典型错误当反面教材)→ 练易错题\n\n" "三、让错误可见\n" "数学最怕假装会。多用板演、白板小测、手势即时暴露思维。错题不只对答案,让学生讲\"我当时怎么想的\"。\n\n" "四、应用题三部曲\n" "读题(圈关键信息)→ 画图/列表(文字转成关系)→ 列式(再算)。低年级卡第一步,高年级卡第二步。\n\n" "告诉我具体课题(如\"分数除法\"\"平行四边形面积\"),我给更细的活动设计。" ) if is_chinese: return ( "【语文教学建议】\n\n" "一、朗读是地基\n" "低中年级把朗读做扎实:教师范读→跟读→指名读→齐读,注意停顿、重音、语气。朗读到位,理解自然跟上。\n\n" "二、课文\"三问法\"\n" "- 写了什么?(整体感知)\n" "- 怎么写的?(品词析句、写法)\n" "- 为什么这样写?(情感、主旨)\n\n" "三、作文从\"有话写\"到\"写得好\"\n" "先解决没素材:多搞观察课、活动作文。再解决写不具体:训练动作分解(把\"他跑了\"拆成 5 个动作)。最后才润色文采。\n\n" "四、识字/字词\n" "字理识字(讲字源)、归类识字(同偏旁)、语境识字(放词句里记),比机械抄写有效。\n\n" "告诉我具体课文或课型,我给针对性设计。" ) if is_english: return ( "【英语教学建议】\n\n" "一、输入先行(i+1 原则)\n" "新语言点先大量听/看→理解,再要求说/写。用 TPR、图片、情境让学生先懂意思,不要一上来就机械跟读。\n\n" "二、词不离句,句不离境\n" "单词放进有意义的句子里记,句子放进情境里用。避免单词—中文翻译的孤立记忆,那样学生只会背不会用。\n\n" "三、PPP 模式\n" "Presentation(呈现)→ Practice(控制练习)→ Production(自由产出)。第三步最常被省略,却最关键——要给真实任务去用语言。\n\n" "四、让沉默期变短\n" "不爱开口的学生:先做非语言回应(指、选、画),再重复性输出(跟读),最后创造性输出。降低开口焦虑。\n\n" "告诉我具体单元/课型,我给细化方案。" ) if is_physics: return ( "【物理教学建议】\n\n" "一、实验是物理的灵魂\n" "能做实验绝不只讲实验。演示实验要让现象出乎意料(引发认知冲突),分组实验让每个人动手。器材不足用生活物品替代(矿泉水瓶测压强)。\n\n" "二、从现象到规律四步\n" "观察现象 → 提出问题 → 猜想/设计实验 → 得出规律。规律不要直接给,让学生发现。\n\n" "三、建模与数学化\n" "讲公式时一定讲清每个字母的物理意义和单位,强调公式是规律的语言,而非要背的符号。\n\n" "四、典型误区预防\n" "力与运动(惯性误解)、电流方向、浮力沉浮条件……把这些高频误区编成判断题课前测,对症讲解。" ) return "" def _chat_lesson_prep(self, ctx: str) -> str: return ( f"关于备课,建议你按\"目标—学情—活动—评价\"四步设计{ctx}:\n\n" "一、明确可检验的教学目标(最关键)\n" "用\"学生能做什么 + 什么条件下 + 达到什么程度\"来写。例:学生能在 5 分钟内独立完成 3 道两位数乘法,正确率≥80%。\n" "避免\"让学生理解…\"\"培养…能力\"这类无法检测的目标。一节课 2-3 个核心目标即可。\n\n" "二、诊断学情\n" "这节课学生已有什么基础?常见误区是什么?课前用 2 道前测题摸底,或翻看上节课作业的典型错误。\n\n" "三、设计主干活动(建议 3 环节)\n" "- 导入(3-5 分钟):旧知、生活情境或认知冲突引入。\n" "- 新知建构(15-20 分钟):不要直接给结论,设计让学生发现的活动——观察、操作、讨论、归纳。\n" "- 巩固迁移(10-15 分钟):模仿→变式→应用,难度阶梯上升。\n\n" "四、嵌入评价(防止假装听懂)\n" "每个环节末尾设一个检测点:提问、小测、板演、同伴互评,让你随时知道学生学到哪了。\n\n" "时间提醒:教师讲授总时长控制在 15 分钟内,把时间还给学生练与思。\n\n" "告诉我具体课题和年级,我给完整的环节脚本与活动设计。" ) def _chat_explain(self, ctx: str) -> str: return ( f"讲清一个难点,核心是搭台阶 + 可视化 + 证伪误区{ctx}:\n\n" "一、先找到学生的卡点\n" "学生不懂通常卡在某一个具体环节,不是整体不懂。问自己:这个知识依赖哪个旧知?哪个概念是转折点?把卡点定位到最小单元。\n\n" "二、搭台阶(最近发展区)\n" "把难点拆成 3-4 个递进小问题,每个学生能答上来,最终自己推出结论。例:讲分数除法→先复习分数意义→再问除以一个数=乘它倒数为什么成立→用面积图验证。\n\n" "三、可视化(多通道呈现)\n" "能画图就画图,能摆实物就摆实物。抽象概念配具象表征(数轴、面积模型、流程图、表格),让学生看见关系。\n\n" "四、用错误反证(认知冲突)\n" "先抛一个看起来对其实是错的判断,让多数学生踩坑,再一起分析为什么错——比直接讲正确结论印象深 3 倍。\n\n" "五、让学生复述/教别人\n" "讲完别问懂了吗(学生都会点头),而让 1-2 个学生用自己的话复述,或讲给同桌听。能讲清楚才是真懂。\n\n" "把具体知识点告诉我,我帮你拆台阶、设计冲突问题。" ) def _chat_intro(self, ctx: str) -> str: return ( f"好的导入能在 3-5 分钟抓住学生,常用 5 种方式{ctx}:\n\n" "1. 情境导入:用学生熟悉的生活场景切入(买菜算账、校园事件、新闻热点),让知识有用。\n" "2. 冲突导入:抛一个反直觉的现象或问题(如:1千克铁和1千克棉花谁重?),引发好奇。\n" "3. 悬念导入:讲半截故事/留个谜,答案藏在今天的内容里。\n" "4. 旧知导入:从上节课内容自然延伸,温故知新。\n" "5. 操作导入:动手做个小实验/小游戏,身体先参与,注意力立刻集中。\n\n" "避坑:导入别超过 5 分钟,别为热闹而热闹——导入必须直接服务于本课目标。\n\n" "告诉我课题,我帮你设计 1-2 个具体导入脚本。" ) def _chat_activity(self, ctx: str) -> str: return ( f"设计有效课堂活动,关键是任务清晰 + 人人有事做 + 及时反馈{ctx}:\n\n" "常用活动类型\n" "- 小组讨论:4 人一组,问题要有争议性/开放性,讨论前给 1 分钟独立思考,避免搭便车。\n" "- 角色扮演:语文/英语/历史特别适合,提前给角色卡和台词框架。\n" "- 操作探究:数学/科学课用学具、实验,让学生做出结论。\n" "- 游戏竞赛:抢答、卡片配对、知识闯关,适合巩固环节,注意控制节奏。\n" "- 同伴互教:会的学生教不会的,教的人反而学得更深。\n\n" "让活动不失控的 3 个原则\n" "1. 活动前讲清规则和产出(讨论结束每组派代表说 1 条),黑板上挂任务。\n" "2. 活动中教师巡视、记录、点拨,不站在讲台。\n" "3. 活动后必须汇报/展示/点评,否则学生会觉得讨论了没用。\n\n" "告诉我学科和课型,我帮你设计一个具体活动。" ) def _chat_questioning(self, ctx: str) -> str: return ( f"高质量课堂提问,记住三秒原则 + 追问 + 全员应答{ctx}:\n\n" "一、提问前的设计\n" "备课时写下 3-5 个核心问题,分三类:\n" "- 记忆型(是什么)→ 唤醒旧知\n" "- 理解型(为什么/怎么说)→ 促思考\n" "- 应用/评价型(如果…会怎样/你同意吗)→ 促迁移\n" "一节好课,后两类要占多数。\n\n" "二、提问技巧\n" "- 先问后叫人:先抛问题,停 3-5 秒(让所有人想),再随机点名。\n" "- 追问:学生答完,追问你怎么想到的?还有别的可能吗?把思维引向深处。\n" "- 不轻易否定:错误答案也是资源,追问这个想法哪里有问题,让全班分析。\n" "- 全员应答:用白板、手势、答题卡、随机抽签,让每个学生被迫回应。\n\n" "避坑:别只问对不对/是不是;别一问完立刻自己答。\n\n" "把要讲的课题告诉我,我帮你设计一组阶梯式提问。" ) def _chat_management(self, ctx: str) -> str: return ( f"课堂纪律靠规则前置 + 节奏控制 + 关系先行,而非吼叫{ctx}:\n\n" "一、预防优于纠正\n" "- 开学第一周把课堂常规立清楚(举手发言、不插嘴、安静信号),坚持执行。\n" "- 用固定的安静信号(拍手节奏、举手倒数、手势),比喊安静有效。\n\n" "二、走神/讲话:用教学手段拉回,而非批评\n" "- 突然停顿 3 秒(全班会安静看你)。\n" "- 把走神学生的名字编进例题(假设小明买了 3 个本子…),温和提醒。\n" "- 走下讲台站到他旁边继续讲,距离本身就是管理。\n\n" "三、调皮/对抗:私下处理,保护自尊\n" "- 课堂上不当众发作(易激化),课后单独谈。\n" "- 谈话用我观察到…+ 我担心…+ 我希望…句式,而非指责。\n" "- 找到行为背后的需求(求关注?听不懂?家庭问题?),对症下药。\n\n" "四、根本:让课足够有趣 + 师生关系好\n" "纪律问题 80% 来自课堂太无聊或学生不喜欢老师。把课上精彩、多关心学生,纪律自然好转。\n\n" "具体场景(哪个年级、什么行为)告诉我,我给针对性策略。" ) def _chat_differentiation(self, ctx: str) -> str: return ( f"应对两极分化/学困生,核心是分层不减负、抓两头带中间{ctx}:\n\n" "一、分层不分班(课内分层)\n" "- 目标分层:同一节课,学困生只要达成基础目标,学优生有挑战任务。\n" "- 任务分层:布置 A(基础)/B(提高)/C(拓展)三档作业,学生选做或指定。\n" "- 提问分层:简单问题给学困生(建立信心),难题给学优生。\n\n" "二、补差要抓前置基础\n" "学困生卡住往往是旧知识漏洞,不是这节课听不懂。诊断他缺哪块基础,用课前 10 分钟或课后针对性补。\n\n" "三、同伴互助(最省力)\n" "小老师制:1 个学优生帮 1 个学困生,捆绑考核(两人都进步才表扬)。教的人学得更深,被教的人压力小、敢问。\n\n" "四、保护学困生自尊\n" "- 永远不当众羞辱(这么简单都不会是毒药)。\n" "- 抓住任何小进步公开表扬,建立我能学的信念。\n" "- 错题私下反馈,不公布排名。\n\n" "五、培优要给空间\n" "学优生最怕吃不饱被打发。给自主探究任务、当小老师、参加竞赛,让能量有出口。\n\n" "具体学科和年级告诉我,我帮你设计分层任务清单。" ) def _chat_parents(self, ctx: str) -> str: return ( f"家校沟通关键是先共情 + 讲事实 + 给方案,而非告状{ctx}:\n\n" "一、沟通前心态\n" "家长不是对手,是同盟。即使投诉的家长,本质也是为孩子好。先听他说完,别急着辩解。\n\n" "二、报问题的标准话术(三明治法)\n" "1. 先说一个孩子的优点/进步(让家长放下防备)。\n" "2. 客观陈述问题(只讲事实,不带评价:最近三次作业有两次没交,而非这孩子太懒)。\n" "3. 表达关心 + 给具体方案(我担心影响他后续学习,咱们一起想想办法,我建议…您看行吗?)。\n\n" "三、家长群沟通禁忌\n" "- 不在群里点名批评个别学生(保护隐私)。\n" "- 不在群里跟家长争论(私聊处理)。\n" "- 通知类信息简洁清晰,避免长语音。\n" "- 投诉先回应已收到,X 点前给您答复,别晾着。\n\n" "四、家访/约谈\n" "- 提前预约,不突袭。\n" "- 先表扬再谈问题,结束时达成 1-2 个可执行小目标。\n" "- 记录谈话要点,下次跟进。\n\n" "具体场景(投诉什么/孩子什么问题)告诉我,我帮你写一段沟通话术。" ) def _chat_assessment(self, ctx: str) -> str: return ( f"作业与评价的核心是少而精 + 即时反馈 + 促学而非排名{ctx}:\n\n" "一、作业设计原则\n" "- 少而精:3 道有思维含量的题 > 30 道机械重复。\n" "- 分层:基础题全员必做,挑战题选做,避免学困生抄、学优生闲。\n" "- 形式多样:除书面外,加口头、实践、项目作业(如用本周学的统计调查家里一周用电量)。\n" "- 必批必改必反馈:不批的作业等于没布置;批改后要讲评典型错题。\n\n" "二、形成性评价(重过程)\n" "不只看期末考试。日常用:课堂提问记录、小测、作品集、同伴互评,让评价贯穿学习全过程。\n\n" "三、反馈的艺术\n" "- 具体:第二步符号写反了 > 粗心。\n" "- 可改进:指出下一步怎么做,而非只判对错。\n" "- 及时:当天/次日反馈,隔一周就失效。\n" "- 成长导向:比上次进步了比 85 分更有激励作用。\n\n" "具体学科和题型告诉我,我帮你设计一份分层作业。" ) def _chat_motivation(self, ctx: str) -> str: return ( f"激发学习动机,从胜任感 + 自主感 + 归属感入手{ctx}:\n\n" "一、胜任感:让他尝到成功的甜头\n" "没兴趣的学生,往往因为长期学不会而放弃。给他够得着的小目标,让他体验我会了——一次小测进步、一道题做对,立刻肯定。信心是兴趣的前提。\n\n" "二、自主感:给他选择权\n" "人对被命令的事天然抗拒。给有限选择:今天作业 A 还是 B?小组讨论你想当记录员还是汇报员?参与感提升投入度。\n\n" "三、归属感:让他感到被看见\n" "记住每个学生的名字和一件小事,课堂上让每个人都有发言机会。学生因为喜欢这个老师而喜欢这门课是真实存在的。\n\n" "四、让知识有用 + 有趣\n" "- 联系生活/热点/学生关心的事,让知识活起来。\n" "- 用游戏、竞赛、悬念,让过程有乐趣。\n" "- 慎用外在奖励:物质奖励/加分短期有效,长期会削弱内驱力。多用成长反馈、公开认可。\n\n" "五、排查外部原因\n" "突然厌学,先了解:家庭变故?同伴关系?沉迷手机?身体/心理问题?对症才能解。\n\n" "具体是哪个学生/什么表现告诉我,我给针对性方案。" ) def _chat_review(self, ctx: str) -> str: return ( f"复习备考要结构化 + 错题驱动 + 模拟实战,而非题海{ctx}:\n\n" "一、先建知识结构(脑图)\n" "复习不是把新课重讲一遍。先带学生画整章/整册知识脑图,理清脉络,让零散知识串成网。结构清晰,提取才快。\n\n" "二、错题驱动(最高效)\n" "把全学期高频错题/易错点整理成专题,针对性突破。会的题不重复刷,时间留给真问题。建议每个学生建错题本,定期重做。\n\n" "三、三轮复习节奏\n" "- 第一轮:单元过关,扫清知识盲点(重基础)。\n" "- 第二轮:专题整合,跨章节串联(重综合)。\n" "- 第三轮:模拟实战,限时训练(重应试与心态)。\n\n" "四、模拟考试要仿真\n" "限时、独立、评分标准对齐正式考试。考后不只对答案,要分析:哪类题失分?知识漏洞还是应试失误?制定针对性补救。\n\n" "五、别忘了心态\n" "考前焦虑的学生很多。适当减压,强调尽力就好,别把分数和人格绑定。\n\n" "具体学科和学段告诉我,我帮你列复习专题清单。" ) def _chat_newteacher(self, ctx: str) -> str: return ( f"新手教师上路,先稳住课堂 + 关系 + 心态三件事{ctx}:\n\n" "一、第一年别追求花哨,先求稳\n" "- 把常规立住(纪律、作业、发言规则),课堂乱什么都做不好。\n" "- 备课写详案(每环节说什么、问什么、学生可能怎么答),别只写框架。\n" "- 多听老教师的课,模仿是最好的学习。\n\n" "二、师生关系:严慈相济\n" "- 开学先立威(规则严、说到做到),再慢慢施恩(关心、幽默、认可)。顺序反了很难管。\n" "- 记住每个学生名字,课后多聊天。被看见的学生才会配合你。\n" "- 永远不当众发火/羞辱学生,一次就可能毁掉整学期关系。\n\n" "三、教学:少讲多练,别贪多\n" "- 一节课 2-3 个核心点足够,讲透练透比走马观花强。\n" "- 教师讲授控制在 15 分钟内,剩下时间给学生。\n" "- 每节课留 5 分钟检测,知道自己教得怎么样。\n\n" "四、心态:别和名师比,和自己比\n" "- 第一年上不好很正常,三年才成型,五年才出风格。\n" "- 别把所有问题归咎于自己(学生基础、家庭都是变量)。\n" "- 找一个愿意带你/听你课的师傅,成长快 3 倍。\n\n" "具体困惑(管不住班?备课没底?家长沟通?)告诉我,我针对性支招。" ) def _chat_general(self, msg: str, ctx: str) -> str: return ( f"我收到了你的问题。为了让建议更精准,先按问题—对象—目标帮你理一理{ctx}:\n\n" "一、你想解决的核心问题是什么?\n" "比如:备课没思路?某个知识点学生听不懂?课堂纪律乱?学生没兴趣?家长难沟通?复习效率低?\n\n" "二、针对的对象和情境?\n" "学科、年级、班级特点(人数、两极分化程度)、这节课/这个学生的具体情况。\n\n" "三、你期望达成的目标?\n" "想要一份完整教案?一个活动设计?一段沟通话术?一个分层作业?还是某个具体问题的解决思路?\n\n" "把这些信息补充给我,我能给出可直接用的方案。你也可以直接试试这些常见问题:\n" "- 如何讲清[某知识点]?\n" "- 设计一节[某课题]的导入/活动\n" "- 班上两极分化怎么办?\n" "- 学生上课走神怎么管理?\n" "- 如何回复家长的投诉?" ) def _fallback_mindmap(self, topic: str, subject: str) -> dict: topic = self._repair_text(topic) title = topic[:24] or "思维导图" for keys, nodes in self._mindmap_kb(): if any(k in topic for k in keys): return {"title": title, "nodes": nodes} return {"title": title, "nodes": self._mindmap_subject_template(subject)} # ---- 主题感知思维导图知识库 ---- _MM_COLORS = ["#00a870", "#2563eb", "#d97706", "#db2777", "#7c3aed", "#0891b2"] def _mm_nodes(self, *branches): out = [] for i, (title, kids) in enumerate(branches): out.append({"title": title, "color": self._MM_COLORS[i % len(self._MM_COLORS)], "children": [{"title": k} for k in kids]}) return out def _mindmap_kb(self): mm = self._mm_nodes # 历史 if False: pass kb = [ (["朝代", "历代", "王朝", "中国古代史"], mm( ("先秦文明", ["夏商周更替", "春秋五霸", "战国七雄"]), ("秦汉大一统", ["秦始皇统一", "汉武帝强盛", "丝绸之路"]), ("隋唐盛世", ["隋朝开科", "贞观之治", "开元盛世"]), ("宋元变革", ["宋代理学", "元朝行省制", "四大发明"]), )), (["丝绸之路"], mm( ("陆上丝路", ["张骞出使西域", "长安出发", "途经河西走廊"]), ("海上丝路", ["泉州广州港口", "瓷器丝绸外销", "郑和下西洋"]), ("贸易商品", ["丝绸", "瓷器", "茶叶", "香料"]), ("文化交流", ["佛教东传", "四大发明西传", "中外互通"]), )), (["工业革命"], mm( ("第一次工业革命", ["蒸汽机", "纺织业机械化", "铁路交通"]), ("第二次工业革命", ["电力", "内燃机", "化学工业"]), ("社会影响", ["城市化", "阶级变化", "殖民扩张"]), ("科技进步", ["生产力飞跃", "世界市场形成", "环境污染"]), )), (["抗日战争", "抗战"], mm( ("局部抗战", ["九一八事变", "东北沦陷", "义勇军抗争"]), ("全面抗战", ["七七事变", "正面战场", "敌后战场"]), ("重大战役", ["台儿庄", "百团大战", "平型关"]), ("伟大胜利", ["统一战线", "世界反法西斯", "日本投降"]), )), (["文艺复兴"], mm( ("兴起背景", ["意大利城邦", "资本主义萌芽", "人文主义"]), ("代表人物", ["但丁", "达芬奇", "莎士比亚"]), ("核心思想", ["以人为本", "个性解放", "反对神权"]), ("深远影响", ["思想解放", "科学革命", "宗教改革"]), )), (["辛亥革命"], mm( ("革命背景", ["清末危机", "民族资本主义", "革命思想传播"]), ("革命过程", ["武昌起义", "各省响应", "建立民国"]), ("历史意义", ["推翻帝制", "民主共和", "思想解放"]), ("局限性", ["革命不彻底", "军阀割据", "任务未完成"]), )), # 地理 (["气候", "气候类型", "气温降水"], mm( ("影响因子", ["纬度", "海陆", "地形", "洋流"]), ("主要类型", ["热带雨林", "季风气候", "温带大陆性", "地中海气候"]), ("降水规律", ["赤道多雨", "副热带少雨", "温带增多"]), ("中国气候", ["季风显著", "雨热同期", "气候复杂多样"]), )), (["地形", "地貌", "地势"], mm( ("基本地形", ["山地", "平原", "高原", "盆地", "丘陵"]), ("中国地势", ["西高东低", "阶梯分布", "大河东流"]), ("外力作用", ["流水侵蚀", "风力沉积", "冰川作用"]), ("典型地貌", ["喀斯特", "丹霞", "黄土高原", "冲积平原"]), )), (["河流", "水系", "长江", "黄河"], mm( ("长江", ["发源青藏", "流经十一省", "入东海", "黄金水道"]), ("黄河", ["发源巴颜喀拉", "泥沙含量大", "地上河", "入渤海"]), ("河流作用", ["供水灌溉", "航运发电", "塑造平原"]), ("治理保护", ["防洪堤坝", "水土保持", "生态修复"]), )), (["地球", "地球运动", "自转公转"], mm( ("自转", ["绕轴旋转", "昼夜交替", "时差"]), ("公转", ["绕日运行", "四季更替", "五带划分"]), ("黄赤交角", ["23.5度", "太阳直射点移动", "昼夜长短变化"]), ("地理意义", ["正午太阳高度", "节气", "气候形成"]), )), # 生物 (["细胞"], mm( ("细胞结构", ["细胞膜", "细胞质", "细胞核", "细胞壁(植物)"]), ("细胞器", ["线粒体", "叶绿体", "核糖体", "液泡"]), ("细胞分裂", ["有丝分裂", "减数分裂", "无丝分裂"]), ("细胞分化", ["形态变化", "功能特化", "形成组织"]), )), (["光合作用"], mm( ("反应条件", ["光照", "叶绿体", "适宜温度"]), ("原料产物", ["二氧化碳", "水", "氧气", "有机物"]), ("两个阶段", ["光反应", "暗反应", "能量转化"]), ("重要意义", ["制造有机物", "释放氧气", "碳氧平衡"]), )), (["生态系统"], mm( ("组成成分", ["非生物物质", "生产者", "消费者", "分解者"]), ("食物链网", ["捕食关系", "能量单向", "物质循环"]), ("能量流动", ["太阳能输入", "逐级递减", "10%-20%传递"]), ("生态平衡", ["自我调节", "稳定性", "人类影响"]), )), (["遗传", "基因", "DNA"], mm( ("遗传物质", ["DNA", "基因", "染色体"]), ("基本规律", ["分离定律", "自由组合", "显隐性"]), ("遗传变异", ["基因突变", "基因重组", "染色体变异"]), ("应用拓展", ["育种", "遗传咨询", "基因工程"]), )), (["人体", "消化", "呼吸", "循环"], mm( ("消化系统", ["口腔胃小肠", "消化酶", "吸收营养"]), ("呼吸系统", ["呼吸道", "肺泡", "气体交换"]), ("循环系统", ["心脏", "血管", "血液循环"]), ("神经系统", ["大脑", "神经传导", "反射调节"]), )), # 化学 (["元素周期表", "元素"], mm( ("结构规律", ["周期", "族", "原子序数", "递变规律"]), ("主族元素", ["碱金属", "卤素", "氧族", "氮族"]), ("周期变化", ["原子半径递减", "金属性减弱", "非金属性增强"]), ("应用价值", ["预测性质", "寻找新材料", "指导分类"]), )), (["化学反应"], mm( ("反应类型", ["化合", "分解", "置换", "复分解"]), ("反应条件", ["加热", "点燃", "催化", "通电"]), ("能量变化", ["放热反应", "吸热反应", "化学能转化"]), ("方程式", ["质量守恒", "配平", "符号规范"]), )), (["酸碱盐", "酸碱"], mm( ("常见酸", ["盐酸", "硫酸", "硝酸", "醋酸"]), ("常见碱", ["氢氧化钠", "氢氧化钙", "氨水"]), ("酸碱反应", ["中和反应", "生成盐和水", "pH变化"]), ("盐的性质", ["溶解性", "复分解", "焰色反应"]), )), (["原子", "分子", "物质构成"], mm( ("原子结构", ["原子核", "质子", "中子", "核外电子"]), ("分子构成", ["共价键", "分子间作用力", "分子特性"]), ("离子化合物", ["得失电子", "离子键", "晶体结构"]), ("物质分类", ["单质", "化合物", "混合物"]), )), # 物理 (["力学", "力", "牛顿"], mm( ("常见的力", ["重力", "弹力", "摩擦力", "浮力"]), ("牛顿三定律", ["惯性定律", "加速度定律", "作用反作用"]), ("压强", ["固体压强", "液体压强", "大气压强"]), ("简单机械", ["杠杆", "滑轮", "斜面", "功的原理"]), )), (["电学", "电流", "电路", "欧姆"], mm( ("基本物理量", ["电流", "电压", "电阻", "电功率"]), ("欧姆定律", ["I=U/R", "串并联电阻", "电压电流关系"]), ("电路连接", ["串联", "并联", "混联", "短路断路"]), ("电与磁", ["电流磁效应", "电磁感应", "电动机发电机"]), )), (["光学", "光", "反射折射"], mm( ("光的传播", ["直线传播", "光速", "影子小孔成像"]), ("光的反射", ["反射定律", "镜面反射", "漫反射"]), ("光的折射", ["折射定律", "全反射", "色散"]), ("透镜成像", ["凸透镜", "凹透镜", "成像规律", "应用"]), )), (["运动", "速度", "加速度"], mm( ("运动描述", ["参照物", "路程位移", "速度"]), ("匀速运动", ["速度恒定", "s=vt", "图像分析"]), ("变速运动", ["加速度", "v=v0+at", "匀变速直线"]), ("相对运动", ["相对速度", "相遇追及", "实际应用"]), )), # 数学 (["函数"], mm( ("函数概念", ["定义域", "值域", "对应关系"]), ("基本初等函数", ["一次函数", "二次函数", "反比例函数"]), ("指数对数", ["指数函数", "对数函数", "运算法则"]), ("函数性质", ["单调性", "奇偶性", "周期性"]), )), (["几何", "三角形", "圆"], mm( ("线与角", ["点线面", "相交平行", "角的关系"]), ("三角形", ["内角和", "全等相似", "勾股定理"]), ("四边形", ["平行四边形", "矩形菱形", "梯形"]), ("圆", ["圆的性质", "切线", "弧长扇形"]), )), (["方程", "一元", "二元", "不等式"], mm( ("一元一次", ["等式性质", "求解步骤", "应用题"]), ("方程组", ["二元一次", "代入消元", "加减消元"]), ("一元二次", ["因式分解", "公式法", "韦达定理"]), ("不等式", ["性质", "一元一次不等式", "不等式组"]), )), (["概率", "统计"], mm( ("数据收集", ["普查抽样", "频数频率", "统计图表"]), ("数据特征", ["平均数", "中位数众数", "方差极差"]), ("概率初步", ["事件分类", "古典概型", "频率估计", "树状图列举"]), ("应用", ["决策分析", "风险评估", "生活实例"]), )), # 语文 (["古诗", "古诗词", "唐诗宋词"], mm( ("发展脉络", ["诗经楚辞", "唐诗", "宋词", "元曲"]), ("代表诗人", ["李白杜甫", "白居易", "苏轼李清照"]), ("常见题材", ["山水田园", "边塞", "咏物言志", "送别"]), ("鉴赏方法", ["意象意境", "表现手法", "炼字", "情感主旨"]), )), (["记叙文"], mm( ("六要素", ["时间地点", "人物", "起因经过结果"]), ("结构层次", ["开头引入", "主体展开", "结尾点题"]), ("表达方式", ["叙述", "描写", "议论抒情"]), ("写作技巧", ["详略得当", "线索贯穿", "细节描写"]), )), (["议论文"], mm( ("三要素", ["论点", "论据", "论证"]), ("论点确立", ["中心论点", "分论点", "鲜明准确"]), ("论证方法", ["举例论证", "道理论证", "对比比喻"]), ("结构思路", ["提出问题", "分析问题", "解决问题"]), )), # 英语 (["时态"], mm( ("一般时态", ["一般现在", "一般过去", "一般将来"]), ("进行时", ["现在进行", "过去进行", "be+doing"]), ("完成时", ["现在完成", "过去完成", "have+done"]), ("时间状语", ["always/often", "yesterday", "already/yet"]), )), (["从句"], mm( ("名词性从句", ["主语从句", "宾语从句", "表语从句"]), ("定语从句", ["关系代词", "关系副词", "限制非限制"]), ("状语从句", ["时间原因", "条件让步", "目的结果"]), ("用法要点", ["连接词", "语序", "时态呼应"]), )), ] return kb def _mindmap_subject_template(self, subject: str) -> list: subject = (subject or "").strip() mm = self._mm_nodes templates = { "数学": mm( ("概念理解", ["定义", "性质", "判定条件"]), ("公式法则", ["基本公式", "运算法则", "推导过程"]), ("典型例题", ["基础题", "应用题", "易错题"]), ("知识应用", ["实际问题", "跨学科", "拓展提升"]), ), "物理": mm( ("物理概念", ["定义", "物理意义", "单位"]), ("规律定律", ["适用条件", "公式表达", "实验基础"]), ("实验探究", ["实验器材", "操作步骤", "数据处理"]), ("生活应用", ["现象解释", "技术应用", "综合计算"]), ), "化学": mm( ("物质组成", ["元素", "结构", "性质"]), ("变化规律", ["反应类型", "反应条件", "实验现象"]), ("化学计算", ["化学式", "方程式", "质量计算"]), ("实际应用", ["材料能源", "环境保护", "生命健康"]), ), "生物": mm( ("生命现象", ["结构基础", "生理功能", "生命活动"]), ("核心概念", ["定义", "原理", "机制"]), ("实验观察", ["方法", "现象", "结论"]), ("联系应用", ["生产实践", "健康生活", "生态保护"]), ), "历史": mm( ("时代背景", ["政治", "经济", "文化"]), ("重要事件", ["时间", "人物", "过程"]), ("深远影响", ["制度变革", "思想文化", "社会发展"]), ("历史启示", ["经验教训", "规律认识", "现实意义"]), ), "地理": mm( ("空间分布", ["位置", "范围", "界线"]), ("自然要素", ["地形气候", "水文土壤", "植被"]), ("人文要素", ["人口城市", "农业工业", "交通"]), ("人地关系", ["资源利用", "环境问题", "可持续发展"]), ), "语文": mm( ("基础知识", ["字词", "语句", "修辞"]), ("文本理解", ["内容", "结构", "主旨"]), ("表达技巧", ["描写方法", "表现手法", "语言特色"]), ("拓展运用", ["写作借鉴", "迁移阅读", "文化传承"]), ), "英语": mm( ("词汇积累", ["核心单词", "短语搭配", "词形变化"]), ("语法要点", ["句型结构", "时态语态", "从句"]), ("功能话题", ["日常交际", "话题表达", "文化背景"]), ("技能训练", ["听说", "读写", "综合运用"]), ), } if subject in templates: return templates[subject] return mm( ("核心概念", ["定义", "特征", "分类"]), ("知识结构", ["基本原理", "重要规律", "内在联系"]), ("重点方法", ["解题思路", "分析方法", "易错提示"]), ("应用拓展", ["生活实例", "学科关联", "拓展提升"]), ) async def generate_animation(self, prompt: str, anim_type: str = "general") -> dict: prompt = self._repair_text(prompt) system_prompt = f"""你是一位教学动画设计专家。根据教师描述,生成教学动画的HTML页面。 重要规则: - 所有HTML属性必须用单引号,如 style='color:red;' onclick='fn()' - 不要使用" ) if qtype == "fill_blank": return ( "" "
围绕“底面积 × 高”展开探究,通过观察、切分、比较和例题,把抽象公式变成可理解、可操作的课堂体验。
体积和底面积、高有关。先看“底面铺了多少”,再看“竖起来有多高”。
圆柱越像被分成很多很薄的小层,每一层越接近一个圆形小面。层数越多,拼出的形状就越接近长方体。
通过轨道、倾角和阳光照射方向的变化,把春夏秋冬的形成过程讲清楚、看明白、能点击。
地球绕太阳转一圈,需要大约一年。
地轴倾斜让同一地区在不同时间受到的阳光不同。
阳光照射角度和昼夜长短,会随着公转位置变化。
春季时太阳直射位置逐渐北移,北半球白天变长,天气回暖,植物也开始生长。
角度越直,单位面积接收到的热量越多;角度越斜,热量越分散。
白天越长,太阳照射时间越久,地面吸收热量也更多。
用清爽、分层、可点击的页面组织课堂内容,适合投屏展示和逐步讲解。
把主题拆成 2 到 3 个核心问题,再分别展开。
先观察,再归纳,最后用例子验证。
用点击反馈、小练习和总结页收尾。
围绕图片、数据或情境,先让学生说出“看见了什么”,再引导他们说“为什么会这样”。