博主智算菩萨,专注于人工智能、Python编程、音视频处理及UI窗体程序设计等方向。致力于以通俗易懂的方式拆解前沿技术,从零基础入门到高阶实战,陪伴开发者共同成长。目前已开设五大技术专栏,累计发布多篇原创技术文章,深受读者好评。

📌 专栏导航

  • 人工智能前沿知识:深度剖析Transformer架构、生成式AI、强化学习、具身智能、神经符号系统、大模型及智能体(Agent)技术,系统性解析AI核心技术体系与前沿趋势。
  • Python基础小白编程:从零开始,以保姆式教程讲解变量、数据类型、流程控制、函数等核心语法,配有大量实战代码与避坑指南,真正做到学以致用。
  • 机器学习与深度学习:系统化拆解线性模型、决策树、随机森林、梯度提升树、神经网络等算法原理与工程实践,覆盖从公式推导到代码实现的全链路内容。
  • 音频、图像与视频处理理论与实战:涵盖FFmpeg多媒体处理、audio_shop开源工具、ComfyUI-WanVideoWrapper视频生成等实用技术,从基础操作到高级应用一应俱全。
  • UI窗体程序设计实战:深入讲解UI设计、动态窗体生成、游戏UI框架设计等实战技巧,提供从配置到编码的完整解决方案。
    智算菩萨,以代码为经,以算法为纬,在人工智能的星辰大海中,做你前行路上最可靠的导航者。

1. 引言:文言文学习的困境与破局之道

对于初中生和高中生而言,文言文阅读始终是一道“硬骨头”。实词一词多义、虚词用法庞杂、特殊句式难以捉摸、古代文化常识浩如烟海——这些知识点不仅需要记忆,更需要在大量原典阅读中反复体悟。然而,市场上现有的练习材料往往题目陈旧、文章雷同,难以激发学生兴趣。

随着大语言模型技术的成熟,我们完全可以用AI为每位学生实时生成全新的、古奥纯正的文言文,并基于该文自动产出20道覆盖实词、虚词、句式、文化常识的选择题。本文介绍的就是这样一款“文言文训练器”——一个纯前端、调用免费AI模型(gemini-2.5-flash-lite)的HTML应用,无需任何后端部署,打开浏览器即可使用。

免费提示:本工具使用的API接口为 aigc.bar,注册后即可获得免费额度,默认推荐模型 gemini-2.5-flash-lite,足够学生日常练习使用。注册地址:https://api.aigc.bar/register?aff=UP4F(注册后获取类似 sk-xxx 的密钥)。

2. 系统总览:功能模块与技术架构

本训练器采用单HTML文件架构,所有交互均在前端完成,通过调用AI接口实现文言文生成与智能出题。下表概括了核心模块与作用:

模块 技术实现 对学习者的价值
文言文生成引擎 流式调用AI,支持主题、字数控制 每次生成全新文言文,避免重复刷题,内容不僵硬
随堂测验系统 20道选择题(实词10+虚词3+句式2+文化5) 精准覆盖文言文四大考点,且答案分布均匀
答题交互与解析反馈 提交后显示正确答案与详细解析 即时纠错,帮助理解每题背后的知识点
会话历史管理 localStorage持久化存储 可保存多篇文章及相应题目,方便复习回顾
API密钥本地保存 自动记忆用户密钥,无需重复输入 简化操作流程,适合课堂或家庭使用

整个页面由左侧文章目录、中央文言文展示区、右侧答题区三栏构成,界面古朴雅致,符合文言文学习氛围。

3. 核心功能详解

3.1 文言文智能生成:主题与字数随心定

学生或教师可以在参数面板中填写主题(如“古代贤臣传记”“山水游记”“史论”等),并选择目标字数(300/500/800/1000字)。AI会严格使用文言词汇和句式进行创作,严禁出现白话文

  • 生成特点:模型会主动运用较难的实词(如“诣、赍、觇、罹、稔、逋”)、虚词(之、乎、者、也、矣、焉、哉)以及典故进行行文,使文章具备真实的古韵。
  • 流式展示:生成过程逐字输出在屏幕上,如同古代翰林现场挥毫,增加学习的沉浸感。

3.2 配套20道选择题:考点分布科学均衡

