diff --git a/.gitignore b/.gitignore index 4c41ce2..8edec37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store node_modules +.worktrees /build /.svelte-kit /package @@ -16,4 +17,4 @@ __pycache__ params_output -static/consent-form.pdf \ No newline at end of file +static/consent-form.pdf diff --git a/docs/superpowers/plans/2026-05-16-chinese-localization.md b/docs/superpowers/plans/2026-05-16-chinese-localization.md new file mode 100644 index 0000000..267946d --- /dev/null +++ b/docs/superpowers/plans/2026-05-16-chinese-localization.md @@ -0,0 +1,542 @@ +# Chinese Localization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a Chinese-first version of Transformer Explainer with professional English terminology preserved where useful. + +**Architecture:** Add a small static localization module for short reusable UI labels and glossary terms, while translating rich article/tutorial HTML in place to avoid risky Svelte restructuring. Add a lightweight Node-based localization check that grows with each task and guards against accidental regression to the original English UI copy. + +**Tech Stack:** SvelteKit, Svelte 5, TypeScript, Node.js scripts, existing `~` alias from `svelte.config.js`. + +--- + +## File Structure + +- Modify `package.json` and `package-lock.json`: restore a buildable baseline by aligning Vite with `@sveltejs/vite-plugin-svelte`. +- Create `src/utils/i18n.ts`: Chinese UI strings and glossary constants for short labels reused by Svelte components. +- Create `scripts/check-localization.mjs`: static text assertions for required Chinese strings and banned original English strings. +- Modify `package.json`: add `localization:check`. +- Modify `src/app.html`: set `lang="zh-CN"` and Chinese title/meta descriptions. +- Modify core UI components: + - `src/components/InputForm.svelte` + - `src/components/Topbar.svelte` + - `src/components/Header.svelte` + - `src/components/Sampling.svelte` + - `src/components/Temperature.svelte` + - `src/components/Embedding.svelte` + - `src/components/Attention.svelte` + - `src/components/AttentionMatrix.svelte` + - `src/components/BlockTransition.svelte` + - `src/components/HeadStack.svelte` + - `src/components/LinearSoftmax.svelte` + - `src/components/Mlp.svelte` + - `src/components/Operation.svelte` + - `src/components/SubsequentBlocks.svelte` + - selected `src/components/Popovers/*.svelte` +- Modify `src/utils/textbookPages.ts`: translate guided tutorial cards while preserving `id`, selectors, and analytics event names. +- Modify `src/components/article/Article.svelte`: translate long explanatory article while preserving links, formulas, component imports, classes, and IDs. + +## Task 0: Restore Build Baseline + +**Files:** +- Modify: `package.json` +- Modify: `package-lock.json` + +- [ ] **Step 1: Verify the current build fails from dependency incompatibility** + +Run: `PATH=/Users/jiangxu/.nvm/versions/node/v22.14.0/bin:$PATH npm run build` + +Expected: FAIL with `Cannot read properties of undefined (reading 'config')` from `@sveltejs/vite-plugin-svelte`. + +- [ ] **Step 2: Update Vite to the lowest compatible major range** + +Run: + +```bash +PATH=/Users/jiangxu/.nvm/versions/node/v22.14.0/bin:$PATH npm install vite@^6.3.0 --save-dev --legacy-peer-deps --registry=https://registry.npmjs.org +``` + +Expected: `package.json` changes `vite` from `^5.4.21` to `^6.3.0`, and `package-lock.json` resolves a Vite 6 release compatible with `@sveltejs/vite-plugin-svelte@6.2.4`. + +- [ ] **Step 3: Verify package install consistency** + +Run: + +```bash +PATH=/Users/jiangxu/.nvm/versions/node/v22.14.0/bin:$PATH npm ci --legacy-peer-deps --registry=https://registry.npmjs.org +``` + +Expected: install completes successfully. + +- [ ] **Step 4: Verify build baseline** + +Run: `PATH=/Users/jiangxu/.nvm/versions/node/v22.14.0/bin:$PATH npm run build` + +Expected: PASS or, if unrelated pre-existing Svelte/TypeScript errors remain, the Vite plugin `reading 'config'` crash must be gone and the new failure must be documented. + +- [ ] **Step 5: Commit** + +Run: + +```bash +git add --sparse package.json package-lock.json +git commit -m "fix: align vite with svelte plugin" +``` + +## Task 1: Localization Guard And Metadata + +**Files:** +- Create: `src/utils/i18n.ts` +- Create: `scripts/check-localization.mjs` +- Modify: `package.json` +- Modify: `src/app.html` + +- [ ] **Step 1: Write the failing localization check** + +Create `scripts/check-localization.mjs` with this content: + +```js +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const root = process.cwd(); + +function read(path) { + return readFileSync(join(root, path), 'utf8'); +} + +function assertIncludes(path, expected) { + const content = read(path); + if (!content.includes(expected)) { + throw new Error(`${path} should include: ${expected}`); + } +} + +function assertExcludes(path, banned) { + const content = read(path); + if (content.includes(banned)) { + throw new Error(`${path} should not include original English copy: ${banned}`); + } +} + +assertIncludes('src/app.html', ''); +assertIncludes('src/app.html', 'Transformer Explainer:可视化理解 LLM Transformer 模型'); +assertExcludes('src/app.html', 'LLM Transformer Model Visually Explained'); + +assertIncludes('src/utils/i18n.ts', '多层感知机(MLP, Multi-Layer Perceptron)'); +assertIncludes('src/utils/i18n.ts', '词元(Token)'); +assertIncludes('src/utils/i18n.ts', '自注意力(Self-Attention)'); + +console.log('Localization checks passed.'); +``` + +- [ ] **Step 2: Run the check and verify it fails** + +Run: `node scripts/check-localization.mjs` + +Expected: FAIL with an error that `src/app.html` does not include `` or that `src/utils/i18n.ts` does not exist. + +- [ ] **Step 3: Add the localization module** + +Create `src/utils/i18n.ts`: + +```ts +export const zhCN = { + app: { + title: 'Transformer Explainer:可视化理解 LLM Transformer 模型', + description: + '一个交互式可视化工具,帮助你理解 GPT 等大语言模型(LLM)中的 Transformer 模型如何工作。' + }, + controls: { + examples: '示例', + generate: '生成', + inputPlaceholder: '输入你自己的英文提示词', + mobileExampleOnly: '请先试试示例。自定义输入建议在桌面端使用。', + modelDownloading: 'GPT-2 模型正在下载(600MB),你可以先试试示例。', + wordLimit: (limit: number) => `最多可输入 ${limit} 个英文单词。`, + temperature: 'Temperature', + sampling: 'Sampling' + }, + glossary: { + token: '词元(Token)', + selfAttention: '自注意力(Self-Attention)', + multiHeadSelfAttention: '多头自注意力(Multi-Head Self-Attention)', + mlp: '多层感知机(MLP, Multi-Layer Perceptron)', + transformerBlock: 'Transformer Block', + positionalEncoding: '位置编码(Positional Encoding)', + tokenEmbedding: '词元嵌入(Token Embedding)', + outputProbabilities: '输出概率(Output Probabilities)' + } +} as const; +``` + +- [ ] **Step 4: Translate app metadata** + +Change `src/app.html`: + +```html + +``` + +Use this title everywhere title content appears: + +```html +Transformer Explainer:可视化理解 LLM Transformer 模型 +``` + +Use this description everywhere description content appears: + +```html +一个交互式可视化工具,帮助你理解 GPT 等大语言模型(LLM)中的 Transformer 模型如何工作。 +``` + +- [ ] **Step 5: Add the npm script** + +Modify `package.json` scripts: + +```json +"localization:check": "node scripts/check-localization.mjs" +``` + +- [ ] **Step 6: Run the check and verify it passes** + +Run: `npm run localization:check` + +Expected: PASS with `Localization checks passed.` + +- [ ] **Step 7: Commit** + +Run: + +```bash +git add --sparse src/utils/i18n.ts scripts/check-localization.mjs package.json src/app.html +git commit -m "test: add chinese localization guard" +``` + +## Task 2: Core UI And Visualization Labels + +**Files:** +- Modify: `scripts/check-localization.mjs` +- Modify: core UI component files listed in File Structure + +- [ ] **Step 1: Extend the failing check for core UI** + +Append these assertions to `scripts/check-localization.mjs` before the final `console.log`: + +```js +assertIncludes('src/components/InputForm.svelte', '生成'); +assertIncludes('src/components/InputForm.svelte', '示例'); +assertIncludes('src/components/InputForm.svelte', '输入你自己的英文提示词'); +assertExcludes('src/components/InputForm.svelte', 'Test your own input text'); +assertExcludes('src/components/InputForm.svelte', 'Generate'); + +assertIncludes('src/components/Embedding.svelte', 'Embedding'); +assertIncludes('src/components/Embedding.svelte', 'Tokenization'); +assertIncludes('src/components/Embedding.svelte', 'Token Embedding'); +assertIncludes('src/components/Attention.svelte', '多头自注意力'); +assertIncludes('src/components/LinearSoftmax.svelte', '输出概率'); +assertIncludes('src/components/SubsequentBlocks.svelte', '个相同的'); +assertIncludes('src/components/Operation.svelte', 'Layer Normalization'); +``` + +- [ ] **Step 2: Run the check and verify it fails** + +Run: `npm run localization:check` + +Expected: FAIL on `InputForm.svelte` or another core UI assertion because the old English labels are still present. + +- [ ] **Step 3: Import reusable strings where it helps** + +In `src/components/InputForm.svelte`, add: + +```ts +import { zhCN } from '~/utils/i18n'; +``` + +Replace visible strings: + +```svelte +{zhCN.controls.examples} +placeholder={zhCN.controls.inputPlaceholder} +{zhCN.controls.generate} +{zhCN.controls.mobileExampleOnly} +{zhCN.controls.modelDownloading} +{zhCN.controls.wordLimit(wordLimit)} +``` + +- [ ] **Step 4: Translate compact component labels** + +Use these exact label choices: + +```text +Topbar logo: Transformer Explainer +Header link: Transformer Explainer +Temperature control label: Temperature +Sampling control label: Sampling +Embedding title: Embedding +Tokenization label: Tokenization +Token Embedding label: Token Embedding +Positional Encoding label: Positional Encoding +Multi-head Self Attention title: 多头自注意力 +Subsequent blocks guide: {count} 个相同的 Transformer Blocks +Probabilities title: 输出概率 +Tokens: Tokens +Scaled logits: 缩放后的 logits +Dot product: 点积(Dot Product) +Scaling · Mask: 缩放 · Mask +Softmax: Softmax +Normalization: 归一化(Normalization) +``` + +Keep `Q`, `K`, `V`, `Query`, `Key`, `Value`, `Out`, `Softmax`, `MLP`, `GeLU`, `Dropout`, `Layer Normalization`, and `Residual` where labels are too narrow or already standard technical labels. + +- [ ] **Step 5: Run checks** + +Run: `npm run localization:check` + +Expected: PASS. + +Run: `npm run check` + +Expected: PASS with no Svelte or TypeScript errors. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add --sparse scripts/check-localization.mjs src/components src/utils/i18n.ts +git commit -m "feat: localize core interface labels" +``` + +## Task 3: Guided Textbook Cards + +**Files:** +- Modify: `scripts/check-localization.mjs` +- Modify: `src/utils/textbookPages.ts` + +- [ ] **Step 1: Extend the failing check for textbook cards** + +Append: + +```js +const textbook = read('src/utils/textbookPages.ts'); +for (const expected of [ + "title: '什么是 Transformer?'", + "title: 'Transformer 如何工作?'", + "title: 'Transformer 架构'", + "title: 'Embedding'", + "title: 'Token Embedding'", + "title: 'Positional Encoding'", + "title: '重复堆叠的 Transformer Blocks'", + "title: '多头自注意力(Multi-Head Self-Attention)'", + "title: 'Query、Key、Value'", + "title: 'Multi-head'", + "title: 'Masked Self-Attention'", + "title: 'Attention 输出与拼接'", + "title: 'MLP(Multi-Layer Perceptron)'", + "title: '输出 Logit'", + "title: '输出概率'", + "title: 'Temperature'", + "title: 'Sampling 策略'", + "title: '残差连接(Residual Connection)'", + "title: 'Layer Normalization'", + "title: 'Dropout'" +]) { + if (!textbook.includes(expected)) { + throw new Error(`src/utils/textbookPages.ts should include: ${expected}`); + } +} + +assertExcludes('src/utils/textbookPages.ts', 'What is Transformer?'); +assertExcludes('src/utils/textbookPages.ts', 'How Transformers Work?'); +assertExcludes('src/utils/textbookPages.ts', 'Transformer Architecture'); +``` + +- [ ] **Step 2: Run the check and verify it fails** + +Run: `npm run localization:check` + +Expected: FAIL on the first missing Chinese textbook title. + +- [ ] **Step 3: Translate textbook titles and contents** + +Translate each `title` exactly as listed in Step 1. Translate each `content` HTML string into Chinese while preserving: + +```ts +id: 'existing-id' +on: () => {} +out: () => {} +complete: () => {} +highlightElements([...]) +expandedBlock.set({ id: '...' }) +window.dataLayer?.push(...) +``` + +Use these recurring terminology forms in the card bodies: + +```text +Transformer +GPT-2(small) +词元(Token) +Tokenization +Token Embedding +Positional Encoding +Multi-Head Self-Attention +Query、Key、Value +Masked Self-Attention +Attention scores +Softmax +MLP(Multi-Layer Perceptron) +logits +Temperature +top-k +top-p +Residual Connection +Layer Normalization +Dropout +``` + +- [ ] **Step 4: Run checks** + +Run: `npm run localization:check` + +Expected: PASS. + +Run: `npm run check` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +Run: + +```bash +git add --sparse scripts/check-localization.mjs src/utils/textbookPages.ts +git commit -m "feat: localize guided textbook" +``` + +## Task 4: Long Article + +**Files:** +- Modify: `scripts/check-localization.mjs` +- Modify: `src/components/article/Article.svelte` + +- [ ] **Step 1: Extend the failing check for the long article** + +Append: + +```js +for (const expected of [ + '

