ソースを参照

Merge pull request #537 from xujikai/feature/sentence-expand-agent

[毕业设计] SentenceExpandAgent - 英语句子扩写智能体
jjyaoao 1 ヶ月 前
コミット
73effc408f
39 ファイル変更5327 行追加0 行削除
  1. 229 0
      Co-creation-projects/xujikai-SentenceExpandAgent/README.md
  2. 11 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/.env.example
  3. 51 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/.gitignore
  4. 11 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/requirements.txt
  5. 1 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/__init__.py
  6. 55 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/__init__.py
  7. 333 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/auto_mode_agent.py
  8. 106 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/evaluator.py
  9. 116 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/interviewer.py
  10. 305 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/orchestrator.py
  11. 115 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/polisher.py
  12. 205 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/prompts.py
  13. 63 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/config.py
  14. 68 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/main.py
  15. 1 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/models/__init__.py
  16. 56 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/models/entities.py
  17. 1 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/routers/__init__.py
  18. 153 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/routers/expand.py
  19. 1 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/services/__init__.py
  20. 120 0
      Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/services/session_store.py
  21. 2 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/.env.example
  22. 29 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/.gitignore
  23. 13 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/index.html
  24. 1464 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/package-lock.json
  25. 22 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/package.json
  26. 15 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/App.vue
  27. 130 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/api/expand.ts
  28. 142 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/FinalResult.vue
  29. 186 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/MessageItem.vue
  30. 185 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/SeedInput.vue
  31. 136 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/UserInput.vue
  32. 10 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/main.ts
  33. 232 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/stores/session.ts
  34. 18 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/style.css
  35. 62 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/types/expand.ts
  36. 618 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/views/HomeView.vue
  37. 31 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/tsconfig.json
  38. 10 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/tsconfig.node.json
  39. 21 0
      Co-creation-projects/xujikai-SentenceExpandAgent/frontend/vite.config.ts

+ 229 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/README.md