文章生成后,系统会立即调用AI根据该文自动生成20道四选一选择题,构成一份完整的随堂测验。题目类型与数量如下表:

题目类型 数量 考查目标举例
实词解释 10题 “诣”意为“到访”;“赍”意为“携带”
虚词用法 3题 “之”的取消句子独立性;“而”的表转折或修饰
文言句式 2题 宾语前置(“何陋之有”)、状语后置(“战于长勺”)
古代文化常识 5题 科举制度、官职名称、礼仪礼节、地理称谓等

尤为重要的是,正确答案在A、B、C、D四个选项中的分布概率经AI提示严格约束,基本保证每种选项出现5次左右,避免学生靠猜“C位”蒙混过关。

3.3 答题交互与解析反馈:即时学习闭环

右侧答题区每题均提供“提交答案”和“下一题”按钮。提交后页面会:

  • 用绿色/红色标识回答是否正确;
  • 显示正确答案的完整内容;
  • 提供详细的解析文字(如“该词在文中意为……,常见于《史记》……”)。

这种设计使学生在做题后立刻理解错误原因,而不是只看一个对错符号。教师也可以利用该工具在课堂中快速生成不同主题的文言文随堂测。

4. 使用教程:从获取免费API密钥到生成训练

以下步骤专为零基础学生和教师设计,全程无需编程知识。

4.1 获取免费API密钥

  1. 点击 https://api.aigc.bar/register?aff=UP4F 打开注册页面。

  2. 完成邮箱注册或第三方登录,进入后台。

  3. 在“API Keys”菜单中创建一个新的密钥,复制以 sk- 开头的字符串(例如 sk-ysCTpytZmPYNnWhXN8Wk3e09AfjN5mIvm4tDYAl6gnYS6pTX 格式)。
    在这里插入图片描述

  4. 注意:该平台提供的免费额度足够学生日常进行数十次文言文生成练习。

4.2 打开文言文训练器

  • 将我们提供的完整HTML代码保存为 classical_trainer.html 文件,用任意现代浏览器(Chrome/Edge/火狐)打开。
  • 若不具备保存条件,也可直接在在线HTML编辑器中运行。

4.3 配置与生成

  • 在顶部工具栏的 “API 密钥” 框中粘贴刚才复制的 sk-xxx 密钥,模型名称保持 gemini-2.5-flash-lite
  • 点击 “生成新篇” 按钮(该按钮在工具栏右侧)。
  • 在展开的参数面板中填入主题(例如“北宋名臣包拯传”)、选择字数
  • 等待约10~30秒,左侧文章区域会出现流式输出的文言文,右侧会自动生成20道题目。
    在这里插入图片描述

4.4 开始答题

  • 依次完成每道题,选择选项后点击“提交答案”,查看解析后点击“下一题”。
  • 左侧“文集册”会自动保存每次生成的文章和题目,方便日后复习。

5. 技术实现亮点

为了让读者了解其背后的工作机制,这里简要说明几个关键设计:

  • 提示词工程:在生成文言文时,系统给AI的指令中明确要求“含有较多困难实词、虚词,严禁白话文”;在出题阶段,要求AI按10+3+2+5的数量输出JSON,并显式约束选项分布均匀。
  • 流式解析与渲染:利用 fetch + ReadableStream 实时接收AI返回的文本块,并借助 marked 库渲染到页面,提升用户体验。
  • 题型平衡算法:出题提示词中写入“正确答案在A、B、C、D四个选项中各占约5次”,由AI模型自主遵循;经多次测试,分布基本理想。
  • 无后端部署:所有会话数据保存在浏览器的 localStorage 中,即使关闭页面,再次打开仍可继续上次练习。

6. 注意事项:文言文内容仅供参考

⚠️ 重要声明:本工具生成的文言文及选择题、解析均由大语言模型自动生成,并非出自古籍原文。模型可能在某些字词释义或历史背景上存在偏差,甚至可能产生不符合史实的虚构内容。
建议使用方法

  • 将生成的文章作为文言文阅读材料练习素材,但不应作为学术引用或考试依据。
  • 对于关键的实词释义和文化常识,鼓励学生查阅纸质词典或《古汉语常用字字典》进行核实。
  • 教师可先利用本工具快速获得一篇陌生文言文,再人工校正疑点后发给学生练习。