什么是 Transformer?

', + '

Transformer 架构

', + '

Embedding

', + '

Transformer Block

', + '

Multi-Head Self-Attention

', + '

MLP:Multi-Layer Perceptron

', + '

输出概率

', + '

辅助架构特性

', + '

交互功能

', + '

视频教程

', + '

Transformer Explainer 是如何实现的?

', + '

谁开发了 Transformer Explainer?

' +]) { + assertIncludes('src/components/article/Article.svelte', expected); +} + +assertExcludes('src/components/article/Article.svelte', '

What is a Transformer?

'); +assertExcludes('src/components/article/Article.svelte', '

Interactive Features

'); +assertExcludes('src/components/article/Article.svelte', '

Output Probabilities

'); +``` + +- [ ] **Step 2: Run the check and verify it fails** + +Run: `npm run localization:check` + +Expected: FAIL on `

什么是 Transformer?

`. + +- [ ] **Step 3: Translate the article** + +Translate visible prose, list items, headings, and captions in `Article.svelte`. Preserve all of these exactly: + +```svelte + +``` + +Preserve all `href`, `target`, `title`, `id`, `data-click`, `class`, `Katex math={...}`, image paths, and code examples. Translate text around inline code and links. Keep library names, model names, author names, paper title `"Attention is All You Need"`, and project name `Transformer Explainer` in English. + +- [ ] **Step 4: Run checks** + +Run: `npm run localization:check` + +Expected: PASS. + +Run: `npm run check` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +Run: + +```bash +git add --sparse scripts/check-localization.mjs src/components/article/Article.svelte +git commit -m "feat: localize explanatory article" +``` + +## Task 5: Final Verification + +**Files:** +- No production file changes expected unless verification reveals a concrete issue. + +- [ ] **Step 1: Run static localization check** + +Run: `npm run localization:check` + +Expected: PASS. + +- [ ] **Step 2: Run Svelte/TypeScript check** + +Run: `npm run check` + +Expected: PASS. + +- [ ] **Step 3: Run production build** + +Run: `npm run build` + +Expected: PASS and output written to `build/`. + +- [ ] **Step 4: Start local dev server** + +Run: `npm run dev -- --host 127.0.0.1` + +Expected: Vite prints a local URL such as `http://127.0.0.1:5173/`. + +- [ ] **Step 5: Browser smoke check** + +Open the local URL and verify: + +```text +The metadata title is Chinese. +The input controls show 示例 and 生成. +The tutorial card titles are Chinese. +The article headings are Chinese. +Narrow labels such as Q/K/V/MLP/Softmax remain readable. +No obvious text overlaps appear in the first viewport. +``` + +- [ ] **Step 6: Commit final fixes if any** + +If Step 5 required fixes, run: + +```bash +git add --sparse src scripts package.json +git commit -m "fix: polish chinese localization" +``` + +If no fixes were required, do not create an empty commit. diff --git a/docs/superpowers/specs/2026-05-16-chinese-localization-design.md b/docs/superpowers/specs/2026-05-16-chinese-localization-design.md new file mode 100644 index 0000000..be87f58 --- /dev/null +++ b/docs/superpowers/specs/2026-05-16-chinese-localization-design.md @@ -0,0 +1,79 @@ +# Transformer Explainer 中文汉化设计 + +## 背景 + +本项目是 `poloclub/transformer-explainer` 的 fork,当前页面文案以英文为主。目标是面向中文技术读者提供中文界面和中文讲解,同时保留关键技术术语的英文表达,避免翻译后失去专业语境或与模型可视化标签脱节。 + +仓库是 SvelteKit 应用。主要英文来源包括: + +- `src/components/article/Article.svelte`:页面下方长文章正文。 +- `src/utils/textbookPages.ts`:交互式教程卡片标题与内容。 +- `src/components/*.svelte`:按钮、控件、图表短标签、popover 标签、提示语。 +- `src/store/index.ts`:示例 prompt 文本。 +- `src/app.html`:页面标题、meta 描述和分享文案。 + +## 用户需求 + +中文应自然、清楚,适合技术专业读者。专业术语可保留英文;缩写或重要术语首次出现时,需要给出英文全称或中英解释。例如 RAG 应写作「检索增强生成(Retrieval-Augmented Generation, RAG)」。 + +## 推荐方案 + +采用「中文静态汉化 + 术语英文保留」方案。 + +不实现完整语言切换系统。当前 fork 的目标是中文版本,完整 i18n 会增加额外运行时状态、切换 UI 和维护成本。也不完全原地硬改所有文案;短文案应尽量集中在中文文案文件或术语文件中,长文章和富文本教程可以在原组件/数据结构中翻译,以减少 Svelte 富文本重构风险。 + +## 翻译规则 + +1. UI 操作文案使用中文,例如「生成」「示例」「试试示例」「最多可输入 12 个英文单词」。 +2. 专业术语保留英文或中英并列,例如「词元(Token)」「自注意力(Self-Attention)」「多层感知机(MLP, Multi-Layer Perceptron)」。 +3. 图表中的极短标签优先保持可读和不换行。`Q`、`K`、`V`、`MLP`、`Softmax` 等短标签可保留英文;标题和教程正文给出中文解释。 +4. 缩写首次出现时补充英文全称。之后可直接使用缩写或英文术语。 +5. 链接、论文标题、模型名、作者名、库名保持原文。 +6. 示例 prompt 暂时保留英文。GPT-2 tokenizer、缓存示例数据和模型输出都围绕英文 prompt 工作,直接改成中文会让演示行为和缓存数据不匹配。 + +## 实现范围 + +需要汉化: + +- `src/app.html` 的页面标题和 meta 文案。 +- `src/components/InputForm.svelte` 的按钮、占位符、helper 文案。 +- `src/components/Topbar.svelte` 和 `src/components/Header.svelte` 的可见标题或链接文本。 +- `src/components/Sampling.svelte`、`src/components/Temperature.svelte` 的控件标题和说明。 +- 主要可视化组件中的阶段名、矩阵标签、popover 标题和公式辅助文字。 +- `src/utils/textbookPages.ts` 中的交互式教程卡片。 +- `src/components/article/Article.svelte` 中的长文章正文、标题、图注和功能说明。 +- `src/store/index.ts` 中与示例相关的中文说明仅在不影响模型输入的前提下调整。 + +不需要汉化: + +- 源代码变量名、CSS class、analytics event name、DOM selector、数据结构字段。 +- 模型输出 token、用户输入 prompt、缓存数据文件 `src/constants/examples/*.js`。 +- 外部链接标题、作者姓名、论文名和库名。 + +## 文件结构 + +新增 `src/lib/i18n/zh-CN.ts` 或类似位置,用于收纳短 UI 文案和术语常量。长文章 `Article.svelte` 与教程数据 `textbookPages.ts` 可以直接翻译现有 HTML 内容;若某些术语多次出现且容易不一致,再提取到小型 glossary 常量。 + +预期结构: + +```text +src/lib/i18n/zh-CN.ts +src/lib/i18n/glossary.ts +``` + +如果项目现有别名不方便从 `src/lib` 引入,可改用 `src/utils/i18n.ts`,保持与现有 `~/utils/*` 引入风格一致。 + +## 测试与验证 + +1. 运行 `npm run check`,确保 Svelte 和 TypeScript 通过。 +2. 运行 `npm run build`,确保静态构建通过。 +3. 启动本地开发服务器,检查首页主要区域、教程卡片、长文章、popover 和移动端提示没有明显未翻译英文。 +4. 用文本扫描辅助检查剩余英文文案。允许保留术语、代码名、链接、模型名、作者名、库名和 prompt。 +5. 因当前 sparse checkout 未包含 `static/model-v2` 大模型分片,本地运行可能会在真实模型下载处不可用;验证时主要依赖缓存示例数据和构建检查。 + +## 风险 + +- 中文文案比英文更长,可能造成按钮、图表短标签或 popover 布局拥挤。短标签应保守处理,必要时保留英文。 +- 长文章包含大量 HTML 和 KaTeX,翻译时必须保留标签结构和公式内容。 +- 教程页的 `on/out/complete` 行为依赖 selector 和 page id,翻译不得修改这些 id 或 selector。 +- 示例 prompt 与缓存数据耦合,若未来要中文 prompt,需要重新生成缓存示例或改模型演示策略。 diff --git a/package-lock.json b/package-lock.json index e9a5887..f32c955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "tailwindcss": "^3.4.1", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.21" + "vite": "^6.4.2" }, "engines": { "node": ">=20.0.0", @@ -69,9 +69,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -82,13 +82,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -99,13 +99,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -116,13 +116,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -133,13 +133,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -150,13 +150,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -167,13 +167,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -184,13 +184,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -201,13 +201,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -218,13 +218,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -235,13 +235,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -252,13 +252,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -269,13 +269,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -286,13 +286,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -303,13 +303,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -320,13 +320,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -337,13 +337,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -354,13 +354,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -371,13 +388,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -388,13 +422,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -405,13 +456,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -422,13 +473,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -439,13 +490,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -456,7 +507,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3679,9 +3730,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3689,32 +3740,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "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" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -4088,6 +4142,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5512,9 +5584,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7331,6 +7403,23 @@ "node": ">=0.8" } }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7520,21 +7609,24 @@ "license": "MIT" }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -7543,19 +7635,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -7576,6 +7674,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, diff --git a/package.json b/package.json index d680935..ecc94e2 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "localization:check": "node scripts/check-localization.mjs", "lint": "prettier --check . && eslint .", "format": "prettier --write .", "deploy": "gh-pages -d build --nojekyll", @@ -47,7 +48,7 @@ "tailwindcss": "^3.4.1", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.21" + "vite": "^6.4.2" }, "type": "module", "dependencies": { diff --git a/scripts/check-localization.mjs b/scripts/check-localization.mjs new file mode 100644 index 0000000..38a7536 --- /dev/null +++ b/scripts/check-localization.mjs @@ -0,0 +1,104 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const root = process.cwd(); + +function read(path) { + return readFileSync(join(root, path), 'utf8'); +} + +function assertIncludes(path, expected) { + const content = read(path); + if (!content.includes(expected)) { + throw new Error(`${path} should include: ${expected}`); + } +} + +function assertExcludes(path, banned) { + const content = read(path); + if (content.includes(banned)) { + throw new Error(`${path} should not include original English copy: ${banned}`); + } +} + +assertIncludes('src/app.html', ''); +assertIncludes('src/app.html', 'Transformer Explainer:可视化理解 LLM Transformer 模型'); +assertExcludes('src/app.html', 'LLM Transformer Model Visually Explained'); + +assertIncludes('src/utils/i18n.ts', '多层感知机(MLP, Multi-Layer Perceptron)'); +assertIncludes('src/utils/i18n.ts', '词元(Token)'); +assertIncludes('src/utils/i18n.ts', '自注意力(Self-Attention)'); + +assertIncludes('src/components/InputForm.svelte', '生成'); +assertIncludes('src/components/InputForm.svelte', '示例'); +assertIncludes('src/components/InputForm.svelte', '输入你自己的英文提示词'); +assertExcludes('src/components/InputForm.svelte', 'Test your own input text'); +assertExcludes('src/components/InputForm.svelte', 'Generate'); + +assertIncludes('src/components/Embedding.svelte', 'Embedding'); +assertIncludes('src/components/Embedding.svelte', 'Tokenization'); +assertIncludes('src/components/Embedding.svelte', 'Token Embedding'); +assertIncludes('src/components/Attention.svelte', '多头自注意力'); +assertIncludes('src/components/LinearSoftmax.svelte', '输出概率'); +assertIncludes('src/components/SubsequentBlocks.svelte', '个相同的'); +assertIncludes('src/components/Operation.svelte', 'Layer Normalization'); + +const textbook = read('src/utils/textbookPages.ts'); +for (const expected of [ + "title: '什么是 Transformer?'", + "title: 'Transformer 如何工作?'", + "title: 'Transformer 架构'", + "title: 'Embedding'", + "title: 'Token Embedding'", + "title: 'Positional Encoding'", + "title: '重复堆叠的 Transformer Blocks'", + "title: '多头自注意力(Multi-Head Self-Attention)'", + "title: 'Query、Key、Value'", + "title: 'Multi-head'", + "title: 'Masked Self-Attention'", + "title: 'Attention 输出与拼接'", + "title: 'MLP(Multi-Layer Perceptron)'", + "title: '输出 Logit'", + "title: '输出概率'", + "title: 'Temperature'", + "title: 'Sampling 策略'", + "title: '残差连接(Residual Connection)'", + "title: 'Layer Normalization'", + "title: 'Dropout'" +]) { + if (!textbook.includes(expected)) { + throw new Error(`src/utils/textbookPages.ts should include: ${expected}`); + } +} + +assertExcludes('src/utils/textbookPages.ts', 'What is Transformer?'); +assertExcludes('src/utils/textbookPages.ts', 'How Transformers Work?'); +assertExcludes('src/utils/textbookPages.ts', 'Transformer Architecture'); +assertExcludes('src/utils/textbookPages.ts', "Transformers aren't magic"); +assertExcludes('src/utils/textbookPages.ts', 'What is the most probable next word'); + +for (const expected of [ + '

