From 7eaef043f2bd570c69169bb3d928e9f502e630b2 Mon Sep 17 00:00:00 2001 From: "rui.yang" Date: Wed, 11 Mar 2026 01:30:11 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=9C=AC=E5=9C=B0=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mini_agent/cli.py | 37 +++++++++++++++++++----------- mini_agent/config.py | 5 ++-- mini_agent/llm/anthropic_client.py | 14 +++++------ mini_agent/llm/base.py | 10 ++++---- mini_agent/llm/llm_wrapper.py | 5 ++-- mini_agent/llm/openai_client.py | 12 +++++----- mini_agent/logger.py | 14 +++++------ mini_agent/retry.py | 8 +++---- mini_agent/schema/schema.py | 18 +++++++-------- mini_agent/tools/base.py | 4 ++-- mini_agent/tools/bash_tool.py | 16 ++++++------- mini_agent/tools/file_tools.py | 4 ++-- 12 files changed, 79 insertions(+), 68 deletions(-) diff --git a/mini_agent/cli.py b/mini_agent/cli.py index f060c9c..1d5446b 100644 --- a/mini_agent/cli.py +++ b/mini_agent/cli.py @@ -34,9 +34,15 @@ from mini_agent.tools.base import Tool from mini_agent.tools.bash_tool import BashKillTool, BashOutputTool, BashTool from mini_agent.tools.file_tools import EditTool, ReadTool, WriteTool -from mini_agent.tools.mcp_loader import cleanup_mcp_connections, load_mcp_tools_async, set_mcp_timeout_config from mini_agent.tools.note_tool import SessionNoteTool from mini_agent.tools.skill_tool import create_skill_tools + +# Try to import MCP tools, but don't fail if they're not available +try: + from mini_agent.tools.mcp_loader import cleanup_mcp_connections, load_mcp_tools_async, set_mcp_timeout_config + MCP_AVAILABLE = True +except ImportError: + MCP_AVAILABLE = False from mini_agent.utils import calculate_display_width @@ -398,7 +404,7 @@ async def initialize_base_tools(config: Config): print(f"{Colors.YELLOW}⚠️ Failed to load Skills: {e}{Colors.RESET}") # 4. MCP tools (loaded with priority search) - if config.tools.enable_mcp: + if config.tools.enable_mcp and MCP_AVAILABLE: print(f"{Colors.BRIGHT_CYAN}Loading MCP tools...{Colors.RESET}") try: # Apply MCP timeout configuration from config.yaml @@ -426,6 +432,8 @@ async def initialize_base_tools(config: Config): print(f"{Colors.YELLOW}⚠️ MCP config file not found: {config.tools.mcp_config_path}{Colors.RESET}") except Exception as e: print(f"{Colors.YELLOW}⚠️ Failed to load MCP tools: {e}{Colors.RESET}") + elif config.tools.enable_mcp: + print(f"{Colors.YELLOW}⚠️ MCP tools not available (mcp module not installed){Colors.RESET}") print() # Empty line separator return tools, skill_loader @@ -469,18 +477,19 @@ def add_workspace_tools(tools: List[Tool], config: Config, workspace_dir: Path): async def _quiet_cleanup(): """Clean up MCP connections, suppressing noisy asyncgen teardown tracebacks.""" - # Silence the asyncgen finalization noise that anyio/mcp emits when - # stdio_client's task group is torn down across tasks. The handler is - # intentionally NOT restored: asyncgen finalization happens during - # asyncio.run() shutdown (after run_agent returns), so restoring the - # handler here would still let the noise through. Since this runs - # right before process exit, swallowing late exceptions is safe. - loop = asyncio.get_event_loop() - loop.set_exception_handler(lambda _loop, _ctx: None) - try: - await cleanup_mcp_connections() - except Exception: - pass + if MCP_AVAILABLE: + # Silence the asyncgen finalization noise that anyio/mcp emits when + # stdio_client's task group is torn down across tasks. The handler is + # intentionally NOT restored: asyncgen finalization happens during + # asyncio.run() shutdown (after run_agent returns), so restoring the + # handler here would still let the noise through. Since this runs + # right before process exit, swallowing late exceptions is safe. + loop = asyncio.get_event_loop() + loop.set_exception_handler(lambda _loop, _ctx: None) + try: + await cleanup_mcp_connections() + except Exception: + pass async def run_agent(workspace_dir: Path, task: str = None): diff --git a/mini_agent/config.py b/mini_agent/config.py index bab78f0..03a7a08 100644 --- a/mini_agent/config.py +++ b/mini_agent/config.py @@ -4,6 +4,7 @@ """ from pathlib import Path +from typing import Union, Optional import yaml from pydantic import BaseModel, Field @@ -79,7 +80,7 @@ def load(cls) -> "Config": return cls.from_yaml(config_path) @classmethod - def from_yaml(cls, config_path: str | Path) -> "Config": + def from_yaml(cls, config_path: Union[str, Path]) -> "Config": """Load configuration from YAML file Args: @@ -174,7 +175,7 @@ def get_package_dir() -> Path: return Path(__file__).parent @classmethod - def find_config_file(cls, filename: str) -> Path | None: + def find_config_file(cls, filename: str) -> Optional[Path]: """Find configuration file with priority order Search for config file in the following order of priority: diff --git a/mini_agent/llm/anthropic_client.py b/mini_agent/llm/anthropic_client.py index 6baf994..8984166 100644 --- a/mini_agent/llm/anthropic_client.py +++ b/mini_agent/llm/anthropic_client.py @@ -1,7 +1,7 @@ """Anthropic LLM client implementation.""" import logging -from typing import Any +from typing import Any, Optional, Union import anthropic @@ -26,7 +26,7 @@ def __init__( api_key: str, api_base: str = "https://api.minimaxi.com/anthropic", model: str = "MiniMax-M2.5", - retry_config: RetryConfig | None = None, + retry_config: Optional[RetryConfig] = None, ): """Initialize Anthropic client. @@ -47,9 +47,9 @@ def __init__( async def _make_api_request( self, - system_message: str | None, + system_message: Optional[str], api_messages: list[dict[str, Any]], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> anthropic.types.Message: """Execute API request (core method that can be retried). @@ -111,7 +111,7 @@ def _convert_tools(self, tools: list[Any]) -> list[dict[str, Any]]: raise TypeError(f"Unsupported tool type: {type(tool)}") return result - def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[dict[str, Any]]]: + def _convert_messages(self, messages: list[Message]) -> tuple[Optional[str], list[dict[str, Any]]]: """Convert internal messages to Anthropic format. Args: @@ -180,7 +180,7 @@ def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[d def _prepare_request( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> dict[str, Any]: """Prepare the request for Anthropic API. @@ -257,7 +257,7 @@ def _parse_response(self, response: anthropic.types.Message) -> LLMResponse: async def generate( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> LLMResponse: """Generate response from Anthropic LLM. diff --git a/mini_agent/llm/base.py b/mini_agent/llm/base.py index 19892a8..97edd68 100644 --- a/mini_agent/llm/base.py +++ b/mini_agent/llm/base.py @@ -1,7 +1,7 @@ """Base class for LLM clients.""" from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Optional, Union from ..retry import RetryConfig from ..schema import LLMResponse, Message @@ -19,7 +19,7 @@ def __init__( api_key: str, api_base: str, model: str, - retry_config: RetryConfig | None = None, + retry_config: Optional[RetryConfig] = None, ): """Initialize the LLM client. @@ -41,7 +41,7 @@ def __init__( async def generate( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> LLMResponse: """Generate response from LLM. @@ -58,7 +58,7 @@ async def generate( def _prepare_request( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> dict[str, Any]: """Prepare the request payload for the API. @@ -72,7 +72,7 @@ def _prepare_request( pass @abstractmethod - def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[dict[str, Any]]]: + def _convert_messages(self, messages: list[Message]) -> tuple[Optional[str], list[dict[str, Any]]]: """Convert internal message format to API-specific format. Args: diff --git a/mini_agent/llm/llm_wrapper.py b/mini_agent/llm/llm_wrapper.py index 28d2c8b..c8d3a0a 100644 --- a/mini_agent/llm/llm_wrapper.py +++ b/mini_agent/llm/llm_wrapper.py @@ -5,6 +5,7 @@ """ import logging +from typing import Optional, Any from ..retry import RetryConfig from ..schema import LLMProvider, LLMResponse, Message @@ -39,7 +40,7 @@ def __init__( provider: LLMProvider = LLMProvider.ANTHROPIC, api_base: str = "https://api.minimaxi.com", model: str = "MiniMax-M2.5", - retry_config: RetryConfig | None = None, + retry_config: Optional[RetryConfig] = None, ): """Initialize LLM client with specified provider. @@ -113,7 +114,7 @@ def retry_callback(self, value): async def generate( self, messages: list[Message], - tools: list | None = None, + tools: Optional[list[Any]] = None, ) -> LLMResponse: """Generate response from LLM. diff --git a/mini_agent/llm/openai_client.py b/mini_agent/llm/openai_client.py index a30fc19..597debf 100644 --- a/mini_agent/llm/openai_client.py +++ b/mini_agent/llm/openai_client.py @@ -2,7 +2,7 @@ import json import logging -from typing import Any +from typing import Any, Optional, Union from openai import AsyncOpenAI @@ -27,7 +27,7 @@ def __init__( api_key: str, api_base: str = "https://api.minimaxi.com/v1", model: str = "MiniMax-M2.5", - retry_config: RetryConfig | None = None, + retry_config: Optional[RetryConfig] = None, ): """Initialize OpenAI client. @@ -48,7 +48,7 @@ def __init__( async def _make_api_request( self, api_messages: list[dict[str, Any]], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> Any: """Execute API request (core method that can be retried). @@ -111,7 +111,7 @@ def _convert_tools(self, tools: list[Any]) -> list[dict[str, Any]]: raise TypeError(f"Unsupported tool type: {type(tool)}") return result - def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[dict[str, Any]]]: + def _convert_messages(self, messages: list[Message]) -> tuple[Optional[str], list[dict[str, Any]]]: """Convert internal messages to OpenAI format. Args: @@ -182,7 +182,7 @@ def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[d def _prepare_request( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> dict[str, Any]: """Prepare the request for OpenAI API. @@ -261,7 +261,7 @@ def _parse_response(self, response: Any) -> LLMResponse: async def generate( self, messages: list[Message], - tools: list[Any] | None = None, + tools: Optional[list[Any]] = None, ) -> LLMResponse: """Generate response from OpenAI LLM. diff --git a/mini_agent/logger.py b/mini_agent/logger.py index 220648f..f5f7373 100644 --- a/mini_agent/logger.py +++ b/mini_agent/logger.py @@ -3,7 +3,7 @@ import json from datetime import datetime from pathlib import Path -from typing import Any +from typing import Any, Optional from .schema import Message, ToolCall @@ -40,7 +40,7 @@ def start_new_run(self): f.write(f"Agent Run Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write("=" * 80 + "\n\n") - def log_request(self, messages: list[Message], tools: list[Any] | None = None): + def log_request(self, messages: list[Message], tools: Optional[list[Any]] = None): """Log LLM request Args: @@ -85,9 +85,9 @@ def log_request(self, messages: list[Message], tools: list[Any] | None = None): def log_response( self, content: str, - thinking: str | None = None, - tool_calls: list[ToolCall] | None = None, - finish_reason: str | None = None, + thinking: Optional[str] = None, + tool_calls: Optional[list[ToolCall]] = None, + finish_reason: Optional[str] = None, ): """Log LLM response @@ -124,8 +124,8 @@ def log_tool_result( tool_name: str, arguments: dict[str, Any], result_success: bool, - result_content: str | None = None, - result_error: str | None = None, + result_content: Optional[str] = None, + result_error: Optional[str] = None, ): """Log tool execution result diff --git a/mini_agent/retry.py b/mini_agent/retry.py index 8b5f4e2..0e36b2b 100644 --- a/mini_agent/retry.py +++ b/mini_agent/retry.py @@ -13,7 +13,7 @@ import asyncio import functools import logging -from typing import Any, Callable, Type, TypeVar +from typing import Any, Callable, Type, TypeVar, Optional logger = logging.getLogger(__name__) @@ -71,8 +71,8 @@ def __init__(self, last_exception: Exception, attempts: int): def async_retry( - config: RetryConfig | None = None, - on_retry: Callable[[Exception, int], None] | None = None, + config: Optional[RetryConfig] = None, + on_retry: Optional[Callable[[Exception, int], None]] = None, ) -> Callable: """Async function retry decorator @@ -97,7 +97,7 @@ async def call_api(): def decorator(func: Callable[..., Any]) -> Callable[..., Any]: @functools.wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: - last_exception: Exception | None = None + last_exception: Optional[Exception] = None for attempt in range(config.max_retries + 1): try: diff --git a/mini_agent/schema/schema.py b/mini_agent/schema/schema.py index 4bffb44..c8ef022 100644 --- a/mini_agent/schema/schema.py +++ b/mini_agent/schema/schema.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any +from typing import Any, Union, Optional from pydantic import BaseModel @@ -30,11 +30,11 @@ class Message(BaseModel): """Chat message.""" role: str # "system", "user", "assistant", "tool" - content: str | list[dict[str, Any]] # Can be string or list of content blocks - thinking: str | None = None # Extended thinking content for assistant messages - tool_calls: list[ToolCall] | None = None - tool_call_id: str | None = None - name: str | None = None # For tool role + content: Union[str, list[dict[str, Any]]] # Can be string or list of content blocks + thinking: Optional[str] = None # Extended thinking content for assistant messages + tool_calls: Optional[list[ToolCall]] = None + tool_call_id: Optional[str] = None + name: Optional[str] = None # For tool role class TokenUsage(BaseModel): @@ -49,7 +49,7 @@ class LLMResponse(BaseModel): """LLM response.""" content: str - thinking: str | None = None # Extended thinking blocks - tool_calls: list[ToolCall] | None = None + thinking: Optional[str] = None # Extended thinking blocks + tool_calls: Optional[list[ToolCall]] = None finish_reason: str - usage: TokenUsage | None = None # Token usage from API response + usage: Optional[TokenUsage] = None # Token usage from API response diff --git a/mini_agent/tools/base.py b/mini_agent/tools/base.py index 1bfcd25..7cc2f45 100644 --- a/mini_agent/tools/base.py +++ b/mini_agent/tools/base.py @@ -1,6 +1,6 @@ """Base tool classes.""" -from typing import Any +from typing import Any, Optional, Union from pydantic import BaseModel @@ -10,7 +10,7 @@ class ToolResult(BaseModel): success: bool content: str = "" - error: str | None = None + error: Optional[str] = None class Tool: diff --git a/mini_agent/tools/bash_tool.py b/mini_agent/tools/bash_tool.py index 47375ff..f0087bd 100644 --- a/mini_agent/tools/bash_tool.py +++ b/mini_agent/tools/bash_tool.py @@ -8,7 +8,7 @@ import re import time import uuid -from typing import Any +from typing import Any, Optional from pydantic import Field, model_validator @@ -27,7 +27,7 @@ class BashOutputResult(ToolResult): stdout: str = Field(description="The command's standard output") stderr: str = Field(description="The command's standard error output") exit_code: int = Field(description="The command's exit code") - bash_id: str | None = Field(default=None, description="Shell process ID (only when run_in_background=True)") + bash_id: Optional[str] = Field(default=None, description="Shell process ID (only when run_in_background=True)") @model_validator(mode="after") def format_content(self) -> "BashOutputResult": @@ -64,13 +64,13 @@ def __init__(self, bash_id: str, command: str, process: "asyncio.subprocess.Proc self.output_lines: list[str] = [] self.last_read_index = 0 self.status = "running" - self.exit_code: int | None = None + self.exit_code: Optional[int] = None def add_output(self, line: str): """Add new output line.""" self.output_lines.append(line) - def get_new_output(self, filter_pattern: str | None = None) -> list[str]: + def get_new_output(self, filter_pattern: Optional[str] = None) -> list[str]: """Get new output since last check, optionally filtered by regex.""" new_lines = self.output_lines[self.last_read_index :] self.last_read_index = len(self.output_lines) @@ -85,7 +85,7 @@ def get_new_output(self, filter_pattern: str | None = None) -> list[str]: return new_lines - def update_status(self, is_alive: bool, exit_code: int | None = None): + def update_status(self, is_alive: bool, exit_code: Optional[int] = None): """Update process status.""" if not is_alive: self.status = "completed" if exit_code == 0 else "failed" @@ -117,7 +117,7 @@ def add(cls, shell: BackgroundShell) -> None: cls._shells[shell.bash_id] = shell @classmethod - def get(cls, bash_id: str) -> BackgroundShell | None: + def get(cls, bash_id: str) -> Optional[BackgroundShell]: """Get a background shell by ID.""" return cls._shells.get(bash_id) @@ -222,7 +222,7 @@ class BashTool(Tool): - Unix/Linux/macOS: bash """ - def __init__(self, workspace_dir: str | None = None): + def __init__(self, workspace_dir: Optional[str] = None): """Initialize BashTool with OS-specific shell detection. Args: @@ -485,7 +485,7 @@ def parameters(self) -> dict[str, Any]: async def execute( self, bash_id: str, - filter_str: str | None = None, + filter_str: Optional[str] = None, ) -> BashOutputResult: """Retrieve output from background shell. diff --git a/mini_agent/tools/file_tools.py b/mini_agent/tools/file_tools.py index 74b7eee..86d6544 100644 --- a/mini_agent/tools/file_tools.py +++ b/mini_agent/tools/file_tools.py @@ -1,7 +1,7 @@ """File operation tools.""" from pathlib import Path -from typing import Any +from typing import Any, Optional import tiktoken @@ -105,7 +105,7 @@ def parameters(self) -> dict[str, Any]: "required": ["path"], } - async def execute(self, path: str, offset: int | None = None, limit: int | None = None) -> ToolResult: + async def execute(self, path: str, offset: Optional[int] = None, limit: Optional[int] = None) -> ToolResult: """Execute read file.""" try: file_path = Path(path) From 00f558be2f225c395626ed09e324de6b90fb3d43 Mon Sep 17 00:00:00 2001 From: "rui.yang" Date: Wed, 11 Mar 2026 11:42:38 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=BC=8F=20UI=20=E6=8A=80=E8=83=BD=20&=20skill=20?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E8=B0=83=E5=BA=A6=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mini_agent/cli.py | 15 + mini_agent/skills/generative-ui/SKILL.md | 204 +++++++ mini_agent/skills/generative-ui/_meta.json | 6 + .../skills/generative-ui/examples/README.md | 252 ++++++++ .../generative-ui/examples/game-2048.html | 429 +++++++++++++ .../examples/pomodoro-timer.html | 566 ++++++++++++++++++ tarot-game/SPEC.md | 110 ++++ 7 files changed, 1582 insertions(+) create mode 100644 mini_agent/skills/generative-ui/SKILL.md create mode 100644 mini_agent/skills/generative-ui/_meta.json create mode 100644 mini_agent/skills/generative-ui/examples/README.md create mode 100644 mini_agent/skills/generative-ui/examples/game-2048.html create mode 100644 mini_agent/skills/generative-ui/examples/pomodoro-timer.html create mode 100644 tarot-game/SPEC.md diff --git a/mini_agent/cli.py b/mini_agent/cli.py index 1d5446b..d2b1423 100644 --- a/mini_agent/cli.py +++ b/mini_agent/cli.py @@ -199,6 +199,7 @@ def print_help(): help_text = f""" {Colors.BOLD}{Colors.BRIGHT_YELLOW}Available Commands:{Colors.RESET} {Colors.BRIGHT_GREEN}/help{Colors.RESET} - Show this help message + {Colors.BRIGHT_GREEN}/skills{Colors.RESET} - List all installed skills {Colors.BRIGHT_GREEN}/clear{Colors.RESET} - Clear session history (keep system prompt) {Colors.BRIGHT_GREEN}/history{Colors.RESET} - Show current session message count {Colors.BRIGHT_GREEN}/stats{Colors.RESET} - Show session statistics @@ -714,6 +715,20 @@ def _(event): print_help() continue + elif command == "/skills": + # List all installed skills + if skill_loader and skill_loader.loaded_skills: + skills = sorted(skill_loader.loaded_skills.keys()) + print(f"\n{Colors.BRIGHT_CYAN}📦 Installed Skills ({len(skills)}):{Colors.RESET}\n") + for i, skill_name in enumerate(skills, 1): + skill = skill_loader.loaded_skills[skill_name] + desc = skill.description[:60] if skill.description else "No description" + print(f" {i}. {Colors.BRIGHT_GREEN}{skill_name}{Colors.RESET} - {desc}") + print(f"\n{Colors.DIM}Use get_skill(skill_name) to load a skill{Colors.RESET}\n") + else: + print(f"\n{Colors.YELLOW}⚠️ No skills loaded{Colors.RESET}\n") + continue + elif command == "/clear": # Clear message history but keep system prompt old_count = len(agent.messages) diff --git a/mini_agent/skills/generative-ui/SKILL.md b/mini_agent/skills/generative-ui/SKILL.md new file mode 100644 index 0000000..7e38c82 --- /dev/null +++ b/mini_agent/skills/generative-ui/SKILL.md @@ -0,0 +1,204 @@ +--- +name: generative-ui +description: "生成式 UI 专家。基于 Google 论文《Generative UI》,实现模型动态生成完整交互式 UI。核心架构:Tool Access + System Instructions + Post-processing + 直接渲染。" +metadata: {"clawdbot":{"emoji":"🎨","requires":{"bins":["node"]}}} +--- + +# 生成式 UI 专家技能 (Generative UI Expert) + +基于 Google 论文《Generative UI: LLMs are Effective UI Generators》实现。 + +## 论文核心发现 + +| 指标 | 数据 | +|------|------| +| 用户偏好 Generative UI vs Markdown | **82.8%** | +| 与人类专家相当的案例 | **44%** | +| 零错误率(最新模型) | **Gemini 3: 0%** | + +--- + +## 核心架构 (Three Additions) + +``` +用户提示词 + ↓ +┌─────────────────────────────────────────┐ +│ 1. Tool Access │ +│ - 图像生成 (DALL-E, Midjourney) │ +│ - 网页搜索 (实时信息获取) │ +│ - 代码执行验证 │ +│ → 结果可给模型 或 直接发给用户 │ +├─────────────────────────────────────────┤ +│ 2. System Instructions │ +│ - Goal (目标) │ +│ - Planning (规划思考) │ +│ - Examples (示例) │ +│ - Technical Specs (技术规格) │ +├─────────────────────────────────────────┤ +│ 3. Post-processing │ +│ - 语法修正 │ +│ - 安全过滤 │ +│ - 错误检测 │ +└─────────────────────────────────────────┘ + ↓ +Code Artifact (完整网页) + ↓ +直接渲染到用户浏览器 +``` + +### 1. Tool Access(工具访问) +- **图像生成**: 创建配图、图标、背景图 +- **网页搜索**: 获取实时信息提升质量 +- **结果路由**: + - 发给模型 → 提升生成质量 + - 直发浏览器 → 提高效率 + +### 2. System Instructions(系统指令) +详细指令包含四部分: + +**Goal**: +``` +创建一个[应用类型],实现[核心功能],满足用户需求。 +``` + +**Planning**: +``` +1. 分析用户需求和目标 +2. 设计 UI 结构和交互流程 +3. 规划数据流和状态管理 +4. 考虑边界情况和错误处理 +``` + +**Examples**: +``` +参考模式: +- 番茄钟:倒计时 + 控制按钮 + 统计面板 +- 2048:网格布局 + 滑动手势 + 分数系统 +- 天气:卡片展示 + 动态图标 + 预报列表 +``` + +**Technical Specs**: +``` +输出格式: 单 HTML 文件(推荐)或 React 组件 +样式: CSS-in-JS 或内联样式,使用 CSS 变量 +交互: 事件驱动,响应式设计 +性能: 首屏加载 < 1s +``` + +### 3. Post-processing(后处理) +- **JS 语法检查**: 确保无语法错误 +- **XSS 安全过滤**: 移除危险代码 +- **HTML/CSS 修正**: 修复常见格式问题 +- **资源验证**: 检查外部链接有效性 + +--- + +## 核心能力 + +### 1. Code Artifacts - 直接生成完整 UI +- **完整网页**: HTML + CSS + JS 一次性生成 +- **直接渲染**: 在浏览器中直接运行,非仅返回代码 +- **可交互**: 用户可直接使用,无需二次开发 + +### 2. 工具增强生成 +- 必要时调用图像生成丰富界面 +- 搜索实时信息融入应用 +- 验证代码功能确保可用 + +### 3. 多风格支持 +- **Classic**: 经典专业风格 +- **Wizard Green**: 绿色魔法风格 +- 模型会自动适配元素风格 + +--- + +## 触发条件 + +**创建类**: "创建一个..."、"生成..."、"做..." +**交互类**: "交互式..."、"可视化..."、"模拟器" +**游戏类**: "游戏"、"2048"、"贪吃蛇" +**工具类**: "计时器"、"计算器"、"追踪器" + +--- + +## 输出规范 + +### 完整 Code Artifact 模板 +```html + + + + + + 应用标题 + + + +
+ +
+ + + +``` + +### System Instructions 生成模板 + +```markdown +## Goal +创建一个{应用类型},实现{核心功能}。 + +## Planning +1. 分析需求:{用户目标和场景} +2. UI 设计:{布局、颜色、交互} +3. 数据流:{状态管理、持久化} +4. 边界处理:{错误、空状态、加载} + +## Examples +参考: +- {示例1} +- {示例2} + +## Technical Specs +- 格式: 单 HTML 文件 +- 样式: 现代简约,CSS 变量 +- 交互: 响应式,事件驱动 +- 性能: 轻量,无外部依赖 +``` + +--- + +## 工作流程 + +``` +用户请求 + ↓ +[Goal + Planning + Examples + Tech Specs] + ↓ +模型生成 Code Artifact + ↓ +Post-processing (语法 + 安全 + 格式) + ↓ +直接渲染给用户 + ↓ +用户即时交互使用 +``` + +--- + +## 论文关键结论 + +> "Generative UI 是最新强大模型的**新兴能力** (Emergent Capability)" +> — Gemini 3 达到 0% 错误率 + +**核心价值**: 模型不仅生成内容,还生成**整个用户界面**,实现真正的"AI 即时创建应用"! diff --git a/mini_agent/skills/generative-ui/_meta.json b/mini_agent/skills/generative-ui/_meta.json new file mode 100644 index 0000000..4549c6d --- /dev/null +++ b/mini_agent/skills/generative-ui/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn70pywhg0fyz996kpa8xj89s57yhv26", + "slug": "generative-ui", + "version": "1.0.0", + "publishedAt": 1767545394459 +} diff --git a/mini_agent/skills/generative-ui/examples/README.md b/mini_agent/skills/generative-ui/examples/README.md new file mode 100644 index 0000000..b68623c --- /dev/null +++ b/mini_agent/skills/generative-ui/examples/README.md @@ -0,0 +1,252 @@ +# Generative UI 示例应用 + +这个目录包含了使用 Generative UI Expert skill 可以创建的示例应用。 + +## 📦 示例列表 + +### 1. 冒泡排序可视化 (bubble-sort-visualizer) +- **文件**: `bubble-sort-visualizer.html` (在 SKILL.md 中) +- **类型**: 交互式学习工具 +- **功能**: + - 可视化冒泡排序算法过程 + - 实时显示比较和交换次数 + - 可调速度控制 + - 动画效果展示 + +### 2. 2048 游戏 (game-2048.html) +- **文件**: `game-2048.html` +- **类型**: 实时游戏 +- **功能**: + - 经典 2048 游戏机制 + - 得分和最高分记录 + - 键盘和触摸操作支持 + - 优美的动画效果 + - 本地存储最佳成绩 + +### 3. 番茄工作法计时器 (pomodoro-timer.html) +- **文件**: `pomodoro-timer.html` +- **类型**: 实用工具应用 +- **功能**: + - 25/5/15 分钟标准番茄钟 + - 自定义时长设置 + - 统计今日完成的番茄数 + - 浏览器通知提醒 + - 自动切换工作/休息模式 + - 数据持久化 + +## 🚀 如何使用 + +### 方法 1: 直接打开 +双击任意 `.html` 文件,在浏览器中直接打开即可使用。 + +### 方法 2: 本地服务器 +如果需要更完整的功能(如某些浏览器 API),可以使用本地服务器: + +```bash +# 使用 Python +python3 -m http.server 8000 + +# 或使用 Node.js +npx serve + +# 然后访问 http://localhost:8000 +``` + +## 💡 学习要点 + +### 从这些示例中你可以学到: + +#### 1. **状态管理** +```javascript +// 游戏状态管理示例 (2048) +let grid = []; +let score = 0; + +function updateState() { + // 更新逻辑 + renderGrid(); +} +``` + +#### 2. **动画实现** +```css +/* CSS 过渡动画 */ +.tile { + transition: all 0.15s ease-in-out; +} + +/* CSS 关键帧动画 */ +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} +``` + +#### 3. **事件处理** +```javascript +// 键盘事件 +document.addEventListener('keydown', (e) => { + if (e.key === 'ArrowLeft') { + move('left'); + } +}); + +// 触摸事件 +document.addEventListener('touchstart', handleTouchStart); +document.addEventListener('touchend', handleTouchEnd); +``` + +#### 4. **数据持久化** +```javascript +// localStorage 使用 +localStorage.setItem('bestScore', score); +const bestScore = localStorage.getItem('bestScore') || 0; +``` + +#### 5. **浏览器 API** +```javascript +// Notification API +if ('Notification' in window) { + Notification.requestPermission(); + new Notification('标题', { body: '内容' }); +} + +// Web Audio API +const audioContext = new AudioContext(); +const oscillator = audioContext.createOscillator(); +``` + +## 🎨 设计模式 + +### 1. **模块化设计** +每个功能都封装在独立的函数中,便于维护和扩展。 + +### 2. **响应式布局** +使用 Flexbox 和 Grid 实现自适应布局。 + +### 3. **渐进增强** +从基础功能开始,逐步添加高级特性。 + +### 4. **用户体验优先** +- 即时反馈 +- 流畅动画 +- 清晰的视觉层次 +- 直观的交互 + +## 🔧 自定义指南 + +### 修改样式 +所有样式都在 ` + + +
+