本工具的核心价值在于提供无限量的、符合文言文语法特征的训练文本,帮助学生克服“陌生感”和“畏难情绪”,其产出质量已远超普通随机填空软件。

7. 总结与展望

本文介绍的文言文训练器巧妙结合了大语言模型的生成能力与中学文言文教学的实际需求。它实现了:

  • 一键生成主题可定制、字数可控的古奥文言文;
  • 自动配套20道科学分布的选择题(实词、虚词、句式、文化常识);
  • 完整的答题、解析、历史保存功能;
  • 完全免费(利用gemini-2.5-flash-lite模型)且无需部署。

未来可以扩展的方向包括:增加文言文朗读音频(TTS)、错题本自动收录、以及基于学生答题情况调整题目难度的个性化学习路径。欢迎广大师生和教育工作者使用并反馈,让AI真正赋能传统语文教学。

附:完整源代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title>文言文训练器 | 古文学堂AI</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: #f3efe7;
            height: 100vh;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }
        .toolbar {
            background: #fff9ef;
            border-bottom: 1px solid #e2dcd0;
            padding: 12px 20px;
            display: flex;
            flex-wrap: wrap;
            gap: 12px;
            align-items: center;
            box-shadow: 0 1px 4px rgba(0,0,0,0.03);
            z-index: 2;
        }
        .api-section {
            display: flex;
            gap: 10px;
            align-items: center;
            flex-wrap: wrap;
            background: #f0f2f5;
            padding: 6px 12px;
            border-radius: 24px;
        }
        .api-section input, .api-section .model-input {
            padding: 6px 12px;
            border: 1px solid #c8cdd4;
            border-radius: 32px;
            font-size: 13px;
            background: white;
            width: 180px;
        }
        button {
            background: #6b4c3b;
            color: white;
            border: none;
            border-radius: 32px;
            padding: 6px 16px;
            font-size: 13px;
            cursor: pointer;
            transition: all 0.2s ease;
        }
        button:hover { background: #4e3629; transform: translateY(-1px); }
        .param-toggle {
            background: #e8e0d3;
            color: #4e3629;
            padding: 6px 14px;
            border-radius: 32px;
            cursor: pointer;
            font-size: 13px;
        }
        .param-panel {
            background: #fefaf2;
            border-bottom: 1px solid #e2dcd0;
            display: none;
            padding: 12px 20px;
            flex-wrap: wrap;
            gap: 15px;
            font-size: 13px;
        }
        .param-panel.show { display: flex; }
        .param-item {
            display: flex;
            align-items: center;
            gap: 8px;
            background: #fff;
            padding: 4px 12px;
            border-radius: 32px;
            border: 1px solid #e2dcd0;
        }
        .info-tip {
            background: #f6f0e6;
            border-bottom: 1px solid #e2dcd0;
            padding: 8px 20px;
            font-size: 12px;
            color: #7a5a48;
        }
        .app-container {
            display: flex;
            flex: 1;
            overflow: hidden;
        }
        /* 左侧历史 */
        .sidebar {
            width: 260px;
            background: #fcf9f2;
            border-right: 1px solid #e2dcd0;
            display: flex;
            flex-direction: column;
            overflow-y: auto;
            flex-shrink: 0;
        }
        .sidebar-header {
            padding: 18px 16px 12px;
            font-weight: 600;
            color: #5a3e2e;
            border-bottom: 1px solid #ede5d8;
        }
        .new-chat-btn {
            margin: 12px;
            background: #8b6b54;
            width: calc(100% - 24px);
        }
        .conversation-list { flex: 1; overflow-y: auto; }
        .conv-item {
            padding: 12px 16px;
            border-bottom: 1px solid #ede5d8;
            cursor: pointer;
            font-size: 13px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .conv-item.active {
            background: #e8dfd1;
            border-left: 3px solid #6b4c3b;
            font-weight: 500;
        }
        /* 中央:文章区 */
        .main {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            background: #fffbf4;
        }
        .article-area {
            flex: 1;
            overflow-y: auto;
            padding: 20px 24px;
            border-bottom: 1px solid #ede5d8;
        }
        .article-title {
            font-size: 18px;
            font-weight: bold;
            color: #6b4c3b;
            margin-bottom: 12px;
        }
        .article-content {
            font-family: 'Times New Roman', '宋体', serif;
            font-size: 16px;
            line-height: 1.75;
            color: #2c2a29;
            white-space: pre-wrap;
        }
        /* 右侧答题区 */
        .quiz-sidebar {
            width: 380px;
            background: #fefaf2;
            border-left: 1px solid #e2dcd0;
            display: flex;
            flex-direction: column;
            overflow-y: hidden;
            flex-shrink: 0;
        }
        .quiz-header {
            padding: 16px;
            background: #f5ede0;
            border-bottom: 1px solid #e2dcd0;
            font-weight: 600;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .quiz-body {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }
        .question-card {
            background: white;
            border-radius: 20px;
            padding: 18px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
            margin-bottom: 20px;
        }
        .question-text {
            font-weight: 600;
            margin-bottom: 16px;
            line-height: 1.5;
            color: #4a3728;
        }
        .options {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 20px;
        }
        .option {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 8px 12px;
            background: #f9f6ef;
            border-radius: 40px;
            cursor: pointer;
            transition: 0.1s;
            border: 1px solid transparent;
        }
        .option.selected {
            border-color: #6b4c3b;
            background: #ede0d3;
        }
        .option-letter {
            font-weight: bold;
            width: 28px;
        }
        .submit-btn, .next-btn {
            background: #6b4c3b;
            width: 100%;
            padding: 10px;
            margin-top: 12px;
        }
        .next-btn { background: #8b6b54; }
        .result-area {
            margin-top: 16px;
            padding: 12px;
            background: #f0ebe2;
            border-radius: 16px;
            font-size: 13px;
            color: #2c5a2e;
        }
        .loader {
            text-align: center;
            padding: 40px;
            color: #8b6b54;
        }
        .status {
            font-size: 12px;
            padding: 8px 20px;
            background: #fcf9f2;
            border-top: 1px solid #ede5d8;
        }
        button:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>

<div class="toolbar">
    <div class="api-section">
        <input type="password" id="apiKeyInput" placeholder="API 密钥" style="width:200px">
        <input type="text" id="modelNameInput" placeholder="模型" value="gemini-2.5-flash-lite" style="width:160px">
        <button id="getKeyBtn">获取密钥</button>
    </div>
    <div class="param-toggle" id="paramToggle">⚙️ 生成参数</div>
    <div style="margin-left:auto; font-size:12px;">文言文训练器 | 实词·虚词·句式·文化常识</div>
</div>
<div class="param-panel" id="paramPanel">
    <div class="param-item"><label>📖 主题</label><input type="text" id="topicInput" placeholder="如: 名臣传、山水游记、史论" value="古代贤臣传记"></div>
    <div class="param-item"><label>📏 字数</label><select id="wordCountSelect"><option>300</option><option selected>500</option><option>800</option><option>1000</option></select></div>
    <div class="param-item"><label>🌡️ 温度</label><input type="number" id="temperature" value="0.85" step="0.1" min="0" max="2" style="width:60px"></div>
    <div class="param-item"><label>📤 Max Token</label><input type="number" id="maxTokens" value="4000" step="500" min="1000" max="8000" style="width:80px"></div>
</div>
<div class="info-tip">
    🏮 基于AI生成纯正文言文(严禁白话),自动配套20道选择题:10个实词、3个虚词、2个句式、5个古代文化常识。选项分布均匀。
</div>

<div class="app-container">
    <div class="sidebar">
        <div class="sidebar-header">📜 文集册</div>
        <button class="new-chat-btn" id="newChatBtn">➕ 新篇</button>
        <div id="conversationList" class="conversation-list"></div>
    </div>
    <div class="main">
        <div class="article-area">
            <div class="article-title" id="articleTitle">待生成文言文</div>
            <div id="articleContent" class="article-content">点击「生成新篇」或选择右侧参数,AI将创作典雅文言文并生成题目。</div>
        </div>
        <div id="status" class="status">✨ 就绪 | 需配置API密钥</div>
    </div>
    <div class="quiz-sidebar">
        <div class="quiz-header">
            <span>📖 随堂测验 (20)</span>
            <span id="questionCounter">0 / 0</span>
        </div>
        <div id="quizBody" class="quiz-body">
            <div class="loader">生成文章后自动出题,请稍候。</div>
        </div>
    </div>
</div>

<script>
(function(){
    // DOM 元素
    const apiKeyInput = document.getElementById('apiKeyInput');
    const modelNameInput = document.getElementById('modelNameInput');
    const getKeyBtn = document.getElementById('getKeyBtn');
    const paramToggle = document.getElementById('paramToggle');
    const paramPanel = document.getElementById('paramPanel');
    const topicInput = document.getElementById('topicInput');
    const wordCountSelect = document.getElementById('wordCountSelect');
    const temperatureInput = document.getElementById('temperature');
    const maxTokensInput = document.getElementById('maxTokens');
    const newChatBtn = document.getElementById('newChatBtn');
    const conversationListDiv = document.getElementById('conversationList');
    const articleTitleSpan = document.getElementById('articleTitle');
    const articleContentDiv = document.getElementById('articleContent');
    const statusDiv = document.getElementById('status');
    const quizBodyDiv = document.getElementById('quizBody');

    let currentConversationId = null;
    let conversationsMap = {};
    let currentArticle = { title: "", content: "" };
    let currentQuestions = [];        // 存储题目对象: { text, options, answer, explanation, type }
    let currentQIndex = 0;
    let selectedOption = null;        // 当前题目用户临时选择的选项字母
    let answeredStatus = false;        // 当前题目是否已提交(显示解析)
    let currentResultHtml = "";

    // API 配置保存
    function saveApiCredentials() {
        localStorage.setItem('wy_api_key', apiKeyInput.value.trim());
        localStorage.setItem('wy_model', modelNameInput.value.trim());
    }
    function loadApiCredentials() {
        let key = localStorage.getItem('wy_api_key');
        let model = localStorage.getItem('wy_model');
        if(key) apiKeyInput.value = key;
        if(model) modelNameInput.value = model;
    }
    apiKeyInput.addEventListener('input', saveApiCredentials);
    modelNameInput.addEventListener('input', saveApiCredentials);

    const setStatus = (msg, isErr=false) => {
        statusDiv.textContent = msg;
        if(isErr) setTimeout(()=>{ if(statusDiv.textContent===msg) statusDiv.textContent="✅ 就绪"; }, 4000);
    };
    const getApiKey = () => apiKeyInput.value.trim();
    const getModel = () => modelNameInput.value.trim() || "gemini-2.5-flash-lite";

    // 存储会话
    function persistConversations() {
        let store = {};
        for(let id in conversationsMap) {
            store[id] = { messages: conversationsMap[id].messages, title: conversationsMap[id].title, lastUpdated: conversationsMap[id].lastUpdated, article: conversationsMap[id].article, questions: conversationsMap[id].questions };
        }
        localStorage.setItem('wy_conversations', JSON.stringify(store));
        if(currentConversationId) localStorage.setItem('wy_lastId', currentConversationId);
    }
    function loadConversations() {
        let raw = localStorage.getItem('wy_conversations');
        if(raw) {
            try {
                let parsed = JSON.parse(raw);
                conversationsMap = {};
                for(let id in parsed) {
                    conversationsMap[id] = { messages: parsed[id].messages || [], title: parsed[id].title || '未名篇', lastUpdated: parsed[id].lastUpdated || Date.now(), article: parsed[id].article || null, questions: parsed[id].questions || [] };
                }
            } catch(e) {}
        }
        let lastId = localStorage.getItem('wy_lastId');
        if(lastId && conversationsMap[lastId]) currentConversationId = lastId;
        else currentConversationId = null;
        if(!currentConversationId || !conversationsMap[currentConversationId]) createNewConversation();
    }
    function createNewConversation() {
        let newId = 'wy_'+Date.now()+'_'+Math.random().toString(36).substr(2,6);
        conversationsMap[newId] = { messages: [], title: '新篇', lastUpdated: Date.now(), article: null, questions: [] };
        currentConversationId = newId;
        persistConversations();
        renderConvList();
        renderCurrentArticleAndQuiz();
        setStatus("新会话已创建,可生成文言文");
    }
    function renderConvList() {
        conversationListDiv.innerHTML = '';
        let sorted = Object.entries(conversationsMap).sort((a,b)=> b[1].lastUpdated - a[1].lastUpdated);
        for(let [id, conv] of sorted) {
            let div = document.createElement('div');
            div.className = 'conv-item';
            if(id === currentConversationId) div.classList.add('active');
            div.textContent = conv.title || '未名篇';
            div.addEventListener('click', ()=>switchConversation(id));
            conversationListDiv.appendChild(div);
        }
    }
    function switchConversation(id) {
        if(id === currentConversationId) return;
        currentConversationId = id;
        persistConversations();
        renderConvList();
        renderCurrentArticleAndQuiz();
        setStatus(`切换到「${conversationsMap[id].title}`);
    }
    function renderCurrentArticleAndQuiz() {
        let conv = conversationsMap[currentConversationId];
        if(!conv) return;
        if(conv.article) {
            articleTitleSpan.innerText = conv.article.title || '文言文';
            articleContentDiv.innerHTML = marked.parse(conv.article.content || '');
            currentArticle = conv.article;
            currentQuestions = conv.questions || [];
            if(currentQuestions.length === 20) {
                currentQIndex = 0;
                selectedOption = null;
                answeredStatus = false;
                renderCurrentQuestion();
            } else {
                quizBodyDiv.innerHTML = '<div class="loader">当前无题目,请点击生成新篇。</div>';
            }
        } else {
            articleTitleSpan.innerText = '待生成文言文';
            articleContentDiv.innerText = '点击「生成新篇」按钮,AI将创作典雅文言文并自动出题。';
            quizBodyDiv.innerHTML = '<div class="loader">暂无题目,请先生成文言文。</div>';
            currentQuestions = [];
        }
    }

    // 渲染当前题目
    function renderCurrentQuestion() {
        if(!currentQuestions.length || currentQIndex >= currentQuestions.length) {
            quizBodyDiv.innerHTML = '<div class="loader">题目加载完毕,可生成新篇继续练习。</div>';
            return;
        }
        let q = currentQuestions[currentQIndex];
        let html = `<div class="question-card">
            <div class="question-text"><strong>${currentQIndex+1}. ${q.text}</strong></div>
            <div class="options" id="optionsContainer">`;
        for(let opt of ['A','B','C','D']) {
            let optText = q.options[opt];
            let isSelected = (selectedOption === opt);
            let selectedClass = isSelected ? 'selected' : '';
            html += `<div class="option ${selectedClass}" data-opt="${opt}">
                        <span class="option-letter">${opt}.</span>
                        <span>${optText}</span>
                    </div>`;
        }
        html += `</div>
            <button class="submit-btn" id="submitAnswerBtn">提交答案</button>
            <button class="next-btn" id="nextQuestionBtn" style="display:${answeredStatus ? 'block' : 'none'};">下一题 →</button>
            <div id="resultArea" class="result-area">${currentResultHtml || ''}</div>
        </div>`;
        quizBodyDiv.innerHTML = html;
        document.querySelectorAll('.option').forEach(optDiv => {
            optDiv.addEventListener('click', (e) => {
                if(answeredStatus) return;
                let chosen = optDiv.dataset.opt;
                selectedOption = chosen;
                renderCurrentQuestion();  // 刷新高亮
            });
        });
        const submitBtn = document.getElementById('submitAnswerBtn');
        const nextBtn = document.getElementById('nextQuestionBtn');
        if(submitBtn) submitBtn.onclick = () => submitCurrentAnswer();
        if(nextBtn) nextBtn.onclick = () => loadNextQuestion();
        if(answeredStatus) {
            if(submitBtn) submitBtn.style.display = 'none';
            if(nextBtn) nextBtn.style.display = 'block';
        } else {
            if(submitBtn) submitBtn.style.display = 'block';
            if(nextBtn) nextBtn.style.display = 'none';
        }
    }

    function submitCurrentAnswer() {
        if(answeredStatus) return;
        if(!selectedOption) {
            setStatus("请先选择一个选项", true);
            return;
        }
        let q = currentQuestions[currentQIndex];
        let isCorrect = (selectedOption === q.answer);
        let correctLetter = q.answer;
        let correctText = q.options[correctLetter];
        let explanation = q.explanation || "无解析";
        let resultHtml = `<strong>${isCorrect ? '✓ 回答正确' : '✗ 回答错误'}</strong><br>
                          正确答案:${correctLetter}. ${correctText}<br>
                          📚 解析:${explanation}`;
        currentResultHtml = resultHtml;
        answeredStatus = true;
        // 保存本次答题记录(不持久化到题目,只用于展示)
        renderCurrentQuestion();
    }

    function loadNextQuestion() {
        if(currentQIndex + 1 < currentQuestions.length) {
            currentQIndex++;
            selectedOption = null;
            answeredStatus = false;
            currentResultHtml = "";
            renderCurrentQuestion();
            setStatus(`${currentQIndex+1} / ${currentQuestions.length}`);
        } else {
            setStatus("恭喜完成全部20题!可生成新篇继续挑战。");
            quizBodyDiv.innerHTML = '<div class="loader">🎉 已完成所有题目,请点击“生成新篇”继续训练。</div>';
        }
    }

    // 调用API生成文言文
    async function generateClassicalChinese(topic, wordCount, temperature, max_tokens, apiKey, model) {
        const prompt = `请用纯正文言文创作一篇关于“${topic}”的文章,字数控制在${wordCount}字左右。必须严格使用文言词汇和句式,含有较多困难实词(如“诣、赍、觇、罹、稔、逋”等)、虚词(之乎者也矣焉哉耳)以及典故。严禁出现白话文或现代汉语表达。全文典雅古朴。只输出文言文正文,不需要标题和注释。`;
        const url = "https://api.aigc.bar/v1/chat/completions";
        const payload = { model, messages: [{role:"system", content:"你是一位精通文言文的翰林学士,擅长撰写古奥纯正的文言文。"},{role:"user", content:prompt}], temperature, max_tokens, stream: true };
        let fullText = "";
        const response = await fetch(url, {
            method:'POST', headers:{'Content-Type':'application/json','Authorization':`Bearer ${apiKey}`},
            body:JSON.stringify(payload)
        });
        if(!response.ok) throw new Error(`生成文言文失败: ${response.status}`);
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";
        while(true) {
            const {done,value} = await reader.read();
            if(done) break;
            buffer += decoder.decode(value, {stream:true});
            const lines = buffer.split('\n');
            buffer = lines.pop();
            for(let line of lines) {
                if(line.startsWith('data: ')) {
                    let data = line.slice(6);
                    if(data === '[DONE]') continue;
                    try{
                        let parsed = JSON.parse(data);
                        let delta = parsed.choices[0]?.delta?.content;
                        if(delta) fullText += delta;
                        articleContentDiv.innerHTML = marked.parse(fullText);
                        articleTitleSpan.innerText = `${topic}】文言`;
                    } catch(e){}
                }
            }
        }
        if(!fullText.trim()) throw new Error("未生成任何内容");
        return fullText.trim();
    }

    // 生成20道选择题(约束分布均匀)
    async function generateQuestions(articleText, topic, apiKey, model) {
        const prompt = `根据以下文言文,生成20道四选一选择题,要求:
1. 10道实词解释题(字词含义,例如“诣”意为“到访”等)
2. 3道虚词用法题(之、其、而、以、于、矣、乎等)
3. 2道文言句式题(判断句、宾语前置、状语后置等)
4. 5道古代文化常识题(官职、科举、礼仪、地理等)
务必使正确答案在A,B,C,D四个选项中分布均匀(每种选项正确答案出现5次左右)。
每题包含:题干、四个选项(标记A/B/C/D)、正确答案字母、详细解析。
以JSON格式输出,严格如下结构,不要有任何其他文本:
[
  {
    "text": "题干",
    "options": {"A":"选项A","B":"选项B","C":"选项C","D":"选项D"},
    "answer": "A",
    "explanation": "解析内容",
    "type": "实词"
  },
  ...
]
确保生成20题且每种类型数量正确。依据文章中的实词/虚词/句式和文化点。`;
        const url = "https://api.aigc.bar/v1/chat/completions";
        const payload = { model, messages: [{role:"system", content:"你是一位严谨的文言文教师,只输出JSON数组,不附加任何说明。"},{role:"user", content:`文言文:\n${articleText}\n\n${prompt}`}], temperature:0.3, max_tokens:8000, stream:false };
        const resp = await fetch(url, {
            method:'POST', headers:{'Content-Type':'application/json','Authorization':`Bearer ${apiKey}`},
            body:JSON.stringify(payload)
        });
        if(!resp.ok) throw new Error("题目生成失败");
        let data = await resp.json();
        let content = data.choices[0]?.message?.content || "[]";
        try {
            let jsonMatch = content.match(/\[\s*\{.*\}\s*\]/s);
            if(jsonMatch) content = jsonMatch[0];
            let questions = JSON.parse(content);
            if(!Array.isArray(questions) || questions.length !== 20) throw new Error("题目数量不符");
            // 验证类型分布大致正确
            return questions;
        } catch(e) {
            console.warn(e);
            throw new Error("解析题目JSON失败,请重试");
        }
    }

    // 主流程: 生成文章 + 题目
    let isGenerating = false;
    async function onGenerate() {
        if(isGenerating) { setStatus("正在生成中,请稍后", true); return; }
        const apiKey = getApiKey();
        if(!apiKey) { setStatus("请填写API密钥", true); return; }
        const model = getModel();
        let topic = topicInput.value.trim();
        if(!topic) topic = "古代名贤传";
        let wordCount = wordCountSelect.value;
        let temperature = parseFloat(temperatureInput.value) || 0.85;
        let max_tokens = parseInt(maxTokensInput.value) || 4000;
        isGenerating = true;
        setStatus("📜 AI正在创作文言文...");
        try {
            articleTitleSpan.innerText = "⏳ 生成中...";
            articleContentDiv.innerText = "正在写作,请稍等...";
            quizBodyDiv.innerHTML = '<div class="loader">文章生成后自动出题,请耐心等待~</div>';
            // 生成文言文
            let classicalText = await generateClassicalChinese(topic, wordCount, temperature, max_tokens, apiKey, model);
            setStatus("📖 文言文生成完毕,正在出题(20道选择题)");
            // 生成题目
            let questions = await generateQuestions(classicalText, topic, apiKey, model);
            if(!questions || questions.length !== 20) throw new Error("题目生成失败");
            // 保存到当前会话
            let conv = conversationsMap[currentConversationId];
            conv.article = { title: `${topic}`, content: classicalText };
            conv.questions = questions;
            conv.title = topic.length>20 ? topic.slice(0,20)+'…' : topic;
            conv.lastUpdated = Date.now();
            persistConversations();
            renderConvList();
            currentArticle = conv.article;
            currentQuestions = questions;
            currentQIndex = 0;
            selectedOption = null;
            answeredStatus = false;
            currentResultHtml = "";
            renderCurrentQuestion();
            setStatus("✅ 文言文与20道选择题已就绪!");
        } catch(err) {
            console.error(err);
            setStatus(`生成失败: ${err.message}`, true);
            articleContentDiv.innerText = `生成出错:${err.message}`;
        } finally {
            isGenerating = false;
        }
    }

    function getApiKeyAction() { window.open("https://api.aigc.bar/register?aff=UP4F", "_blank"); setStatus("打开注册页面获取免费API密钥"); }
    function handleNewConv() { createNewConversation(); renderCurrentArticleAndQuiz(); }

    function init() {
        loadApiCredentials();
        loadConversations();
        renderConvList();
        renderCurrentArticleAndQuiz();
        paramToggle.addEventListener('click', ()=>paramPanel.classList.toggle('show'));
        const generateBtn = document.createElement('button');
        generateBtn.textContent = "✍️ 生成新篇";
        generateBtn.style.background = "#6b4c3b";
        generateBtn.style.marginLeft = "12px";
        document.querySelector('.toolbar').appendChild(generateBtn);
        generateBtn.addEventListener('click', onGenerate);
        newChatBtn.addEventListener('click', handleNewConv);
        getKeyBtn.addEventListener('click', getApiKeyAction);
        setStatus("已加载文言文训练器 | 可点击「生成新篇」");
    }
    init();
})();
</script>
</body>
</html>

让我们用AI解锁文言文的魅力,让每篇古文都不再是枯燥的陌生文字,而是一场跨越千年的智慧对话。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