@@ -0,0 +1,229 @@
+# 英语句子扩写智能体
+
+> 基于多智能体协作的英语写作教练应用,通过记者提问法将简单英文句子逐步扩写为高级长句
+
+## 📝 项目简介
+
+本项目是一个创新的英语写作学习工具,旨在帮助英语学习者通过系统化的方法提升写作能力。项目采用多智能体协作架构,模拟真实的写作教学场景:
+
+- **解决什么问题?**
+  - 英语学习者常面临句子简单、缺乏变化的问题
+  - 传统写作练习缺乏系统性的指导和反馈
+  - 难以掌握从简单句到复杂句的渐进式扩写技巧
+
+- **有什么特色功能?**
+  - 记者提问法:通过三个阶段的针对性提问,引导学生逐步扩写句子
+  - 双模式交互:支持手动模式(逐步引导)和自动模式(一键演示)
+  - 实时语法点评:智能评估学生提交的句子,提供语法纠错和改进建议
+  - 满分润色:基于三轮扩写记录,生成润色后的高级句子和结构分析
+
+- **适用于什么场景?**
+  - 英语写作教学和练习
+  - 句子扩写技巧学习
+  - 语法结构进阶训练
+  - 自主学习和教师辅助教学
+
+## ✨ 核心功能
+
+- [x] **手动模式**:逐步引导,逐阶段回答记者提问,适合深度学习
+- [x] **自动模式**:一键自动演示全过程,适合快速了解和展示
+- [x] **三阶段扩写流程**:
+  - Stage 1:增加细节(使用形容词、副词等修饰成分)
+  - Stage 2:增加时空背景(使用介词短语补充时间或地点信息)
+  - Stage 3:增加结构深度(使用非谓语动词、定语从句或状语从句修饰)
+- [x] **智能语法点评**:实时评估语法正确性,提供纠错建议
+- [x] **最终润色生成**:基于三轮扩写记录生成满分句子和结构分析
+- [x] **SSE流式传输**:自动模式支持实时流式推送,提升用户体验
+
+## 🛠️ 技术栈
+
+### 前端技术
+- **Vue 3**:渐进式 JavaScript 框架
+- **TypeScript**:类型安全的 JavaScript 超集
+- **Vite**:新一代前端构建工具
+- **Pinia**:Vue 3 官方状态管理库
+- **SSE (Server-Sent Events)**:服务器推送技术,用于自动模式的实时流式传输
+
+### 后端技术
+- **FastAPI**:现代、高性能的 Python Web 框架
+- **HelloAgents框架**:多智能体协作框架
+- **Pydantic**:数据验证和设置管理
+
+### 智能体架构
+项目采用多智能体协作范式,包含以下核心 Agent:
+
+1. **OrchestratorAgent(流程调度 Agent)**
+   - 负责整体流程调度和状态管理
+   - 协调其他 Agent 的协作
+   - 决定下一阶段的执行策略
+
+2. **InterviewerAgent(记者提问 Agent)**
+   - 扮演记者角色,根据当前阶段生成针对性提问
+   - 提供语法结构提示和示范改写
+   - 三个阶段各有不同的提问策略
+
+3. **EvaluatorAgent(语法点评 Agent)**
+   - 对学生提交的句子进行语法评估
+   - 提供详细的点评和纠错建议
+   - 判断句子是否符合阶段目标
+
+4. **PolisherAgent(满分润色 Agent)**
+   - 接收三轮扩写的完整记录
+   - 生成润色后的满分句子
+   - 提供语法结构分析和亮点总结
+
+5. **AutoModeAgent(自动演示 Agent)**
+   - 自动完成三轮扩写流程
+   - 模拟学生回答和智能体反馈
+   - 通过 SSE 流式推送进度
+
+## 🚀 快速开始
+
+### 环境要求
+
+- Python 3.10+
+- Node.js 16+
+- npm 或 yarn
+
+### 后端设置
+
+1. **进入后端目录**
+```bash
+cd backend
+```
+
+2. **创建虚拟环境**
+```bash
+python -m venv .venv
+```
+
+3. **激活虚拟环境**
+```bash
+# Windows
+.venv\Scripts\activate
+
+# macOS/Linux
+source .venv/bin/activate
+```
+
+4. **安装依赖**
+```bash
+pip install -r requirements.txt
+```
+
+5. **配置环境变量**
+```bash
+# 复制环境变量模板
+cp .env.example .env
+
+# 编辑 .env 文件,填入你的 API 配置
+```
+
+6. **启动后端服务**
+```bash
+python src/main.py
+```
+
+后端服务将在 `http://localhost:8000` 启动
+
+### 前端设置
+
+1. **进入前端目录**
+```bash
+cd frontend
+```
+
+2. **安装依赖**
+```bash
+npm install
+```
+
+3. **配置环境变量**
+```bash
+# 复制环境变量模板
+cp .env.example .env
+
+# 编辑 .env 文件,配置后端 API 地址
+# VITE_API_BASE_URL=http://localhost:8000
+```
+
+4. **启动开发服务器**
+```bash
+npm run dev
+```
+
+前端应用将在 `http://localhost:3000` 启动(具体端口以终端输出为准)
+
+### 访问应用
+
+打开浏览器访问前端地址,即可开始使用英语句子扩写智能体。
+
+## 📖 使用示例
+
+### 手动模式示例
+
+1. **输入种子句**
+   ```
+   I like reading.
+   ```
+
+2. **选择手动模式并开始**
+
+3. **Stage 1 - 增加细节**
+   - 记者提问:"你有多喜欢读什么样风格或题材的书籍相关内容呢?"
+   - 学生回答:"I really like reading science fiction novels."
+   - 语法点评:"很棒!你在动词like前加了表示程度的副词really,在名词novels前加了复合形容词science fiction,完美符合本阶段添加修饰细节的目标。"
+
+4. **Stage 2 - 增加时空背景**
+   - 记者提问:"你通常喜欢在什么时候、什么地点读科幻小说呢?"
+   - 学生回答:"I really like reading science fiction novels in my free time at my home."
+   - 语法点评:"很好地延续了上阶段对动词和名词的修饰,还精准补充了记者提问中涉及的时间和地点背景,介词短语使用准确。"
+
+5. **Stage 3 - 增加结构深度**
+   - 记者提问:"你喜欢在家空闲时间读的科幻小说通常有什么吸引人的核心特点,或者你喜欢读这类小说的具体深层原因是什么?"
+   - 学生回答:"I really like reading science fiction novels in my free time at my home because they stimulate my imagination and take me to new worlds."
+   - 语法点评:"非常出色!你不仅延续了前两个阶段对动词、名词、时间地点的修饰,还精准添加了记者提问想要的逻辑因果状语从句作为右分支结构,内容紧扣主题。"
+
+### 自动模式示例
+
+1. **输入种子句**
+   ```
+   She enjoys music.
+   ```
+
+2. **选择自动模式并开始**
+
+3. **观看自动演示**
+   - 系统自动完成三轮扩写
+   - 实时显示每个阶段的提问、回答和点评
+   - 最终展示润色后的满分句子
+
+## 🎯 项目亮点
+
+- **多智能体协作架构**:采用专业的多智能体设计,每个 Agent 职责明确,协作高效
+- **渐进式学习路径**:三阶段扩写流程符合认知规律,从简单到复杂循序渐进
+- **双模式交互设计**:手动模式适合深度学习,自动模式适合快速演示和展示
+- **实时反馈机制**:语法点评和错误纠正帮助学生及时发现问题
+- **流式用户体验**:SSE 技术实现自动模式的实时推送,交互更加流畅
+- **类型安全**:前端使用 TypeScript,后端使用 Pydantic,确保数据类型安全
+- **现代化技术栈**:Vue 3 + FastAPI 组合,开发效率和运行性能兼顾
+
+## 🔮 未来计划
+
+- [ ] 持久化存储:将会话数据保存到数据库
+- [ ] 用户系统:支持用户注册、登录和历史记录
+- [ ] 更多扩写模板:支持不同类型的句子扩写模板
+- [ ] 难度分级:根据用户水平调整提问难度
+
+## 📄 许可证
+
+MIT License
+
+## 👤 作者
+
+- GitHub: [@xujikai](https://github.com/xujikai)
+- 项目: SentenceExpandAgent
+
+## 🙏 致谢
+
+感谢 Datawhale 社区和 Hello-Agents 项目提供的框架支持!

+ 11 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/.env.example

@@ -0,0 +1,11 @@
+# HelloAgents LLM 配置
+# 复制此文件为 .env 并填入实际值
+
+# API Key(必填)
+LLM_API_KEY=your_api_key_here
+
+# 模型 ID(可选,默认 gpt-4o-mini)
+LLM_MODEL_ID=gpt-4o-mini
+
+# Base URL(可选,默认 OpenAI API)
+LLM_BASE_URL=https://api.openai.com/v1

+ 51 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/.gitignore

@@ -0,0 +1,51 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# 虚拟环境
+venv/
+env/
+ENV/
+.venv
+
+# 环境变量
+.env
+.env.local
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# 日志
+*.log
+
+# 测试
+.pytest_cache/
+.coverage
+htmlcov/
+
+# 操作系统
+.DS_Store
+Thumbs.db
+

+ 11 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/requirements.txt

@@ -0,0 +1,11 @@
+# HelloAgents框架
+hello-agents[protocols]>=0.2.4,<=0.2.9
+
+# FastAPI和相关依赖
+fastapi>=0.115.0
+uvicorn[standard]>=0.32.0
+pydantic>=2.0.0
+pydantic-settings>=2.0.0
+
+# 其他工具
+huggingface_hub>=0.25.0

+ 1 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/__init__.py

@@ -0,0 +1 @@
+  

+ 55 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/__init__.py

@@ -0,0 +1,55 @@
+"""
+Agents 模块 - 英语句子扩写智能体
+"""
+from .interviewer import (
+    InterviewerAgent,
+    get_interviewer,
+    reset_interviewer
+)
+from .evaluator import (
+    EvaluatorAgent,
+    get_evaluator,
+    reset_evaluator
+)
+from .polisher import (
+    PolisherAgent,
+    get_polisher,
+    reset_polisher
+)
+from .orchestrator import (
+    OrchestratorAgent,
+    get_orchestrator,
+    reset_orchestrator
+)
+from .auto_mode_agent import (
+    AutoModeAgent,
+    get_auto_mode,
+    reset_auto_mode
+)
+
+__all__ = [
+    # InterviewerAgent
+    "InterviewerAgent",
+    "get_interviewer",
+    "reset_interviewer",
+    
+    # EvaluatorAgent
+    "EvaluatorAgent",
+    "get_evaluator",
+    "reset_evaluator",
+    
+    # PolisherAgent
+    "PolisherAgent",
+    "get_polisher",
+    "reset_polisher",
+    
+    # OrchestratorAgent
+    "OrchestratorAgent",
+    "get_orchestrator",
+    "reset_orchestrator",
+    
+    # AutoModeAgent
+    "AutoModeAgent",
+    "get_auto_mode",
+    "reset_auto_mode",
+]

+ 333 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/auto_mode_agent.py

@@ -0,0 +1,333 @@
+"""
+AutoModeAgent - 全自动模式 Agent
+自动串联三轮,无用户输入,SSE 流式输出
+"""
+import json
+import asyncio
+from typing import Dict, Any, AsyncGenerator
+from hello_agents.agents.tool_aware_agent import ToolAwareSimpleAgent
+from .prompts import (
+    AUTO_MODE_SYSTEM_PROMPT,
+    AUTO_MODE_USER_PROMPT
+)
+from config import get_llm, tool_listener
+
+
+class AutoModeAgent:
+    """全自动模式 Agent - 自动串联三轮,SSE 流式输出"""
+    
+    def __init__(self):
+        """初始化 AutoModeAgent"""
+        self.llm = get_llm()
+        self.agent = ToolAwareSimpleAgent(
+            name="AutoMode",
+            system_prompt=AUTO_MODE_SYSTEM_PROMPT,
+            llm=self.llm,
+            tool_call_listener=tool_listener
+        )
+
+    def run_auto_mode(self, seed_sentence: str) -> Dict[str, Any]:
+        """
+        执行全自动模式,生成三阶段扩写结果
+        
+        Args:
+            seed_sentence: 种子句
+            
+        Returns:
+            Dict[str, Any]: 完整扩写结果,包含:
+                - stage1: 阶段一结果(question, expanded)
+                - stage2: 阶段二结果(question, expanded)
+                - stage3: 阶段三结果(question, expanded)
+                - polished: 最终满分润色版本
+                - structure_analysis: 语法结构分析
+        """
+        # 构建用户提示词
+        user_prompt = AUTO_MODE_USER_PROMPT.format(
+            seed_sentence=seed_sentence
+        )
+        
+        # 调用 LLM
+        response = self.agent.run(user_prompt)
+        
+        # 解析 JSON 响应
+        try:
+            result = json.loads(response)
+            return result
+        except json.JSONDecodeError as e:
+            # 如果解析失败,返回默认响应
+            return {
+                "stage1": {
+                    "question": "请为这个句子增加一些细节",
+                    "expanded": seed_sentence
+                },
+                "stage2": {
+                    "question": "请为这个句子增加时间或地点信息",
+                    "expanded": seed_sentence
+                },
+                "stage3": {
+                    "question": "请为这个句子增加定语从句或状语从句",
+                    "expanded": seed_sentence
+                },
+                "polished": seed_sentence,
+                "structure_analysis": [
+                    "自动模式生成失败,使用原始句子"
+                ]
+            }
+    
+    def _parse_stream_content(self, buffer: str) -> Dict[str, Any]:
+        """
+        解析流式内容,提取当前已完成的部分
+        
+        Args:
+            buffer: 当前已收集的完整文本
+            
+        Returns:
+            Dict[str, Any]: 解析结果,包含已完成的部分
+        """
+        result = {}
+        
+        # 定义分隔符和对应的键
+        delimiters = [
+            ("===STAGE1_QUESTION===", "stage1_question"),
+            ("===STAGE1_EXPANDED===", "stage1_expanded"),
+            ("===STAGE2_QUESTION===", "stage2_question"),
+            ("===STAGE2_EXPANDED===", "stage2_expanded"),
+            ("===STAGE3_QUESTION===", "stage3_question"),
+            ("===STAGE3_EXPANDED===", "stage3_expanded"),
+            ("===POLISHED===", "polished"),
+            ("===ANALYSIS===", "analysis"),
+            ("===END===", "end"),
+        ]
+        
+        # 查找每个分隔符的位置
+        positions = {}
+        for delimiter, key in delimiters:
+            pos = buffer.find(delimiter)
+            if pos != -1:
+                positions[key] = pos
+        
+        # 提取内容
+        if "stage1_question" in positions and "stage1_expanded" in positions:
+            result["stage1"] = {
+                "question": buffer[positions["stage1_question"] + len("===STAGE1_QUESTION==="):positions["stage1_expanded"]].strip()
+            }
+        
+        if "stage1_expanded" in positions and "stage2_question" in positions:
+            if "stage1" not in result:
+                result["stage1"] = {}
+            result["stage1"]["expanded"] = buffer[positions["stage1_expanded"] + len("===STAGE1_EXPANDED==="):positions["stage2_question"]].strip()
+        
+        if "stage2_question" in positions and "stage2_expanded" in positions:
+            result["stage2"] = {
+                "question": buffer[positions["stage2_question"] + len("===STAGE2_QUESTION==="):positions["stage2_expanded"]].strip()
+            }
+        
+        if "stage2_expanded" in positions and "stage3_question" in positions:
+            if "stage2" not in result:
+                result["stage2"] = {}
+            result["stage2"]["expanded"] = buffer[positions["stage2_expanded"] + len("===STAGE2_EXPANDED==="):positions["stage3_question"]].strip()
+        
+        if "stage3_question" in positions and "stage3_expanded" in positions:
+            result["stage3"] = {
+                "question": buffer[positions["stage3_question"] + len("===STAGE3_QUESTION==="):positions["stage3_expanded"]].strip()
+            }
+        
+        if "stage3_expanded" in positions and "polished" in positions:
+            if "stage3" not in result:
+                result["stage3"] = {}
+            result["stage3"]["expanded"] = buffer[positions["stage3_expanded"] + len("===STAGE3_EXPANDED==="):positions["polished"]].strip()
+        
+        if "polished" in positions and "analysis" in positions:
+            result["polished"] = buffer[positions["polished"] + len("===POLISHED==="):positions["analysis"]].strip()
+        
+        if "analysis" in positions and "end" in positions:
+            analysis_text = buffer[positions["analysis"] + len("===ANALYSIS==="):positions["end"]].strip()
+            result["structure_analysis"] = [line.strip() for line in analysis_text.split('\n') if line.strip()]
+        
+        return result
+    
+    async def run_auto_mode_stream(self, seed_sentence: str) -> AsyncGenerator[Dict[str, Any], None]:
+        """
+        执行全自动模式,流式输出结果(用于 SSE)
+        
+        Args:
+            seed_sentence: 种子句
+            
+        Yields:
+            Dict[str, Any]: 流式输出的事件,包含:
+                - type: 事件类型 (stage1/stage2/stage3/polished/analysis/done/progress)
+                - data: 事件数据
+        """
+        # 构建用户提示词
+        user_prompt = AUTO_MODE_USER_PROMPT.format(
+            seed_sentence=seed_sentence
+        )
+        
+        # 调用 LLM 使用 stream_run(同步方法)
+        response_buffer = []
+        
+        # 记录已经发送过的部分,避免重复发送
+        sent_parts = set()
+        
+        for chunk in self.agent.stream_run(user_prompt):
+            response_buffer.append(chunk)
+            full_response = ''.join(response_buffer)
+            
+            # 解析当前内容
+            parsed = self._parse_stream_content(full_response)
+            
+            # 检查是否有新的完整部分可以发送
+            if "stage1" in parsed and "question" in parsed["stage1"] and "stage1_question" not in sent_parts:
+                yield {"type": "stage1", "data": {"question": parsed["stage1"]["question"]}}
+                sent_parts.add("stage1_question")
+            
+            if "stage1" in parsed and "expanded" in parsed["stage1"] and "stage1_expanded" not in sent_parts:
+                yield {"type": "stage1", "data": {"expanded": parsed["stage1"]["expanded"]}}
+                sent_parts.add("stage1_expanded")
+            
+            if "stage2" in parsed and "question" in parsed["stage2"] and "stage2_question" not in sent_parts:
+                yield {"type": "stage2", "data": {"question": parsed["stage2"]["question"]}}
+                sent_parts.add("stage2_question")
+            
+            if "stage2" in parsed and "expanded" in parsed["stage2"] and "stage2_expanded" not in sent_parts:
+                yield {"type": "stage2", "data": {"expanded": parsed["stage2"]["expanded"]}}
+                sent_parts.add("stage2_expanded")
+            
+            if "stage3" in parsed and "question" in parsed["stage3"] and "stage3_question" not in sent_parts:
+                yield {"type": "stage3", "data": {"question": parsed["stage3"]["question"]}}
+                sent_parts.add("stage3_question")
+            
+            if "stage3" in parsed and "expanded" in parsed["stage3"] and "stage3_expanded" not in sent_parts:
+                yield {"type": "stage3", "data": {"expanded": parsed["stage3"]["expanded"]}}
+                sent_parts.add("stage3_expanded")
+            
+            if "polished" in parsed and "polished" not in sent_parts:
+                yield {"type": "polished", "data": {"sentence": parsed["polished"]}}
+                sent_parts.add("polished")
+            
+            if "structure_analysis" in parsed and "analysis" not in sent_parts:
+                yield {"type": "analysis", "data": {"items": parsed["structure_analysis"]}}
+                sent_parts.add("analysis")
+            
+            # 发送进度更新
+            yield {"type": "progress", "data": {"message": "正在生成...", "partial": full_response}}
+            
+            # 暂停一下以确保异步性
+            await asyncio.sleep(0.01)
+        
+        # 所有块都接收完后,进行最终解析
+        full_response = ''.join(response_buffer)
+        final_parsed = self._parse_stream_content(full_response)
+        
+        # 检查是否有未发送的部分
+        if "stage1" in final_parsed:
+            if "question" in final_parsed["stage1"] and "stage1_question" not in sent_parts:
+                yield {"type": "stage1", "data": {"question": final_parsed["stage1"]["question"]}}
+            if "expanded" in final_parsed["stage1"] and "stage1_expanded" not in sent_parts:
+                yield {"type": "stage1", "data": {"expanded": final_parsed["stage1"]["expanded"]}}
+        
+        if "stage2" in final_parsed:
+            if "question" in final_parsed["stage2"] and "stage2_question" not in sent_parts:
+                yield {"type": "stage2", "data": {"question": final_parsed["stage2"]["question"]}}
+            if "expanded" in final_parsed["stage2"] and "stage2_expanded" not in sent_parts:
+                yield {"type": "stage2", "data": {"expanded": final_parsed["stage2"]["expanded"]}}
+        
+        if "stage3" in final_parsed:
+            if "question" in final_parsed["stage3"] and "stage3_question" not in sent_parts:
+                yield {"type": "stage3", "data": {"question": final_parsed["stage3"]["question"]}}
+            if "expanded" in final_parsed["stage3"] and "stage3_expanded" not in sent_parts:
+                yield {"type": "stage3", "data": {"expanded": final_parsed["stage3"]["expanded"]}}
+        
+        if "polished" in final_parsed and "polished" not in sent_parts:
+            yield {"type": "polished", "data": {"sentence": final_parsed["polished"]}}
+        
+        if "structure_analysis" in final_parsed and "analysis" not in sent_parts:
+            yield {"type": "analysis", "data": {"items": final_parsed["structure_analysis"]}}
+        
+        # 发送完成事件,包含完整数据
+        complete_data = {
+            "stage1": final_parsed.get("stage1", {
+                "question": "请为这个句子增加一些细节",
+                "expanded": seed_sentence
+            }),
+            "stage2": final_parsed.get("stage2", {
+                "question": "请为这个句子增加时间或地点信息",
+                "expanded": seed_sentence
+            }),
+            "stage3": final_parsed.get("stage3", {
+                "question": "请为这个句子增加定语从句或状语从句",
+                "expanded": seed_sentence
+            }),
+            "polished": final_parsed.get("polished", seed_sentence),
+            "structure_analysis": final_parsed.get("structure_analysis", [
+                "自动模式生成"
+            ])
+        }
+        yield {"type": "done", "data": complete_data}
+    
+    def generate_session_state(self, seed_sentence: str, result: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        根据自动模式结果生成会话状态
+        
+        Args:
+            seed_sentence: 种子句
+            result: 自动模式结果
+            
+        Returns:
+            Dict[str, Any]: 会话状态数据
+        """
+        # 构建轮次记录
+        rounds = [
+            {
+                "stage": "stage1",
+                "question": result["stage1"]["question"],
+                "user_answer": result["stage1"]["expanded"],
+                "evaluation": "自动模式生成,语法正确",
+                "expanded_sentence": result["stage1"]["expanded"]
+            },
+            {
+                "stage": "stage2",
+                "question": result["stage2"]["question"],
+                "user_answer": result["stage2"]["expanded"],
+                "evaluation": "自动模式生成,语法正确",
+                "expanded_sentence": result["stage2"]["expanded"]
+            },
+            {
+                "stage": "stage3",
+                "question": result["stage3"]["question"],
+                "user_answer": result["stage3"]["expanded"],
+                "evaluation": "自动模式生成,语法正确",
+                "expanded_sentence": result["stage3"]["expanded"]
+            }
+        ]
+        
+        return {
+            "seed_sentence": seed_sentence,
+            "current_stage": "done",
+            "rounds": rounds,
+            "final_polished": result["polished"],
+            "structure_analysis": result["structure_analysis"]
+        }
+
+
+# 创建全局实例(单例模式)
+_auto_mode_instance = None
+
+
+def get_auto_mode() -> AutoModeAgent:
+    """
+    获取全局 AutoModeAgent 实例(单例模式)
+    
+    Returns:
+        AutoModeAgent: AutoModeAgent 实例
+    """
+    global _auto_mode_instance
+    if _auto_mode_instance is None:
+        _auto_mode_instance = AutoModeAgent()
+    return _auto_mode_instance
+
+
+def reset_auto_mode():
+    """重置 AutoModeAgent 实例(用于测试)"""
+    global _auto_mode_instance
+    _auto_mode_instance = None

+ 106 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/evaluator.py

@@ -0,0 +1,106 @@
+"""
+EvaluatorAgent - 语法点评 Agent
+对用户提交的句子进行语法点评
+"""
+import json
+from typing import Dict, Any
+from hello_agents.agents.tool_aware_agent import ToolAwareSimpleAgent
+from .prompts import (
+    EVALUATOR_SYSTEM_PROMPT,
+    EVALUATOR_USER_PROMPT
+)
+from config import get_llm, tool_listener
+
+
+class EvaluatorAgent:
+    """语法点评 Agent - 对用户提交的句子进行语法点评"""
+    
+    def __init__(self):
+        """初始化 EvaluatorAgent"""
+        self.llm = get_llm()
+        self.agent = ToolAwareSimpleAgent(
+            name="Evaluator",
+            system_prompt=EVALUATOR_SYSTEM_PROMPT,
+            llm=self.llm,
+            tool_call_listener=tool_listener
+        )
+    
+    def evaluate(self, stage_goal: str, question: str, 
+                 seed_sentence: str, user_sentence: str) -> Dict[str, Any]:
+        """
+        对用户提交的句子进行语法点评
+        
+        Args:
+            stage_goal: 当前阶段目标
+            question: 记者提问
+            seed_sentence: 学生原始句子
+            user_sentence: 学生本次提交的句子
+            
+        Returns:
+            Dict[str, Any]: 点评结果,包含:
+                - is_correct: 语法是否正确
+                - comment: 点评内容
+                - corrected_sentence: 修正后的句子
+        """
+        # 构建用户提示词
+        user_prompt = EVALUATOR_USER_PROMPT.format(
+            stage_goal=stage_goal,
+            question=question,
+            seed_sentence=seed_sentence,
+            user_sentence=user_sentence
+        )
+        
+        # 调用 LLM
+        response = self.agent.run(user_prompt)
+        
+        # 解析 JSON 响应
+        try:
+            result = json.loads(response)
+            return result
+        except json.JSONDecodeError as e:
+            # 如果解析失败,返回默认响应
+            return {
+                "is_correct": True,
+                "comment": "你的句子语法正确,符合本阶段的扩写目标。",
+                "corrected_sentence": user_sentence
+            }
+    
+    def is_grammar_correct(self, stage_goal: str, question: str,
+                          seed_sentence: str, user_sentence: str) -> bool:
+        """
+        快速判断用户句子语法是否正确
+        
+        Args:
+            stage_goal: 当前阶段目标
+            question: 记者提问
+            seed_sentence: 学生原始句子
+            user_sentence: 学生本次提交的句子
+            
+        Returns:
+            bool: 语法是否正确
+        """
+        result = self.evaluate(stage_goal, question, seed_sentence, user_sentence)
+        return result.get("is_correct", True)
+
+
+# 创建全局实例(单例模式)
+_evaluator_instance = None
+
+
+def get_evaluator() -> EvaluatorAgent:
+    """
+    获取全局 EvaluatorAgent 实例(单例模式)
+    
+    Returns:
+        EvaluatorAgent: EvaluatorAgent 实例
+    """
+    global _evaluator_instance
+    if _evaluator_instance is None:
+        _evaluator_instance = EvaluatorAgent()
+    return _evaluator_instance
+
+
+def reset_evaluator():
+    """重置 EvaluatorAgent 实例(用于测试)"""
+    global _evaluator_instance
+    _evaluator_instance = None

+ 116 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/interviewer.py

@@ -0,0 +1,116 @@
+"""
+InterviewerAgent - 记者提问 Agent
+扮演记者,根据当前阶段生成提问
+"""
+import json
+from typing import Dict, Any, Literal
+from hello_agents.agents.tool_aware_agent import ToolAwareSimpleAgent
+from .prompts import (
+    INTERVIEWER_SYSTEM_PROMPT,
+    INTERVIEWER_STAGE1_PROMPT,
+    INTERVIEWER_STAGE2_PROMPT,
+    INTERVIEWER_STAGE3_PROMPT,
+    STAGE_GOALS
+)
+from config import get_llm, tool_listener
+
+class InterviewerAgent:
+    """记者提问 Agent - 根据当前阶段生成提问"""
+    
+    def __init__(self):
+        """初始化 InterviewerAgent"""
+        self.llm = get_llm()
+        self.agent = ToolAwareSimpleAgent(
+            name="Interviewer",
+            system_prompt=INTERVIEWER_SYSTEM_PROMPT,
+            llm=self.llm,
+            tool_call_listener=tool_listener
+        )
+    
+    def ask(self, stage: Literal["stage1", "stage2", "stage3"], 
+            current_sentence: str, rounds_history: str = "") -> Dict[str, Any]:
+        """
+        根据当前阶段生成提问
+        
+        Args:
+            stage: 当前阶段 (stage1/stage2/stage3)
+            current_sentence: 当前句子
+            rounds_history: 历史轮次摘要(用于 stage2/stage3)
+            
+        Returns:
+            Dict[str, Any]: 提问结果,包含:
+                - question: 提问内容
+                - hint: 语法结构提示
+                - example: 示范改写(可选)
+                - stage_goal: 当前阶段目标
+        """
+        # 根据阶段选择对应的提示词
+        if stage == "stage1":
+            user_prompt = INTERVIEWER_STAGE1_PROMPT.format(
+                current_sentence=current_sentence
+            )
+        elif stage == "stage2":
+            user_prompt = INTERVIEWER_STAGE2_PROMPT.format(
+                current_sentence=current_sentence,
+                rounds_history=rounds_history
+            )
+        elif stage == "stage3":
+            user_prompt = INTERVIEWER_STAGE3_PROMPT.format(
+                current_sentence=current_sentence,
+                rounds_history=rounds_history
+            )
+        else:
+            raise ValueError(f"Invalid stage: {stage}")
+        
+        # 调用 LLM
+        response = self.agent.run(user_prompt)
+        
+        # 解析 JSON 响应
+        try:
+            result = json.loads(response)
+            # 添加阶段目标信息
+            result["stage_goal"] = self.get_stage_goal(stage)
+            return result
+        except json.JSONDecodeError as e:
+            # 如果解析失败,返回默认响应
+            return {
+                "question": f"请为这个句子增加一些细节:{current_sentence}",
+                "hint": "尝试使用形容词或副词来修饰核心词",
+                "example": "",
+                "stage_goal": self.get_stage_goal(stage)
+            }
+    
+    def get_stage_goal(self, stage: Literal["stage1", "stage2", "stage3"]) -> str:
+        """
+        获取指定阶段的目标描述
+        
+        Args:
+            stage: 当前阶段
+            
+        Returns:
+            str: 阶段目标描述
+        """
+        return STAGE_GOALS.get(stage, "")
+
+
+# 创建全局实例(单例模式)
+_interviewer_instance = None
+
+
+def get_interviewer() -> InterviewerAgent:
+    """
+    获取全局 InterviewerAgent 实例(单例模式)
+    
+    Returns:
+        InterviewerAgent: InterviewerAgent 实例
+    """
+    global _interviewer_instance
+    if _interviewer_instance is None:
+        _interviewer_instance = InterviewerAgent()
+    return _interviewer_instance
+
+
+def reset_interviewer():
+    """重置 InterviewerAgent 实例(用于测试)"""
+    global _interviewer_instance
+    _interviewer_instance = None

+ 305 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/orchestrator.py

@@ -0,0 +1,305 @@
+"""
+OrchestratorAgent - 流程调度 Agent
+阶段调度、更新 SessionState、决定 next stage、内部串联三个 Agent
+"""
+import json
+from typing import Dict, Any
+from hello_agents.agents.tool_aware_agent import ToolAwareSimpleAgent
+from .prompts import (
+    ORCHESTRATOR_SYSTEM_PROMPT,
+    ORCHESTRATOR_USER_PROMPT
+)
+from .interviewer import get_interviewer
+from .evaluator import get_evaluator
+from .polisher import get_polisher
+from config import get_llm, tool_listener
+from models.entities import SessionState, RoundRecord, AgentResponse, Stage
+
+class OrchestratorAgent:
+    """流程调度 Agent - 阶段调度、更新 SessionState、决定 next stage"""
+    
+    def __init__(self):
+        """初始化 OrchestratorAgent"""
+        self.llm = get_llm()
+        self.agent = ToolAwareSimpleAgent(
+            name="Orchestrator",
+            system_prompt=ORCHESTRATOR_SYSTEM_PROMPT,
+            llm=self.llm,
+            tool_call_listener=tool_listener
+        )
+        
+        # 获取子 Agent 实例
+        self.interviewer = get_interviewer()
+        self.evaluator = get_evaluator()
+        self.polisher = get_polisher()
+    
+    def decide_next_action(self, session_state: SessionState) -> Dict[str, Any]:
+        """
+        根据当前会话状态决定下一步动作
+        
+        Args:
+            session_state: 当前会话状态
+            
+        Returns:
+            Dict[str, Any]: 下一步动作,包含:
+                - action: 动作类型 (interview/evaluate/polish)
+                - stage: 阶段 (stage1/stage2/stage3)
+        """
+        # 构建用户提示词
+        user_prompt = ORCHESTRATOR_USER_PROMPT.format(
+            seed_sentence=session_state.seed_sentence,
+            current_stage=session_state.current_stage,
+            rounds_count=len(session_state.rounds),
+            last_evaluated=self._is_last_round_evaluated(session_state)
+        )
+        
+        # 调用 LLM
+        response = self.agent.run(user_prompt)
+        
+        # 解析 JSON 响应
+        try:
+            result = json.loads(response)
+            return result
+        except json.JSONDecodeError as e:
+            # 如果解析失败,使用规则判断
+            return self._decide_next_action_rule_based(session_state)
+    
+    def _is_last_round_evaluated(self, session_state: SessionState) -> bool:
+        """
+        判断最新一轮是否已有点评
+        
+        Args:
+            session_state: 当前会话状态
+            
+        Returns:
+            bool: 最新一轮是否已有点评
+        """
+        if not session_state.rounds:
+            return False
+        
+        last_round = session_state.rounds[-1]
+        return bool(last_round.evaluation)
+    
+    def _decide_next_action_rule_based(self, session_state: SessionState) -> Dict[str, Any]:
+        """
+        基于规则决定下一步动作(LLM 解析失败时的备用方案)
+        
+        Args:
+            session_state: 当前会话状态
+            
+        Returns:
+            Dict[str, Any]: 下一步动作
+        """
+        rounds_count = len(session_state.rounds)
+        last_evaluated = self._is_last_round_evaluated(session_state)
+        
+        # 规则判断
+        if rounds_count == 0:
+            # 会话刚开始,进入 stage1
+            return {"action": "interview", "stage": "stage1"}
+        elif not last_evaluated:
+            # 用户提交了句子但尚未点评
+            return {"action": "evaluate"}
+        elif session_state.current_stage == "stage1":
+            # stage1 点评完成,进入 stage2
+            return {"action": "interview", "stage": "stage2"}
+        elif session_state.current_stage == "stage2":
+            # stage2 点评完成,进入 stage3
+            return {"action": "interview", "stage": "stage3"}
+        elif session_state.current_stage == "stage3":
+            # stage3 点评完成,进入 polish
+            return {"action": "polish"}
+        else:
+            # 默认情况
+            return {"action": "interview", "stage": "stage1"}
+    
+    def start_session(self, session_state: SessionState) -> AgentResponse:
+        """
+        开始会话,生成第一阶段提问
+        
+        Args:
+            session_state: 会话状态
+            
+        Returns:
+            AgentResponse: 智能体响应
+        """
+        # 生成提问
+        interview_result = self.interviewer.ask(
+            stage=session_state.current_stage,
+            current_sentence=session_state.seed_sentence,
+            rounds_history=""
+        )
+        
+        # 构建响应
+        return AgentResponse(
+            session_id=session_state.session_id,
+            stage=session_state.current_stage,
+            question=interview_result["question"],
+            evaluation=None,
+            expanded_sentence=None,
+            final_polished=None,
+            is_done=False
+        )
+    
+    def process_user_input(self, session_state: SessionState, 
+                          user_sentence: str) -> AgentResponse:
+        """
+        处理用户输入,执行点评和可能的下一阶段提问
+        
+        Args:
+            session_state: 会话状态
+            user_sentence: 用户提交的句子
+            
+        Returns:
+            AgentResponse: 智能体响应
+        """
+        # 获取当前阶段目标
+        stage_goal = self.interviewer.get_stage_goal(session_state.current_stage)
+        
+        # 获取当前句子(种子句或上一轮的扩写结果)
+        current_sentence = self._get_current_sentence(session_state)
+        
+        # 生成提问(用于点评上下文)
+        interview_result = self.interviewer.ask(
+            stage=session_state.current_stage,
+            current_sentence=current_sentence,
+            rounds_history=self._format_rounds_history(session_state)
+        )
+        
+        # 执行语法点评
+        evaluation_result = self.evaluator.evaluate(
+            stage_goal=stage_goal,
+            question=interview_result["question"],
+            seed_sentence=session_state.seed_sentence,
+            user_sentence=user_sentence
+        )
+        
+        # 创建轮次记录
+        round_record = RoundRecord(
+            stage=session_state.current_stage,
+            question=interview_result["question"],
+            user_answer=user_sentence,
+            evaluation=evaluation_result["comment"],
+            expanded_sentence=evaluation_result["corrected_sentence"]
+        )
+        
+        # 更新会话状态
+        session_state.rounds.append(round_record)
+        
+        # 决定下一步动作
+        next_action = self.decide_next_action(session_state)
+        
+        # 根据下一步动作构建响应
+        if next_action["action"] == "interview":
+            # 进入下一阶段
+            next_stage = next_action["stage"]
+            session_state.current_stage = next_stage
+            
+            # 生成下一阶段提问
+            next_interview_result = self.interviewer.ask(
+                stage=next_stage,
+                current_sentence=evaluation_result["corrected_sentence"],
+                rounds_history=self._format_rounds_history(session_state)
+            )
+            
+            return AgentResponse(
+                session_id=session_state.session_id,
+                stage=next_stage,
+                question=next_interview_result["question"],
+                evaluation=evaluation_result["comment"],
+                expanded_sentence=evaluation_result["corrected_sentence"],
+                final_polished=None,
+                is_done=False
+            )
+        elif next_action["action"] == "polish":
+            # 进入润色阶段
+            session_state.current_stage = "done"
+            
+            # 生成润色版本
+            rounds_detail = self.polisher.format_rounds_detail(session_state.rounds)
+            polish_result = self.polisher.polish(
+                seed_sentence=session_state.seed_sentence,
+                rounds_detail=rounds_detail
+            )
+            
+            session_state.final_polished = polish_result["polished_sentence"]
+            
+            return AgentResponse(
+                session_id=session_state.session_id,
+                stage="done",
+                question=None,
+                evaluation=evaluation_result["comment"],
+                expanded_sentence=evaluation_result["corrected_sentence"],
+                final_polished=polish_result["polished_sentence"],
+                is_done=True
+            )
+        else:
+            # 默认情况(不应该发生)
+            return AgentResponse(
+                session_id=session_state.session_id,
+                stage=session_state.current_stage,
+                question=None,
+                evaluation=evaluation_result["comment"],
+                expanded_sentence=evaluation_result["corrected_sentence"],
+                final_polished=None,
+                is_done=False
+            )
+    
+    def _get_current_sentence(self, session_state: SessionState) -> str:
+        """
+        获取当前句子(用于生成提问)
+        
+        Args:
+            session_state: 会话状态
+            
+        Returns:
+            str: 当前句子
+        """
+        if not session_state.rounds:
+            return session_state.seed_sentence
+        else:
+            return session_state.rounds[-1].expanded_sentence
+    
+    def _format_rounds_history(self, session_state: SessionState) -> str:
+        """
+        格式化轮次历史为字符串
+        
+        Args:
+            session_state: 会话状态
+            
+        Returns:
+            str: 轮次历史字符串
+        """
+        if not session_state.rounds:
+            return ""
+        
+        history_parts = []
+        for i, round_record in enumerate(session_state.rounds, 1):
+            stage_num = round_record.stage.replace("stage", "")
+            history = f"阶段{stage_num}: {round_record.question} -> {round_record.expanded_sentence}"
+            history_parts.append(history)
+        
+        return "\n".join(history_parts)
+
+
+# 创建全局实例(单例模式)
+_orchestrator_instance = None
+
+
+def get_orchestrator() -> OrchestratorAgent:
+    """
+    获取全局 OrchestratorAgent 实例(单例模式)
+    
+    Returns:
+        OrchestratorAgent: OrchestratorAgent 实例
+    """
+    global _orchestrator_instance
+    if _orchestrator_instance is None:
+        _orchestrator_instance = OrchestratorAgent()
+    return _orchestrator_instance
+
+
+def reset_orchestrator():
+    """重置 OrchestratorAgent 实例(用于测试)"""
+    global _orchestrator_instance
+    _orchestrator_instance = None

+ 115 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/polisher.py

@@ -0,0 +1,115 @@
+"""
+PolisherAgent - 满分润色 Agent
+接收三轮扩写记录,生成润色后的满分句子
+"""
+import json
+from typing import Dict, Any
+from hello_agents.agents.tool_aware_agent import ToolAwareSimpleAgent
+from .prompts import (
+    POLISHER_SYSTEM_PROMPT,
+    POLISHER_USER_PROMPT
+)
+from config import get_llm, tool_listener
+
+
+class PolisherAgent:
+    """满分润色 Agent - 生成根据三轮扩写记录生成润色后的满分句子"""
+    
+    def __init__(self):
+        """初始化 PolisherAgent"""
+        self.llm = get_llm()
+        self.agent = ToolAwareSimpleAgent(
+            name="Polisher",
+            system_prompt=POLISHER_SYSTEM_PROMPT,
+            llm=self.llm,
+            tool_call_listener=tool_listener
+        )
+    
+    def polish(self, seed_sentence: str, rounds_detail: str) -> Dict[str, Any]:
+        """
+        根据三轮扩写记录生成润色后的满分句子
+        
+        Args:
+            seed_sentence: 种子句(原始句子)
+            rounds_detail: 历史轮次详情(包含三个阶段的完整信息)
+            
+        Returns:
+            Dict[str, Any]: 润色结果,包含:
+                - polished_sentence: 最终润色后的英文句子
+                - structure_analysis: 语法结构分析(列表)
+                - highlight: 最亮眼的结构特点
+        """
+        # 构建用户提示词
+        user_prompt = POLISHER_USER_PROMPT.format(
+            seed_sentence=seed_sentence,
+            rounds_detail=rounds_detail
+        )
+        
+        # 调用 LLM
+        response = self.agent.run(user_prompt)
+        
+        # 解析 JSON 响应
+        try:
+            result = json.loads(response)
+            return result
+        except json.JSONDecodeError as e:
+            # 如果解析失败,返回默认响应
+            return {
+                "polished_sentence": seed_sentence,
+                "structure_analysis": [
+                    "润色失败,使用原始句子"
+                ],
+                "highlight": "无法生成润色版本"
+            }
+    
+    def format_rounds_detail(self, rounds: list) -> str:
+        """
+        格式化轮次记录为字符串,供 Polisher 使用
+        
+        Args:
+            rounds: 轮次记录列表(RoundRecord 对象列表)
+            
+        Returns:
+            str: 格式化后的轮次详情字符串
+        """
+        if not rounds:
+            return "暂无扩写记录"
+        
+        detail_parts = []
+        for i, round_record in enumerate(rounds, 1):
+            # 提取阶段编号
+            stage_num = round_record.stage.replace("stage", "")
+            
+            # 格式化单个轮次
+            round_detail = f"""【阶段{stage_num}】
+- 记者提问:{round_record.question}
+- 学生提交:{round_record.user_answer}
+- 语法点评:{round_record.evaluation}
+- 本阶段扩写结果:{round_record.expanded_sentence}"""
+            
+            detail_parts.append(round_detail)
+        
+        return "\n\n".join(detail_parts)
+
+
+# 创建全局实例(单例模式)
+_polisher_instance = None
+
+
+def get_polisher() -> PolisherAgent:
+    """
+    获取全局 PolisherAgent 实例(单例模式)
+    
+    Returns:
+        PolisherAgent: PolisherAgent 实例
+    """
+    global _polisher_instance
+    if _polisher_instance is None:
+        _polisher_instance = PolisherAgent()
+    return _polisher_instance
+
+
+def reset_polisher():
+    """重置 PolisherAgent 实例(用于测试)"""
+    global _polisher_instance
+    _polisher_instance = None

+ 205 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/agents/prompts.py

@@ -0,0 +1,205 @@
+ORCHESTRATOR_SYSTEM_PROMPT = """
+你是一个英语写作教练应用的流程调度器。
+你的职责是根据当前会话状态,判断下一步应该执行哪个动作。
+
+你只能输出以下之一的 JSON 格式,不得有任何其他内容:
+{{"action": "interview", "stage": "stage1"}}
+{{"action": "interview", "stage": "stage2"}}
+{{"action": "interview", "stage": "stage3"}}
+{{"action": "evaluate"}}
+{{"action": "polish"}}
+
+阶段推进规则:
+- 会话刚开始(rounds 为空)→ action=interview, stage=stage1
+- 用户提交了句子且尚未点评 → action=evaluate
+- 点评完成且当前为 stage1 → action=interview, stage=stage2
+- 点评完成且当前为 stage2 → action=interview, stage=stage3
+- 点评完成且当前为 stage3 → action=polish
+"""
+
+ORCHESTRATOR_USER_PROMPT = """
+当前会话状态如下:
+- 种子句:{seed_sentence}
+- 当前阶段:{current_stage}
+- 已完成轮次数:{rounds_count}
+- 最新一轮是否已点评:{last_evaluated}
+
+请根据上述状态,输出下一步动作的 JSON。
+"""
+
+INTERVIEWER_SYSTEM_PROMPT = """
+你是一位专业的英语写作教练,正在用"记者提问法(5W1H)"
+引导学生将简单英文句子逐步扩写为结构丰富的高级长句。
+
+你的任务是:针对当前句子,扮演记者,
+按照指定阶段的要求,向学生提出 1~2 个引导性问题。
+
+输出格式要求(严格 JSON,不含 markdown):
+{{
+  "question": "你的提问内容(中文提问,但引导方向是英文写作)",
+  "hint": "引导学生使用的语法结构提示,例如:形容词前置、介词短语、定语从句等",
+  "example": "一个简短的示范改写,帮助学生理解方向(可选,不超过一句)"
+}}
+"""
+
+# 阶段一:增加细节
+INTERVIEWER_STAGE1_PROMPT = """
+当前句子:{current_sentence}
+
+【阶段一:增加细节】
+请针对句子中的核心名词或动词,提问 Who / What / How 类问题。
+引导学生在词的"前面"加入 形容词 或 副词 来修饰,使句子更生动具体。
+
+请输出阶段一的记者提问 JSON。
+"""
+
+# 阶段二:增加时空背景
+INTERVIEWER_STAGE2_PROMPT = """
+当前句子:{current_sentence}
+历史扩写记录:{rounds_history}
+
+【阶段二:增加时空背景】
+请提问 When / Where 类问题,引导学生补充时间或地点信息。
+引导学生使用"介词短语"(如 in the park / at midnight / on a rainy morning)
+将这些信息补充到句子中。
+
+请输出阶段二的记者提问 JSON。
+"""
+
+# 阶段三:增加结构深度
+INTERVIEWER_STAGE3_PROMPT = """
+当前句子:{current_sentence}
+历史扩写记录:{rounds_history}
+
+【阶段三:增加结构深度(重点)】
+请进行更深度的追问,如:具体身份、原因、结果、手段、背景关系等。
+引导学生在被修饰语"后面"加入以下任一结构(右分支结构):
+  - 定语从句:who / which / that ...
+  - 非谓语动词:doing / done / to do ...
+  - 逻辑状语从句:because / although / when / after ...
+
+必须强调:这些修饰成分要放在被修饰语的"后面"。
+
+请输出阶段三的记者提问 JSON。
+"""
+
+EVALUATOR_SYSTEM_PROMPT = """
+你是一位专业的英语语法教师,擅长对英语学习者的句子改写进行准确、鼓励性的点评。
+
+你的任务是:
+1. 判断学生提交的句子语法是否正确
+2. 指出具体的语法问题(如有),并给出正确形式
+3. 肯定学生扩写方向是否符合本阶段的引导目标
+4. 语气鼓励、简洁,不超过 3 句话
+
+输出格式(严格 JSON,不含 markdown):
+{{
+  "is_correct": true 或 false,
+  "comment": "点评内容(中文,1~3句)",
+  "corrected_sentence": "如语法有误,给出修正后的句子;语法正确则与 user_sentence 相同"
+}}
+"""
+
+EVALUATOR_USER_PROMPT = """
+本阶段目标:{stage_goal}
+记者提问:{question}
+学生原始句子:{seed_sentence}
+学生本次提交:{user_sentence}
+
+请对学生提交的句子进行语法点评,输出 JSON。
+"""
+
+# stage_goal 的值由阶段决定,可用字典映射
+STAGE_GOALS = {
+    "stage1": "在名词或动词前加形容词或副词,增加修饰细节",
+    "stage2": "使用介词短语补充时间或地点背景",
+    "stage3": "在被修饰语后加定语从句、非谓语动词或逻辑状语,形成右分支结构",
+}
+
+POLISHER_SYSTEM_PROMPT = """
+你是一位资深英语写作教练,擅长将学生的扩写习作提炼为地道、优雅的高级英文长句。
+
+你的任务是:
+1. 综合三个阶段的扩写内容,生成一个结构完整、语法正确、表达自然的"满分版本"
+2. 简要说明该句子使用了哪些高级语法结构(用中文列点说明)
+3. 句子应具备:右分支结构、介词短语背景、修饰性定语或状语
+
+输出格式(严格 JSON,不含 markdown):
+{{
+  "polished_sentence": "最终润色后的英文句子",
+  "structure_analysis": [
+    "形容词前置:... 修饰 ...",
+    "介词短语:... 表示时间/地点",
+    "定语从句/非谓语:... 作后置定语"
+  ],
+  "highlight": "用一句话总结这个句子最亮眼的结构特点"
+}}
+"""
+
+POLISHER_USER_PROMPT = """
+种子句(原始):{seed_sentence}
+
+三个阶段的扩写过程如下:
+{rounds_detail}
+
+请综合以上内容,生成一个润色后的满分英文长句,并输出 JSON。
+"""
+
+# rounds_detail 的构建示例(由后端拼接)
+ROUNDS_DETAIL_TEMPLATE = """
+【阶段{stage_num}】
+- 记者提问:{question}
+- 学生提交:{user_sentence}
+- 语法点评:{evaluation}
+- 本阶段扩写结果:{expanded_sentence}
+"""
+
+AUTO_MODE_SYSTEM_PROMPT = """
+你是一位专业英语写作教练,负责演示如何将一个简单英文句子
+通过三个阶段逐步扩写为高级长句。
+ 
+你将自己扮演"记者"和"学生"两个角色,完整演示整个扩写过程。
+每个阶段你都需要:先提出记者问题,再给出一个合理的示范扩写句。
+ 
+输出格式(严格使用以下分隔符,不含任何 markdown、JSON 或多余空行):
+ 
+===STAGE1_QUESTION===
+阶段一记者提问内容
+===STAGE1_EXPANDED===
+阶段一示范扩写句
+===STAGE2_QUESTION===
+阶段二记者提问内容
+===STAGE2_EXPANDED===
+阶段二示范扩写句
+===STAGE3_QUESTION===
+阶段三记者提问内容
+===STAGE3_EXPANDED===
+阶段三示范扩写句
+===POLISHED===
+最终满分润色版本
+===ANALYSIS===
+形容词前置:具体说明
+介词短语:具体说明
+定语从句:具体说明
+===END===
+ 
+规则:
+- 每个分隔符单独占一行,前后不要有空格或空行
+- 分隔符之间只输出纯文本内容,不要重复分隔符本身
+- ===ANALYSIS=== 之后每行写一条结构分析,用中文冒号分隔类型和说明
+- 最后必须以 ===END=== 结尾
+"""
+
+AUTO_MODE_USER_PROMPT = """
+请对以下英文种子句进行完整的三阶段扩写演示:
+ 
+种子句:{seed_sentence}
+ 
+要求:
+- 阶段一:在核心词前加形容词/副词
+- 阶段二:加入介词短语(时间或地点)
+- 阶段三:加入定语从句、非谓语动词或逻辑状语(右分支)
+- 最后输出一个润色后的满分版本
+ 
+请严格按照系统提示中的分隔符格式输出,不要输出 JSON。
+"""

+ 63 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/config.py

@@ -0,0 +1,63 @@
+"""
+LLM 配置 - 英语句子扩写智能体
+"""
+import os
+import logging
+from dotenv import load_dotenv
+from hello_agents import HelloAgentsLLM
+
+load_dotenv()
+
+logger = logging.getLogger(__name__)
+
+def tool_listener(call_info):
+    logger.info(f"Agent: {call_info['agent_name']}")
+    logger.info(f"Tool: {call_info['tool_name']}")
+    logger.info(f"Parameters: {call_info['parsed_parameters']}")
+    logger.info(f"Result: {call_info['result']}")
+
+# LLM 配置
+class LLMConfig:
+    """LLM 配置类"""
+    
+    # 从环境变量读取配置
+    API_KEY = os.getenv("LLM_API_KEY", "")
+    MODEL_ID = os.getenv("LLM_MODEL_ID", "")
+    BASE_URL = os.getenv("LLM_BASE_URL", "")
+    
+    @classmethod
+    def create_llm(cls) -> HelloAgentsLLM:
+        """
+        创建 LLM 实例
+        
+        Returns:
+            HelloAgentsLLM: 配置好的 LLM 实例
+        """
+        return HelloAgentsLLM(
+            api_key=cls.API_KEY,
+            model_id=cls.MODEL_ID,
+            base_url=cls.BASE_URL
+        )
+
+
+# 全局 LLM 实例(懒加载)
+_llm_instance = None
+
+
+def get_llm() -> HelloAgentsLLM:
+    """
+    获取全局 LLM 实例(单例模式)
+    
+    Returns:
+        HelloAgentsLLM: LLM 实例
+    """
+    global _llm_instance
+    if _llm_instance is None:
+        _llm_instance = LLMConfig.create_llm()
+    return _llm_instance
+
+
+def reset_llm():
+    """重置 LLM 实例(用于测试或配置变更)"""
+    global _llm_instance
+    _llm_instance = None

+ 68 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/main.py

@@ -0,0 +1,68 @@
+"""
+FastAPI 应用入口 - 英语句子扩写智能体
+"""
+from fastapi import FastAPI, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse
+import sys
+import os
+
+# 添加当前目录(backend)到 Python 路径
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+
+from routers.expand import router as expand_router
+
+# 创建 FastAPI 应用
+app = FastAPI(
+    title="英语句子扩写智能体 API",
+    description="基于多智能体协作的英语写作教练应用",
+    version="1.0.0"
+)
+
+# 配置 CORS
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],  # 允许所有来源(开发环境)
+    allow_credentials=True,
+    allow_methods=["*"],  # 允许所有 HTTP 方法
+    allow_headers=["*"],  # 允许所有请求头
+)
+
+# 包含路由
+app.include_router(expand_router)
+
+
+# 统一异常处理
+@app.exception_handler(Exception)
+async def global_exception_handler(request: Request, exc: Exception):
+    """全局异常处理器"""
+    return JSONResponse(
+        status_code=500,
+        content={
+            "detail": str(exc),
+            "type": type(exc).__name__
+        }
+    )
+
+
+# 根路径
+@app.get("/")
+async def root():
+    """根路径"""
+    return {
+        "message": "英语句子扩写智能体 API",
+        "version": "1.0.0",
+        "docs": "/docs"
+    }
+
+
+# 健康检查
+@app.get("/health")
+async def health_check():
+    """健康检查"""
+    return {"status": "ok"}
+
+
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8000)

+ 1 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/models/__init__.py

@@ -0,0 +1 @@
+  

+ 56 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/models/entities.py

@@ -0,0 +1,56 @@
+"""
+数据实体定义 - 英语句子扩写智能体
+"""
+from pydantic import BaseModel
+from typing import Optional, Literal
+
+
+# 扩写阶段枚举
+Stage = Literal["stage1", "stage2", "stage3", "done"]
+
+
+# 单次扩写轮次记录
+class RoundRecord(BaseModel):
+    """记录单个扩写轮次的完整信息"""
+    stage: Stage
+    question: str  # 记者提问
+    user_answer: str  # 用户输入的句子
+    evaluation: str  # 语法点评
+    expanded_sentence: str  # 本轮扩写结果
+
+
+# 整个会话状态
+class SessionState(BaseModel):
+    """会话完整状态管理"""
+    session_id: str
+    mode: Literal["manual", "auto"]
+    seed_sentence: str
+    current_stage: Stage
+    rounds: list[RoundRecord] = []
+    final_polished: Optional[str] = None
+
+
+# 前端发起请求
+class StartRequest(BaseModel):
+    """开始新的扩写会话"""
+    seed_sentence: str
+    mode: Literal["manual", "auto"]
+
+
+# 用户提交扩写句子(手动模式)
+class SubmitRequest(BaseModel):
+    """提交用户扩写的句子"""
+    session_id: str
+    user_sentence: str
+
+
+# 智能体单次响应
+class AgentResponse(BaseModel):
+    """智能体响应数据"""
+    session_id: str
+    stage: Stage
+    question: Optional[str] = None
+    evaluation: Optional[str] = None
+    expanded_sentence: Optional[str] = None
+    final_polished: Optional[str] = None
+    is_done: bool = False

+ 1 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/routers/__init__.py

@@ -0,0 +1 @@
+  

+ 153 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/routers/expand.py

@@ -0,0 +1,153 @@
+"""
+FastAPI 路由层 - 英语句子扩写智能体
+"""
+from fastapi import APIRouter, HTTPException
+from fastapi.responses import StreamingResponse
+from typing import AsyncGenerator
+import sys
+import os
+
+# 添加 backend 目录到 Python 路径
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from models.entities import (
+    StartRequest,
+    SubmitRequest,
+    AgentResponse,
+    SessionState
+)
+from services.session_store import get_session_store
+from agents.orchestrator import get_orchestrator
+from agents.auto_mode_agent import get_auto_mode
+
+router = APIRouter(prefix="/api", tags=["expand"])
+
+
+@router.post("/session/start", response_model=AgentResponse)
+async def start_session(request: StartRequest) -> AgentResponse:
+    """
+    创建新会话,返回第一阶段提问
+    
+    Args:
+        request: 开始会话请求,包含种子句和模式
+        
+    Returns:
+        AgentResponse: 智能体响应
+    """
+    # 获取会话存储
+    session_store = get_session_store()
+    
+    # 创建会话
+    session = session_store.create_session(
+        seed_sentence=request.seed_sentence,
+        mode=request.mode
+    )
+    
+    # 获取 Orchestrator
+    orchestrator = get_orchestrator()
+    
+    # 开始会话
+    response = orchestrator.start_session(session)
+    
+    return response
+
+
+@router.post("/session/submit", response_model=AgentResponse)
+async def submit_sentence(request: SubmitRequest) -> AgentResponse:
+    """
+    提交用户扩写句子,返回点评和下一阶段提问(手动模式)
+    
+    Args:
+        request: 提交请求,包含会话 ID 和用户句子
+        
+    Returns:
+        AgentResponse: 智能体响应
+    """
+    # 获取会话存储
+    session_store = get_session_store()
+    
+    # 获取会话
+    session = session_store.get_session(request.session_id)
+    if not session:
+        raise HTTPException(status_code=404, detail="Session not found")
+    
+    # 获取 Orchestrator
+    orchestrator = get_orchestrator()
+    
+    # 处理用户输入
+    response = orchestrator.process_user_input(
+        session_state=session,
+        user_sentence=request.user_sentence
+    )
+    
+    # 更新会话
+    session_store.update_session(session)
+    
+    return response
+
+
+@router.get("/session/{session_id}/auto")
+async def auto_mode_stream(session_id: str) -> StreamingResponse:
+    """
+    SSE 流式推送三轮自动演示
+    
+    Args:
+        session_id: 会话 ID
+        
+    Returns:
+        StreamingResponse: SSE 流式响应
+    """
+    # 获取会话存储
+    session_store = get_session_store()
+    
+    # 获取会话
+    session = session_store.get_session(session_id)
+    if not session:
+        raise HTTPException(status_code=404, detail="Session not found")
+    
+    # 获取 AutoModeAgent
+    auto_mode_agent = get_auto_mode()
+    
+    # 生成流式响应
+    async def event_generator() -> AsyncGenerator[str, None]:
+        import json
+        try:
+            # 使用流式运行
+            async for event in auto_mode_agent.run_auto_mode_stream(session.seed_sentence):
+                yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
+            # 发送结束事件
+            yield "event: done\ndata: {}\n\n"
+        except Exception as e:
+            yield f"event: error\ndata: {json.dumps({'detail': str(e), 'type': type(e).__name__}, ensure_ascii=False)}\n\n"
+    
+    return StreamingResponse(
+        event_generator(),
+        media_type="text/event-stream",
+        headers={
+            "Cache-Control": "no-cache",
+            "Connection": "keep-alive",
+            "X-Accel-Buffering": "no"
+        }
+    )
+
+
+@router.get("/session/{session_id}", response_model=SessionState)
+async def get_session(session_id: str) -> SessionState:
+    """
+    获取当前会话完整状态
+    
+    Args:
+        session_id: 会话 ID
+        
+    Returns:
+        SessionState: 会话状态
+    """
+    # 获取会话存储
+    session_store = get_session_store()
+    
+    # 获取会话
+    session = session_store.get_session(session_id)
+    if not session:
+        raise HTTPException(status_code=404, detail="Session not found")
+    
+    return session

+ 1 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/services/__init__.py

@@ -0,0 +1 @@
+  

+ 120 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/backend/src/services/session_store.py

@@ -0,0 +1,120 @@
+"""
+内存会话管理 - 英语句子扩写智能体
+"""
+import uuid
+from typing import Optional
+from models.entities import SessionState
+
+
+class SessionStore:
+    """内存会话存储(支持并发)"""
+    
+    def __init__(self):
+        """初始化会话存储"""
+        self._sessions: dict[str, SessionState] = {}
+    
+    def create_session(
+        self,
+        seed_sentence: str,
+        mode: str = "manual"
+    ) -> SessionState:
+        """
+        创建新会话
+        
+        Args:
+            seed_sentence: 种子句
+            mode: 模式(manual/auto)
+            
+        Returns:
+            SessionState: 新创建的会话状态
+        """
+        session_id = str(uuid.uuid4())
+        session = SessionState(
+            session_id=session_id,
+            mode=mode,
+            seed_sentence=seed_sentence,
+            current_stage="stage1",
+            rounds=[]
+        )
+        self._sessions[session_id] = session
+        return session
+    
+    def get_session(self, session_id: str) -> Optional[SessionState]:
+        """
+        获取会话
+        
+        Args:
+            session_id: 会话 ID
+            
+        Returns:
+            Optional[SessionState]: 会话状态,不存在则返回 None
+        """
+        return self._sessions.get(session_id)
+    
+    def update_session(self, session: SessionState) -> None:
+        """
+        更新会话
+        
+        Args:
+            session: 更新后的会话状态
+        """
+        self._sessions[session.session_id] = session
+    
+    def delete_session(self, session_id: str) -> bool:
+        """
+        删除会话
+        
+        Args:
+            session_id: 会话 ID
+            
+        Returns:
+            bool: 删除成功返回 True,会话不存在返回 False
+        """
+        if session_id in self._sessions:
+            del self._sessions[session_id]
+            return True
+        return False
+    
+    def list_sessions(self) -> list[SessionState]:
+        """
+        列出所有会话
+        
+        Returns:
+            list[SessionState]: 所有会话状态列表
+        """
+        return list(self._sessions.values())
+    
+    def session_exists(self, session_id: str) -> bool:
+        """
+        检查会话是否存在
+        
+        Args:
+            session_id: 会话 ID
+            
+        Returns:
+            bool: 存在返回 True,否则返回 False
+        """
+        return session_id in self._sessions
+
+
+# 全局会话存储实例(单例)
+_session_store_instance = None
+
+
+def get_session_store() -> SessionStore:
+    """
+    获取全局会话存储实例(单例模式)
+    
+    Returns:
+        SessionStore: 会话存储实例
+    """
+    global _session_store_instance
+    if _session_store_instance is None:
+        _session_store_instance = SessionStore()
+    return _session_store_instance
+
+
+def reset_session_store():
+    """重置会话存储(用于测试)"""
+    global _session_store_instance
+    _session_store_instance = None

+ 2 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/.env.example

@@ -0,0 +1,2 @@
+# API 基础 URL
+VITE_API_BASE_URL=http=http://localhost:8000

+ 29 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/.gitignore

@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Environment variables
+.env
+.env.local
+.env.*.local

+ 13 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" type="image/svg+xml" href="/vite.svg">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>英语句子扩写智能体</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 1464 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/package-lock.json

@@ -0,0 +1,1464 @@
+{
+  "name": "sentence-expand-agent-frontend",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "sentence-expand-agent-frontend",
+      "version": "1.0.0",
+      "dependencies": {
+        "pinia": "^2.1.7",
+        "vue": "^3.4.0",
+        "vue-router": "^4.2.5"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^5.0.0",
+        "typescript": "^5.3.0",
+        "vite": "^5.0.0",
+        "vue-tsc": "^1.8.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz",
+      "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "license": "MIT"
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
+      "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
+      "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
+      "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
+      "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
+      "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
+      "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
+      "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
+      "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
+      "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
+      "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
+      "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
+      "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
+      "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
+      "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
+      "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
+      "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
+      "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
+      "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
+      "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
+      "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
+      "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
+      "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
+      "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
+      "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
+      "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.4",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+      "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@volar/language-core": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz",
+      "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/source-map": "1.11.1"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz",
+      "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "muggle-string": "^0.3.1"
+      }
+    },
+    "node_modules/@volar/typescript": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.11.1.tgz",
+      "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "1.11.1",
+        "path-browserify": "^1.0.1"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.33.tgz",
+      "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.2",
+        "@vue/shared": "3.5.33",
+        "entities": "^7.0.1",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz",
+      "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.33",
+        "@vue/shared": "3.5.33"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz",
+      "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.2",
+        "@vue/compiler-core": "3.5.33",
+        "@vue/compiler-dom": "3.5.33",
+        "@vue/compiler-ssr": "3.5.33",
+        "@vue/shared": "3.5.33",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.21",
+        "postcss": "^8.5.10",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz",
+      "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.33",
+        "@vue/shared": "3.5.33"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/language-core": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
+      "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "~1.11.1",
+        "@volar/source-map": "~1.11.1",
+        "@vue/compiler-dom": "^3.3.0",
+        "@vue/shared": "^3.3.0",
+        "computeds": "^0.0.1",
+        "minimatch": "^9.0.3",
+        "muggle-string": "^0.3.1",
+        "path-browserify": "^1.0.1",
+        "vue-template-compiler": "^2.7.14"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.33.tgz",
+      "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.33"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.33.tgz",
+      "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.33",
+        "@vue/shared": "3.5.33"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz",
+      "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.33",
+        "@vue/runtime-core": "3.5.33",
+        "@vue/shared": "3.5.33",
+        "csstype": "^3.2.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.33.tgz",
+      "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.33",
+        "@vue/shared": "3.5.33"
+      },
+      "peerDependencies": {
+        "vue": "3.5.33"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.33.tgz",
+      "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==",
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz",
+      "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/computeds": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
+      "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "license": "MIT"
+    },
+    "node_modules/de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/entities": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.9",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
+      "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/muggle-string": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.3.1.tgz",
+      "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/pinia": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
+      "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.4.4",
+        "vue": "^2.7.0 || ^3.5.11"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.10",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.10.tgz",
+      "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.60.2",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.2.tgz",
+      "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.60.2",
+        "@rollup/rollup-android-arm64": "4.60.2",
+        "@rollup/rollup-darwin-arm64": "4.60.2",
+        "@rollup/rollup-darwin-x64": "4.60.2",
+        "@rollup/rollup-freebsd-arm64": "4.60.2",
+        "@rollup/rollup-freebsd-x64": "4.60.2",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
+        "@rollup/rollup-linux-arm-musleabihf": "4.60.2",
+        "@rollup/rollup-linux-arm64-gnu": "4.60.2",
+        "@rollup/rollup-linux-arm64-musl": "4.60.2",
+        "@rollup/rollup-linux-loong64-gnu": "4.60.2",
+        "@rollup/rollup-linux-loong64-musl": "4.60.2",
+        "@rollup/rollup-linux-ppc64-gnu": "4.60.2",
+        "@rollup/rollup-linux-ppc64-musl": "4.60.2",
+        "@rollup/rollup-linux-riscv64-gnu": "4.60.2",
+        "@rollup/rollup-linux-riscv64-musl": "4.60.2",
+        "@rollup/rollup-linux-s390x-gnu": "4.60.2",
+        "@rollup/rollup-linux-x64-gnu": "4.60.2",
+        "@rollup/rollup-linux-x64-musl": "4.60.2",
+        "@rollup/rollup-openbsd-x64": "4.60.2",
+        "@rollup/rollup-openharmony-arm64": "4.60.2",
+        "@rollup/rollup-win32-arm64-msvc": "4.60.2",
+        "@rollup/rollup-win32-ia32-msvc": "4.60.2",
+        "@rollup/rollup-win32-x64-gnu": "4.60.2",
+        "@rollup/rollup-win32-x64-msvc": "4.60.2",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/semver": {
+      "version": "7.7.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.4.21",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
+      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.33",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.33.tgz",
+      "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.33",
+        "@vue/compiler-sfc": "3.5.33",
+        "@vue/runtime-dom": "3.5.33",
+        "@vue/server-renderer": "3.5.33",
+        "@vue/shared": "3.5.33"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.6.4",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
+      "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    },
+    "node_modules/vue-template-compiler": {
+      "version": "2.7.16",
+      "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
+      "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "de-indent": "^1.0.2",
+        "he": "^1.2.0"
+      }
+    },
+    "node_modules/vue-tsc": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.8.27.tgz",
+      "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/typescript": "~1.11.1",
+        "@vue/language-core": "1.8.27",
+        "semver": "^7.5.4"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      }
+    }
+  }
+}

+ 22 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/package.json

@@ -0,0 +1,22 @@
+{
+  "name": "sentence-expand-agent-frontend",
+  "version": "1.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "type-check": "vue-tsc --noEmit"
+  },
+  "dependencies": {
+    "vue": "^3.4.0",
+    "pinia": "^2.1.7",
+    "vue-router": "^4.2.5"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.0",
+    "typescript": "^5.3.0",
+    "vite": "^5.0.0",
+    "vue-tsc": "^1.8.0"
+  }
+}

+ 15 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/App.vue

@@ -0,0 +1,15 @@
+<template>
+  <div id="app">
+    <HomeView />
+  </div>
+</template>
+
+<script setup lang="ts">
+import HomeView from './views/HomeView.vue';
+</script>
+
+<style>
+#app {
+  min-height: 100vh;
+}
+</style>

+ 130 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/api/expand.ts

@@ -0,0 +1,130 @@
+/**
+ * 英语句子扩写智能体 - API 封装
+ * 包含 REST API 和 SSE 流式接口
+ */
+
+import type {
+  StartRequest,
+  SubmitRequest,
+  AgentResponse,
+  SessionState,
+  SSEEvent
+} from '../types/expand';
+
+// API 基础 URL
+const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
+
+/**
+ * 开始新的扩写会话
+ */
+export async function startSession(request: StartRequest): Promise<AgentResponse> {
+  const response = await fetch(`${API_BASE_URL}/api/session/start`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify(request),
+  });
+
+  if (!response.ok) {
+    throw new Error(`Failed to start session: ${response.statusText}`);
+  }
+
+  return response.json();
+}
+
+/**
+ * 提交用户扩写句子(手动模式)
+ */
+export async function submitSentence(request: SubmitRequest): Promise<AgentResponse> {
+  const response = await fetch(`${API_BASE_URL}/api/session/submit`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify(request),
+  });
+
+  if (!response.ok) {
+    throw new Error(`Failed to submit sentence: ${response.statusText}`);
+  }
+
+  return response.json();
+}
+
+/**
+ * 获取会话完整状态
+ */
+export async function getSession(sessionId: string): Promise<SessionState> {
+  const response = await fetch(`${API_BASE_URL}/api/session/${sessionId}`);
+
+  if (!response.ok) {
+    throw new Error(`Failed to get session: ${response.statusText}`);
+  }
+
+  return response.json();
+}
+
+/**
+ * SSE 流式自动模式
+ * @param sessionId 会话 ID
+ * @param onMessage 接收到消息消息时的回调
+ * @param onError 发生错误时的回调
+ * @param onComplete 完成时的回调
+ * @returns 清理函数,用于关闭 EventSource
+ */
+export function subscribeAutoMode(
+  sessionId: string,
+  onMessage: (event: SSEEvent) => void,
+  onError?: (error: Error) => void,
+  onComplete?: () => void
+): () => void {
+  const eventSource = new EventSource(
+    `${API_BASE_URL}/api/session/${sessionId}/auto`
+  );
+
+  // 处理普通消息
+  eventSource.onmessage = (event) => {
+    try {
+      const data = JSON.parse(event.data);
+      onMessage({
+        type: data.type || 'question',
+        data: data.data,
+      });
+    } catch (error) {
+      console.error('Failed to parse SSE message:', error);
+    }
+  };
+
+  // 处理完成事件
+  eventSource.addEventListener('done', () => {
+    onComplete?.();
+    eventSource.close();
+  });
+
+  // 处理错误事件
+  eventSource.addEventListener('error', (event) => {
+    try {
+      const data = JSON.parse(event.data);
+      onMessage({
+        type: 'error',
+        error: data.error,
+      });
+    } catch (error) {
+      console.error('Failed to parse error event:', error);
+    }
+    onError?.(new Error('Auto mode error'));
+  });
+
+  // 处理连接错误
+  eventSource.onerror = (error) => {
+    console.error('SSE connection error:', error);
+    onError?.(new Error('SSE connection failed'));
+    eventSource.close();
+  };
+
+  // 返回清理函数
+  return () => {
+    eventSource.close();
+  };
+}

+ 142 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/FinalResult.vue

@@ -0,0 +1,142 @@
+<!--
+最终结果组件
+显示润色后的满分版本
+-->
+<template>
+  <div class="final-result">
+    <div class="result-header">
+      <span class="icon">🏆</span>
+      <h2 class="title">满分版本</h2>
+    </div>
+
+    <div class="result-content">
+      <p class="seed-label">原始句子:</p>
+      <p class="seed-text">{{ seedSentence }}</p>
+
+      <div class="divider"></div>
+
+      <p class="final-label">润色后:</p>
+      <p class="final-text">{{ polishedSentence }}</p>
+    </div>
+
+    <button class="restart-btn" @click="handleRestart">
+      🔄 开始新的扩写
+    </button>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  seedSentence: string;
+  polishedSentence: string;
+}>();
+
+const emit = defineEmits<{
+  restart: [];
+}>();
+
+function handleRestart() {
+  emit('restart');
+}
+</script>
+
+<style scoped>
+.final-result {
+  max-width: 700px;
+  margin: 2rem auto;
+  animation: slideUp 0.5s ease-out;
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.result-header {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 0.75rem;
+  margin-bottom: 1.5rem;
+}
+
+.icon {
+  font-size: 2rem;
+}
+
+.title {
+  margin: 0;
+  font-size: 1.75rem;
+  font-weight: 700;
+  background: linear-gradient(135deg, #ffc107, #ff9800);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.result-content {
+  background: linear-gradient(135deg, #fff9e6, #fff3cd);
+  padding: 2rem;
+  border-radius: 16px;
+  border: 3px solid #ffc107;
+  box-shadow: 0 4px 16px rgba(255, 193, 7, 0.3);
+  margin-bottom: 1.5rem;
+}
+
+.seed-label,
+.final-label {
+  margin: 0 0 0.5rem 0;
+  font-size: 0.85rem;
+  font-weight: 600;
+  color: #666;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.seed-text {
+  margin: 0 0 1rem 0;
+  color: #333;
+  line-height: 1.6;
+  font-size: 1rem;
+  font-style: italic;
+}
+
+.divider {
+  height: 2px;
+  background: linear-gradient(90deg, transparent, #ffc107, transparent);
+  margin: 1.5rem 0;
+}
+
+.final-text {
+  margin: 0;
+  color: #333;
+  line-height: 1.8;
+  font-size: 1.15rem;
+  font-weight: 500;
+}
+
+.restart-btn {
+  display: block;
+  width: 100%;
+  padding: 1rem 2rem;
+  background: linear-gradient(135deg, #28a745, #218843);
+  color: white;
+  border: none;
+  border-radius: 12px;
+  font-size: 1.1rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.restart-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4);
+}
+</style>

+ 186 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/MessageItem.vue

@@ -0,0 +1,186 @@
+<!--
+消息项组件
+统一显示所有类型的消息(记者提问、用户回答、语法点评、系统提示等)
+-->
+<template>
+  <div class="message-item" :class="messageTypeClass">
+    <div class="message-header">
+      <span class="message-avatar">{{ avatar }}</span>
+      <span class="message-sender">{{ senderName }}</span>
+      <span v-if="showStageBadge" class="stage-badge">{{ stageTitle }}</span>
+    </div>
+    <div class="message-content" :class="contentClass">
+      <p class="message-text">{{ message }}</p>
+      <div v-if="expandedSentence" class="expanded-section">
+        <span class="expanded-label">✨ 扩写结果</span>
+        <p class="expanded-text">{{ expandedSentence }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { Stage } from '../types/expand';
+
+const props = defineProps<{
+  type: 'question' | 'user' | 'evaluation' | 'system' | 'thinking';
+  message: string;
+  stage?: Stage;
+  expandedSentence?: string;
+}>();
+
+const avatar = computed(() => {
+  const avatars = {
+    question: '🎤',
+    user: '👤',
+    evaluation: '📝',
+    system: '🔔',
+    thinking: '💭'
+  };
+  return avatars[props.type];
+});
+
+const senderName = computed(() => {
+  const names = {
+    question: '记者',
+    user: '你',
+    evaluation: '语法点评',
+    system: '系统',
+    thinking: '正在思考'
+  };
+  return names[props.type];
+});
+
+const messageTypeClass = computed(() => `message-${props.type}`);
+
+const contentClass = computed(() => `content-${props.type}`);
+
+const showStageBadge = computed(() => props.type === 'question' && props.stage);
+
+const stageTitle = computed(() => {
+  if (!props.stage) return '';
+  const titles: Record<Stage, string> = {
+    stage1: '阶段 1',
+    stage2: '阶段 2',
+    stage3: '阶段 3',
+    done: '完成'
+  };
+  return titles[props.stage];
+});
+</script>
+
+<style scoped>
+.message-item {
+  margin-bottom: 1rem;
+  animation: slideIn 0.3s ease-out;
+}
+
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.message-header {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  margin-bottom: 0.5rem;
+}
+
+.message-avatar {
+  font-size: 1.25rem;
+}
+
+.message-sender {
+  font-weight: 600;
+  color: #333;
+  font-size: 0.9rem;
+}
+
+.stage-badge {
+  margin-left: auto;
+  padding: 0.25rem 0.75rem;
+  background: #e8f4ff;
+  color: #4a90e2;
+  border-radius: 12px;
+  font-size: 0.75rem;
+  font-weight: 600;
+}
+
+.message-content {
+  padding: 0.75rem 1rem;
+  border-radius: 12px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.message-text {
+  margin: 0;
+  color: #333;
+  line-height: 1.6;
+  font-size: 0.95rem;
+}
+
+/* 记者提问样式 */
+.message-question .message-content {
+  background: linear-gradient(135deg, #f8f9fa, #e9ecef);
+  border-bottom-left-radius: 4px;
+}
+
+/* 用户消息样式 */
+.message-user .message-content {
+  background: linear-gradient(135deg, #e3f2fd, #bbdefb);
+  border-bottom-right-radius: 4px;
+}
+
+/* 语法点评样式 */
+.message-evaluation .message-content {
+  background: linear-gradient(135deg, #fff9e6,  #fff3cd);
+  border-left: 4px solid #ffc107;
+}
+
+/* 系统消息样式 */
+.message-system .message-content {
+  background: #f8f9fa;
+  color: #666;
+  font-size: 0.85rem;
+}
+
+/* 思考提示样式 */
+.message-thinking .message-content {
+  background: #f0f4f8;
+  color: #666;
+  font-style: italic;
+}
+
+/* 扩写结果样式 */
+.expanded-section {
+  margin-top: 0.75rem;
+  padding-top: 0.75rem;
+  border-top: 1px dashed rgba(0, 0, 0, 0.1);
+}
+
+.expanded-label {
+  display: block;
+  font-size: 0.75rem;
+  font-weight: 600;
+  color: #4a90e2;
+  margin-bottom: 0.25rem;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.expanded-text {
+  margin: 0;
+  color: #2c3e50;
+  line-height: 1.6;
+  font-size: 0.95rem;
+  font-weight: 500;
+}
+</style>

+ 185 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/SeedInput.vue

@@ -0,0 +1,185 @@
+<!--
+种子句输入组件
+用户输入英文种子句并选择模式
+-->
+<template>
+  <div class="seed-input">
+    <h2 class="title">英语句子扩写智能体</h2>
+    <p class="subtitle">通过记者提问法将简单句子逐步扩写为高级长句</p>
+
+    <div class="input-group">
+      <textarea
+        v-model="inputSentence"
+        class="sentence-input"
+        placeholder="输入一个简单的英文句子,例如:I like reading."
+        rows="3"
+        @keydown.enter.prevent="handleSubmit"
+      />
+    </div>
+
+    <div class="mode-selector">
+      <button
+        class="mode-btn"
+        :class="{ active: selectedMode === 'manual' }"
+        @click="selectedMode = 'manual'"
+      >
+        📝 手动模式
+        <span class="mode-desc">逐步引导,逐阶段回答</span>
+      </button>
+      <button
+        class="mode-btn"
+        :class="{ active: selectedMode === 'auto' }"
+        @click="selectedMode = 'auto'"
+      >
+        🚀 自动模式
+        <span class="mode-desc">一键自动演示全过程</span>
+      </button>
+    </div>
+
+    <button
+      class="submit-btn"
+      :disabled="!inputSentence.trim() || loading"
+      @click="handleSubmit"
+    >
+      {{ loading ? '正在启动...' : '开始扩写' }}
+    </button>
+
+    <p v-if="error" class="error-message">{{ error }}</p>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { useSessionStore } from '../stores/session';
+import type { Mode } from '../types/expand';
+
+const emit = defineEmits<{
+  start: [sentence: string, mode: Mode];
+}>();
+
+const sessionStore = useSessionStore();
+
+const inputSentence = ref('');
+const selectedMode = ref<Mode>('manual');
+const loading = ref(false);
+const error = ref<string | null>(null);
+
+function handleSubmit() {
+  if (!inputSentence.value.trim()) {
+    error.value = '请输入一个英文句子';
+    return;
+  }
+
+  // 通过 emit 事件将数据传递给父组件,由父组件调用 sessionStore
+  emit('start', inputSentence.value.trim(), selectedMode.value);
+}
+</script>
+
+<style scoped>
+.seed-input {
+  max-width: 600px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.title {
+  font-size: 2rem;
+  font-weight: 700;
+  color: #1a1a1a;
+  margin-bottom: 0.5rem;
+}
+
+.subtitle {
+  font-size: 1rem;
+  color: #666;
+  margin-bottom: 2rem;
+}
+
+.input-group {
+  margin-bottom: 1.5rem;
+}
+
+.sentence-input {
+  width: 100%;
+  padding: 1rem;
+  font-size: 1.1rem;
+  border: 2px solid #e0e0e0;
+  border-radius: 12px;
+  resize: vertical;
+  font-family: inherit;
+  transition: border-color 0.3s;
+}
+
+.sentence-input:focus {
+  outline: none;
+  border-color: #4a90e2;
+}
+
+.mode-selector {
+  display: flex;
+  gap: 1rem;
+  margin-bottom: 1.5rem;
+  justify-content: center;
+}
+
+.mode-btn {
+  flex: 1;
+  padding: 1rem 1.5rem;
+  border: 2px solid #e0e0e0;
+  border-radius: 12px;
+  background: white;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.mode-btn:hover {
+  border-color: #4a90e2;
+  background: #f8f9fa;
+}
+
+.mode-btn.active {
+  border-color: #4a90e2;
+  background: #e8f4ff;
+  font-weight: 600;
+}
+
+.mode-desc {
+  font-size: 0.85rem;
+  color: #666;
+  font-weight: normal;
+}
+
+.submit-btn {
+  width: 100%;
+  padding: 1rem 2rem;
+  background: linear-gradient(135deg, #4a90e2, #357abd);
+  color: white;
+  border: none;
+  border-radius: 12px;
+  font-size: 1.1rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.submit-btn:hover:not(:disabled) {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(74, 144, 226, 0.4);
+}
+
+.submit-btn:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.error-message {
+  color: #e74c3c;
+  margin-top: 1rem;
+  font-size: 0.9rem;
+}
+</style>

+ 136 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/components/UserInput.vue

@@ -0,0 +1,136 @@
+<!--
+用户输入框组件
+用于手动模式下用户输入扩写的句子
+-->
+<template>
+  <div class="user-input">
+    <div class="input-header">
+      <span class="user-avatar">✍️</span>
+      <span class="user-name">你</span>
+    </div>
+    <div class="input-area">
+      <textarea
+        v-model="userSentence"
+        class="sentence-textarea"
+        placeholder="输入你的扩写句子..."
+        rows="3"
+        @keydown.enter.prevent="handleSubmit"
+      />
+      <button
+        class="submit-btn"
+        :disabled="!userSentence.trim() || loading"
+        @click="handleSubmit"
+      >
+        {{ loading ? '提交中...' : '提交' }}
+      </button>
+    </div>
+    <p v-if="error" class="error-message">{{ error }}</p>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+const emit = defineEmits<{
+  submit: [sentence: string];
+}>();
+
+const userSentence = ref('');
+const loading = ref(false);
+const error = ref<string | null>(null);
+
+async function handleSubmit() {
+  if (!userSentence.value.trim()) {
+    error.value = '请输入你的扩写句子';
+    return;
+  }
+
+  loading.value = true;
+  error.value = null;
+
+  try {
+    emit('submit', userSentence.value.trim());
+    userSentence.value = '';
+  } catch (e) {
+    error.value = e instanceof Error ? e.message : '提交失败,请重试';
+  } finally {
+    loading.value = false;
+  }
+}
+</script>
+
+<style scoped>
+.user-input {
+  margin-bottom: 1.5rem;
+}
+
+.input-header {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  margin-bottom: 0.5rem;
+}
+
+.user-avatar {
+  font-size: 1.5rem;
+}
+
+.user-name {
+  font-weight: 600;
+  color: #333;
+}
+
+.input-area {
+  background: white;
+  padding: 1rem;
+  border-radius: 16px;
+  border-bottom-right-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.sentence-textarea {
+  width: 100%;
+  padding: 0.75rem;
+  font-size: 1rem;
+  border: 2px solid #e0e0e0;
+  border-radius: 8px;
+  resize: vertical;
+  font-family: inherit;
+  margin-bottom: 0.75rem;
+  transition: border-color 0.3s;
+}
+
+.sentence-textarea:focus {
+  outline: none;
+  border-color: #4a90e2;
+}
+
+.submit-btn {
+  width: 100%;
+  padding: 0.75rem 1.5rem;
+  background: linear-gradient(135deg, #4a90e2, #357abd);
+  color: white;
+  border: none;
+  border-radius: 8px;
+  font-size: 1rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.submit-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
+}
+
+.submit-btn:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.error-message {
+  color: #e74c3c;
+  margin-top: 0.5rem;
+  font-size: 0.9rem;
+}
+</style>

+ 10 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/main.ts

@@ -0,0 +1,10 @@
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import App from './App.vue';
+import './style.css';
+
+const app = createApp(App);
+const pinia = createPinia();
+
+app.use(pinia);
+app.mount('#app');

+ 232 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/stores/session.ts

@@ -0,0 +1,232 @@
+/**
+ * 英语句子扩写智能体 - 会话状态管理
+ * 使用 Pinia 管理会话状态
+ */
+
+import { defineStore } from 'pinia';
+import { ref, computed } from 'vue';
+import type {
+  SessionState,
+  AgentResponse,
+  Mode,
+  Stage,
+  RoundRecord
+} from '../types/expand';
+import { startSession, submitSentence, getSession } from '../api/expand';
+
+export const useSessionStore = defineStore('session', () => {
+  // 状态
+  const session = ref<SessionState | null>(null);
+  const loading = ref(false);
+  const error = ref<string | null>(null);
+  const currentQuestion = ref<string | null>(null);
+
+  // 计算属性
+  const currentStage = computed<Stage>(() => session.value?.current_stage || 'stage1');
+  const mode = computed<Mode>(() => session.value?.mode || 'manual');
+  const seedSentence = computed(() => session.value?.seed_sentence || '');
+  const rounds = computed<RoundRecord[]>(() => session.value?.rounds || []);
+  const finalPolished = computed(() => session.value?.final_polished || null);
+  const isDone = computed(() => session.value?.current_stage === 'done');
+  const sessionId = computed(() => session.value?.session_id || '');
+
+  /**
+   * 开始新的会话
+   */
+  async function startNewSession(seedSentence: string, mode: Mode) {
+    loading.value = true;
+    error.value = null;
+
+    try {
+      session.value = {
+        session_id: "",
+        mode,
+        seed_sentence: seedSentence,
+        current_stage: null,
+        rounds: [],
+        final_polished: null,
+      };
+
+      const response = await startSession({
+        seed_sentence: seedSentence,
+        mode,
+      });
+
+      // 更新状态
+      session.value = {
+        session_id: response.session_id,
+        mode,
+        seed_sentence: seedSentence,
+        current_stage: response.stage,
+        rounds: [],
+        final_polished: response.final_polished || null,
+      };
+
+      // 保存当前提问
+      currentQuestion.value = response.question || null;
+    } catch (e) {
+      error.value = e instanceof Error ? e.message : 'Failed to start session';
+      throw e;
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  /**
+   * 提交用户句子(手动模式)
+   */
+  async function submitUserSentence(userSentence: string) {
+    if (!session.value) {
+      throw new Error('No active session');
+    }
+
+    loading.value = true;
+    error.value = null;
+
+    try {
+      const response = await submitSentence({
+        session_id: session.value.session_id,
+        user_sentence: userSentence,
+      });
+
+      // 直接从响应中获取信息,不调用 refreshSession
+      // 首先,需要手动更新会话状态
+      if (session.value && response.evaluation && response.expanded_sentence) {
+        // 创建新的 round 记录
+        const newRound = {
+          stage: session.value.current_stage,
+          question: currentQuestion.value || '',
+          user_answer: userSentence,
+          evaluation: response.evaluation,
+          expanded_sentence: response.expanded_sentence
+        };
+        
+        // 添加到 rounds 数组
+        session.value.rounds.push(newRound);
+        
+        // 更新当前阶段
+        if (response.stage) {
+          session.value.current_stage = response.stage;
+        }
+        
+        // 更新最终润色结果
+        if (response.final_polished) {
+          session.value.final_polished = response.final_polished;
+        }
+      }
+
+      // 保存当前提问
+      currentQuestion.value = response.question || null;
+    } catch (e) {
+      error.value = e instanceof Error ? e.message : 'Failed to submit sentence';
+      throw e;
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  /**
+   * 刷新会话状态
+   */
+  async function refreshSession() {
+    if (!session.value) {
+      throw new Error('No active session');
+    }
+
+    try {
+      const updatedSession = await getSession(session.value.session_id);
+      session.value = updatedSession;
+    } catch (e) {
+      error.value = e instanceof Error ? e.message : 'Failed to refresh session';
+      throw e;
+    }
+  }
+
+  /**
+   * 添加轮次记录(用于自动模式)
+   */
+  function addRound(round: RoundRecord) {
+    if (!session.value) {
+      throw new Error('No active session');
+    }
+    session.value.rounds.push(round);
+  }
+
+  /**
+   * 更新当前阶段
+   */
+  function updateStage(stage: Stage) {
+    if (!session.value) {
+      throw new Error('No active session');
+    }
+    session.value.current_stage = stage;
+  }
+
+  /**
+   * 设置最终润色版本
+   */
+  function setFinalPolished(polished: string) {
+    if (!session.value) {
+      throw new Error('No active session');
+    }
+    session.value.final_polished = polished;
+  }
+
+  /**
+   * 设置当前提问
+   */
+  function setCurrentQuestion(question: string | null) {
+    currentQuestion.value = question;
+  }
+
+  /**
+   * 清除会话
+   */
+  function clearSession() {
+    session.value = null;
+    currentQuestion.value = null;
+    error.value = null;
+    loading.value = false;
+  }
+
+  /**
+   * 获取当前阶段的标题
+   */
+  function getStageTitle(stage: Stage): string {
+    const titles: Record<Stage, string> = {
+      stage1: '第一阶段:添加时间与地点',
+      stage2: '第二阶段:添加人物与原因',
+      stage3: '第三阶段:添加方式与细节',
+      done: '完成',
+    };
+    return titles[stage];
+  }
+
+  return {
+    // 状态
+    session,
+    loading,
+    error,
+    currentQuestion,
+
+    // 计算属性
+    currentStage,
+    mode,
+    seedSentence,
+    rounds,
+    finalPolished,
+    isDone,
+    sessionId,
+
+    // 方法
+    startNewSession,
+    submitUserSentence,
+    refreshSession,
+    addRound,
+    updateStage,
+    setFinalPolished,
+    setCurrentQuestion,
+    clearSession,
+    getStageTitle,
+  };
+});

+ 18 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/style.css

@@ -0,0 +1,18 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}

+ 62 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/types/expand.ts

@@ -0,0 +1,62 @@
+/**
+ * 英语句子扩写智能体 - 类型定义
+ * 与后端 models/entities.py 对齐
+ */
+
+// 扩写阶段枚举
+export type Stage = 'stage1' | 'stage2' | 'stage3' | 'done';
+
+// 交互模式
+export type Mode = 'manual' | 'auto';
+
+// 单次扩写轮次记录
+export interface RoundRecord {
+  stage: Stage;
+  question: string;           // 记者提问
+  user_answer: string;        // 用户输入的句子
+  evaluation: string;         // 语法点评
+  expanded_sentence: string;  // 本轮扩写结果
+}
+
+// 整个会话状态
+export interface SessionState {
+  session_id: string;
+  mode: Mode;
+  seed_sentence: string;
+  current_stage: Stage | null;
+  rounds: RoundRecord[];
+  final_polished: string | null;
+}
+
+// 开始会话请求
+export interface StartRequest {
+  seed_sentence: string;
+  mode: Mode;
+}
+
+// 提交用户句子请求(手动模式)
+export interface SubmitRequest {
+  session_id: string;
+  user_sentence: string;
+}
+
+// 智能体单次响应
+export interface AgentResponse {
+  session_id: string;
+  stage: Stage;
+  question?: string;
+  evaluation?: string;
+  expanded_sentence?: string;
+  final_polished?: string;
+  is_done: boolean;
+}
+
+// SSE 事件类型
+export interface SSEEvent {
+  type: 'stage1' | 'stage2' | 'stage3' | 'polished' | 'analysis' | 'progress' | 'done' | 'error';
+  data?: any;  // data 的结构取决于 type
+  error?: {
+    detail: string;
+    type: string;
+  };
+}

+ 618 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/src/views/HomeView.vue

@@ -0,0 +1,618 @@
+<!--
+主视图组件
+整合所有子组件,实现完整的交互流程
+-->
+<template>
+  <div class="home-view">
+    <!-- 种子句输入区 -->
+    <SeedInput v-if="!sessionStore.session" @start="handleStart" />
+
+    <!-- 手动模式 -->
+    <div v-else-if="sessionStore.mode === 'manual'" class="manual-mode">
+      <div class="session-header">
+        <h2>手动模式</h2>
+        <p class="seed-display">原始句子:{{ sessionStore.seedSentence }}</p>
+        <button class="reset-btn" @click="handleReset">重新开始</button>
+      </div>
+
+      <!-- 消息列表区域 -->
+      <div class="message-list-container" ref="messageListContainer">
+        <!-- 统一的消息列表 -->
+        <MessageItem
+          v-for="(msg, index) in manualModeMessages"
+          :key="`msg-${index}`"
+          :type="msg.type"
+          :message="msg.message"
+          :stage="msg.stage"
+          :expanded-sentence="msg.expandedSentence"
+        />
+
+        <!-- 最终结果 -->
+        <FinalResult
+          v-if="sessionStore.isDone && sessionStore.finalPolished"
+          :seed-sentence="sessionStore.seedSentence"
+          :polished-sentence="sessionStore.finalPolished"
+          @restart="handleReset"
+        />
+      </div>
+
+      <!-- 用户输入框 -->
+      <div v-if="!sessionStore.isDone" class="input-container">
+        <UserInput
+          @submit="handleSubmit"
+        />
+      </div>
+    </div>
+
+    <!-- 自动模式 -->
+    <div v-else-if="sessionStore.mode === 'auto'" class="auto-mode">
+      <div class="session-header">
+        <h2>自动模式</h2>
+        <p class="seed-display">原始句子:{{ sessionStore.seedSentence }}</p>
+        <button class="reset-btn" @click="handleReset">重新开始</button>
+      </div>
+
+      <!-- 消息列表区域 -->
+      <div class="message-list-container" ref="autoMessageListContainer">
+        <!-- 统一的消息列表 -->
+        <MessageItem
+          v-for="(msg, index) in autoModeMessages"
+          :key="`auto-msg-${index}`"
+          :type="msg.type"
+          :message="msg.message"
+          :stage="msg.stage"
+          :expanded-sentence="msg.expandedSentence"
+        />
+
+        <!-- 加载指示器 -->
+        <div v-if="autoLoading" class="loading-indicator">
+          <div class="spinner"></div>
+          <p>{{ loadingText }}</p>
+        </div>
+
+        <!-- 最终结果 -->
+        <FinalResult
+          v-if="sessionStore.isDone && sessionStore.finalPolished"
+          :seed-sentence="sessionStore.seedSentence"
+          :polished-sentence="sessionStore.finalPolished"
+          @restart="handleReset"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onUnmounted, computed, nextTick } from 'vue';
+import { useSessionStore } from '../stores/session';
+import { subscribeAutoMode } from '../api/expand';
+import type { Mode, Stage, RoundRecord, SSEEvent } from '../types/expand';
+
+import SeedInput from '../components/SeedInput.vue';
+import UserInput from '../components/UserInput.vue';
+import FinalResult from '../components/FinalResult.vue';
+import MessageItem from '../components/MessageItem.vue';
+
+const sessionStore = useSessionStore();
+
+// 手动模式状态
+const currentUserMessage = ref<string | null>(null);
+const isThinking = ref(false);
+const messageListContainer = ref<HTMLElement | null>(null);
+
+// 自动模式状态
+const autoLoading = ref(false);
+const loadingText = ref('正在思考...');
+const autoMessageListContainer = ref<HTMLElement | null>(null);
+// 用于存储当前正在显示的临时消息(还未保存到 rounds 的)
+const autoTempMessages = ref<Array<{
+  type: 'question' | 'user' | 'evaluation' | 'system' | 'thinking';
+  message: string;
+  stage?: Stage;
+  expandedSentence?: string;
+}>>([]);
+const currentRoundData = ref<Partial<RoundRecord>>({});
+let cleanupSSE: (() => void) | null = null;
+
+// 计算属性:手动模式统一的消息列表
+const manualModeMessages = computed(() => {
+  const messages: Array<{
+    type: 'question' | 'user' | 'evaluation' | 'system' | 'thinking';
+    message: string;
+    stage?: Stage;
+    expandedSentence?: string;
+  }> = [];
+
+  messages.push({
+    type: 'user',
+    message: sessionStore.seedSentence
+  });
+
+  // 添加已完成的轮次消息
+  const roundsArray = sessionStore.rounds;
+  roundsArray.forEach((round) => {
+    // 记者提问
+    if (round.question) {
+      messages.push({
+        type: 'question',
+        message: round.question,
+        stage: round.stage
+      });
+    }
+
+    // 用户回答
+    messages.push({
+      type: 'user',
+      message: round.user_answer
+    });
+
+    // 语法点评
+    messages.push({
+      type: 'evaluation',
+      message: round.evaluation,
+      expandedSentence: round.user_answer !== round.expanded_sentence ? round.expanded_sentence : undefined
+    });
+  });
+
+  // 添加当前记者提问(如果有)
+  if (sessionStore.currentQuestion && !sessionStore.isDone) {
+    messages.push({
+      type: 'question',
+      message: sessionStore.currentQuestion,
+      stage: sessionStore.currentStage
+    });
+  }
+
+  // 添加当前用户消息(如果有)
+  if (currentUserMessage.value) {
+    messages.push({
+      type: 'user',
+      message: currentUserMessage.value
+    });
+  }
+
+  // 添加思考提示(如果有)
+  if (isThinking.value) {
+    messages.push({
+      type: 'thinking',
+      message: '正在思考...'
+    });
+  }
+
+  return messages;
+});
+
+// 计算属性:自动模式统一的消息列表
+const autoModeMessages = computed(() => {
+  const messages: Array<{
+    type: 'question' | 'user' | 'evaluation' | 'system' | 'thinking';
+    message: string;
+    stage?: Stage;
+    expandedSentence?: string;
+  }> = [];
+
+  // 添加种子句
+  messages.push({
+    type: 'user',
+    message: sessionStore.seedSentence
+  });
+
+  // 添加已完成的轮次消息
+  const roundsArray = sessionStore.rounds;
+  roundsArray.forEach((round) => {
+    // 记者提问
+    if (round.question) {
+      messages.push({
+        type: 'question',
+        message: round.question,
+        stage: round.stage
+      });
+    }
+
+    // AI回答
+    messages.push({
+      type: 'user',
+      message: round.user_answer
+    });
+
+    // 语法点评
+    messages.push({
+      type: 'evaluation',
+      message: round.evaluation,
+      expandedSentence: round.user_answer !== round.expanded_sentence ? round.expanded_sentence : undefined
+    });
+  });
+
+  // 添加临时消息(当前正在进行的阶段)
+  messages.push(...autoTempMessages.value);
+
+  return messages;
+});
+
+/**
+ * 开始新会话
+ */
+async function handleStart(sentence: string, mode: Mode) {
+  try {
+    if (mode == 'auto') {
+      autoLoading.value = true;
+      loadingText.value = '正在启动自动模式...';
+    } else {
+      isThinking.value = true;
+    }
+
+    await sessionStore.startNewSession(sentence, mode);
+
+    // 如果是自动模式,启动自动流程
+    if (mode === 'auto') {
+      startAutoMode();
+    } else {
+      isThinking.value = false;
+    }
+  } catch (error) {
+    console.error('Failed to start session:', error);
+    if (mode === 'auto') {
+      autoLoading.value = false;
+      loadingText.value = '连接失败,请重试';
+    } else {
+      isThinking.value = false;
+    }
+  }
+}
+
+/**
+ * 手动模式提交
+ */
+async function handleSubmit(sentence: string) {
+  // 立即显示用户消息
+  currentUserMessage.value = sentence;
+  isThinking.value = true;
+
+  // 滚动到底部
+  await nextTick();
+  scrollToBottom();
+
+  try {
+    // 提交到后端(submitUserSentence 内部已经会刷新会话状态)
+    await sessionStore.submitUserSentence(sentence);
+
+    // 清除当前用户消息(因为它已经在 rounds 中了)
+    currentUserMessage.value = null;
+    isThinking.value = false;
+
+    // 滚动到底部
+    await nextTick();
+    scrollToBottom();
+  } catch (error) {
+    console.error('Failed to submit sentence:', error);
+    isThinking.value = false;
+  }
+}
+
+/**
+ * 滚动消息列表到底部
+ */
+function scrollToBottom(containerRef: HTMLElement | null = messageListContainer.value) {
+  if (containerRef) {
+    containerRef.scrollTop = containerRef.scrollHeight;
+  }
+}
+
+/**
+ * 重置会话
+ */
+function handleReset() {
+  if (cleanupSSE) {
+    cleanupSSE();
+    cleanupSSE = null;
+  }
+  sessionStore.clearSession();
+  autoTempMessages.value = [];
+  currentRoundData.value = {};
+  autoLoading.value = false;
+}
+
+/**
+ * 启动自动模式
+ */
+function startAutoMode() {
+  loadingText.value = '正在思考...';
+  // 订阅 SSE 流
+  cleanupSSE = subscribeAutoMode(
+    sessionStore.sessionId,
+    handleSSEMessage,
+    handleSSEError,
+    handleSSEComplete
+  );
+}
+
+/**
+ * 处理 SSE 消息
+ */
+function handleSSEMessage(event: SSEEvent) {
+  if (!event.type || !event.data) {
+    console.warn('Invalid SSE event format:', event);
+    return;
+  }
+
+  const eventType = event.type;
+  const eventData = event.data as any;
+
+  switch (eventType) {
+    case 'stage1':
+    case 'stage2':
+    case 'stage3':
+      // 处理阶段事件
+      const stage = eventType as Stage;
+
+      if (eventData.question) {
+        // 显示提问 - 添加到临时消息
+        autoTempMessages.value = [
+          {
+            type: 'question',
+            message: eventData.question,
+            stage: stage
+          }
+        ];
+        // 保存到当前轮次数据
+        currentRoundData.value.question = eventData.question;
+        loadingText.value = `阶段 ${eventType.slice(-1)}:记者正在提问...`;
+        
+        // 滚动到底部
+        nextTick(() => {
+          scrollToBottom(autoMessageListContainer.value);
+        });
+      }
+
+      if (eventData.expanded) {
+        // 扩写完成,先添加临时消息显示AI回答和点评
+        autoTempMessages.value = [
+          {
+            type: 'question',
+            message: currentRoundData.value.question || `请为这个句子增加细节`,
+            stage: stage
+          },
+          {
+            type: 'user',
+            message: eventData.expanded
+          },
+          {
+            type: 'evaluation',
+            message: '自动模式生成,语法正确',
+            expandedSentence: eventData.expanded
+          }
+        ];
+
+        // 保存轮次数据
+        if (!currentRoundData.value.question) {
+          const defaultQuestions = {
+            stage1: '请为这个句子增加一些细节',
+            stage2: '请为这个句子增加时间或地点信息',
+            stage3: '请为这个句子增加定语从句或状语从句',
+            done: '',
+          };
+          currentRoundData.value.question = defaultQuestions[stage];
+        }
+
+        currentRoundData.value.stage = stage;
+        currentRoundData.value.user_answer = eventData.expanded;
+        currentRoundData.value.evaluation = '自动模式生成,语法正确';
+        currentRoundData.value.expanded_sentence = eventData.expanded;
+
+        // 保存轮次到 sessionStore
+        const round: RoundRecord = {
+          stage,
+          question: currentRoundData.value.question,
+          user_answer: currentRoundData.value.user_answer ?? '',
+          evaluation: currentRoundData.value.evaluation,
+          expanded_sentence: currentRoundData.value.expanded_sentence ?? '',
+        };
+        sessionStore.addRound(round);
+
+        // 清空临时消息和当前轮次数据,准备下一阶段
+        autoTempMessages.value = [];
+        currentRoundData.value = {};
+        
+        loadingText.value = `阶段 ${eventType.slice(-1)}:扩写完成`;
+        
+        // 滚动到底部
+        nextTick(() => {
+          scrollToBottom(autoMessageListContainer.value);
+        });
+      }
+      break;
+
+    case 'polished':
+      // 处理润色版本
+      if (eventData.sentence) {
+        sessionStore.setFinalPolished(eventData.sentence);
+        loadingText.value = '正在生成最终润色版本...';
+        // 更新阶段为完成
+        sessionStore.updateStage('done');
+      }
+      break;
+
+    case 'analysis':
+      // 处理结构分析
+      console.log('Structure analysis:', eventData.items);
+      loadingText.value = '正在分析句子结构...';
+      break;
+
+    case 'progress':
+      // 处理进度更新
+      if (eventData.message) {
+        loadingText.value = eventData.message;
+      }
+      break;
+
+    case 'done':
+      // 完成事件
+      console.log('Auto mode completed with data:', eventData);
+      loadingText.value = '完成!';
+
+      // 如果 done 事件包含完整数据,可以更新 sessionStore
+      if (eventData.stage1 || eventData.stage2 || eventData.stage3) {
+        // 确保所有轮次都已保存
+        ['stage1', 'stage2', 'stage3'].forEach((stageName, index) => {
+          const stageData = eventData[stageName as keyof typeof eventData];
+          if (stageData && sessionStore.rounds.length <= index) {
+            const round: RoundRecord = {
+              stage: stageName as Stage,
+              question: stageData.question || '请扩写这个句子',
+              'user_answer': stageData.expanded || sessionStore.seedSentence,
+              evaluation: '自动模式生成,语法正确',
+              'expanded_sentence': stageData.expanded || sessionStore.seedSentence
+            };
+            sessionStore.addRound(round);
+          }
+        });
+
+        // 设置最终润色版本
+        if (eventData.polished) {
+          sessionStore.setFinalPolished(eventData.polished);
+        }
+      }
+
+      // 标记会话完成
+      sessionStore.updateStage('done');
+      // 清空临时消息
+      autoTempMessages.value = [];
+      break;
+
+    default:
+      console.warn('Unknown SSE event type:', eventType);
+  }
+}
+
+/**
+ * 处理 SSE 错误
+ */
+function handleSSEError(error: Error) {
+  console.error('SSE Error:', error);
+  autoLoading.value = false;
+  loadingText.value = '连接失败,请重试';
+}
+
+/**
+ * 处理 SSE 完成
+ */
+async function handleSSEComplete() {
+  console.log('SSE Stream completed');
+  autoLoading.value = false;
+  loadingText.value = '完成!';
+
+  // 注意:不调用 refreshSession(),避免覆盖前端维护的状态
+  // 自动模式的数据已经通过 SSE 事件在前端维护好了
+  // currentStreamQuestion 和 currentStreamEvaluation 保持原样
+  // sessionStore.rounds 中的所有轮次数据都会保留
+}
+
+// 清理
+onUnmounted(() => {
+  if (cleanupSSE) {
+    cleanupSSE();
+  }
+});
+</script>
+
+<style scoped>
+.home-view {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  padding: 2rem;
+}
+
+.session-header {
+  max-width: 800px;
+  margin: 0 auto 2rem;
+  padding: 1.5rem;
+  background: white;
+  border-radius: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.session-header h2 {
+  margin: 0 0 0.5rem 0;
+  font-size: 1.5rem;
+  color: #333;
+}
+
+.seed-display {
+  margin: 0;
+  color: #666;
+  font-size: 0.95rem;
+}
+
+.reset-btn {
+  margin-top: 1rem;
+  padding: 0.5rem 1rem;
+  background: #6c757d;
+  color: white;
+  border: none;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.reset-btn:hover {
+  background: #5a6268;
+}
+
+.manual-mode,
+.auto-mode {
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.message-list-container {
+  background: white;
+  border-radius: 16px;
+  padding: 1.5rem;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  min-height: 400px;
+  max-height: 60vh;
+  overflow-y: auto;
+  margin-bottom: 1rem;
+}
+
+.input-container {
+  background: white;
+  border-radius: 16px;
+  padding: 1rem;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  position: sticky;
+  bottom: 0;
+}
+
+.loading-indicator {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 1rem;
+  padding: 2rem;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid #f3f3f3;
+  border-top: 4px solid #4a90e2;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.loading-indicator p {
+  margin: 0;
+  color: #666;
+  font-size: 0.95rem;
+}
+</style>

+ 31 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/tsconfig.json

@@ -0,0 +1,31 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "preserve",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+
+    /* Path alias */
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 10 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/tsconfig.node.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 21 - 0
Co-creation-projects/xujikai-SentenceExpandAgent/frontend/vite.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import { fileURLToPath, URL } from 'node:url';
+
+export default defineConfig({
+  plugins: [vue()],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
+    },
+  },
+  server: {
+    port: 3000,
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8000',
+        changeOrigin: true,
+      },
+    },
+  },
+});