2048

+ +
+
+
得分
+
0
+
+ +
+
最高分
+
0
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ 使用方向键 ← ↑ → ↓ 移动方块,合并相同数字! +
+
+ +
+
+

+ +
+
+ + + + diff --git a/mini_agent/skills/generative-ui/examples/pomodoro-timer.html b/mini_agent/skills/generative-ui/examples/pomodoro-timer.html new file mode 100644 index 0000000..2971de9 --- /dev/null +++ b/mini_agent/skills/generative-ui/examples/pomodoro-timer.html @@ -0,0 +1,566 @@ + + + + + + 🍅 番茄工作法计时器 + + + +
+

🍅 番茄钟

+

专注工作,高效休息

+ +
+
+
+
+
+
+ +
25:00
+ +
+ + + + +
+ +
+
⚙️ 设置
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
0
+
完成番茄
+
+
+
0
+
总分钟
+
+
+
0
+
连续
+
+
+
+ +
+ 🎉 番茄完成!休息一下~ +
+ + + + diff --git a/tarot-game/SPEC.md b/tarot-game/SPEC.md new file mode 100644 index 0000000..22f7035 --- /dev/null +++ b/tarot-game/SPEC.md @@ -0,0 +1,110 @@ +# 塔罗牌发牌游戏 - 规格说明书 + +## 1. 项目概述 + +- **项目名称**: Mystic Tarot - 神秘塔罗牌 +- **项目类型**: 单页交互式网页应用 +- **核心功能**: 塔罗牌占卜游戏,支持多种牌阵和牌义解读 +- **目标用户**: 对塔罗牌感兴趣的用户 + +## 2. UI/UX 规格 + +### 布局结构 + +- **Header**: 标题 + 副标题,带有神秘发光效果 +- **控制面板**: 牌阵选择 + 洗牌 + 发牌按钮 +- **牌阵展示区**: 动态显示选择的牌阵布局 +- **解读面板**: 显示选中牌的牌义 +- **Footer**: 简洁的版权信息 + +### 响应式断点 + +- 移动端: < 768px +- 平板: 768px - 1024px +- 桌面: > 1024px + +### 视觉设计 + +**色彩方案 - 神秘星空主题** +- 背景: 深紫黑渐变 `#0a0a1a` → `#1a1a3a` +- 主色调: 紫金色 `#9b59b6` + `#f1c40f` +- 辅助色: 星空蓝 `#3498db` +- 文字: 象牙白 `#f5f5dc` +- 卡牌背面: 深紫配金星纹 + +**字体** +- 标题: 'Cinzel Decorative', serif (Google Fonts) +- 正文: 'Noto Sans SC', sans-serif (支持中文) + +**视觉效果** +- 星星粒子背景动画 +- 卡牌悬停发光效果 +- 卡牌翻转动画 (3D transform) +- 神秘光环/光晕效果 +- 缓慢呼吸动画 + +### 组件 + +**塔罗牌组件** +- 尺寸: 120px × 200px (桌面) / 80px × 130px (移动) +- 状态: 背面朝上 / 正面朝上 / 悬停 / 选中 +- 动画: 翻转、飘入、移动到牌阵 + +**按钮组件** +- 风格: 半透明玻璃拟态 + 金色边框 +- 悬停: 发光 + 轻微放大 + +**牌阵选择器** +- 下拉菜单风格 +- 包含牌阵名称和简介 + +## 3. 功能规格 + +### 牌阵系统 + +1. **单张牌阵** (1张) + - 适用: 快速占卜、今日指引 + +2. **三张牌阵** (3张) + - 适用: 过去-现在-未来 + +3. **五张牌阵** (5张) + - 适用: ситуация - 挑战 - 建议 - 环境影响 - 结果 + +4. **凯尔特十字** (10张) + - 经典复杂牌阵 + +### 牌义系统 + +- 大阿尔卡纳 (22张): 核心主题牌 +- 小阿尔卡纳 (56张): + - 权杖 (Wands) - 14张 + - 圣杯 (Cups) - 14张 + - 宝剑 (Swords) - 14张 + - 金币 (Pentacles) - 14张 + +### 用户交互流程 + +1. 选择牌阵类型 +2. 点击"洗牌"按钮(动画效果) +3. 点击"抽牌"按钮 +4. 卡牌依次飞入牌阵位置 +5. 点击卡牌查看牌义解读 +6. 可选择"重新占卜" + +## 4. 技术实现 + +- 纯 HTML + CSS + JavaScript +- CSS Grid/Flexbox 布局 +- CSS 3D 变换实现翻牌 +- CSS 动画实现粒子背景 +- 字体图标或 Unicode 符号 + +## 5. 验收标准 + +- [ ] 页面加载无错误 +- [ ] 4种牌阵都能正确发牌 +- [ ] 卡牌翻转动画流畅 +- [ ] 点击卡牌显示牌义 +- [ ] 响应式布局在各设备正常 +- [ ] 背景动画流畅运行 From 1dc8c56704d0278bfa9cf117206d513c3ef8b8af Mon Sep 17 00:00:00 2001 From: "rui.yang" Date: Thu, 12 Mar 2026 00:15:32 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20generative-ui?= =?UTF-8?q?=20skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mini_agent/skills/generative-ui/SKILL.md | 535 ++++++++++++++++++----- 1 file changed, 428 insertions(+), 107 deletions(-) diff --git a/mini_agent/skills/generative-ui/SKILL.md b/mini_agent/skills/generative-ui/SKILL.md index 7e38c82..d7e563f 100644 --- a/mini_agent/skills/generative-ui/SKILL.md +++ b/mini_agent/skills/generative-ui/SKILL.md @@ -1,115 +1,383 @@ --- name: generative-ui -description: "生成式 UI 专家。基于 Google 论文《Generative UI》,实现模型动态生成完整交互式 UI。核心架构:Tool Access + System Instructions + Post-processing + 直接渲染。" -metadata: {"clawdbot":{"emoji":"🎨","requires":{"bins":["node"]}}} +description: "生产级交互式 Web 体验生成器。作为完整的 AI 产品团队(PM + UX 设计师 + 工程师),生成精致、可动画、功能完整的单文件 HTML 应用。" +metadata: {"emoji": "🎨", "requires": {"bins": ["node"]}} --- # 生成式 UI 专家技能 (Generative UI Expert) -基于 Google 论文《Generative UI: LLMs are Effective UI Generators》实现。 +你是一个**专业、细致、有创意**的 AI 产品团队,同时扮演三个角色来打造精美的交互式 Web 体验。 -## 论文核心发现 +--- -| 指标 | 数据 | -|------|------| -| 用户偏好 Generative UI vs Markdown | **82.8%** | -| 与人类专家相当的案例 | **44%** | -| 零错误率(最新模型) | **Gemini 3: 0%** | +## 你的角色 + +### 产品经理 (Product Manager) +- 解读用户提示词,定义**核心体验目标** +- 识别**关键用户交互**和信息架构 +- **强制要求**:头脑风暴 **~12 个特性**,然后筛选出**最佳 5-8 个**控制范围 +- 即使是信息类或简单查询,也必须成为交互式应用 — 而非静态文本 +- 定义成功标准:什么让这个体验感觉"真实"而非"演示" + +### UX 设计师 (UX Designer) +- 为提示词量身定制**视觉惊艳、信息密集**的界面 +- 选择统一的配色方案、字体搭配和布局系统 +- 规划**每个交互元素的微交互** — hover、click、focus、active 状态 +- 设计令人愉悦的效果:动画、过渡和视觉反馈 +- 确保响应式设计,支持桌面 (1024px+) 和移动端 (375px) +- 无障碍:交互元素添加 ARIA 标签,关键流程支持键盘导航 + +### 前端工程师 (Frontend Engineer) +- 将整个体验实现为**单个自包含的 HTML 文件** +- 编写**生产级质量**的 HTML5 + Tailwind CSS + 原生 JavaScript +- 所有 JS 包装在 `DOMContentLoaded` 中,所有异步操作在 `try/catch` 中 +- **禁止**:`window.parent`、`window.top`、`window.postMessage` +- 所有外部链接:`target="_blank" rel="noopener noreferrer"` +- 优化首屏加载的即时视觉冲击 --- -## 核心架构 (Three Additions) +## 核心哲学 -``` -用户提示词 - ↓ -┌─────────────────────────────────────────┐ -│ 1. Tool Access │ -│ - 图像生成 (DALL-E, Midjourney) │ -│ - 网页搜索 (实时信息获取) │ -│ - 代码执行验证 │ -│ → 结果可给模型 或 直接发给用户 │ -├─────────────────────────────────────────┤ -│ 2. System Instructions │ -│ - Goal (目标) │ -│ - Planning (规划思考) │ -│ - Examples (示例) │ -│ - Technical Specs (技术规格) │ -├─────────────────────────────────────────┤ -│ 3. Post-processing │ -│ - 语法修正 │ -│ - 安全过滤 │ -│ - 错误检测 │ -└─────────────────────────────────────────┘ - ↓ -Code Artifact (完整网页) - ↓ -直接渲染到用户浏览器 -``` +> **每个提示词都值得一个独特、定制的交互体验 — 而非一大段文字。** +> **构建一个真正的、功能完整的应用程序来服务真实内容 — 而非演示或骨架。** -### 1. Tool Access(工具访问) -- **图像生成**: 创建配图、图标、背景图 -- **网页搜索**: 获取实时信息提升质量 -- **结果路由**: - - 发给模型 → 提升生成质量 - - 直发浏览器 → 提高效率 +- **应用优先**:即使是简单的事实查询("什么导致地震?")也必须成为交互式应用(板块运动模拟器),而非文字解释 +- **拒绝大段文字**:用交互功能、视觉元素和数据展示替代段落 +- **深度优于广度**:精心打磨的特性子集胜过残缺的全特性集 +- **"展示给朋友"测试**:你会自豪地分享这个吗?如果不是,就还没完成 -### 2. System Instructions(系统指令) -详细指令包含四部分: +--- -**Goal**: -``` -创建一个[应用类型],实现[核心功能],满足用户需求。 -``` +## MANDATORY 内部思考流程 + +在生成**任何代码之前**,你**必须**完成以下 7 个步骤: + +### Step 1: 意图分类 (Intent Classification) +- 这是什么类型的体验?映射到:游戏 / 仪表盘 / 工具 / 教育 / 创意 / 落地页 / 混合 +- 用户真正的目标是什么,超出字面意思? +- 什么会让这个体验**超出预期**地令人愉悦? + +### Step 2: 实体与事实识别 (Entity & Fact Identification) +- 列出所有提到的现实世界实体(人物、地点、公司、日期、产品、事件) +- 列出所有需要验证的事实 +- 标记任何时间敏感的数据(价格、排名、分数、天气、新闻) +- **绝对强制**:如果存在现实世界实体或时间敏感的事实,**必须**在代码生成前使用 `WebSearch`。这不是可选的。 + +### Step 3: 特性头脑风暴 (Feature Brainstorming) +- 为这个体验生成 **~12 个可能的特性** +- 每个特性评分:影响力 (1-5)、单文件可行性 (1-5)、愉悦因子 (1-5) +- 选择**最能提升体验的 top 5-8 个特性** +- 识别 **2-3 个"惊喜"特性**,将实现提升到基础实现之上 + +### Step 4: 视觉设计决策 (Visual Design Decision) +- 选择配色方案(具体 hex 代码) +- 选择字体(Google Font 搭配 — 一个展示字体,一个正文字体) +- 选择布局模式:网格、单列、仪表盘、画布、分屏 +- 选择动画风格:微妙/专业、活泼/弹跳、戏剧/电影级 +- 暗色模式作为默认,可选切换亮色模式 + +### Step 5: 技术架构 (Technical Architecture) +- 需要哪些 CDN 库?(最小化 — 每个都会增加加载时间) +- 状态管理方案:原生 JS 对象、类模式、模块模式 +- 核心实体的数据结构设计 +- 事件处理计划(适当的地方使用事件委托) +- 性能考虑:游戏使用 requestAnimationFrame,输入使用 debounce + +### Step 6: 内容策略 (Content Strategy) +- 需要什么真实内容/数据?从哪里来? +- 集成搜索结果?真实生成的数据? +- 图片策略:内联 SVG、CSS 艺术、Unsplash、Emoji 还是 data URI? +- 音频策略:需要吗?用什么方式? + +### Step 7: 质量预检 (Quality Pre-check) +- 这个计划创建的是真实应用还是只是演示? +- 用户真的会重复使用这个吗? +- 信息密度合适吗?(仪表盘应该密集,游戏应该专注) +- 有遗漏的边缘情况吗?(空状态、错误状态、加载状态) -**Planning**: -``` -1. 分析用户需求和目标 -2. 设计 UI 结构和交互流程 -3. 规划数据流和状态管理 -4. 考虑边界情况和错误处理 -``` +--- -**Examples**: -``` -参考模式: -- 番茄钟:倒计时 + 控制按钮 + 统计面板 -- 2048:网格布局 + 滑动手势 + 分数系统 -- 天气:卡片展示 + 动态图标 + 预报列表 -``` +## 技术指南 + +### 架构 +- 生成**单个 HTML 文件**,包含所有 HTML、CSS 和 JavaScript +- 使用内联 ` - +
- +
@@ -158,11 +443,14 @@ Code Artifact (完整网页) ## Goal 创建一个{应用类型},实现{核心功能}。 -## Planning -1. 分析需求:{用户目标和场景} -2. UI 设计:{布局、颜色、交互} -3. 数据流:{状态管理、持久化} -4. 边界处理:{错误、空状态、加载} +## Planning(7 步强制思考) +1. 意图分类:{体验类型 — 游戏/仪表盘/工具/教育/创意/落地页/混合} +2. 实体与事实识别:{需要 WebSearch 验证的现实世界实体列表} +3. 特性头脑风暴:{~12 个特性 → 筛选 top 5-8} +4. 视觉设计:{配色 hex、字体搭配、布局模式、动画风格} +5. 技术架构:{CDN 库、状态管理、数据结构、事件处理} +6. 内容策略:{图片策略、音频策略、数据来源} +7. 质量预检:{真实 vs 演示、信息密度、边缘情况} ## Examples 参考: @@ -171,9 +459,25 @@ Code Artifact (完整网页) ## Technical Specs - 格式: 单 HTML 文件 -- 样式: 现代简约,CSS 变量 -- 交互: 响应式,事件驱动 -- 性能: 轻量,无外部依赖 +- 样式: Tailwind CSS(强制主框架)+ 自定义 CSS(仅 @keyframes 等) +- JS: DOMContentLoaded + try/catch + addEventListener + const/let +- 交互: 响应式(375px-1280px),事件驱动 +- 动画: 入场动画 + hover/click 反馈 + prefers-reduced-motion +- 性能: CDN 最小化,游戏使用 requestAnimationFrame + +## Quality Checklist +- [ ] 单文件自包含 HTML +- [ ] Tailwind CSS 作为主框架 +- [ ] 入场动画(交错延迟) +- [ ] 响应式(375px 可用) +- [ ] 零占位符内容 +- [ ] 零控制台错误 +- [ ] DOMContentLoaded + try/catch +- [ ] const/let only + addEventListener only +- [ ] 图片有 fallback +- [ ] 音频默认静音 + 可见切换 +- [ ] prefers-reduced-motion +- [ ] favicon(内联 SVG data URI) ``` --- @@ -183,13 +487,27 @@ Code Artifact (完整网页) ``` 用户请求 ↓ -[Goal + Planning + Examples + Tech Specs] +Step 1: 7 步强制内部思考 + 意图分类 → 实体识别 → 特性头脑风暴(~12→5-8) + → 视觉设计 → 技术架构 → 内容策略 → 质量预检 + ↓ +Step 2: 研究与验证 + WebSearch (现实实体/时效数据 → 强制) + WebFetch (特定文档/数据源 → 按需) ↓ -模型生成 Code Artifact +Step 3: 生成 HTML 文件 + 解析输出目录(用户指定 or 默认 ~/Desktop/genui-output/) + mkdir → Write/Edit → [输出目录]/[name].html + Tailwind CSS + DOMContentLoaded + try/catch ↓ -Post-processing (语法 + 安全 + 格式) +Step 4: 强制自动审查(工具验证) + Read 回读文件 → 确认完整无截断 + Grep 扫描禁止词 → 零匹配才通过 + Grep 扫描 var / onclick → 零匹配才通过 + 发现问题 → Edit 修复 → 重新扫描 ↓ -直接渲染给用户 +Step 5: 呈现给用户 + 2-3 句描述 + 关键交互 + 浏览器打开 ↓ 用户即时交互使用 ``` @@ -202,3 +520,6 @@ Post-processing (语法 + 安全 + 格式) > — Gemini 3 达到 0% 错误率 **核心价值**: 模型不仅生成内容,还生成**整个用户界面**,实现真正的"AI 即时创建应用"! + +> **每个提示词都值得一个独特、定制的交互体验 — 而非一大段文字。** +> **构建一个真正的、功能完整的应用程序来服务真实内容 — 而非演示或骨架。** From 2087692482ba300d7fc80517060be48c476b3a7c Mon Sep 17 00:00:00 2001 From: "rui.yang" Date: Thu, 12 Mar 2026 18:50:04 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E5=85=B3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tarot-game/SPEC.md | 110 --------------------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 tarot-game/SPEC.md diff --git a/tarot-game/SPEC.md b/tarot-game/SPEC.md deleted file mode 100644 index 22f7035..0000000 --- a/tarot-game/SPEC.md +++ /dev/null @@ -1,110 +0,0 @@ -# 塔罗牌发牌游戏 - 规格说明书 - -## 1. 项目概述 - -- **项目名称**: Mystic Tarot - 神秘塔罗牌 -- **项目类型**: 单页交互式网页应用 -- **核心功能**: 塔罗牌占卜游戏,支持多种牌阵和牌义解读 -- **目标用户**: 对塔罗牌感兴趣的用户 - -## 2. UI/UX 规格 - -### 布局结构 - -- **Header**: 标题 + 副标题,带有神秘发光效果 -- **控制面板**: 牌阵选择 + 洗牌 + 发牌按钮 -- **牌阵展示区**: 动态显示选择的牌阵布局 -- **解读面板**: 显示选中牌的牌义 -- **Footer**: 简洁的版权信息 - -### 响应式断点 - -- 移动端: < 768px -- 平板: 768px - 1024px -- 桌面: > 1024px - -### 视觉设计 - -**色彩方案 - 神秘星空主题** -- 背景: 深紫黑渐变 `#0a0a1a` → `#1a1a3a` -- 主色调: 紫金色 `#9b59b6` + `#f1c40f` -- 辅助色: 星空蓝 `#3498db` -- 文字: 象牙白 `#f5f5dc` -- 卡牌背面: 深紫配金星纹 - -**字体** -- 标题: 'Cinzel Decorative', serif (Google Fonts) -- 正文: 'Noto Sans SC', sans-serif (支持中文) - -**视觉效果** -- 星星粒子背景动画 -- 卡牌悬停发光效果 -- 卡牌翻转动画 (3D transform) -- 神秘光环/光晕效果 -- 缓慢呼吸动画 - -### 组件 - -**塔罗牌组件** -- 尺寸: 120px × 200px (桌面) / 80px × 130px (移动) -- 状态: 背面朝上 / 正面朝上 / 悬停 / 选中 -- 动画: 翻转、飘入、移动到牌阵 - -**按钮组件** -- 风格: 半透明玻璃拟态 + 金色边框 -- 悬停: 发光 + 轻微放大 - -**牌阵选择器** -- 下拉菜单风格 -- 包含牌阵名称和简介 - -## 3. 功能规格 - -### 牌阵系统 - -1. **单张牌阵** (1张) - - 适用: 快速占卜、今日指引 - -2. **三张牌阵** (3张) - - 适用: 过去-现在-未来 - -3. **五张牌阵** (5张) - - 适用: ситуация - 挑战 - 建议 - 环境影响 - 结果 - -4. **凯尔特十字** (10张) - - 经典复杂牌阵 - -### 牌义系统 - -- 大阿尔卡纳 (22张): 核心主题牌 -- 小阿尔卡纳 (56张): - - 权杖 (Wands) - 14张 - - 圣杯 (Cups) - 14张 - - 宝剑 (Swords) - 14张 - - 金币 (Pentacles) - 14张 - -### 用户交互流程 - -1. 选择牌阵类型 -2. 点击"洗牌"按钮(动画效果) -3. 点击"抽牌"按钮 -4. 卡牌依次飞入牌阵位置 -5. 点击卡牌查看牌义解读 -6. 可选择"重新占卜" - -## 4. 技术实现 - -- 纯 HTML + CSS + JavaScript -- CSS Grid/Flexbox 布局 -- CSS 3D 变换实现翻牌 -- CSS 动画实现粒子背景 -- 字体图标或 Unicode 符号 - -## 5. 验收标准 - -- [ ] 页面加载无错误 -- [ ] 4种牌阵都能正确发牌 -- [ ] 卡牌翻转动画流畅 -- [ ] 点击卡牌显示牌义 -- [ ] 响应式布局在各设备正常 -- [ ] 背景动画流畅运行