diff --git a/AGENTS.md b/AGENTS.md index 11e35cf..c581463 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,16 +17,22 @@ mkdocs-template.yml ─┤ generate() → cache/ ─copytree→ generated/ (mkdo redirects.json ──────┘ (含生成的 assets/js/redirects.js) ``` +构建后还有一步:`stage_markdown_copies()` 把 `cache/` 下每个 `.md` 复制到 `site/` +同路径(如 `site/nmbot-telegram/mcp.md`),作为每页的 Markdown 版本,供 +`/llms.txt` 链接、“复制本文 Markdown”按钮、View-as-Markdown 菜单项与 AI +打开菜单。 + `generate()`(`src/nmteam_support/generator.py`)编排的完整链路: 1. 清空重建 `cache/` -2. `scanner.scan_docs()` 递归扫描 `docs/` 得到文档树(`SKIP_DIRS`/`INTERNAL_DIRS` 过滤) -3. 写入处理后的页面(非 index.md 注入贡献提示,`contributing.py`) -4. `index.py` 为每目录生成 `index.md`(自动 generated 标记 + docsList 卡片,`docslist.py` 渲染 HTML) -5. `image_pipeline.py` 用 PIL 为 assets 中每张 PNG/JPEG 生成 `.webp` 兄弟文件(质量 80;PNG 另出 256 色有损 fallback) -6. `nav.py` 生成 nav YAML,`template.py` 替换 `mkdocs-template.yml` 的 `# NAV_ARIA_START`/`# NAV_ARIA_END` 标记块写入 `mkdocs.yml` -7. `redirects.py` 读 `redirects.json` 生成 `cache/assets/js/redirects.js` -8. copytree 到 `generated/`,交给 `mkdocs build --strict` +1. `scanner.scan_docs()` 递归扫描 `docs/` 得到文档树(`SKIP_DIRS`/`INTERNAL_DIRS` 过滤) +1. 写入处理后的页面(非 index.md 注入贡献提示,`contributing.py`) +1. `index.py` 为每目录生成 `index.md`(自动 generated 标记 + docsList 卡片,`docslist.py` 渲染 HTML) +1. `image_pipeline.py` 用 PIL 为 assets 中每张 PNG/JPEG 生成 `.webp` 兄弟文件(质量 80;PNG 另出 256 色有损 fallback) +1. `nav.py` 生成 nav YAML,`template.py` 替换 `mkdocs-template.yml` 的 `# NAV_ARIA_START`/`# NAV_ARIA_END` 标记块写入 `mkdocs.yml` +1. `redirects.py` 读 `redirects.json` 生成 `cache/assets/js/redirects.js` +1. `llms.py` 用扫描树 + 模板 `site_url` 生成 `cache/llms.txt`(llmstxt.org 规范:H1 + blockquote + H2 分节链接,链接指向 `.md` 版本;非 md 文件会被 mkdocs 原样拷到 `site/llms.txt`) +1. copytree 到 `generated/`,交给 `mkdocs build --strict` **图片双层管线**(关键机制):生成期 `image_pipeline.py` 产出同名 `.webp` 兄弟文件;渲染期 `markdown_images.py`(mkdocs 扩展,注册在 mkdocs-template.yml 的 markdown_extensions 中)把本地栅格图 `` 改写为 WebP-first ``,靠同名 `.webp` 约定对接。外部 URL 不下载不镜像。Markdown 中仍写普通图片语法。 @@ -34,18 +40,18 @@ redirects.json ──────┘ (含生成的 assets/js/redirects.js ## Key Directories -| 路径 | 用途 | -|---|---| -| `src/nmteam_support/` | 工具链包(15 个模块,见 Important Files) | -| `docs/` | **真实文档源**(唯一需要手工编辑的内容位置) | -| `docs/nmbot-telegram/` | 产品中枢:`panel/`、`plus/`、`legal/`、`group/`、`faq/`、`business/`、`tools/`、`nmbot-intelligence/`、`credit/`、`update-log/`(`YYYY-MM.md` 月度日志)、`mcp/` | -| `docs/contact-us/`、`docs/nmteam-account/` | 其他产品线 | -| `docs/superpowers/` | 本地设计与实现工件(plans/specs),**已 gitignore,勿提交** | -| `assets/images/` | 图片母版:`shared/`(站级共享)、`nmbot/`(含 `mcp/`、`update-pictures/` 子目录);`assets/icons/`(SVG)、`assets/styles/`(CSS) | -| `overrides/` | mkdocs `custom_dir`,`main.html` 覆写 site_meta 移除主题版本号 | -| `scripts/` | 三平台薄启动器(`nmteam.sh` / `nmteam.ps1` / `nmteam.bat`) | -| `tests/` | pytest 测试(15 个文件 + conftest.py) | -| `cache/`、`generated/`、`site/`、`mkdocs.yml` | 生成产物,勿手改勿提交 | +| 路径 | 用途 | +| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/nmteam_support/` | 工具链包(16 个模块,见 Important Files) | +| `docs/` | **真实文档源**(唯一需要手工编辑的内容位置) | +| `docs/nmbot-telegram/` | 产品中枢:`panel/`、`plus/`、`legal/`、`group/`、`faq/`、`business/`、`tools/`、`nmbot-intelligence/`、`credit/`、`update-log/`(`YYYY-MM.md` 月度日志)、`mcp/` | +| `docs/contact-us/`、`docs/nmteam-account/` | 其他产品线 | +| `docs/superpowers/` | 本地设计与实现工件(plans/specs),**已 gitignore,勿提交** | +| `assets/images/` | 图片母版:`shared/`(站级共享)、`nmbot/`(含 `mcp/`、`update-pictures/` 子目录);`assets/icons/`(SVG)、`assets/styles/`(CSS)、`assets/js/`(AI 工具脚本 `ai-tools.js`,随构建 stage 到生成站) | +| `overrides/` | mkdocs `custom_dir`:`main.html` 覆写 site_meta 移除主题版本号;`partials/actions.html` 追加 Fumadocs 风格文章操作区(复制 Markdown + GitHub / Markdown / Perplexity / Grok / ChatGPT / Claude Web / Claude Desktop / Claude Code / OpenAI Codex / Cursor 打开菜单);`.icons/ai/` 存放菜单品牌图标 | +| `scripts/` | 三平台薄启动器(`nmteam.sh` / `nmteam.ps1` / `nmteam.bat`) | +| `tests/` | pytest 测试(17 个文件 + conftest.py) | +| `cache/`、`generated/`、`site/`、`mkdocs.yml` | 生成产物,勿手改勿提交 | ## Development Commands @@ -54,7 +60,8 @@ uv sync # 安装依赖(按 .python-version 3.14 自 uv run nmteam install # 同上,CLI 封装 uv run nmteam dev # 生成文档结构 + mkdocs serve http://127.0.0.1:8000, # 监听 docs/、assets/、mkdocs-template.yml 变化自动重生成 + 热更新 -uv run nmteam build # generate() + mkdocs build --strict → site/ +uv run nmteam build # generate() + mkdocs build --strict → site/, + # 再 stage 每页 .md 副本到 site/ uv run nmteam generate # 仅重新生成文档结构 uv run nmteam clean # 清理 cache/ generated/ site/ uv run nmteam check # 全部质量检查(见下) @@ -65,6 +72,7 @@ uv run nmteam --help 平台启动器(定位仓库根后原样透传参数,无业务逻辑):`scripts/nmteam.sh dev`、`.\scripts\nmteam.ps1 dev`、`scripts\nmteam.bat dev`。 **质量检查**(`nmteam check`,CI 同样执行): + ```bash uv run ruff check . uv run ruff format --check . @@ -95,25 +103,29 @@ Markdown 文档(`docs/`): ## Important Files -| 文件 | 职责 | -|---|---| -| `src/nmteam_support/cli.py` | Typer 入口 `main`;install/dev/generate/build/clean/check + redirects 子命令 | -| `src/nmteam_support/generator.py` | `generate()` 端到端编排 + `GeneratorOptions`/`default_options` | -| `src/nmteam_support/scanner.py` | 递归扫描 docs/ 树,SKIP_DIRS/INTERNAL_DIRS 过滤 | -| `src/nmteam_support/frontmatter.py` | frontmatter 解析(title/description/index/flag) | -| `src/nmteam_support/index.py` + `docslist.py` | 每目录 index.md 生成 + docsList 卡片 HTML | -| `src/nmteam_support/nav.py` + `template.py` | nav YAML 生成;NAV_ARIA 标记块替换写 mkdocs.yml | -| `src/nmteam_support/contributing.py` | 非 index.md 注入贡献提示 admonition | -| `src/nmteam_support/redirects.py` | redirects.json 管理(损坏保护)+ redirects.js 生成 | -| `src/nmteam_support/image_pipeline.py` + `markdown_images.py` | PIL 生成 .webp 兄弟文件;mkdocs 扩展包 WebP-first `` | -| `src/nmteam_support/models.py` | `PageMetadata`/`DocEntry` frozen dataclass | -| `pyproject.toml` | 包元数据、依赖、入口、pytest/ruff/hatchling 配置 | -| `uv.lock` | 锁定依赖(mkdocs 1.6.1、mkdocs-material 9.7.7、mkdocs-minify-plugin 0.8.0、pillow 12.3.0、typer 0.27.1、pytest 9.1.1、ruff 0.16.2、mdformat 1.0.0 等) | -| `mkdocs-template.yml` | mkdocs 配置模板(material zh 黄色双 palette、minify 插件、custom_dir overrides、`nmteam_support.markdown_images` 扩展、NAV_ARIA 标记) | -| `redirects.json` | 顶层 `redirects` 对象:`{旧路径带斜杠: 新路径}` | -| `.github/workflows/ci.yml` | 三 OS 矩阵 CI(push main/dev + PR):uv sync --frozen → nmteam check → 验证三个启动器 | -| `.mdformat.toml` | mdformat 配置(wrap=keep、LF) | -| `overrides/main.html` | 移除 meta 中 mkdocs-material 版本号 | +| 文件 | 职责 | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `src/nmteam_support/cli.py` | Typer 入口 `main`;install/dev/generate/build/clean/check + redirects 子命令 | +| `src/nmteam_support/generator.py` | `generate()` 端到端编排 + `GeneratorOptions`/`default_options` | +| `src/nmteam_support/scanner.py` | 递归扫描 docs/ 树,SKIP_DIRS/INTERNAL_DIRS 过滤 | +| `src/nmteam_support/frontmatter.py` | frontmatter 解析(title/description/index/flag) | +| `src/nmteam_support/index.py` + `docslist.py` | 每目录 index.md 生成 + docsList 卡片 HTML | +| `src/nmteam_support/nav.py` + `template.py` | nav YAML 生成;NAV_ARIA 标记块替换写 mkdocs.yml | +| `src/nmteam_support/contributing.py` | 非 index.md 注入贡献提示 admonition | +| `src/nmteam_support/redirects.py` | redirects.json 管理(损坏保护)+ redirects.js 生成 | +| `src/nmteam_support/image_pipeline.py` + `markdown_images.py` | PIL 生成 .webp 兄弟文件;mkdocs 扩展包 WebP-first `` | +| `src/nmteam_support/llms.py` | `render_llms_txt()` 从扫描树生成 `/llms.txt`(llmstxt.org 规范;链接指向各页 `.md` 版本) | +| `src/nmteam_support/models.py` | `PageMetadata`/`DocEntry` frozen dataclass | +| `pyproject.toml` | 包元数据、依赖、入口、pytest/ruff/hatchling 配置 | +| `uv.lock` | 锁定依赖(mkdocs 1.6.1、mkdocs-material 9.7.7、mkdocs-minify-plugin 0.8.0、pillow 12.3.0、typer 0.27.1、pytest 9.1.1、ruff 0.16.2、mdformat 1.0.0 等) | +| `mkdocs-template.yml` | mkdocs 配置模板(material zh 黄色双 palette、minify 插件、custom_dir overrides、`nmteam_support.markdown_images` 扩展、NAV_ARIA 标记) | +| `redirects.json` | 顶层 `redirects` 对象:`{旧路径带斜杠: 新路径}` | +| `.github/workflows/ci.yml` | 三 OS 矩阵 CI(push main/dev + PR):uv sync --frozen → nmteam check → 验证三个启动器 | +| `.mdformat.toml` | mdformat 配置(wrap=keep、LF) | +| `overrides/main.html` | 移除 meta 中 mkdocs-material 版本号 | +| `overrides/partials/actions.html` | 覆盖 material actions partial:保留编辑/查看按钮,追加复制 Markdown 按钮与十项文章打开菜单 | +| `assets/js/ai-tools.js` | 文章操作交互:复制当前页 Markdown、菜单开关、外部点击/Escape 关闭、开发模式提示,以及八种 AI 服务的页面 URL 深链 | +| `assets/styles/ai-tools.css` | 文章操作按钮组与半透明弹层样式,适配明暗主题和窄屏 | ## Runtime/Tooling Preferences @@ -127,6 +139,7 @@ Markdown 文档(`docs/`): - **pytest**(dev 组 `pytest>=9.0`),配置在 pyproject.toml:`testpaths = ["tests"]`、`pythonpath = ["src"]`、`addopts = "-ra"`。 - 测试约定:`tests/` 单层目录,每个模块一个 `test_.py`(test_cli、test_generator、test_scanner、test_frontmatter、test_nav、test_index、test_docslist、test_contributing、test_redirects、test_image_pipeline、test_markdown_images、test_models、test_scripts、test_build_config 等)。 +- `test_llms.py` 覆盖 llms.txt 生成(标题/摘要/分节/.md 链接/base_url/空树);`test_generator.py` 覆盖 llms.txt 落盘与 `stage_markdown_copies` 镜像 cache。 - 输入构造分级:conftest 的 `docs_dir` fixture(tmp_path 构造最小 docs 树,含 index/hide_docs_list/hide frontmatter)→ `tmp_path` 手写单文件 → 全流程 generate 集成。**不依赖真实 docs/ 内容**。 - CLI 测试用 `typer.testing.CliRunner` + monkeypatch;启动器测试用 subprocess + 假 uv 脚本。 - **无覆盖率门槛**(无 pytest-cov、CI 无 coverage 步骤)——新增功能时给模块补 `test_.py` 行为测试即可。 diff --git a/README.md b/README.md index 9d3329f..e3263dc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,23 @@ uv run nmteam build Markdown 中仍使用普通图片语法,构建工具会自动输出 WebP 优先的 `` 元素。外部 URL 不会被下载或镜像。 +## AI 支持 + +站点面向语言模型提供以下能力: + +- `/llms.txt`:按 [llmstxt.org](https://llmstxt.org/) 规范生成的站点索引, + 每个链接指向页面的 Markdown 版本。 +- 每页 Markdown 版本:`nmteam build` 时在每个页面旁生成同路径的 `.md` 文件 + (如 `/nmbot-telegram/mcp.md`)。 +- 页面顶部的文章操作区:**复制本文 Markdown** 直接复制当前页面原文; + **使用[品牌图标]打开本文章** 菜单提供 GitHub 源文件、Markdown 版本, + 以及 Perplexity、Grok、ChatGPT、Claude Web、Claude Desktop、 + Claude Code、OpenAI Codex、Cursor 八种 AI 打开方式。 + +注意:`llms.txt` 与 `.md` 版本由 `nmteam build` 输出到 `site/`;开发模式 +(`nmteam dev`)下复制和 View as Markdown 会提示先构建,AI 操作仍可通过 +当前页面 URL 打开。 + ### 其他命令 ```bash diff --git a/assets/js/ai-tools.js b/assets/js/ai-tools.js new file mode 100644 index 0000000..705edef --- /dev/null +++ b/assets/js/ai-tools.js @@ -0,0 +1,250 @@ +// Fumadocs-style page actions menu. +(function () { + "use strict"; + + var ENDPOINTS = { + perplexity: "https://www.perplexity.ai/search?q=", + grok: "https://grok.com/?q=", + chatgpt: "https://chatgpt.com/?prompt=", + claude: "https://claude.ai/new?q=", + "claude-desktop": "claude://claude.ai/new?q=", + "claude-code": "https://claude.ai/code/new?q=", + codex: "codex://new?prompt=", + cursor: "https://cursor.com/link/prompt?text=", + }; + + var CLAUDE_CODE_REPOSITORY = "nm-Team/Support"; + var CLAUDE_CODE_BRANCH = "main"; + + function mdUrl(raw) { + if (!raw) { + return "/index.md"; + } + return "/" + raw.replace(/\/+$/, "") + ".md"; + } + + function pageUrl(raw) { + return location.origin + "/" + (raw || ""); + } + + function providerPrompt(url) { + return ( + "请阅读以下 nmTeam 帮助文档,并依据原文回答我接下来的问题;" + + "若文档未提及,请如实说明:\n\n" + + url + ); + } + + function providerUrl(provider, prompt) { + var url = ENDPOINTS[provider] + encodeURIComponent(prompt); + if (provider === "chatgpt") { + return url + "&hints=search"; + } + if (provider === "claude-code") { + return ( + url + + "&repo=" + + encodeURIComponent(CLAUDE_CODE_REPOSITORY) + + "&branch=" + + encodeURIComponent(CLAUDE_CODE_BRANCH) + ); + } + return url; + } + + function setOpen(box, open, restoreFocus) { + var trigger = box.querySelector(".ai-tools__trigger"); + var menu = box.querySelector(".ai-tools__menu"); + box.classList.toggle("is-open", open); + trigger.setAttribute("aria-expanded", String(open)); + menu.hidden = !open; + if (!open && restoreFocus) { + trigger.focus(); + } + } + + function showMarkdownUnavailable() { + window.alert( + "Markdown 版本在构建产物中提供。请先运行 `uv run nmteam build`," + + "或使用其他打开方式。" + ); + } + + function showCopyFailed() { + window.alert("无法复制 Markdown,请稍后重试。"); + } + + function copyWithSelection(text) { + var textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + + var copied = document.execCommand("copy"); + textarea.remove(); + if (!copied) { + return Promise.reject(new Error("Copy command failed")); + } + return Promise.resolve(); + } + + function copyText(text) { + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text).catch(function () { + return copyWithSelection(text); + }); + } + return copyWithSelection(text); + } + + function setCopyState(button, copied) { + button.querySelector(".ai-tools__copy-icon--idle").hidden = copied; + button.querySelector(".ai-tools__copy-icon--success").hidden = !copied; + button.querySelector(".ai-tools__copy-label").textContent = copied + ? "已复制" + : "复制本文 Markdown"; + } + + function wireCopy(button, md, availability) { + var resetTimer; + + button.addEventListener("click", function () { + button.disabled = true; + availability + .then(function (available) { + if (!available) { + showMarkdownUnavailable(); + return null; + } + return fetch(md); + }) + .then(function (response) { + if (!response) { + return null; + } + var contentType = response.headers.get("Content-Type") || ""; + if (!response.ok || contentType.indexOf("html") !== -1) { + throw new Error("Markdown is unavailable"); + } + return response.text(); + }) + .then(function (markdown) { + if (markdown === null) { + return; + } + return copyText(markdown).then(function () { + window.clearTimeout(resetTimer); + setCopyState(button, true); + resetTimer = window.setTimeout(function () { + setCopyState(button, false); + }, 1800); + }); + }) + .catch(showCopyFailed) + .then(function () { + button.disabled = false; + }); + }); + } + + function wireMarkdown(link, md) { + var availability = fetch(md, { method: "HEAD" }) + .then(function (response) { + var contentType = response.headers.get("Content-Type") || ""; + return response.ok && contentType.indexOf("html") === -1; + }) + .catch(function () { + return false; + }) + .then(function (available) { + link.dataset.rawAvailable = String(available); + return available; + }); + + link.href = md; + link.addEventListener("click", function (event) { + if (link.dataset.rawAvailable === "true") { + return; + } + + event.preventDefault(); + if (link.dataset.rawAvailable === "false") { + showMarkdownUnavailable(); + return; + } + + var target = window.open("about:blank", "_blank"); + if (target) { + target.opener = null; + } + availability.then(function (available) { + if (available && target) { + target.location.replace(md); + return; + } + if (target) { + target.close(); + } + showMarkdownUnavailable(); + }); + }); + + return availability; + } + + function wireMenu(box) { + var trigger = box.querySelector(".ai-tools__trigger"); + var copy = box.querySelector(".ai-tools__copy"); + var menu = box.querySelector(".ai-tools__menu"); + var raw = box.getAttribute("data-md-url") || ""; + var markdown = menu.querySelector('[data-ai="markdown"]'); + var md = mdUrl(raw); + var prompt = providerPrompt(pageUrl(raw)); + var availability = wireMarkdown(markdown, md); + + wireCopy(copy, md, availability); + [ + "perplexity", + "grok", + "chatgpt", + "claude", + "claude-desktop", + "claude-code", + "codex", + "cursor", + ].forEach(function (provider) { + menu.querySelector('[data-ai="' + provider + '"]').href = + providerUrl(provider, prompt); + }); + + trigger.addEventListener("click", function () { + setOpen(box, menu.hidden, false); + }); + + menu.addEventListener("click", function (event) { + if (event.target.closest(".ai-tools__item")) { + setOpen(box, false, false); + } + }); + + document.addEventListener("click", function (event) { + if (!menu.hidden && !box.contains(event.target)) { + setOpen(box, false, false); + } + }); + + document.addEventListener("keydown", function (event) { + if (event.key === "Escape" && !menu.hidden) { + event.preventDefault(); + setOpen(box, false, true); + } + }); + } + + document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".ai-tools").forEach(wireMenu); + }); +})(); diff --git a/assets/styles/ai-tools.css b/assets/styles/ai-tools.css new file mode 100644 index 0000000..34e7791 --- /dev/null +++ b/assets/styles/ai-tools.css @@ -0,0 +1,227 @@ +/* Fumadocs-style page actions menu. */ +.ai-tools { + position: relative; + z-index: 4; + display: inline-flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 8px; + float: right; + margin: 0 0 12px 12px; +} + +.ai-tools__copy, +.ai-tools__trigger { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 36px; + gap: 7px; + padding: 7px 11px; + font: inherit; + font-size: 13px; + font-weight: 600; + line-height: 20px; + color: var(--md-default-fg-color); + background: var(--md-default-bg-color); + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + cursor: pointer; + transition: background-color 120ms ease, border-color 120ms ease; +} + +.ai-tools__copy:hover, +.ai-tools__trigger:hover, +.ai-tools.is-open .ai-tools__trigger { + background: var(--md-default-fg-color--lightest); + border-color: var(--md-default-fg-color--lighter); +} + +.ai-tools__copy:focus-visible, +.ai-tools__trigger:focus-visible, +.ai-tools__item:focus-visible { + outline: 2px solid var(--md-accent-fg-color); + outline-offset: 2px; +} + +.ai-tools__copy:disabled { + cursor: wait; + opacity: 0.65; +} + +.ai-tools__copy-icon { + display: inline-flex; + flex: 0 0 auto; + width: 16px; + height: 16px; + color: var(--md-default-fg-color--light); +} + +.ai-tools__copy-icon--success { + color: var(--md-accent-fg-color); +} + +.ai-tools__copy-icon[hidden] { + display: none; +} + +.ai-tools__copy-icon svg { + width: 100%; + height: 100%; + fill: currentcolor; +} + +.ai-tools__provider-icons { + display: inline-flex; + align-items: center; + padding-left: 1px; +} + +.ai-tools__provider-icon { + display: inline-flex; + flex: 0 0 auto; + width: 16px; + height: 16px; + padding: 1px; + background: var(--md-default-bg-color); + border-radius: 4px; + box-shadow: 0 0 0 1px var(--md-default-bg-color); +} + +.ai-tools__provider-icon + .ai-tools__provider-icon { + margin-left: -4px; +} + +.ai-tools__provider-icon svg { + width: 100%; + height: 100%; +} + +.ai-tools__provider-icon--github svg { + fill: currentcolor; +} + +.ai-tools__chevron, +.ai-tools__item-icon, +.ai-tools__external { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; +} + +.ai-tools__chevron { + width: 15px; + height: 15px; + color: var(--md-default-fg-color--light); + transition: transform 140ms ease; +} + +.ai-tools.is-open .ai-tools__chevron { + transform: rotate(180deg); +} + +.ai-tools__menu { + position: absolute; + top: calc(100% + 6px); + right: 0; + display: grid; + width: 248px; + max-width: calc(100vw - 32px); + max-height: min(70vh, 420px); + overflow-y: auto; + padding: 8px; + color: var(--md-default-fg-color); + background: color-mix(in srgb, var(--md-default-bg-color) 88%, transparent); + -webkit-backdrop-filter: blur(16px) saturate(135%); + backdrop-filter: blur(16px) saturate(135%); + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 12px; + box-shadow: 0 16px 40px color-mix(in srgb, #000 22%, transparent); +} + +.ai-tools__menu[hidden] { + display: none; +} + +.md-typeset .ai-tools__item { + display: grid; + grid-template-columns: 18px minmax(0, 1fr) 16px; + align-items: center; + gap: 10px; + position: relative; + min-height: 32px; + padding: 6px 8px; + font-size: 14px; + line-height: 20px; + color: var(--md-default-fg-color); + border-radius: 8px; + text-decoration: none; + transition: color 100ms ease, background-color 100ms ease; +} + +.md-typeset .ai-tools__item:hover { + color: var(--md-default-fg-color); + background: var(--md-default-fg-color--lightest); +} + +.md-typeset .ai-tools__item--group-start { + margin-top: 5px; +} + +.md-typeset .ai-tools__item--group-start::before { + content: ""; + position: absolute; + top: -3px; + right: 8px; + left: 8px; + border-top: 1px solid var(--md-default-fg-color--lightest); +} + +.ai-tools__item-icon { + width: 18px; + height: 18px; +} + +.ai-tools__label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ai-tools__external { + width: 16px; + height: 16px; + color: var(--md-default-fg-color--light); +} + +.ai-tools__chevron svg, +.ai-tools__item-icon:not(.ai-tools__item-icon--brand) svg, +.ai-tools__external svg { + width: 100%; + height: 100%; + fill: currentcolor; +} + +.ai-tools__item-icon--brand svg { + width: 100%; + height: 100%; +} + +@media (max-width: 44.984375em) { + .ai-tools { + flex-wrap: nowrap; + gap: 6px; + margin-right: 4px; + } + + .ai-tools__copy, + .ai-tools__trigger { + gap: 5px; + padding-right: 8px; + padding-left: 8px; + font-size: 12px; + } +} diff --git a/docs/index.md b/docs/index.md index d9b990d..7bc6ea2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,11 +12,11 @@ hide: 在此获取 nmTeam 旗下产品的支持。 - + ## 产品和服务 - + @@ -32,7 +32,7 @@ hide: - + ## 关于帮助文档 @@ -48,9 +48,12 @@ nmTeam 帮助文档由 nmTeam 成员和社区志愿者共同编辑。您可以 访问 [nmTeam 官网](https://nmteam.xyz)了解 nmTeam 的最新动态和产品信息。 - +
在此获取 nmTeam 旗下产品的支持。