什么是 Transformer?

', + '

Transformer 架构

', + '

Embedding

', + '

Transformer Block

', + '

Multi-Head Self-Attention

', + '

MLP:Multi-Layer Perceptron

', + '

输出概率

', + '

辅助架构特性

', + '

交互功能

', + '

视频教程

', + '

Transformer Explainer 是如何实现的?

', + '

谁开发了 Transformer Explainer?

' +]) { + assertIncludes('src/components/article/Article.svelte', expected); +} + +assertExcludes('src/components/article/Article.svelte', '

What is a Transformer?

'); +assertExcludes('src/components/article/Article.svelte', '

Interactive Features

'); +assertExcludes('src/components/article/Article.svelte', '

Output Probabilities

'); +assertExcludes('src/components/article/Article.svelte', 'and even'); +assertExcludes('src/components/article/Article.svelte', 'This expansion step allows'); +assertExcludes('src/components/article/Article.svelte', 'Transformer Explainer was created by'); + +console.log('Localization checks passed.'); diff --git a/src/app.html b/src/app.html index 53fee17..996119e 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + @@ -7,13 +7,13 @@ - Transformer Explainer: LLM Transformer Model Visually Explained + Transformer Explainer:可视化理解 LLM Transformer 模型 - + @@ -21,11 +21,11 @@
- Multi-head Self Attention + 多头自注意力
diff --git a/src/components/AttentionMatrix.svelte b/src/components/AttentionMatrix.svelte index 66bf244..4b8f3ba 100644 --- a/src/components/AttentionMatrix.svelte +++ b/src/components/AttentionMatrix.svelte @@ -353,7 +353,7 @@ {showTooltip} /> -
Dot product
+
点积(Dot Product)
@@ -419,7 +419,7 @@ />
-
Scaling · Mask
+
缩放 · Mask
diff --git a/src/components/Embedding.svelte b/src/components/Embedding.svelte index 25d3258..a30de08 100644 --- a/src/components/Embedding.svelte +++ b/src/components/Embedding.svelte @@ -196,7 +196,8 @@
- Token
Embedding
Token
Embedding
-
Expanded
Embeddings
+
扩展后的
Embeddings
-
Position
+
位置
{#each $tokens as token, token_idx}
-
Encoding Matrix
+
编码矩阵
diff --git a/src/components/Popovers/QKVWeightPopover.svelte b/src/components/Popovers/QKVWeightPopover.svelte index 1e0609c..3fbbd3e 100644 --- a/src/components/Popovers/QKVWeightPopover.svelte +++ b/src/components/Popovers/QKVWeightPopover.svelte @@ -308,7 +308,7 @@ // }; - +
@@ -319,8 +319,8 @@
- Embeddings{`Embeddings originate from tokens \nbut evolve through blocks, becoming \nabstract representations.`}{`Embedding 起源于词元(Token),\n并在经过多个 Block 后逐步演化为\n更抽象的表示。`}
@@ -339,8 +339,8 @@
×
- Q·K·V Weights{`Transforms embedding vectors into Query, Key, and Value vectors. \nParameters were learned in training, fixed in prediction.`}{`把 embedding 向量变换为 Query、Key、Value 向量。\n参数在训练中学习得到,在预测时保持固定。`}
@@ -362,8 +362,8 @@
+
- Q·K·V Bias{`Offsets added after transformation. \nParameters that learned in training, fixed in prediction.`}{`变换后额外加上的偏移量。\n这些参数在训练中学习得到,在预测时保持固定。`}
-
- Adds layer input to output to help preserve information. -
把层输入加回输出,帮助保留信息。