commit 472bf8b7324708e1241aeb3c77a699a418cd6f81 Author: HouYunFei <1844025705@qq.com> Date: Tue May 19 07:53:53 2026 +0800 first commit Co-Authored-By: Codex diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c751e7c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.idea +docs +data +web/.next +web/node_modules +web/out +*.log +.env* +!.env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9e16cfe --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# 管理员账号,首次启动会自动创建。 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=infinite-canvas + +# JWT 登录密钥和过期时间,正式部署请修改 JWT_SECRET。 +JWT_SECRET=infinite-canvas +JWT_EXPIRE_HOURS=168 + +# 后端对外监听端口,Docker 默认由 Go 统一暴露 3000。 +PORT=3000 + +# 数据库配置,默认使用本地 SQLite。 +STORAGE_DRIVER=sqlite +# sqlite: DATABASE_DSN=data/infinite-canvas.db +# mysql: DATABASE_DSN=user:password@tcp(127.0.0.1:3306)/infinite_canvas?parseTime=true +# postgres: DATABASE_DSN=postgres://user:password@127.0.0.1:5432/infinite_canvas?sslmode=disable +DATABASE_DSN=data/infinite-canvas.db diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..f0c5967 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,44 @@ +name: Docker image + +on: + push: + branches: [main] + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=tag + type=sha,prefix= + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b4f90e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.next/ +out/ +node_modules/ +.env* +!.env.example +data/ +*.log +*.tsbuildinfo +.DS_Store +.idea diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c3767d6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md + +本文档用于约束本项目中的 AI / 自动化开发行为。开发时优先遵循本文件,其次遵循用户当前消息。 + +## 基本原则 + +- 先读现有代码,再动手修改,优先沿用项目已有结构和写法。 +- 写代码保持最少行数,能简单实现就不要引入复杂抽象。 +- 不要为了“兼容更多场景”写大量分支,只实现当前明确需要的功能。 +- 项目尚未上线,不需要兼容旧数据;表结构或字段调整时直接按新设计修改,不写旧字段兼容、数据迁移兜底或删除旧表的清理逻辑,除非用户明确要求。 +- 每次写完代码,不需要检查语法,不需要执行构建,用户会自己做。 +- 不要改无关文件,不要顺手重构。 +- 如果工作区已有用户改动,不要回滚,不要覆盖;只在必要范围内追加修改。 + +## 反复提醒沉淀 + +- 如果开发过程中总是遇到某个问题,或者用户反复提醒同一个注意事项,需要把该注意事项补充到本文件。 +- 补充时写成明确、可执行的规则,避免只写模糊描述。 +- 新规则应放到最相关的章节;找不到合适章节时放到“项目注意事项”。 + +## 后端规范 + +- 后端使用 Go + Gin + GORM。 +- `handler/` 只处理 HTTP 入参、调用 service、返回 `OK` / `Fail`。 +- `service/` 放业务逻辑、默认值、校验、时间、ID、鉴权等处理。 +- `repository/` 只做数据库访问和 GORM 查询。 +- `model/` 只定义数据结构、枚举和简单模型方法。 +- 列表接口优先沿用 `model.Query`、`Normalize`、分页和标签筛选方式。 +- 业务接口保持 `{ code, data, msg }` 的响应结构。 +- 新增数据表时同步更新 `docs/backend-database.md`。 + +## 前端规范 + +- 前端使用 Next.js App Router、React、TypeScript、Ant Design、Tailwind、Zustand。 +- API 请求统一放在 `web/src/services/api/`。 +- 全局或跨页面状态优先放在 `web/src/stores/`。 +- 画布相关状态和组件放在 `web/src/app/(user)/canvas/` 内部。 +- 页面里只有一个主业务组件时直接写在 `page.tsx`,不要单独拆 `Manager` 组件再传一堆 props。 +- 管理后台页面私有组件放到各自页面目录的 `components/` 下,例如 `admin/assets/components/`、`admin/prompts/components/`;不要为了单页面使用放到 `admin/components/` 共享目录。 +- 管理后台主题、背景、卡片阴影、表格配色等统一在全局 `AntThemeProvider` 或全局 CSS 作用域中配置;页面私有组件不要自己写 `dark ? ...` 主题分支。 +- 组件优先使用函数组件和现有 hooks,不新增大型状态管理方案。 +- UI 图标优先使用 `lucide-react` 或项目已经使用的 Ant Design 图标。 +- 页面文案保持中文。 +- 不要在组件里堆太多无关逻辑;复杂逻辑优先抽成同目录工具函数或小组件。 +- 前端业务数据需要浏览器本地持久化时,默认使用 `localforage`;`localStorage` 只用于极小的简单配置,不要用来保存业务列表、生成记录、图片、base64 或大 JSON。 + +## 画布 UI 规范 + +- 做 canvas 前端 UI 时必须遵循当前画布主题。 +- 优先使用 `canvasThemes`、`useThemeStore` 或 Ant Design `ConfigProvider` token。 +- 不要硬编码黑白、stone、slate 等颜色导致浅色/深色主题不一致。 +- 新增画布按钮、弹窗、浮层时,尽量复用已有工具栏、节点面板、Modal 的视觉风格。 +- 图片节点尺寸逻辑要尊重原始比例,除非功能明确要求自由变形。 +- 批量生成、多图展示、助手面板等画布交互要尽量简洁,不要占用过多画布空间。 + +## 文档规范 + +- README 保持简洁,只放项目介绍、核心功能、快速开始和文档入口。 +- 详细功能介绍写到 `docs/features.md`。 +- 后续待办写到 `docs/todo.md`。 +- 已实现但还需要用户测试确认的事项写到 `docs/pending-test.md`。 +- 面向用户的新增、调整、修复等版本变更写到根目录 `CHANGELOG.md` 的 `Unreleased` 中。 +- 每次 todo 事项完成后,先从 `docs/todo.md` 移到 `docs/pending-test.md`,不要直接写进正式功能说明;用户确认测试通过后再更新 `docs/features.md`。 +- 每次任务完成前,都要根据实际变更检查并更新 `docs/todo.md` 和 `docs/pending-test.md`;如果功能或待办没有变化,也要确认无需修改。 +- 接口响应规则写到 `docs/api-response.md`。 +- 数据库结构写到 `docs/backend-database.md`。 +- 文档不要写过期日期;除非用户明确要求记录具体时间。 + +## 项目注意事项 + +- 当前画布项目和“我的素材”主要保存在浏览器本地,不要在文档中误写成已支持云同步。 +- 当前 AI API Key 存在浏览器本地,并由前端直接请求 OpenAI 兼容接口;涉及安全说明时要写清楚。 +- Docker 静态资源路径目前仍是待办项,文档中不要过度承诺生产部署已经完全验证。 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..98efbfa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# CHANGELOG + +## Unreleased + +1. [新增] 增加生图工作台功能,支持文生图、图生图、查看历史记录,并增加移动端适配。 +2. [修复] Docker 镜像启动时 Next.js 监听地址改为 `0.0.0.0`,便于容器环境访问。 +3. [修复] 画布生成尺寸控件支持选择更多常用比例,并可直接输入自定义比例。 + +## v0.0.1 - 2026-05-19 + +1. [新增] 首次开源版本,包含无限画布能力:多画布项目、节点拖拽缩放、连线、小地图、撤销重做、导入导出。 +2. [新增] AI 创作能力:支持 OpenAI 兼容接口的文生图、图生图、参考图编辑和文本问答。 +3. [新增] 画布助手能力:支持围绕选中节点和上游节点对话、生图,并把结果插回画布。 +4. [新增] 提示词库能力:抓取多个 GitHub 开源项目,按案例整理数百个图片提示词。 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2768b4a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# 构建 Next.js 前端产物。 +FROM oven/bun:1 AS web-build + +WORKDIR /app/web +COPY web/package.json web/bun.lock ./ +RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile --registry=https://registry.npmmirror.com --cache-dir=/root/.bun/install/cache +COPY VERSION /app/VERSION +COPY web ./ +RUN bun run build + +# 构建 Go 后端入口。 +FROM golang:1.25-alpine AS api-build + +WORKDIR /app +COPY go.mod go.sum ./ +COPY config ./config +COPY handler ./handler +COPY middleware ./middleware +COPY model ./model +COPY repository ./repository +COPY router ./router +COPY service ./service +COPY main.go ./ +RUN go build -o /server . + +# 运行镜像:Go 对外监听 3000,Next.js 只在容器内部监听 3001。 +FROM oven/bun:1 + +WORKDIR /app +COPY VERSION /app/VERSION +COPY --from=api-build /server /app/server +COPY --from=web-build /app/web /app/web +ENV PROMPT_DATA_DIR=/app/data/prompts +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /app/data/prompts + +EXPOSE 3000 +# 先启动内部 Next.js,再由 Go 统一处理 /api/* 和页面反代。 +CMD ["sh", "-c", "cd /app/web && HOSTNAME=0.0.0.0 PORT=3001 bun run start & PORT=3000 /app/server"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29ebfa5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..47cb765 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +

+ infinite-canvas logo +

+ +

无限画布 (infinite-canvas)

+ +无限画布是一款面向图片创作的开源工作台。它把画布编排、AI 图片生成、参考图编辑、对话助手、提示词库和素材沉淀放在同一个界面里,适合用来探索视觉方案并连续迭代图片结果。 + +> [!CAUTION] +> 项目目前处于开发阶段,不保证历史数据兼容。各种数据库结构和存储格式都可能直接调整,欢迎关注后续更新,当前更适合个人/本地部署,不建议直接公网多人共用。 +> +> 如果你需要稳定维护自己的分支,建议自行 fork 后独立开发。二次开发与 PR 请保留原作者信息和前端页面标识。 + +## 核心功能 + +- 无限画布:多画布项目、节点拖拽缩放、连线、小地图、撤销重做、导入导出。 +- AI 创作:支持 OpenAI 兼容接口的文生图、图生图、参考图编辑和文本问答。 +- 画布助手:围绕选中节点和上游节点对话、生图,并把结果插回画布。 +- 提示词库:抓取多个 GitHub 开源项目,按案例整理数百个图片提示词。 + +完整功能说明见 [docs/features.md](docs/features.md)。 + +如果你在为担心没有合适的生图API来发愁,可以查看该免费生图项目:[chatgpt2api](https://github.com/basketikun/chatgpt2api) + +## 技术栈 + +- 前端:Next.js、React、TypeScript、Tailwind CSS、Ant Design、Zustand、TanStack Query。 +- 后端:Go、Gin、GORM。 +- 部署:Docker。 + +## 快速开始 + +```bash +git clone git@github.com:basketikun/infinite-canvas.git +cd infinite-canvas +cp .env.example .env +# 修改默认账号密码等信息 +docker-compose up -d +``` + +本地源码构建运行: + +```bash +cp .env.example .env +docker compose -f docker-compose.local.yml up -d --build +``` + +运行后默认端口3000,可访问 `http://localhost:3000`。 + +如需要拉取提示词,可前往:`http://localhost:3000/admin/prompts` + +## 效果展示 + + + + + + + + + + + + + + +
imageimage
imageimage
5image
+ +## 文档 + +- [功能介绍](docs/features.md) +- [画布节点操作手册](docs/canvas-node-manual.md) +- [画布快捷键](docs/canvas-shortcuts.md) +- [待办事项](docs/todo.md) +- [后端数据库说明](docs/backend-database.md) +- [接口响应约定](docs/api-response.md) + +## 社区支持 + +学 AI,上 L 站:[LinuxDO](https://linux.do/) + +点击链接加入群聊【AI开源交流】:https://qm.qq.com/q/DFnKzZ807u + +## 开源协议 + +本项目使用 GNU Affero General Public License v3.0,见 [LICENSE](LICENSE)。 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..95e94cd --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v0.0.1 \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..aee35f1 --- /dev/null +++ b/config/config.go @@ -0,0 +1,23 @@ +package config + +import ( + "github.com/caarlos0/env/v11" + "github.com/joho/godotenv" +) + +type Config struct { + Port string `env:"PORT" envDefault:"8080"` + AdminUsername string `env:"ADMIN_USERNAME" envDefault:"admin"` + AdminPassword string `env:"ADMIN_PASSWORD" envDefault:"infinite-canvas"` + JWTSecret string `env:"JWT_SECRET" envDefault:"infinite-canvas"` + JWTExpireHours int `env:"JWT_EXPIRE_HOURS" envDefault:"168"` + StorageDriver string `env:"STORAGE_DRIVER" envDefault:"sqlite"` + DatabaseDSN string `env:"DATABASE_DSN" envDefault:"data/infinite-canvas.db"` +} + +var Cfg Config + +func Load() error { + _ = godotenv.Load() + return env.Parse(&Cfg) +} diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..54f6bf4 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,13 @@ +services: + app: + image: infinite-canvas:local + build: + context: . + dockerfile: Dockerfile + env_file: + - .env + volumes: + - ./data:/app/data + ports: + - "3000:3000" + restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bf21af8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + app: + image: ghcr.io/basketikun/infinite-canvas:latest + container_name: infinite-canvas + env_file: + - .env + volumes: + - ./data:/app/data + ports: + - "3000:3000" + restart: unless-stopped diff --git a/docs/api-response.md b/docs/api-response.md new file mode 100644 index 0000000..ce45f81 --- /dev/null +++ b/docs/api-response.md @@ -0,0 +1,19 @@ +# 前后端接口响应约定 + +后端业务接口统一返回 JSON: + +```json +{ + "code": 0, + "data": {}, + "msg": "ok" +} +``` + +- `code`: 业务状态码,`0` 表示成功,非 `0` 表示失败。 +- `data`: 业务数据。失败时通常为 `null`。 +- `msg`: 响应消息。成功默认为 `ok`,失败时放错误原因。 + +前端请求逻辑以 `code` 判断业务是否成功。当前后端业务失败也会返回 HTTP 200,前端不要只依赖 HTTP 状态码判断结果。 + +接口连接失败、服务不可达、返回体不是约定 JSON 时,前端按网络或接口异常处理。 diff --git a/docs/backend-database.md b/docs/backend-database.md new file mode 100644 index 0000000..2e1c033 --- /dev/null +++ b/docs/backend-database.md @@ -0,0 +1,234 @@ +# 后端数据库说明 + +本文档记录后端当前已经使用,以及后续规划会用到的主要数据表。 + +## 数据库 + +后端使用 GORM 管理数据库连接和表结构迁移。 + +支持的存储驱动: + +- `sqlite` +- `mysql` +- `postgresql` + +当前启动时执行 `AutoMigrate`,自动维护以下表: + +- `users` +- `prompts` +- `assets` + +后续新增表时,优先保持表数量少,能用字段或 JSON 表达的配置、状态、统计和扩展信息先不拆表。 + +### users + +系统用户表。用户基础信息、角色、算力点余额和邀请关系放在该表中。 + +| 字段 | 类型 | 说明 | +|-----------------|--------|--------------------------| +| `id` | string | 主键 | +| `username` | string | 用户名,唯一索引 | +| `password` | string | 密码哈希 | +| `email` | string | 邮箱 | +| `display_name` | string | 昵称 | +| `avatar_url` | string | 头像地址 | +| `role` | string | 角色:`user`、`admin` | +| `credits` | number | 算力点余额,规划字段 | +| `aff_code` | string | 用户自己的邀请码,唯一索引,规划字段 | +| `aff_count` | number | 已邀请用户数量,冗余统计字段,规划字段 | +| `inviter_id` | string | 邀请人用户 ID,规划字段 | +| `github_id` | string | GitHub 用户 ID,规划字段 | +| `linux_do_id` | string | Linux.do 用户 ID,规划字段 | +| `wechat_id` | string | 微信用户 ID,规划字段 | +| `status` | string | 用户状态:`active`、`ban`,规划字段 | +| `last_login_at` | string | 最近登录时间 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +### prompts + +提示词表。后续公开提示词、内置 GitHub 系统提示词、分类和扩展信息都优先放在该表字段或 JSON 中。 + +| 字段 | 类型 | 说明 | +|--------------|--------|------------------------------| +| `id` | string | 主键 | +| `title` | string | 标题 | +| `cover_url` | string | 封面图 | +| `prompt` | string | 提示词内容 | +| `tags` | json | 标签列表 | +| `category` | string | 分类标识 | +| `visibility` | string | 可见性:公开、私有、系统内置等,规划字段 | +| `preview` | text | Markdown 展示内容,可包含文本、图片、视频链接等 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +`github_url` 仅用于接口返回,不写入数据库。 + +### assets + +素材表。当前用于素材库;后续通过 `user_id`、`visibility`、`type` 区分系统公开素材和用户私有素材。 + +| 字段 | 类型 | 说明 | +|------------------|--------|-------------------------------| +| `id` | string | 主键 | +| `user_id` | string | 所属用户,为空或系统用户表示公开素材,规划字段 | +| `title` | string | 标题 | +| `type` | string | 素材类型:`text`、`image`、`video` 等 | +| `visibility` | string | 可见性:公开、私有,规划字段 | +| `cover_url` | string | 封面图 | +| `tags` | json | 标签列表 | +| `category` | string | 分类标识 | +| `description` | string | 描述 | +| `content` | text | 文本或 Markdown 内容 | +| `url` | string | 图片、视频等媒体地址 | +| `like_count` | number | 点赞量,规划字段 | +| `favorite_count` | number | 收藏量,规划字段 | +| `view_count` | number | 查看量,规划字段 | +| `extra` | json | 扩展信息,规划字段 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +### settings + +系统配置表,只保存两行数据:`public` 放前端可读取的公开配置,`private` 放仅后端和管理员可读取的私有配置,配置值都用 JSON。 + +| 字段 | 类型 | 说明 | +|--------------|--------|-----------------------| +| `key` | string | 主键:`public`、`private` | +| `value` | json | 配置内容 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +`public.value` 常放前端展示和可公开读取的配置,例如模型列表、订阅套餐、功能开关等。 +`private.value` 常放渠道密钥、支付配置、奖励规则、后台内部开关等。 + +### dicts + +字典表。一个字典一行,具体字典项数据放在 `items`。 + +| 字段 | 类型 | 说明 | +|--------------|--------|-------| +| `code` | string | 字典编码 | +| `name` | string | 字典名称 | +| `remark` | string | 备注 | +| `items` | text | 字典值数据 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +可维护分类、标签、业务枚举、模型分类、日志类型等。 + +### credit_logs + +用户算力点变更流水表。充值、消费、订阅扣减、邀请奖励、后台调整等余额变化都写入该表。 + +| 字段 | 类型 | 说明 | +|--------------|--------|--------------------------| +| `id` | string | 主键 | +| `user_id` | string | 关联用户 ID | +| `type` | string | 类型:充值、消费、订阅扣减、邀请奖励、后台调整等 | +| `amount` | number | 本次变动数量,增加为正,扣减为负 | +| `balance` | number | 变动后的用户算力点余额 | +| `related_id` | string | 关联订单、任务或日志 ID,可为空 | +| `remark` | string | 备注 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | + +### orders + +订单表。统一记录充值、订阅购买等支付订单。 + +| 字段 | 类型 | 说明 | +|---------------------|--------|----------------------| +| `id` | string | 主键 | +| `user_id` | string | 关联用户 ID | +| `type` | string | 订单类型:充值、订阅等 | +| `provider` | string | 支付渠道:Linux LDC、聚合支付等 | +| `amount` | number | 支付金额 | +| `credits` | number | 到账算力点 | +| `status` | string | 订单状态:待支付、已支付、失败、关闭等 | +| `provider_order_id` | string | 第三方订单号 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | +| `paid_at` | string | 支付时间 | +| `updated_at` | string | 更新时间 | + +### subscriptions + +用户订阅表。一个用户可以有多个订阅记录,套餐配置放在 `settings.public.value` 中。 + +| 字段 | 类型 | 说明 | +|-----------------|--------|------------------------------------------| +| `id` | string | 主键 | +| `user_id` | string | 关联用户 ID | +| `plan_key` | string | 套餐标识,对应 `settings.public.value` 中的订阅套餐配置 | +| `order_id` | string | 关联订单 ID,可为空 | +| `status` | string | 状态:生效中、已过期、已取消等 | +| `total_credits` | number | 订阅总额度 | +| `used_credits` | number | 已使用额度 | +| `started_at` | string | 开始时间 | +| `expired_at` | string | 过期时间 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +### files + +文件表。用于统一管理上传图片、视频等文件,保存最终可访问地址。缩略图和视频封面优先按 URL 命名规则推导,特殊情况放在 +`extra.coverUrl`。 + +| 字段 | 类型 | 说明 | +|--------------|--------|-------------| +| `id` | string | 主键 | +| `user_id` | string | 上传用户 ID,可为空 | +| `name` | string | 原始文件名 | +| `url` | string | 完整可访问地址 | +| `mime_type` | string | MIME 类型 | +| `size` | number | 文件大小 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | + +### canvases + +画布表。保存用户私有画布、公开画布和模板,分享、协作、审核等低频配置放在 `extra`。 + +| 字段 | 类型 | 说明 | +|------------------|--------|---------------------------------------------| +| `id` | string | 主键 | +| `user_id` | string | 所属用户 ID | +| `title` | string | 画布标题 | +| `description` | string | 描述 | +| `cover_url` | string | 封面图 | +| `data` | json | 画布节点、边、视图等数据 | +| `visibility` | string | 可见性:`private`、`public` | +| `status` | string | 状态:`draft`、`pending`、`published`、`rejected` | +| `is_template` | bool | 是否模板 | +| `view_count` | number | 查看量 | +| `like_count` | number | 点赞量 | +| `favorite_count` | number | 收藏量 | +| `copy_count` | number | 复制量 | +| `extra` | json | 扩展信息,如分享、协作、审核备注等 | +| `created_at` | string | 创建时间 | +| `updated_at` | string | 更新时间 | + +### generation_tasks + +接口调用队列表。用于图片、文本、图生图等后端模型调用的排队、状态和结果记录。 + +| 字段 | 类型 | 说明 | +|---------------|--------|----------------------| +| `id` | string | 主键 | +| `user_id` | string | 发起用户 ID | +| `type` | string | 任务类型:文本生成、文生图、图生图等 | +| `model` | string | 使用模型 | +| `channel` | string | 使用渠道 | +| `status` | string | 状态:排队中、执行中、成功、失败、取消等 | +| `credits` | number | 扣除算力点 | +| `input` | json | 请求参数 | +| `output` | json | 生成结果 | +| `error` | string | 错误信息 | +| `extra` | json | 扩展信息 | +| `created_at` | string | 创建时间 | +| `started_at` | string | 开始时间 | +| `finished_at` | string | 完成时间 | diff --git a/docs/canvas-data-structure.md b/docs/canvas-data-structure.md new file mode 100644 index 0000000..dd34b5c --- /dev/null +++ b/docs/canvas-data-structure.md @@ -0,0 +1,282 @@ +# 画布数据结构 + +本文档说明当前画布在前端本地保存的数据结构、图片文件的存储和清理方式,以及后续接入后端存储时建议保持的兼容边界。 + +## 当前存储位置 + +当前画布项目主要保存在浏览器本地: + +- 画布项目 JSON:`localForage`,数据库名 `infinite-canvas`,storeName `app_state`,key 为 `infinite-canvas:canvas_store`。 +- 我的素材 JSON:`localForage`,数据库名 `infinite-canvas`,storeName `app_state`,key 为 `infinite-canvas:asset_store`。 +- 图片 Blob:单独存到 `localForage` 实例,数据库名 `infinite-canvas`,storeName `image_files`。 + +画布 JSON 不直接长期保存大体积 base64 图片。图片节点、助手图片和素材图片只保存展示 URL、`storageKey` 和图片元信息,真实图片 Blob 通过 `storageKey` 读取。 + +## 画布项目结构 + +每个画布项目是一个 `CanvasProject`: + +```ts +type CanvasProject = { + id: string; + title: string; + createdAt: string; + updatedAt: string; + nodes: CanvasNodeData[]; + connections: CanvasConnection[]; + chatSessions: CanvasAssistantSession[]; + activeChatId: string | null; + backgroundMode: "lines" | "dots" | "blank"; + viewport: { x: number; y: number; k: number }; +}; +``` + +字段说明: + +- `id`:画布项目 ID,当前前端生成。 +- `title`:画布名称。 +- `createdAt` / `updatedAt`:ISO 字符串。 +- `nodes`:画布节点列表。 +- `connections`:节点连线列表。 +- `chatSessions`:右侧画布助手会话。 +- `activeChatId`:当前选中的助手会话 ID。 +- `backgroundMode`:画布背景模式。 +- `viewport`:视口变换,`x/y` 是屏幕平移,`k` 是缩放比例。 + +## 节点结构 + +每个节点是一个 `CanvasNodeData`: + +```ts +type CanvasNodeData = { + id: string; + type: "image" | "text" | "config"; + title: string; + position: { x: number; y: number }; + width: number; + height: number; + metadata?: CanvasNodeMetadata; +}; +``` + +通用字段: + +- `id`:节点 ID。 +- `type`:节点类型,当前有图片、文本、生成配置三类。 +- `title`:节点标题。 +- `position`:画布世界坐标,不是屏幕坐标。 +- `width` / `height`:画布世界坐标下的节点尺寸。 +- `metadata`:节点内容和业务状态。 + +`metadata` 当前常用字段: + +```ts +type CanvasNodeMetadata = { + content?: string; + prompt?: string; + status?: "idle" | "success" | "loading" | "error"; + errorDetails?: string; + fontSize?: number; + generationMode?: "text" | "image"; + model?: string; + size?: string; + count?: number; + naturalWidth?: number; + naturalHeight?: number; + freeResize?: boolean; + isBatchRoot?: boolean; + batchRootId?: string; + batchChildIds?: string[]; + primaryImageId?: string; + imageBatchExpanded?: boolean; + inputOrder?: string[]; + storageKey?: string; + mimeType?: string; + bytes?: number; +}; +``` + +不同节点的使用方式: + +- 图片节点:`content` 是当前可展示的图片 URL,通常是 `blob:` URL;`storageKey` 指向本地图片 Blob;`naturalWidth/naturalHeight/bytes/mimeType` 保存原图信息。 +- 文本节点:`content` 保存文本内容;`fontSize` 保存字体大小;`prompt/status/errorDetails` 保存生成状态。 +- 生成配置节点:`generationMode/model/size/count/inputOrder` 保存生成配置;上游输入通过 `connections` 计算。 +- 图片组节点:根节点用 `isBatchRoot/batchChildIds/primaryImageId/imageBatchExpanded` 记录批量生成结果;子图节点用 `batchRootId` 指回根节点。 + +## 连线结构 + +每条连线是一个 `CanvasConnection`: + +```ts +type CanvasConnection = { + id: string; + fromNodeId: string; + toNodeId: string; +}; +``` + +连线只保存节点 ID,不保存端口坐标。渲染时根据节点位置和尺寸计算路径。 + +删除节点时会同步删除以该节点为起点或终点的连线。删除图片组根节点时,会把对应子节点一起删除。 + +## 助手会话结构 + +助手会话保存在画布项目内: + +```ts +type CanvasAssistantSession = { + id: string; + title: string; + messages: CanvasAssistantMessage[]; + createdAt: string; + updatedAt: string; +}; +``` + +消息结构: + +```ts +type CanvasAssistantMessage = { + id: string; + role: "user" | "assistant"; + mode: "ask" | "image"; + text: string; + isLoading?: boolean; + references?: CanvasAssistantReference[]; + images?: CanvasAssistantImage[]; +}; +``` + +图片引用和助手生成图片也遵循同一套图片存储规则: + +- `dataUrl` 字段当前可能是 `blob:` URL,也可能是旧数据中的 `data:image/...`。 +- `storageKey` 存在时,以 `storageKey` 为准读取图片 Blob。 +- 发送到 AI 接口前,如果接口需要 base64,会通过 `imageToDataUrl` 临时把 Blob URL 转成 data URL。 + +## 图片写入流程 + +所有新增图片应通过 `uploadImage(input)` 写入: + +1. 传入 `Blob` 或 data URL。 +2. 内部转成 `Blob`。 +3. 生成 `storageKey`,格式为 `image:`。 +4. 把 Blob 写入 `image_files`。 +5. 创建 `blob:` URL,并缓存在内存 `objectUrls`。 +6. 读取图片宽高,返回: + +```ts +type UploadedImage = { + url: string; + storageKey: string; + width: number; + height: number; + bytes: number; + mimeType: string; +}; +``` + +图片节点会通过 `imageMetadata(image)` 写入: + +```ts +{ + content: image.url, + storageKey: image.storageKey, + status: "success", + naturalWidth: image.width, + naturalHeight: image.height, + bytes: image.bytes, + mimeType: image.mimeType +} +``` + +因此,`content` 只适合当前浏览器会话展示,不能作为长期文件标识;长期标识是 `storageKey`。 + +## 图片读取和旧数据迁移 + +打开画布时会执行图片补水: + +- 如果图片节点有 `storageKey`,通过 `resolveImageUrl(storageKey, fallback)` 读取 Blob 并生成新的 `blob:` URL。 +- 如果图片节点没有 `storageKey`,但 `content` 是旧的 `data:image/...`,会调用 `uploadImage(content)` 迁移到 `image_files`,并补上 `storageKey`。 +- 助手消息里的引用图和生成图也会执行同类逻辑。 + +我的素材读取时也会做迁移: + +- 有 `storageKey`:恢复 `coverUrl` 和 `data.dataUrl` 的可展示 URL。 +- 无 `storageKey` 且保存了 base64:写入 `image_files`,然后更新素材里的 `storageKey`。 + +## 图片移除和清理 + +图片不是在删除节点时立即按节点逐张删除,而是做引用清理: + +1. 删除节点、清空画布、删除画布、删除素材、删除助手会话时,会触发 `cleanupImages`。 +2. `cleanupImages` 会收集当前仍被画布项目、素材和额外传入数据引用的所有 `storageKey`。 +3. `cleanupUnusedImages` 遍历 `image_files` 中的全部图片。 +4. 不在引用集合里的图片会被删除。 +5. 删除时会同时 `URL.revokeObjectURL`,并从内存缓存 `objectUrls` 移除。 + +这套方式可以避免同一张图片被画布、素材或助手同时引用时误删。 + +需要注意: + +- 只要某个 JSON 结构里仍有 `storageKey`,清理逻辑就会认为图片仍被使用。 +- `collectImageStorageKeys` 会递归扫描对象中的 `storageKey` 字段,字段值必须以 `image:` 开头才会被当成本地图片。 +- 如果后续新增保存图片引用的数据结构,也要确保它能传入清理上下文,或者位于现有项目/素材结构内。 + +## 后端存储兼容建议 + +后续接入后端时,建议保持“画布 JSON”和“图片文件”分离: + +- 画布表保存项目元信息和画布 JSON。 +- 文件表保存图片文件、访问 URL、哈希、大小、MIME、宽高、归属用户等信息。 +- 画布节点中继续保存轻量图片引用,不把图片二进制或 base64 写进画布 JSON。 + +建议图片引用逐步扩展为兼容本地和云端的结构: + +```ts +type ImageRef = { + storageKey?: string; + fileId?: string; + url?: string; + width?: number; + height?: number; + bytes?: number; + mimeType?: string; +}; +``` + +兼容规则: + +- 本地旧数据:有 `storageKey`,无 `fileId`,通过 IndexedDB 读取。 +- 已上传后端:有 `fileId`,展示时优先使用后端返回的签名 URL 或公开 URL。 +- 迁移过渡期:可以同时保留 `storageKey` 和 `fileId`;确认云端文件可用后,再按清理策略删除本地 Blob。 +- `content/dataUrl/coverUrl` 仍只作为当前可展示 URL,不作为稳定 ID。 + +建议读取优先级: + +1. 有 `fileId`:向后端换取可访问 URL。 +2. 有 `storageKey`:从本地 IndexedDB 生成 `blob:` URL。 +3. 有旧 `data:image/...`:先写入本地图片存储,再视需要上传后端。 +4. 只有普通 URL:直接展示,但不要假设可长期访问。 + +建议删除策略: + +- 删除节点只删除画布 JSON 引用,不直接删除后端文件。 +- 后端文件删除应按引用计数或定期扫描未引用文件处理。 +- 保存到“我的素材”的图片,即使原画布节点删除,也应继续保留文件引用。 +- 删除画布、删除素材、删除助手会话后,再由后端清理任务判断文件是否无人引用。 + +建议同步流程: + +1. 前端保存画布 JSON 时,保持节点 ID、连线 ID、`storageKey/fileId` 不变。 +2. 遇到只有 `storageKey` 的图片,后台同步前先上传 Blob,得到 `fileId`。 +3. 上传成功后给对应图片引用补 `fileId` 和云端元信息。 +4. 服务端保存更新后的画布 JSON。 +5. 前端下次打开时优先走 `fileId`,本地 `storageKey` 只作为缓存或离线回退。 + +## 后续改动约束 + +- 不要把新生成的大图直接长期写入画布 JSON。 +- 新增图片来源时统一走 `uploadImage` 或未来的文件上传服务。 +- 新增图片引用字段时,应保留 `storageKey` 兼容旧本地数据。 +- 新增清理入口时,要把仍需保留的画布、素材、助手数据传给 `cleanupUnusedImages`。 +- 后端同步完成前,文档和 UI 不要写成已支持云同步。 diff --git a/docs/canvas-node-manual.md b/docs/canvas-node-manual.md new file mode 100644 index 0000000..6e27513 --- /dev/null +++ b/docs/canvas-node-manual.md @@ -0,0 +1,39 @@ +# 画布节点操作手册 + +本文档记录画布节点的主要用途和操作流程,方便使用和后续开发维护。当前先介绍文本节点。 + +## 文本节点 + +文本节点用于保存提示词、草稿、说明文案和 AI 生成的文字结果。它既可以作为独立文本内容,也可以作为下游生成配置节点的输入。 + +### 编辑文本内容 + +- 双击文本节点内容区域,直接编辑节点里的文字。 +- 选中文本节点后,点击顶部工具栏的“编辑文字”,进入文字编辑状态。 +- 顶部工具栏的“缩小”和“放大”用于调整文本节点字号。 + +### 用下方对话框生成或修改文本 + +- 选中文本节点后,点击顶部工具栏的“编辑”,打开节点下方对话框。 +- 文本节点下方对话框只用于生成或改写文本,不承担生图功能。 +- 当文本节点为空时,输入框用于填写想生成的文本内容;点击发送后,结果会回填到当前文本节点。 +- 当文本节点已有内容时,输入框用于填写想把本段文本修改成什么;点击发送后,会在右侧生成新的文本节点,并自动连接原节点和新节点。 +- 输入内容可以手写,也可以从提示词库选择。 +- 对话框里的模型下拉来自全局配置里已拉取的模型列表;选择结果只作用于当前节点,不会修改其它节点或全局默认模型。 +- 如果下拉中没有模型,需要先打开配置弹窗拉取模型列表,并设置默认生图模型和默认文本模型。 + +### 用文本节点生成图片 + +- 先在文本节点中准备好要用于生图的文本内容。 +- 点击文本节点顶部工具栏的“生图”按钮。 +- 系统会在文本节点右侧自动创建一个生成配置节点,并连接文本节点到生成配置节点。 +- 生成配置节点会读取上游文本内容作为生图提示词,并立即开始生成图片。 +- 后续需要调整模型、比例、数量时,可以在生成配置节点里修改后再次生成。 + +### 推荐流程 + +1. 新建文本节点,写入图片创作想法。 +2. 打开文本节点下方对话框,让 AI 优化或扩写这段提示词。 +3. 改写结果会生成到新的文本节点,保留原始文本方便对比。 +4. 确认文本节点内容后,点击顶部工具栏“生图”。 +5. 在自动创建的生成配置节点中继续调整图片参数或重新生成。 diff --git a/docs/canvas-shortcuts.md b/docs/canvas-shortcuts.md new file mode 100644 index 0000000..c61539d --- /dev/null +++ b/docs/canvas-shortcuts.md @@ -0,0 +1,36 @@ +# 画布快捷键 + +本文档记录画布里常用的鼠标和键盘操作。 + +## 视图 + +- 拖动画布空白处:平移视图。 +- 鼠标滚轮:缩放画布。 +- 缩放滑杆:精确调整缩放。 +- 重置视图按钮:回到默认缩放和居中位置。 + +## 选择 + +- `Ctrl / Cmd` + 拖动:框选多个节点。 +- `Shift / Ctrl / Cmd` + 点击节点:追加或取消选择节点。 +- `Ctrl / Cmd` + `A`:全选画布节点。 +- `Esc`:取消选择,并关闭当前浮层。 + +## 编辑 + +- `Ctrl / Cmd` + `C`:复制选中节点。 +- `Ctrl / Cmd` + `V`:粘贴节点。 +- `Delete / Backspace`:删除选中的节点或连线。 +- `Ctrl / Cmd` + `Z`:撤销。 +- `Ctrl / Cmd` + `Shift` + `Z`:重做。 +- `Ctrl / Cmd` + `Y`:重做。 + +## 图片和素材 + +- 拖入图片文件:上传图片到画布。 +- 导入图片按钮:从本地选择图片。 +- 素材库或我的素材:选择素材后插入画布。 + +## 撤销和重做范围 + +撤销和重做会记录画布节点、连线、视口、背景模式和助手会话变化。画布项目名称、账号状态、全局 AI 配置和“我的素材”保存操作不属于当前画布历史。 diff --git a/docs/features.md b/docs/features.md new file mode 100644 index 0000000..f05f192 --- /dev/null +++ b/docs/features.md @@ -0,0 +1,166 @@ +# 功能介绍 + +本文档记录当前项目已经实现的主要功能。 + +## 画布项目 + +- 支持创建多个画布项目。 +- 支持项目重命名、删除、批量选择和批量删除。 +- 支持单个画布项目导出为 JSON,也支持从 JSON 导入画布。 +- 画布项目保存在浏览器本地,登录账号后暂不会自动同步到服务器。 + +## 无限画布 + +- 支持拖动画布、滚轮缩放、缩放滑杆和重置视图。 +- 支持小地图定位,可开关小地图。 +- 支持点阵、网格线、空白三种背景。 +- 支持浅色和深色主题。 +- 支持框选、多选、全选、取消选择、删除选中。 +- 支持复制粘贴节点和节点之间的连线。 +- 支持撤销和重做节点、连线、视口、背景和助手会话变化。 +- 支持节点连线,并高亮当前节点相关的上下游节点和连线。 +- 支持快捷键帮助,覆盖缩放、框选、全选、复制粘贴、撤销重做、删除、退出选择和拖入图片。 + +## 节点 + +目前画布中有三类节点: + +- 图片节点:展示上传图片、生成图片或素材库图片。 +- 文本节点:保存提示词、说明文案、AI 文字回答等文本内容。 +- 生成配置节点:汇总上游文本和图片,统一配置模型、比例、数量后批量生成图片或文本。 + +节点支持: + +- 拖拽移动。 +- 四角缩放。 +- 图片节点等比缩放或自由比例切换。 +- 查看节点基础信息和 JSON。 +- 删除、复制、粘贴。 +- 通过左右连接点建立上下游关系。 + +## 图片工作流 + +- 支持上传图片到新节点。 +- 支持拖拽图片文件到画布。 +- 支持替换已有图片节点内容。 +- 支持下载图片节点。 +- 支持把图片节点保存到“我的素材”。 +- 支持图片裁剪,并把裁剪结果生成为新的图片节点。 +- 支持本地多角度变换,并把结果生成为新的图片节点。 +- 支持生成失败后重试。 +- 批量生成多张图片时会先展示为图片组节点,支持叠卡预览、展开查看全部结果并设置主图。 + +## AI 生成 + +项目通过前端直接请求 OpenAI 兼容接口: + +- `/v1/images/generations`:文生图。 +- `/v1/images/edits`:图生图/参考图编辑。 +- `/v1/chat/completions`:文本问答和带图问答。 +- `/v1/models`:读取模型列表。 + +可配置项: + +- Base URL。 +- API Key。 +- 默认模型。 +- 图片质量。 +- 图片比例。 +- 生成数量。 + +普通图片/文本节点可以直接输入提示词生成结果。生成配置节点可以读取上游节点内容,并按节点自己的配置批量生成多个图片或文本结果。生成配置节点支持预览当前提示词和参考图输入,并调整输入顺序。 + +## 画布助手 + +画布右侧助手面板支持: + +- 文本问答。 +- 生图。 +- 读取当前选中节点作为引用。 +- 自动把选中节点的上游节点也纳入引用。 +- 粘贴图片到助手输入框并插入画布。 +- 历史会话。 +- 删除单条或多条会话。 +- 重试回答。 +- 把助手生成的文本插入画布。 +- 把助手生成的图片插入画布。 +- 折叠和展开助手面板。 + +## 提示词库 + +前台提示词库支持: + +- 按标题搜索。 +- 按标签筛选。 +- 按来源筛选。 +- 查看提示词详情。 +- 查看封面和结果图。 +- 复制提示词。 +- 把提示词加入“我的素材”。 + +后台提示词管理支持: + +- 查询提示词。 +- 新增、编辑、删除提示词。 +- 按分组和标签筛选。 +- 查看远程提示词源。 +- 同步内置远程提示词源。 + +当前内置远程源包括多个 GPT Image / GPT-4o / Nano Banana Pro 相关提示词仓库。 + +## 素材 + +“我的素材”是浏览器本地素材库,支持: + +- 新增文本素材和图片素材。 +- 编辑素材标题、封面、标签、来源、备注和内容。 +- 删除素材。 +- 按关键词搜索。 +- 按类型筛选。 +- 分页浏览。 +- 复制文本素材。 +- 下载图片素材。 +- 从提示词库、画布节点和服务器素材库加入素材。 +- 在画布中插入素材。 + +“素材库”是服务器素材库,支持: + +- 按标题搜索。 +- 按类型筛选。 +- 按标签筛选。 +- 查看素材详情。 +- 复制文本或图片链接。 +- 加入“我的素材”。 +- 在画布中插入素材。 + +后台素材库管理支持: + +- 查询素材。 +- 新增、编辑、删除素材。 +- 按类型和标签筛选。 + +## 账号和后台 + +- 注册功能暂时关闭。 +- 仅允许管理员账号登录。 +- 支持 JWT 会话。 +- `/api/auth/me` 可读取当前用户,未登录时返回访客用户。 +- 首次启动时可根据环境变量创建默认管理员。 +- 管理员后台目前包含提示词管理和素材库管理。 +- 后端已有用户管理接口,但前端暂未实现用户管理页面。 + +## 后端能力 + +- Gin 提供 API 服务。 +- Docker 运行时由 Go 提供统一入口,`/api/*` 直接处理,其它页面请求转到内部 Next.js 服务。 +- GORM 管理数据库连接和自动迁移。 +- 支持 SQLite、MySQL、PostgreSQL。 +- 数据库保存用户、提示词分组、提示词和服务器素材。 +- 业务接口统一返回 `{ code, data, msg }`。 + +## 当前限制 + +- 画布项目和“我的素材”目前只保存在浏览器本地,不会随账号同步。 +- AI API Key 目前保存在浏览器本地,并由浏览器直接请求配置的 OpenAI 兼容接口。 +- 服务器素材库目前主要保存 URL 或文本,暂未提供文件上传接口。 +- 画布更适合桌面端使用,移动端触控体验还未系统完善。 diff --git a/docs/pending-test.md b/docs/pending-test.md new file mode 100644 index 0000000..bcab4b0 --- /dev/null +++ b/docs/pending-test.md @@ -0,0 +1 @@ +# 待测试 diff --git a/docs/third-party-prompt-repositories.md b/docs/third-party-prompt-repositories.md new file mode 100644 index 0000000..cb6d0b3 --- /dev/null +++ b/docs/third-party-prompt-repositories.md @@ -0,0 +1,9 @@ +# 第三方 GitHub 提示词仓库 + +| 地址 | 状态 | +| --- | --- | +| https://github.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts | 已实现同步逻辑 | +| https://github.com/ZeroLu/awesome-gpt-image | 已实现同步逻辑 | +| https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts | 已实现同步逻辑 | +| https://github.com/YouMind-OpenLab/awesome-gpt-image-2 | 已实现同步逻辑 | +| https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts | 已实现同步逻辑 | diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000..a9345e8 --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,49 @@ +# TODO + +本文档用来记录当前项目后续比较值得处理的事项。 + +## P0 近期优先 + +暂无。 + +## P1 核心账号和后台 + +- 登录注册和用户模块:新增 `users` 表,用户角色、算力点余额、邀请码、邀请人、邀请人数和第三方平台用户 ID + 等直接放在用户表字段里;补齐登录、注册、第三方登录、用户信息、管理员用户管理等基础能力。 +- 系统设置和模型配置:新增 `settings` 表,只保存 `public` 和 `private` 两行 JSON 配置,用于系统可用模型、模型渠道、系统提示词、用户是否允许自定义模型等后台开关。 +- 字典管理:新增 `dicts` 表,一个字典一行,具体字典项用 JSON 保存;用于维护分类、标签、业务枚举、模型分类、日志类型等可配置项。 +- 日志管理:新增 `credit_logs` 记录用户算力点变更流水,新增 `api_logs` 记录第三方大模型 API 调用情况。 +- 对话工具调用:在助手对话中增加类似工具调用的形式,展示模型调用、生成图片、插入画布等步骤。 +- 画布助手对话框优化:把当前对话面板调整得更简洁,减少干扰,突出输入、引用和结果。 +- 考虑引入视频生成能力 + +## P1 算力点和商业化 + +- 算力点模块:使用 `users` 表里的算力点余额字段,用于调用后端生图、文本等接口时扣除;扣除记录写入 `credit_logs`。 +- 算力点订阅模块:订阅套餐配置先放 `settings.public.value`,用户订阅记录新增 `subscriptions` + 表;用户购买订阅后,模型调用优先从可用订阅额度扣除,不足时再扣用户余额。 +- 支付模块:新增 `orders` 表,统一记录充值、订阅购买等订单,预留 Linux LDC 支付和第三方聚合支付;该模块优先级较低,先只设计结构,暂不重点实现。 +- 邀请奖励模块:邀请码、邀请人和邀请人数放 `users`,注册奖励、充值奖励配置放 `settings.private.value`,奖励发放记录写入 + `credit_logs`。 + +## P1 内容、素材和画布 + +- 文件存储管理:新增 `files` 表和存储接口,文件表只记录最终可访问 URL 等基础信息;后续再考虑图片缩略图、文件清理等能力,后台可查询所有图片和文件。 +- 提示词管理:新增 `prompts` 表,公开提示词、内置 GitHub 系统提示词、分类和扩展信息等尽量放字段或 JSON,支持后台维护和随时爬取更新。 +- 提示词分类:已移除 `prompt_groups` 表;分类数据很少,并且必须和抓取代码一一对应,直接在代码内存里写死维护。 +- 画布管理:新增 `canvases` 表,后台管理员可以查看每个人的画布数据,普通用户可以查看自己的画布列表。 +- 公开画布:在 `canvases` 表增加公开状态、审核状态、公开信息等字段;管理员可将自己的画布设为公开,用户也可以申请公开并由管理员审核。 +- 画布分享与模板:在云端画布基础上支持只读分享、复制为模板、团队内共享,分享配置和模板标记优先放在 `canvases` 字段或 JSON 中。 +- 项目库:基于公开画布增加系统公开项目库,用于筛选、打开、复制公开画布项目。 +- 画布协作能力:后续如果支持多人协作,协作成员、节点操作事件、锁定/光标、冲突处理和增量同步优先放在 `canvases` 的协作 JSON + 中,确实不够用时再拆表。 +- 素材管理和我的素材:新增 `assets` 表,用 `user_id`、`visibility`、`type` 区分系统公开素材和用户私有素材;管理员可管理公开素材,也可以查看每个用户自己的素材。 +- 素材互动:公开素材增加点赞量、收藏量、查看量等统计字段;用户侧我的收藏、我的点赞先放在 `assets` 或用户偏好 JSON + 中,后续数据量大了再拆互动表。 + +## P2 体验和工程 + +- 移动端适配:先不整体展开,只围绕在线生图等移动端必要页面做基础适配。 +- 接口调用队列:图片/文本生成接口增加队列、并发限制、后台状态和基础失败处理,避免大量请求直接打到模型渠道。 +- README 优化:保持 README 简洁,补项目介绍、核心功能、快速开始和文档入口。 +- 版本发布流程:规范版本号、changelog、release 脚本、镜像 tag 规则和升级说明。 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a365603 --- /dev/null +++ b/go.mod @@ -0,0 +1,64 @@ +module github.com/basketikun/infinite-canvas + +go 1.25.0 + +require ( + github.com/caarlos0/env/v11 v11.3.1 + github.com/gin-gonic/gin v1.11.0 + github.com/glebarez/sqlite v1.11.0 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/joho/godotenv v1.5.1 + golang.org/x/crypto v0.48.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.2.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/google/uuid v1.3.0 + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..452518f --- /dev/null +++ b/go.sum @@ -0,0 +1,138 @@ +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/handler/admin.go b/handler/admin.go new file mode 100644 index 0000000..4f39d0a --- /dev/null +++ b/handler/admin.go @@ -0,0 +1,60 @@ +package handler + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/service" +) + +type adminSyncRequest struct { + Category string `json:"category"` +} + +func AdminPromptCategories(w http.ResponseWriter, r *http.Request) { + OK(w, service.ListPromptCategories()) +} + +func AdminPrompts(w http.ResponseWriter, r *http.Request) { + result, err := service.ListPrompts(parseQuery(r)) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} + +func AdminSavePrompt(w http.ResponseWriter, r *http.Request) { + var item model.Prompt + _ = json.NewDecoder(r.Body).Decode(&item) + result, err := service.SavePrompt(item) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} + +func AdminDeletePrompt(w http.ResponseWriter, r *http.Request, id string) { + if err := service.DeletePrompt(id); err != nil { + Fail(w, err.Error()) + return + } + OK(w, true) +} + +func AdminSyncPromptCategories(w http.ResponseWriter, r *http.Request) { + var request adminSyncRequest + _ = json.NewDecoder(r.Body).Decode(&request) + log.Printf("sync prompt category start category=%s", request.Category) + categories, err := service.SyncPromptCategory(request.Category) + if err != nil { + log.Printf("sync prompt category failed category=%s err=%v", request.Category, err) + Fail(w, err.Error()) + return + } + log.Printf("sync prompt category done category=%s", request.Category) + OK(w, categories) +} diff --git a/handler/assets.go b/handler/assets.go new file mode 100644 index 0000000..71a748a --- /dev/null +++ b/handler/assets.go @@ -0,0 +1,46 @@ +package handler + +import ( + "encoding/json" + "net/http" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/service" +) + +func Assets(w http.ResponseWriter, r *http.Request) { + result, err := service.ListAssets(parseQuery(r)) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} + +func AdminAssets(w http.ResponseWriter, r *http.Request) { + result, err := service.ListAssets(parseQuery(r)) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} + +func AdminSaveAsset(w http.ResponseWriter, r *http.Request) { + var item model.Asset + _ = json.NewDecoder(r.Body).Decode(&item) + result, err := service.SaveAsset(item) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} + +func AdminDeleteAsset(w http.ResponseWriter, r *http.Request, id string) { + if err := service.DeleteAsset(id); err != nil { + Fail(w, err.Error()) + return + } + OK(w, true) +} diff --git a/handler/auth.go b/handler/auth.go new file mode 100644 index 0000000..7a0f1d7 --- /dev/null +++ b/handler/auth.go @@ -0,0 +1,107 @@ +package handler + +import ( + "encoding/json" + "net/http" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/service" +) + +type loginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type registerRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type saveUserRequest struct { + ID string `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + Role model.UserRole `json:"role"` +} + +func Register(w http.ResponseWriter, r *http.Request) { + var request registerRequest + _ = json.NewDecoder(r.Body).Decode(&request) + session, err := service.Register(request.Username, request.Password) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, session) +} + +func Login(w http.ResponseWriter, r *http.Request) { + var request loginRequest + _ = json.NewDecoder(r.Body).Decode(&request) + session, err := service.Login(request.Username, request.Password) + if err != nil { + Fail(w, err.Error()) + return + } + if session.User.Role != model.UserRoleAdmin { + Fail(w, "需要管理员权限") + return + } + OK(w, session) +} + +func AdminLogin(w http.ResponseWriter, r *http.Request) { + var request loginRequest + _ = json.NewDecoder(r.Body).Decode(&request) + session, err := service.Login(request.Username, request.Password) + if err != nil { + Fail(w, err.Error()) + return + } + if session.User.Role != model.UserRoleAdmin { + Fail(w, "需要管理员权限") + return + } + OK(w, session) +} + +func CurrentUser(w http.ResponseWriter, r *http.Request) { + if user, ok := service.UserFromContext(r.Context()); ok { + OK(w, user) + return + } + OK(w, service.GuestUser()) +} + +func AdminUsers(w http.ResponseWriter, r *http.Request) { + users, err := service.ListUsers(parseQuery(r)) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, users) +} + +func AdminSaveUser(w http.ResponseWriter, r *http.Request) { + var request saveUserRequest + _ = json.NewDecoder(r.Body).Decode(&request) + user, err := service.SaveUser(model.User{ + ID: request.ID, + Username: request.Username, + Role: request.Role, + }, request.Password) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, user) +} + +func AdminDeleteUser(w http.ResponseWriter, r *http.Request, id string) { + if err := service.DeleteUser(id); err != nil { + Fail(w, err.Error()) + return + } + OK(w, true) +} diff --git a/handler/prompts.go b/handler/prompts.go new file mode 100644 index 0000000..e8b6c60 --- /dev/null +++ b/handler/prompts.go @@ -0,0 +1,16 @@ +package handler + +import ( + "net/http" + + "github.com/basketikun/infinite-canvas/service" +) + +func Prompts(w http.ResponseWriter, r *http.Request) { + result, err := service.ListPrompts(parseQuery(r)) + if err != nil { + Fail(w, err.Error()) + return + } + OK(w, result) +} diff --git a/handler/response.go b/handler/response.go new file mode 100644 index 0000000..5ab66e4 --- /dev/null +++ b/handler/response.go @@ -0,0 +1,42 @@ +package handler + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/basketikun/infinite-canvas/model" +) + +type response struct { + Code int `json:"code"` + Data any `json:"data"` + Msg string `json:"msg"` +} + +func OK(w http.ResponseWriter, data any) { + writeJSON(w, response{Code: 0, Data: data, Msg: "ok"}) +} + +func Fail(w http.ResponseWriter, msg string) { + writeJSON(w, response{Code: 1, Data: nil, Msg: msg}) +} + +func writeJSON(w http.ResponseWriter, value any) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(value) +} + +func parseQuery(r *http.Request) model.Query { + q := r.URL.Query() + page, _ := strconv.Atoi(q.Get("page")) + pageSize, _ := strconv.Atoi(q.Get("pageSize")) + return model.Query{ + Keyword: q.Get("keyword"), + Tags: q["tag"], + Category: q.Get("category"), + Type: q.Get("type"), + Page: page, + PageSize: pageSize, + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..650105f --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + + "github.com/basketikun/infinite-canvas/config" + "github.com/basketikun/infinite-canvas/router" + "github.com/basketikun/infinite-canvas/service" +) + +func main() { + if err := config.Load(); err != nil { + log.Fatal(err) + } + if err := service.EnsureDefaultAdmin(); err != nil { + log.Fatal(err) + } + log.Fatal(router.New().Run(":" + config.Cfg.Port)) +} diff --git a/middleware/admin.go b/middleware/admin.go new file mode 100644 index 0000000..9995596 --- /dev/null +++ b/middleware/admin.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/basketikun/infinite-canvas/handler" + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/service" + "github.com/gin-gonic/gin" +) + +func AdminAuth(c *gin.Context) { + user, ok := authUser(c) + if !ok || user.Role != model.UserRoleAdmin { + handler.Fail(c.Writer, "未登录或权限不足") + c.Abort() + return + } + c.Request = c.Request.WithContext(service.WithUser(c.Request.Context(), user)) + c.Next() +} + +func OptionalAuth(c *gin.Context) { + if user, ok := authUser(c); ok { + c.Request = c.Request.WithContext(service.WithUser(c.Request.Context(), user)) + } + c.Next() +} + +func NotFoundJSON(c *gin.Context) { + c.JSON(http.StatusNotFound, gin.H{"code": 1, "data": nil, "msg": "接口不存在"}) +} + +func authUser(c *gin.Context) (model.AuthUser, bool) { + token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ") + if strings.TrimSpace(token) == "" { + return model.AuthUser{}, false + } + return service.CurrentAuthUser(token) +} diff --git a/model/asset.go b/model/asset.go new file mode 100644 index 0000000..db3a414 --- /dev/null +++ b/model/asset.go @@ -0,0 +1,30 @@ +package model + +type AssetType string + +const ( + AssetTypeText AssetType = "text" + AssetTypeImage AssetType = "image" +) + +// Asset 素材记录。 +type Asset struct { + ID string `json:"id" gorm:"primaryKey"` + Title string `json:"title"` + Type AssetType `json:"type"` + CoverURL string `json:"coverUrl"` + Tags []string `json:"tags" gorm:"serializer:json"` + Category string `json:"category"` + Description string `json:"description"` + Content string `json:"content,omitempty"` + URL string `json:"url,omitempty"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// AssetList 素材分页结果。 +type AssetList struct { + Items []Asset `json:"items"` + Tags []string `json:"tags"` + Total int `json:"total"` +} diff --git a/model/prompt.go b/model/prompt.go new file mode 100644 index 0000000..2f84ee8 --- /dev/null +++ b/model/prompt.go @@ -0,0 +1,33 @@ +package model + +// Prompt 提示词记录。 +type Prompt struct { + ID string `json:"id" gorm:"primaryKey"` + Title string `json:"title"` + CoverURL string `json:"coverUrl"` + Prompt string `json:"prompt"` + Tags []string `json:"tags" gorm:"serializer:json"` + Category string `json:"category" gorm:"index"` + GithubURL string `json:"githubUrl" gorm:"-"` + Preview string `json:"preview"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// PromptList 提示词分页结果。 +type PromptList struct { + Items []Prompt `json:"items"` + Tags []string `json:"tags"` + Categories []string `json:"categories"` + Total int `json:"total"` +} + +// PromptCategory 提示词分类。 +type PromptCategory struct { + Category string `json:"category" gorm:"primaryKey"` + Name string `json:"name"` + Description string `json:"description"` + GithubURL string `json:"githubUrl"` + Remote bool `json:"remote"` + UpdatedAt string `json:"updatedAt"` +} diff --git a/model/query.go b/model/query.go new file mode 100644 index 0000000..8d9e88f --- /dev/null +++ b/model/query.go @@ -0,0 +1,29 @@ +package model + +const MaxPageSize = 500 + +// Query 列表筛选和分页参数。 +type Query struct { + Keyword string + Tags []string + Category string + Type string + Page int + PageSize int +} + +func (q *Query) Normalize() { + if q.Page < 1 { + q.Page = 1 + } + if q.PageSize < 1 { + q.PageSize = 20 + } + if q.PageSize > MaxPageSize { + q.PageSize = MaxPageSize + } +} + +func (q *Query) Offset() int { + return (q.Page - 1) * q.PageSize +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..770643c --- /dev/null +++ b/model/user.go @@ -0,0 +1,50 @@ +package model + +type UserRole string + +const ( + UserRoleGuest UserRole = "guest" + UserRoleUser UserRole = "user" + UserRoleAdmin UserRole = "admin" +) + +// User 系统用户。 +type User struct { + ID string `json:"id" gorm:"primaryKey"` + Username string `json:"username" gorm:"uniqueIndex"` + Password string `json:"password,omitempty"` + Role UserRole `json:"role"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// UserList 用户分页结果。 +type UserList struct { + Items []User `json:"items"` + Total int `json:"total"` +} + +// AuthUser 用户公开信息。 +type AuthUser struct { + ID string `json:"id"` + Username string `json:"username"` + Role UserRole `json:"role"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// AuthSession 登录会话信息。 +type AuthSession struct { + Token string `json:"token"` + User AuthUser `json:"user"` +} + +func PublicUser(user User) AuthUser { + return AuthUser{ + ID: user.ID, + Username: user.Username, + Role: user.Role, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } +} diff --git a/repository/asset.go b/repository/asset.go new file mode 100644 index 0000000..d0493f9 --- /dev/null +++ b/repository/asset.go @@ -0,0 +1,132 @@ +package repository + +import ( + "errors" + + "github.com/basketikun/infinite-canvas/model" + "gorm.io/gorm" +) + +// ListAssets 按查询条件返回素材分页列表。 +func ListAssets(q model.Query) ([]model.Asset, int64, error) { + db, err := DB() + if err != nil { + return nil, 0, err + } + q.Normalize() + tx := applyAssetFilters(db.Model(&model.Asset{}), q) + + var total int64 + if err := tx.Count(&total).Error; err != nil { + return nil, 0, err + } + + var items []model.Asset + err = tx.Order("updated_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&items).Error + return items, total, err +} + +// ListAssetTags 返回当前素材查询条件下的全部标签。 +func ListAssetTags(q model.Query) ([]string, error) { + db, err := DB() + if err != nil { + return nil, err + } + q.Normalize() + q.Tags = nil + tx := applyAssetFilters(db.Model(&model.Asset{}), q) + + var items []model.Asset + if err := tx.Select("tags").Find(&items).Error; err != nil { + return nil, err + } + return assetTagsFromItems(items), nil +} + +// SaveAsset 保存素材,并在更新时保留原创建时间。 +func SaveAsset(item model.Asset) (model.Asset, error) { + db, err := DB() + if err != nil { + return item, err + } + if saved, ok, err := findAsset(db, item.ID); err != nil { + return item, err + } else if ok && item.CreatedAt == "" { + item.CreatedAt = saved.CreatedAt + } + return item, db.Save(&item).Error +} + +// DeleteAsset 删除指定素材。 +func DeleteAsset(id string) error { + db, err := DB() + if err != nil { + return err + } + return db.Delete(&model.Asset{}, "id = ?", id).Error +} + +// applyAssetFilters 应用素材列表的搜索条件。 +func applyAssetFilters(tx *gorm.DB, q model.Query) *gorm.DB { + if q.Keyword != "" { + like := "%" + q.Keyword + "%" + tx = tx.Where("title LIKE ? OR description LIKE ? OR content LIKE ?", like, like, like) + } + if isActiveAssetOption(q.Type) { + tx = tx.Where("type = ?", q.Type) + } + return applyAssetTagsFilter(tx, q.Tags) +} + +// findAsset 根据 ID 查询素材。 +func findAsset(db *gorm.DB, id string) (model.Asset, bool, error) { + item := model.Asset{} + err := db.Where("id = ?", id).First(&item).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return model.Asset{}, false, nil + } + return item, err == nil, err +} + +// applyAssetTagsFilter 应用 JSON 标签条件。 +func applyAssetTagsFilter(tx *gorm.DB, tags []string) *gorm.DB { + if len(tags) == 0 { + return tx + } + condition := tx.Session(&gorm.Session{NewDB: true}) + for _, tag := range tags { + condition = condition.Or(assetJSONTagsContains(tx), tag) + } + return tx.Where(condition) +} + +func assetTagsFromItems(items []model.Asset) []string { + seen := map[string]bool{} + tags := []string{} + for _, item := range items { + for _, tag := range item.Tags { + if tag != "" && !seen[tag] { + seen[tag] = true + tags = append(tags, tag) + } + } + } + return tags +} + +// assetJSONTagsContains 返回素材 tags 的 JSON 包含条件。 +func assetJSONTagsContains(tx *gorm.DB) string { + switch tx.Dialector.Name() { + case "mysql": + return "JSON_CONTAINS(tags, JSON_QUOTE(?))" + case "postgres": + return "jsonb_exists(tags::jsonb, ?)" + default: + return "EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)" + } +} + +// isActiveAssetOption 判断素材筛选项有效状态。 +func isActiveAssetOption(value string) bool { + return value != "" && value != "全部" && value != "all" +} diff --git a/repository/db.go b/repository/db.go new file mode 100644 index 0000000..03b43b1 --- /dev/null +++ b/repository/db.go @@ -0,0 +1,65 @@ +package repository + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "github.com/basketikun/infinite-canvas/config" + "github.com/basketikun/infinite-canvas/model" + "github.com/glebarez/sqlite" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var promptCategories = []model.PromptCategory{ + {Category: "system", Name: "系统", Description: "系统提示词分类"}, + {Category: "gpt-image-2-prompts", Name: "GPT Image 2 Prompts", Description: "EvoLinkAI 的 GPT Image 2 案例提示词分类", GithubURL: "https://github.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts", Remote: true}, + {Category: "awesome-gpt-image", Name: "Awesome GPT Image", Description: "ZeroLu 的中文 GPT Image 提示词分类", GithubURL: "https://github.com/ZeroLu/awesome-gpt-image", Remote: true}, + {Category: "awesome-gpt4o-image-prompts", Name: "Awesome GPT4o Image Prompts", Description: "ImgEdify 的 GPT-4o 图像提示词分类", GithubURL: "https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts", Remote: true}, + {Category: "youmind-gpt-image-2", Name: "YouMind GPT Image 2", Description: "YouMind OpenLab 的 GPT Image 2 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-gpt-image-2", Remote: true}, + {Category: "youmind-nano-banana-pro", Name: "YouMind Nano Banana Pro", Description: "YouMind OpenLab 的 Nano Banana Pro 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts", Remote: true}, +} + +var ( + db *gorm.DB + dbOnce sync.Once + dbErr error +) + +// DB 初始化并返回全局数据库连接。 +func DB() (*gorm.DB, error) { + dbOnce.Do(func() { + driver := strings.ToLower(strings.TrimSpace(config.Cfg.StorageDriver)) + if driver == "" { + driver = "sqlite" + } + dsn := config.Cfg.DatabaseDSN + if driver == "sqlite" && dsn != ":memory:" { + _ = os.MkdirAll(filepath.Dir(dsn), 0755) + } + db, dbErr = gorm.Open(dialector(driver, dsn), &gorm.Config{}) + if dbErr != nil { + return + } + dbErr = db.AutoMigrate( + &model.User{}, + &model.Prompt{}, + &model.Asset{}, + ) + }) + return db, dbErr +} + +func dialector(driver string, dsn string) gorm.Dialector { + switch driver { + case "mysql": + return mysql.Open(dsn) + case "postgres", "postgresql": + return postgres.Open(dsn) + default: + return sqlite.Open(dsn) + } +} diff --git a/repository/prompt.go b/repository/prompt.go new file mode 100644 index 0000000..0d1cfaa --- /dev/null +++ b/repository/prompt.go @@ -0,0 +1,186 @@ +package repository + +import ( + "errors" + + "github.com/basketikun/infinite-canvas/model" + "gorm.io/gorm" +) + +// PromptCategories 返回内置提示词分类的副本。 +func PromptCategories() []model.PromptCategory { + result := make([]model.PromptCategory, len(promptCategories)) + copy(result, promptCategories) + return result +} + +// PromptCategoryByCode 根据分类编码查找内置提示词分类。 +func PromptCategoryByCode(category string) (model.PromptCategory, bool) { + for _, item := range promptCategories { + if item.Category == category { + return item, true + } + } + return model.PromptCategory{}, false +} + +// ListPromptCategories 返回内置提示词分类。 +func ListPromptCategories() ([]model.PromptCategory, error) { + return PromptCategories(), nil +} + +// ListPrompts 按查询条件返回提示词分页列表。 +func ListPrompts(q model.Query) ([]model.Prompt, int64, error) { + db, err := DB() + if err != nil { + return nil, 0, err + } + q.Normalize() + tx := applyPromptFilters(db.Model(&model.Prompt{}), q) + + var total int64 + if err := tx.Count(&total).Error; err != nil { + return nil, 0, err + } + + var items []model.Prompt + if err := tx.Order("updated_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&items).Error; err != nil { + return nil, 0, err + } + categories, _ := ListPromptCategories() + githubURLs := map[string]string{} + for _, item := range categories { + githubURLs[item.Category] = item.GithubURL + } + for i := range items { + items[i].GithubURL = githubURLs[items[i].Category] + } + return items, total, nil +} + +// ListPromptTags 返回当前提示词查询条件下的全部标签。 +func ListPromptTags(q model.Query) ([]string, error) { + db, err := DB() + if err != nil { + return nil, err + } + q.Normalize() + q.Tags = nil + tx := applyPromptFilters(db.Model(&model.Prompt{}), q) + + var items []model.Prompt + if err := tx.Select("tags").Find(&items).Error; err != nil { + return nil, err + } + return promptTagsFromItems(items), nil +} + +// SavePrompt 保存提示词,并在更新时保留原创建时间。 +func SavePrompt(item model.Prompt) (model.Prompt, error) { + db, err := DB() + if err != nil { + return item, err + } + if saved, ok, err := findPrompt(db, item.ID); err != nil { + return item, err + } else if ok && item.CreatedAt == "" { + item.CreatedAt = saved.CreatedAt + } + item.GithubURL = "" + return item, db.Save(&item).Error +} + +// DeletePrompt 删除指定提示词。 +func DeletePrompt(id string) error { + db, err := DB() + if err != nil { + return err + } + return db.Delete(&model.Prompt{}, "id = ?", id).Error +} + +// ReplacePromptCategory 用远程同步结果替换整个提示词分类。 +func ReplacePromptCategory(category model.PromptCategory, items []model.Prompt) error { + db, err := DB() + if err != nil { + return err + } + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.Where("category = ?", category.Category).Delete(&model.Prompt{}).Error; err != nil { + return err + } + if len(items) == 0 { + return nil + } + for i := range items { + items[i].Category = category.Category + items[i].GithubURL = "" + } + return tx.Create(&items).Error + }) +} + +// applyPromptFilters 应用提示词列表的搜索条件。 +func applyPromptFilters(tx *gorm.DB, q model.Query) *gorm.DB { + if q.Keyword != "" { + like := "%" + q.Keyword + "%" + tx = tx.Where("title LIKE ? OR prompt LIKE ?", like, like) + } + if isActivePromptOption(q.Category) { + tx = tx.Where("category = ?", q.Category) + } + return applyPromptTagsFilter(tx, q.Tags) +} + +// findPrompt 根据 ID 查询提示词。 +func findPrompt(db *gorm.DB, id string) (model.Prompt, bool, error) { + item := model.Prompt{} + err := db.Where("id = ?", id).First(&item).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return model.Prompt{}, false, nil + } + return item, err == nil, err +} + +// applyPromptTagsFilter 应用 JSON 标签条件。 +func applyPromptTagsFilter(tx *gorm.DB, tags []string) *gorm.DB { + if len(tags) == 0 { + return tx + } + condition := tx.Session(&gorm.Session{NewDB: true}) + for _, tag := range tags { + condition = condition.Or(promptJSONTagsContains(tx), tag) + } + return tx.Where(condition) +} + +func promptTagsFromItems(items []model.Prompt) []string { + seen := map[string]bool{} + tags := []string{} + for _, item := range items { + for _, tag := range item.Tags { + if tag != "" && !seen[tag] { + seen[tag] = true + tags = append(tags, tag) + } + } + } + return tags +} + +// promptJSONTagsContains 返回提示词 tags 的 JSON 包含条件。 +func promptJSONTagsContains(tx *gorm.DB) string { + switch tx.Dialector.Name() { + case "mysql": + return "JSON_CONTAINS(tags, JSON_QUOTE(?))" + case "postgres": + return "jsonb_exists(tags::jsonb, ?)" + default: + return "EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)" + } +} + +// isActivePromptOption 判断提示词筛选项有效状态。 +func isActivePromptOption(value string) bool { + return value != "" && value != "全部" && value != "all" +} diff --git a/repository/user.go b/repository/user.go new file mode 100644 index 0000000..1ef2615 --- /dev/null +++ b/repository/user.go @@ -0,0 +1,94 @@ +package repository + +import ( + "errors" + + "github.com/basketikun/infinite-canvas/model" + "gorm.io/gorm" +) + +// ListUsers 分页查询用户。 +func ListUsers(q model.Query) ([]model.User, int64, error) { + db, err := DB() + if err != nil { + return nil, 0, err + } + q.Normalize() + tx := db.Model(&model.User{}) + + var total int64 + if err := tx.Count(&total).Error; err != nil { + return nil, 0, err + } + + var users []model.User + err = tx.Order("created_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&users).Error + return users, total, err +} + +// CountUsers 返回用户总数。 +func CountUsers() (int64, error) { + db, err := DB() + if err != nil { + return 0, err + } + var total int64 + return total, db.Model(&model.User{}).Count(&total).Error +} + +// HasAdmin 判断系统中是否存在管理员。 +func HasAdmin() (bool, error) { + db, err := DB() + if err != nil { + return false, err + } + var total int64 + err = db.Model(&model.User{}).Where("role = ?", model.UserRoleAdmin).Count(&total).Error + return total > 0, err +} + +// GetUserByID 根据 ID 查询用户。 +func GetUserByID(id string) (model.User, bool, error) { + db, err := DB() + if err != nil { + return model.User{}, false, err + } + return findUser(db, "id = ?", id) +} + +// GetUserByUsername 根据用户名查询用户。 +func GetUserByUsername(username string) (model.User, bool, error) { + db, err := DB() + if err != nil { + return model.User{}, false, err + } + return findUser(db, "username = ?", username) +} + +// SaveUser 保存用户信息。 +func SaveUser(user model.User) (model.User, error) { + db, err := DB() + if err != nil { + return user, err + } + return user, db.Save(&user).Error +} + +// DeleteUser 删除指定用户。 +func DeleteUser(id string) error { + db, err := DB() + if err != nil { + return err + } + return db.Delete(&model.User{}, "id = ?", id).Error +} + +// findUser 查询单个用户,并将未命中转换为 ok=false。 +func findUser(db *gorm.DB, query string, args ...any) (model.User, bool, error) { + user := model.User{} + err := db.Where(query, args...).First(&user).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return model.User{}, false, nil + } + return user, err == nil, err +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..55e582a --- /dev/null +++ b/router/router.go @@ -0,0 +1,62 @@ +package router + +import ( + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/basketikun/infinite-canvas/handler" + "github.com/basketikun/infinite-canvas/middleware" + "github.com/gin-gonic/gin" +) + +const webBaseURL = "http://127.0.0.1:3001" + +func New() *gin.Engine { + router := gin.Default() + router.RedirectTrailingSlash = false + _ = router.SetTrustedProxies(nil) + api := router.Group("/api") + api.GET("/health", func(c *gin.Context) { + c.String(http.StatusOK, "ok") + }) + api.POST("/auth/register", gin.WrapF(handler.Register)) + api.POST("/auth/login", gin.WrapF(handler.Login)) + api.GET("/auth/me", middleware.OptionalAuth, gin.WrapF(handler.CurrentUser)) + api.GET("/prompts", middleware.OptionalAuth, gin.WrapF(handler.Prompts)) + api.GET("/assets", middleware.OptionalAuth, gin.WrapF(handler.Assets)) + api.POST("/admin/login", gin.WrapF(handler.AdminLogin)) + + admin := api.Group("/admin", middleware.AdminAuth) + admin.GET("/users", gin.WrapF(handler.AdminUsers)) + admin.POST("/users", gin.WrapF(handler.AdminSaveUser)) + admin.DELETE("/users/:id", func(c *gin.Context) { + handler.AdminDeleteUser(c.Writer, c.Request, c.Param("id")) + }) + admin.GET("/prompt-categories", gin.WrapF(handler.AdminPromptCategories)) + admin.POST("/prompt-categories/sync", gin.WrapF(handler.AdminSyncPromptCategories)) + admin.GET("/prompts", gin.WrapF(handler.AdminPrompts)) + admin.POST("/prompts", gin.WrapF(handler.AdminSavePrompt)) + admin.DELETE("/prompts/:id", func(c *gin.Context) { + handler.AdminDeletePrompt(c.Writer, c.Request, c.Param("id")) + }) + admin.GET("/assets", gin.WrapF(handler.AdminAssets)) + admin.POST("/assets", gin.WrapF(handler.AdminSaveAsset)) + admin.DELETE("/assets/:id", func(c *gin.Context) { + handler.AdminDeleteAsset(c.Writer, c.Request, c.Param("id")) + }) + + webURL, _ := url.Parse(webBaseURL) + webProxy := httputil.NewSingleHostReverseProxy(webURL) + router.NoRoute(func(c *gin.Context) { + path := c.Request.URL.Path + if path == "/api" || strings.HasPrefix(path, "/api/") { + middleware.NotFoundJSON(c) + return + } + webProxy.ServeHTTP(c.Writer, c.Request) + }) + + return router +} diff --git a/service/assets.go b/service/assets.go new file mode 100644 index 0000000..34aac06 --- /dev/null +++ b/service/assets.go @@ -0,0 +1,50 @@ +package service + +import ( + "time" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/repository" +) + +func ListAssets(q model.Query) (model.AssetList, error) { + items, total, err := repository.ListAssets(q) + if err != nil { + return model.AssetList{}, err + } + tags, err := repository.ListAssetTags(q) + if err != nil { + return model.AssetList{}, err + } + return model.AssetList{Items: items, Tags: tags, Total: int(total)}, nil +} + +func SaveAsset(item model.Asset) (model.Asset, error) { + now := time.Now().Format(time.RFC3339) + if item.Type == "" { + item.Type = model.AssetTypeText + } + if item.ID == "" { + item.ID = newID("asset") + item.CreatedAt = now + } + item.UpdatedAt = now + if item.CoverURL == "" { + item.CoverURL = assetCoverURL(item) + } + return repository.SaveAsset(item) +} + +func DeleteAsset(id string) error { + return repository.DeleteAsset(id) +} + +func assetCoverURL(item model.Asset) string { + if item.CoverURL != "" { + return item.CoverURL + } + if item.Type == model.AssetTypeImage { + return item.URL + } + return "" +} diff --git a/service/auth.go b/service/auth.go new file mode 100644 index 0000000..bf1def6 --- /dev/null +++ b/service/auth.go @@ -0,0 +1,221 @@ +package service + +import ( + "errors" + "log" + "strings" + "time" + + "github.com/basketikun/infinite-canvas/config" + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/repository" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" +) + +type TokenClaims struct { + UserID string `json:"userId"` + Username string `json:"username"` + Role model.UserRole `json:"role"` + jwt.RegisteredClaims +} + +func EnsureDefaultAdmin() error { + if strings.TrimSpace(config.Cfg.AdminUsername) == "" || strings.TrimSpace(config.Cfg.AdminPassword) == "" { + return nil + } + WarnDefaultSecurityConfig() + hasAdmin, err := repository.HasAdmin() + if err != nil || hasAdmin { + return err + } + hash, err := hashPassword(config.Cfg.AdminPassword) + if err != nil { + return err + } + _, err = repository.SaveUser(model.User{ + ID: newID("user"), + Username: strings.TrimSpace(config.Cfg.AdminUsername), + Password: hash, + Role: model.UserRoleAdmin, + CreatedAt: now(), + UpdatedAt: now(), + }) + return err +} + +func Register(username string, password string) (model.AuthSession, error) { + return model.AuthSession{}, errors.New("注册功能暂时关闭") + username = strings.TrimSpace(username) + if strings.ContainsAny(username, " \t\r\n") { + return model.AuthSession{}, errors.New("用户名不能包含空格") + } + if username == "" || password == "" { + return model.AuthSession{}, errors.New("用户名和密码不能为空") + } + if _, ok, err := repository.GetUserByUsername(username); err != nil || ok { + if err != nil { + return model.AuthSession{}, err + } + return model.AuthSession{}, errors.New("用户名已存在") + } + hash, err := hashPassword(password) + if err != nil { + return model.AuthSession{}, err + } + user, err := repository.SaveUser(model.User{ + ID: newID("user"), + Username: username, + Password: hash, + Role: model.UserRoleUser, + CreatedAt: now(), + UpdatedAt: now(), + }) + if err != nil { + return model.AuthSession{}, err + } + return newSession(user) +} + +func Login(username string, password string) (model.AuthSession, error) { + user, ok, err := repository.GetUserByUsername(strings.TrimSpace(username)) + if err != nil { + return model.AuthSession{}, err + } + if !ok || bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil { + return model.AuthSession{}, errors.New("用户名或密码错误") + } + return newSession(user) +} + +func ParseToken(tokenText string) (TokenClaims, error) { + claims := TokenClaims{} + token, err := jwt.ParseWithClaims(tokenText, &claims, func(token *jwt.Token) (any, error) { + return []byte(config.Cfg.JWTSecret), nil + }) + if err != nil || !token.Valid { + return TokenClaims{}, errors.New("登录状态无效") + } + return claims, nil +} + +func CurrentAuthUser(tokenText string) (model.AuthUser, bool) { + claims, err := ParseToken(tokenText) + if err != nil { + return model.AuthUser{}, false + } + user, ok, err := repository.GetUserByID(claims.UserID) + if err != nil || !ok { + return model.AuthUser{}, false + } + return model.PublicUser(user), true +} + +func ListUsers(q model.Query) (model.UserList, error) { + users, total, err := repository.ListUsers(q) + if err != nil { + return model.UserList{}, err + } + for i := range users { + users[i].Password = "" + } + return model.UserList{Items: users, Total: int(total)}, nil +} + +func SaveUser(user model.User, password string) (model.User, error) { + user.Username = strings.TrimSpace(user.Username) + if strings.ContainsAny(user.Username, " \t\r\n") { + return user, errors.New("用户名不能包含空格") + } + if user.Username == "" { + return user, errors.New("用户名不能为空") + } + if user.Role == "" || user.Role == model.UserRoleGuest { + user.Role = model.UserRoleUser + } + if saved, ok, err := repository.GetUserByUsername(user.Username); err != nil { + return user, err + } else if ok && saved.ID != user.ID { + return user, errors.New("用户名已存在") + } + if user.ID == "" { + user.ID = newID("user") + user.CreatedAt = now() + } else if saved, ok, err := repository.GetUserByID(user.ID); err != nil { + return user, err + } else if ok { + user.CreatedAt = saved.CreatedAt + user.Password = saved.Password + } + if password != "" { + hash, err := hashPassword(password) + if err != nil { + return user, err + } + user.Password = hash + } + if user.Password == "" { + return user, errors.New("密码不能为空") + } + user.UpdatedAt = now() + user, err := repository.SaveUser(user) + user.Password = "" + return user, err +} + +func DeleteUser(id string) error { + return repository.DeleteUser(id) +} + +func GuestUser() model.AuthUser { + return model.AuthUser{ID: "", Username: "guest", Role: model.UserRoleGuest} +} + +func newSession(user model.User) (model.AuthSession, error) { + token, err := newToken(user) + if err != nil { + return model.AuthSession{}, err + } + return model.AuthSession{Token: token, User: model.PublicUser(user)}, nil +} + +func newToken(user model.User) (string, error) { + expireHours := config.Cfg.JWTExpireHours + if expireHours <= 0 { + expireHours = 168 + } + claims := TokenClaims{ + UserID: user.ID, + Username: user.Username, + Role: user.Role, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expireHours) * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + Subject: user.ID, + }, + } + return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(config.Cfg.JWTSecret)) +} + +func hashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hash), err +} + +func now() string { + return time.Now().Format(time.RFC3339) +} + +func newID(prefix string) string { + return prefix + "-" + uuid.NewString() +} + +func WarnDefaultSecurityConfig() { + if config.Cfg.AdminUsername == "admin" && config.Cfg.AdminPassword == "infinite-canvas" { + log.Println("WARNING: using default admin credentials, please set ADMIN_USERNAME and ADMIN_PASSWORD to safer values before deployment") + } + if config.Cfg.JWTSecret == "infinite-canvas" { + log.Println("WARNING: using default JWT_SECRET, please set a long random value before deployment") + } +} diff --git a/service/context.go b/service/context.go new file mode 100644 index 0000000..8e1da5c --- /dev/null +++ b/service/context.go @@ -0,0 +1,18 @@ +package service + +import ( + "context" + + "github.com/basketikun/infinite-canvas/model" +) + +type userContextKey struct{} + +func WithUser(ctx context.Context, user model.AuthUser) context.Context { + return context.WithValue(ctx, userContextKey{}, user) +} + +func UserFromContext(ctx context.Context) (model.AuthUser, bool) { + user, ok := ctx.Value(userContextKey{}).(model.AuthUser) + return user, ok +} diff --git a/service/prompt_fetch.go b/service/prompt_fetch.go new file mode 100644 index 0000000..e12d016 --- /dev/null +++ b/service/prompt_fetch.go @@ -0,0 +1,285 @@ +package service + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "regexp" + "strconv" + "strings" + "time" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/repository" +) + +const ( + gptImage2RawBase = "https://raw.githubusercontent.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts/main" + awesomeGptImageRawBase = "https://raw.githubusercontent.com/ZeroLu/awesome-gpt-image/main" + awesomeGpt4oImagePromptsBase = "https://raw.githubusercontent.com/ImgEdify/Awesome-GPT4o-Image-Prompts/main" + youMindGptImage2RawBase = "https://raw.githubusercontent.com/YouMind-OpenLab/awesome-gpt-image-2/main" + youMindNanoBananaProRawBase = "https://raw.githubusercontent.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts/main" +) + +var gptImage2CaseFiles = []string{"README.md", "cases/ad-creative.md", "cases/character.md", "cases/comparison.md", "cases/ecommerce.md", "cases/portrait.md", "cases/poster.md", "cases/ui.md"} + +type gptImage2Data struct { + Records []struct { + Title string `json:"title"` + TweetURL string `json:"tweet_url"` + ImageDir string `json:"image_dir"` + Category string `json:"category"` + AddedAt string `json:"added_at"` + } `json:"records"` +} + +func SyncPromptCategory(category string) ([]model.PromptCategory, error) { + for _, item := range repository.PromptCategories() { + if item.Category != category { + continue + } + items, err := buildPromptCategory(item.Category) + if err != nil { + return nil, err + } + if err := repository.ReplacePromptCategory(item, items); err != nil { + return nil, err + } + return repository.ListPromptCategories() + } + return nil, errors.New("未知提示词分类") +} + +func buildPromptCategory(category string) ([]model.Prompt, error) { + switch category { + case "gpt-image-2-prompts": + return buildGptImage2Prompts() + case "awesome-gpt-image": + return buildAwesomeGptImagePrompts() + case "awesome-gpt4o-image-prompts": + return buildAwesomeGpt4oImagePrompts() + case "youmind-gpt-image-2": + return buildYouMindGptImage2Prompts() + case "youmind-nano-banana-pro": + return buildYouMindNanoBananaProPrompts() + } + return nil, errors.New("未知提示词分类") +} + +func fetchText(baseURL, file string) (string, error) { + request, _ := http.NewRequest(http.MethodGet, baseURL+"/"+file, nil) + client := http.Client{Timeout: 30 * time.Second} + response, err := client.Do(request) + if err != nil { + return "", err + } + defer response.Body.Close() + if response.StatusCode < 200 || response.StatusCode >= 300 { + return "", errors.New(file + " 拉取失败") + } + data, err := io.ReadAll(response.Body) + return string(data), err +} + +func buildGptImage2Prompts() ([]model.Prompt, error) { + cases := map[string]string{} + raw, err := fetchText(gptImage2RawBase, "data/ingested_tweets.json") + if err != nil { + return nil, err + } + data := gptImage2Data{} + if err := json.Unmarshal([]byte(raw), &data); err != nil { + return nil, err + } + for _, file := range gptImage2CaseFiles { + markdown, err := fetchText(gptImage2RawBase, file) + if err != nil { + return nil, err + } + collectGptImage2Cases(cases, markdown) + } + items := []model.Prompt{} + for _, item := range data.Records { + prompt := cases[item.TweetURL] + if prompt == "" { + continue + } + image := gptImage2RawBase + "/" + item.ImageDir + "/output.jpg" + items = append(items, model.Prompt{ID: "gpt-image-2-prompts-" + leftPad(len(items)+1), Title: item.Title, CoverURL: image, Prompt: prompt, Tags: tagsFromCategory(item.Category), CreatedAt: item.AddedAt, UpdatedAt: item.AddedAt, Preview: markdownPreview([]string{image})}) + } + return items, nil +} + +func collectGptImage2Cases(cases map[string]string, markdown string) { + re := regexp.MustCompile("(?s)### Case \\d+: \\[[^\\]]+\\]\\(([^)]+)\\).*?\\*\\*Prompt:\\*\\*\\s*\\r?\\n\\s*```[\\w-]*\\r?\\n(.*?)\\r?\\n```") + for _, match := range re.FindAllStringSubmatch(markdown, -1) { + cases[match[1]] = strings.TrimSpace(match[2]) + } +} + +func buildAwesomeGptImagePrompts() ([]model.Prompt, error) { + markdown, err := fetchText(awesomeGptImageRawBase, "README.zh-CN.md") + if err != nil { + return nil, err + } + items := []model.Prompt{} + for _, section := range splitBeforeHeading(markdown, "## ") { + tags := tagsFromHeading(firstMatch(section, `(?m)^##\s+(.+)$`)) + for _, block := range splitBeforeHeading(section, "### ") { + title := strings.TrimSpace(regexp.MustCompile(`\[([^\]]+)]\([^)]+\)`).ReplaceAllString(firstMatch(block, `(?m)^###\s+(.+)$`), "$1")) + prompt := strings.TrimSpace(firstMatch(block, "(?s)\\*\\*提示词:\\*\\*\\s*\\r?\\n\\s*```[\\w-]*\\r?\\n(.*?)\\r?\\n```")) + if title == "" || prompt == "" { + continue + } + images := extractMarkdownImages(awesomeGptImageRawBase, block) + cover := "" + if len(images) > 0 { + cover = images[0] + } + items = append(items, model.Prompt{ID: "awesome-gpt-image-" + leftPad(len(items)+1), Title: title, CoverURL: cover, Prompt: prompt, Tags: tags, Preview: markdownPreview(images)}) + } + } + return items, nil +} + +func buildAwesomeGpt4oImagePrompts() ([]model.Prompt, error) { + markdown, err := fetchText(awesomeGpt4oImagePromptsBase, "README.zh-CN.md") + if err != nil { + return nil, err + } + items := []model.Prompt{} + for _, block := range splitBeforeHeading(markdown, "### ") { + title := strings.TrimSpace(firstMatch(block, `(?m)^###\s+(.+)$`)) + prompt := strings.TrimSpace(firstMatch(block, "(?s)- \\*\\*提示词文本:\\*\\*\\s*`(.*?)`")) + if title == "" || prompt == "" { + continue + } + images := extractMarkdownImages(awesomeGpt4oImagePromptsBase, block) + cover := "" + if len(images) > 0 { + cover = images[0] + } + items = append(items, model.Prompt{ID: "awesome-gpt4o-image-prompts-" + leftPad(len(items)+1), Title: title, CoverURL: cover, Prompt: prompt, Tags: []string{"gpt4o"}, Preview: markdownPreview(images)}) + } + return items, nil +} + +func buildYouMindGptImage2Prompts() ([]model.Prompt, error) { + return buildYouMindPrompts(youMindGptImage2RawBase, "youmind-gpt-image-2", "gpt-image-2") +} + +func buildYouMindNanoBananaProPrompts() ([]model.Prompt, error) { + return buildYouMindPrompts(youMindNanoBananaProRawBase, "youmind-nano-banana-pro", "nano-banana-pro") +} + +func buildYouMindPrompts(baseURL, idPrefix, modelTag string) ([]model.Prompt, error) { + markdown, err := fetchText(baseURL, "README_zh.md") + if err != nil { + return nil, err + } + items := []model.Prompt{} + for _, block := range splitBeforeHeading(markdown, "### ") { + title := strings.TrimSpace(firstMatch(block, `(?m)^###\s+No\.\s*\d+:\s*(.+)$`)) + prompt := strings.TrimSpace(firstMatch(block, "(?s)#### .*?提示词\\s*\\r?\\n\\s*```[\\w-]*\\r?\\n(.*?)\\r?\\n```")) + if title == "" || prompt == "" { + continue + } + images := extractMarkdownImages(baseURL, block) + cover := "" + if len(images) > 0 { + cover = images[0] + } + items = append(items, model.Prompt{ID: idPrefix + "-" + leftPad(len(items)+1), Title: title, CoverURL: cover, Prompt: prompt, Tags: youMindTags(title, modelTag), Preview: markdownPreview(images)}) + } + return items, nil +} + +func splitBeforeHeading(markdown string, prefix string) []string { + blocks := []string{} + lines := strings.Split(markdown, "\n") + current := []string{} + for _, line := range lines { + if strings.HasPrefix(line, prefix) && len(current) > 0 { + blocks = append(blocks, strings.Join(current, "\n")) + current = []string{} + } + current = append(current, line) + } + return append(blocks, strings.Join(current, "\n")) +} + +func firstMatch(value string, pattern string) string { + match := regexp.MustCompile(pattern).FindStringSubmatch(value) + if len(match) > 1 { + return match[1] + } + return "" +} + +func tagsFromCategory(category string) []string { + return splitTags(regexp.MustCompile(`(?i)\s+Cases$`).ReplaceAllString(category, ""), `\s*(&|and)\s*`) +} + +func tagsFromHeading(heading string) []string { + return splitTags(regexp.MustCompile(`[^\p{L}\p{N}/&、与 ]`).ReplaceAllString(heading, ""), `\s*(/|&|、|与)\s*`) +} + +func youMindTags(title, modelTag string) []string { + tags := []string{modelTag} + parts := strings.SplitN(title, " - ", 2) + if len(parts) > 1 { + tags = append(tags, tagsFromHeading(parts[0])...) + } + return tags +} + +func splitTags(value string, pattern string) []string { + tags := []string{} + for _, tag := range regexp.MustCompile(pattern).Split(value, -1) { + if tag = strings.ToLower(strings.TrimSpace(tag)); tag != "" { + tags = append(tags, tag) + } + } + return tags +} + +func markdownPreview(images []string) string { + lines := []string{} + for _, image := range images { + if image != "" { + lines = append(lines, "![]("+image+")") + } + } + return strings.Join(lines, "\n\n") +} + +func extractMarkdownImages(baseURL string, block string) []string { + seen := map[string]bool{} + images := []string{} + for _, pattern := range []string{`]+src="([^"]+)"`, `!\[[^\]]*]\(([^)]+)\)`} { + for _, match := range regexp.MustCompile(pattern).FindAllStringSubmatch(block, -1) { + image := absoluteImage(baseURL, match[1]) + if image != "" && !seen[image] { + seen[image] = true + images = append(images, image) + } + } + } + return images +} + +func absoluteImage(baseURL, image string) string { + if image == "" || strings.HasPrefix(image, "http://") || strings.HasPrefix(image, "https://") { + return image + } + return baseURL + "/" + strings.TrimLeft(strings.TrimPrefix(image, "."), "/") +} + +func leftPad(value int) string { + if value >= 1000 { + return strconv.Itoa(value) + } + text := "000" + strconv.Itoa(value) + return text[len(text)-3:] +} diff --git a/service/prompts.go b/service/prompts.go new file mode 100644 index 0000000..b744686 --- /dev/null +++ b/service/prompts.go @@ -0,0 +1,59 @@ +package service + +import ( + "time" + + "github.com/basketikun/infinite-canvas/model" + "github.com/basketikun/infinite-canvas/repository" +) + +func ListPrompts(q model.Query) (model.PromptList, error) { + items, total, err := repository.ListPrompts(q) + if err != nil { + return model.PromptList{}, err + } + tags, err := repository.ListPromptTags(q) + if err != nil { + return model.PromptList{}, err + } + categories := promptCategoryCodes(ListPromptCategories()) + return model.PromptList{Items: items, Tags: tags, Categories: categories, Total: int(total)}, nil +} + +func ListPromptCategories() []model.PromptCategory { + categories, _ := repository.ListPromptCategories() + return categories +} + +func SavePrompt(item model.Prompt) (model.Prompt, error) { + now := time.Now().Format(time.RFC3339) + if item.Category == "" { + item.Category = repository.PromptCategories()[0].Category + } + if item.ID == "" { + item.ID = newID(item.Category) + item.CreatedAt = now + } + item.UpdatedAt = now + category, ok := repository.PromptCategoryByCode(item.Category) + if !ok { + category = repository.PromptCategories()[0] + item.Category = category.Category + } + item.GithubURL = "" + return repository.SavePrompt(item) +} + +func DeletePrompt(id string) error { + return repository.DeletePrompt(id) +} + +func promptCategoryCodes(items []model.PromptCategory) []string { + codes := []string{} + for _, item := range items { + if item.Category != "" { + codes = append(codes, item.Category) + } + } + return codes +} diff --git a/web/bun.lock b/web/bun.lock new file mode 100644 index 0000000..34eb66e --- /dev/null +++ b/web/bun.lock @@ -0,0 +1,1554 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "infinite-canvas", + "dependencies": { + "@ant-design/icons": "^6.1.1", + "@ant-design/pro-components": "3.0.0-beta.3", + "@tanstack/react-query": "^5.100.9", + "antd": "^6.4.2", + "axios": "^1.16.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "localforage": "^1.10.0", + "lucide-react": "^1.16.0", + "motion": "^12.38.0", + "next": "16.2.3", + "radix-ui": "^1.4.3", + "react": "19.2.5", + "react-dom": "19.2.5", + "shadcn": "^4.7.0", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "zustand": "^5.0.12", + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", + "typescript": "^5", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@ant-design/colors": ["@ant-design/colors@8.0.1", "https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.1.tgz", { "dependencies": { "@ant-design/fast-color": "^3.0.0" } }, "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ=="], + + "@ant-design/cssinjs": ["@ant-design/cssinjs@1.24.0", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "classnames": "^2.3.1", "csstype": "^3.1.3", "rc-util": "^5.35.0", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg=="], + + "@ant-design/cssinjs-utils": ["@ant-design/cssinjs-utils@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", { "dependencies": { "@ant-design/cssinjs": "^2.1.2", "@babel/runtime": "^7.23.2", "@rc-component/util": "^1.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA=="], + + "@ant-design/fast-color": ["@ant-design/fast-color@3.0.1", "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.1.tgz", {}, "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw=="], + + "@ant-design/icons": ["@ant-design/icons@6.2.2", "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.2.2.tgz", { "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/icons-svg": "^4.4.2", "@rc-component/util": "^1.10.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-zlJtE7AMbG12TeYVPhtBXwNpFInNy8mjLzcIm+0BPw16/b8ODG87YJ1G37VIF5VFscdgfsf6EweAFPTobu/3iQ=="], + + "@ant-design/icons-svg": ["@ant-design/icons-svg@4.4.2", "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", {}, "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="], + + "@ant-design/pro-components": ["@ant-design/pro-components@3.0.0-beta.3", "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-3.0.0-beta.3.tgz", { "dependencies": { "@ant-design/cssinjs": "^1.24.0", "@ant-design/icons": "^5.6.1", "@babel/runtime": "^7.27.6", "@ctrl/tinycolor": "^4.1.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^6.0.1", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.2", "@emotion/cache": "^11.14.0", "@emotion/css": "^11.13.5", "@emotion/serialize": "^1.3.3", "@rc-component/util": "^1.2.2", "@umijs/route-utils": "^4.0.1", "@umijs/use-params": "^1.0.9", "classnames": "^2.5.1", "dayjs": "^1.11.13", "lodash-es": "^4.17.21", "path-to-regexp": "^6.3.0", "rc-field-form": "^2.7.0", "rc-footer": "^0.6.8", "rc-resize-observer": "^1.4.3", "rc-steps": "^6.0.1", "rc-table": "^7.51.1", "react-draggable": "^4.5.0", "react-layout-kit": "^1.9.2", "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.7", "react-syntax-highlighter": "^15.6.1", "safe-stable-stringify": "^2.5.0", "shiki-es": "^0.2.0", "swr": "^2.3.4" }, "peerDependencies": { "antd": "^5.11.2", "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-ehdIGlDcy4J5kv6E3fZ8e4YLQY4+7S3ZsIZnZ//vtfd+s0Qkv13hQ65CepoMiC/x6gNBlDnHozKANNi7JOtJAw=="], + + "@ant-design/react-slick": ["@ant-design/react-slick@2.0.0", "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.4", "clsx": "^2.1.1", "json2mq": "^0.2.0", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.3", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.3.tgz", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], + + "@babel/core": ["@babel/core@7.29.0", "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.3", "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.3", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.3.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], + + "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@babel/template": ["@babel/template@7.28.6", "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="], + + "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "https://registry.npmmirror.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], + + "@dnd-kit/core": ["@dnd-kit/core@6.3.1", "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.3.1.tgz", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="], + + "@dnd-kit/modifiers": ["@dnd-kit/modifiers@6.0.1", "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz", { "dependencies": { "@dnd-kit/utilities": "^3.2.1", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.0.6", "react": ">=16.8.0" } }, "sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A=="], + + "@dnd-kit/sortable": ["@dnd-kit/sortable@7.0.2", "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz", { "dependencies": { "@dnd-kit/utilities": "^3.2.0", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.0.7", "react": ">=16.8.0" } }, "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA=="], + + "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "https://registry.npmmirror.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], + + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.66.0", "https://registry.npmmirror.com/@dotenvx/dotenvx/-/dotenvx-1.66.0.tgz", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-qlQFhHUjhRDybrinqLAD0MClVZDOrsq80O8eD5iSjz3Qa/4f3Jg7SQrOaSobrRyP1QaWIYLGtGpj2c7H0D8NUw=="], + + "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "https://registry.npmmirror.com/@ecies/ciphers/-/ciphers-0.2.6.tgz", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="], + + "@emotion/cache": ["@emotion/cache@11.14.0", "https://registry.npmmirror.com/@emotion/cache/-/cache-11.14.0.tgz", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="], + + "@emotion/css": ["@emotion/css@11.13.5", "https://registry.npmmirror.com/@emotion/css/-/css-11.13.5.tgz", { "dependencies": { "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.13.5", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2" } }, "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w=="], + + "@emotion/hash": ["@emotion/hash@0.8.0", "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], + + "@emotion/memoize": ["@emotion/memoize@0.9.0", "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="], + + "@emotion/serialize": ["@emotion/serialize@1.3.3", "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.3.tgz", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="], + + "@emotion/sheet": ["@emotion/sheet@1.4.0", "https://registry.npmmirror.com/@emotion/sheet/-/sheet-1.4.0.tgz", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="], + + "@emotion/unitless": ["@emotion/unitless@0.7.5", "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", {}, "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="], + + "@emotion/utils": ["@emotion/utils@1.4.2", "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.2.tgz", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="], + + "@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + + "@hono/node-server": ["@hono/node-server@1.19.14", "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.14.tgz", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@inquirer/ansi": ["@inquirer/ansi@2.0.5", "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-2.0.5.tgz", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="], + + "@inquirer/confirm": ["@inquirer/confirm@6.0.13", "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-6.0.13.tgz", { "dependencies": { "@inquirer/core": "^11.1.10", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw=="], + + "@inquirer/core": ["@inquirer/core@11.1.10", "https://registry.npmmirror.com/@inquirer/core/-/core-11.1.10.tgz", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", "@inquirer/type": "^4.0.5", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-a4Q5BXHQAHa9eO202sTaFCHFYVB3x5fauDuThEAdZ9gfn76pSxiKU7wWcEH0N1O0XmQvNfQNU6QXpiRxmYQx+A=="], + + "@inquirer/figures": ["@inquirer/figures@2.0.5", "https://registry.npmmirror.com/@inquirer/figures/-/figures-2.0.5.tgz", {}, "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ=="], + + "@inquirer/type": ["@inquirer/type@4.0.5", "https://registry.npmmirror.com/@inquirer/type/-/type-4.0.5.tgz", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], + + "@mswjs/interceptors": ["@mswjs/interceptors@0.41.9", "https://registry.npmmirror.com/@mswjs/interceptors/-/interceptors-0.41.9.tgz", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w=="], + + "@next/env": ["@next/env@16.2.3", "", {}, "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw=="], + + "@noble/ciphers": ["@noble/ciphers@1.3.0", "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-1.3.0.tgz", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@1.9.7", "https://registry.npmmirror.com/@noble/curves/-/curves-1.9.7.tgz", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@open-draft/deferred-promise": ["@open-draft/deferred-promise@3.0.0", "https://registry.npmmirror.com/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", {}, "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA=="], + + "@open-draft/logger": ["@open-draft/logger@0.3.0", "https://registry.npmmirror.com/@open-draft/logger/-/logger-0.3.0.tgz", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], + + "@open-draft/until": ["@open-draft/until@2.1.0", "https://registry.npmmirror.com/@open-draft/until/-/until-2.1.0.tgz", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], + + "@radix-ui/number": ["@radix-ui/number@1.1.1", "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.1.tgz", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.3.tgz", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], + + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], + + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + + "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], + + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], + + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], + + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "https://registry.npmmirror.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + + "@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "https://registry.npmmirror.com/@radix-ui/react-form/-/react-form-0.1.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="], + + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], + + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + + "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="], + + "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], + + "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "https://registry.npmmirror.com/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="], + + "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "https://registry.npmmirror.com/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="], + + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], + + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], + + "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="], + + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.6.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], + + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], + + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], + + "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], + + "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "https://registry.npmmirror.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], + + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], + + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.1.tgz", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + + "@rc-component/async-validator": ["@rc-component/async-validator@5.1.0", "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.0.tgz", { "dependencies": { "@babel/runtime": "^7.24.4" } }, "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA=="], + + "@rc-component/cascader": ["@rc-component/cascader@1.15.0", "https://registry.npmmirror.com/@rc-component/cascader/-/cascader-1.15.0.tgz", { "dependencies": { "@rc-component/select": "~1.6.0", "@rc-component/tree": "~1.3.0", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-ZzpMtwFCRo3fbXHuDnncARJMZQjdqA2w7aDuPofNQt+aDx39st1hgfIpEwTBLhe2Hqsvs/zOr8RTtgxTkCPySw=="], + + "@rc-component/checkbox": ["@rc-component/checkbox@2.0.0", "https://registry.npmmirror.com/@rc-component/checkbox/-/checkbox-2.0.0.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ=="], + + "@rc-component/collapse": ["@rc-component/collapse@1.2.0", "https://registry.npmmirror.com/@rc-component/collapse/-/collapse-1.2.0.tgz", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/motion": "^1.1.4", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw=="], + + "@rc-component/color-picker": ["@rc-component/color-picker@3.1.1", "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-3.1.1.tgz", { "dependencies": { "@ant-design/fast-color": "^3.0.1", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg=="], + + "@rc-component/context": ["@rc-component/context@1.4.0", "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", { "dependencies": { "@babel/runtime": "^7.10.1", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w=="], + + "@rc-component/dialog": ["@rc-component/dialog@1.9.0", "https://registry.npmmirror.com/@rc-component/dialog/-/dialog-1.9.0.tgz", { "dependencies": { "@rc-component/motion": "^1.1.3", "@rc-component/portal": "^2.1.0", "@rc-component/util": "^1.9.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-zbAAogkg4kkKum79sLE6M+vq1jSAW25zdkafrahgcTP9t9S//SD634Znd1A4c8F2Gc12ZKnehGLsVaaOvZzD2A=="], + + "@rc-component/drawer": ["@rc-component/drawer@1.4.2", "https://registry.npmmirror.com/@rc-component/drawer/-/drawer-1.4.2.tgz", { "dependencies": { "@rc-component/motion": "^1.1.4", "@rc-component/portal": "^2.1.3", "@rc-component/util": "^1.9.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q=="], + + "@rc-component/dropdown": ["@rc-component/dropdown@1.0.2", "https://registry.npmmirror.com/@rc-component/dropdown/-/dropdown-1.0.2.tgz", { "dependencies": { "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.11.0", "react-dom": ">=16.11.0" } }, "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg=="], + + "@rc-component/form": ["@rc-component/form@1.8.1", "https://registry.npmmirror.com/@rc-component/form/-/form-1.8.1.tgz", { "dependencies": { "@rc-component/async-validator": "^5.1.0", "@rc-component/util": "^1.6.2", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-8O7TB55Fi2mWIGvSnwZjk8jFqVNYyKDAswglwGShcbndxqzKz4cHwNtNaLjZlAeRge9wcB0LL8IWsC/Bl18raQ=="], + + "@rc-component/image": ["@rc-component/image@1.9.0", "https://registry.npmmirror.com/@rc-component/image/-/image-1.9.0.tgz", { "dependencies": { "@rc-component/motion": "^1.0.0", "@rc-component/portal": "^2.1.2", "@rc-component/util": "^1.10.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ=="], + + "@rc-component/input": ["@rc-component/input@1.3.0", "https://registry.npmmirror.com/@rc-component/input/-/input-1.3.0.tgz", { "dependencies": { "@rc-component/resize-observer": "^1.1.1", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-IUUNOdAuWuEvDEFFgfmwQl818tiDbvXwLgon4HL1q2hJeYkqrRrYwYhJN0zfPHGTDxs3gvyVC/C02D4hWFoIcA=="], + + "@rc-component/input-number": ["@rc-component/input-number@1.6.2", "https://registry.npmmirror.com/@rc-component/input-number/-/input-number-1.6.2.tgz", { "dependencies": { "@rc-component/mini-decimal": "^1.0.1", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w=="], + + "@rc-component/mentions": ["@rc-component/mentions@1.9.0", "https://registry.npmmirror.com/@rc-component/mentions/-/mentions-1.9.0.tgz", { "dependencies": { "@rc-component/input": "~1.3.0", "@rc-component/menu": "~1.3.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-WUwfFKDSOF5S9UPsNsXcLYtzjTxBGsftTXWRbZuxX6BYrsySISTnujfJNgaaQ6qVzaCDJ35QUkZKvsYxip1C5g=="], + + "@rc-component/menu": ["@rc-component/menu@1.3.0", "https://registry.npmmirror.com/@rc-component/menu/-/menu-1.3.0.tgz", { "dependencies": { "@rc-component/motion": "^1.1.4", "@rc-component/overflow": "^1.0.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-u3NfiwpiEgT177qa5Yxm5QsI8i/93EBGpWj8HYZQDnh2pCZ2xtQCe/+w3pSR2NlwKOZDTCKzEhEyD09mGphssA=="], + + "@rc-component/mini-decimal": ["@rc-component/mini-decimal@1.1.3", "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", { "dependencies": { "@babel/runtime": "^7.18.0" } }, "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw=="], + + "@rc-component/motion": ["@rc-component/motion@1.3.2", "https://registry.npmmirror.com/@rc-component/motion/-/motion-1.3.2.tgz", { "dependencies": { "@rc-component/util": "^1.2.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ=="], + + "@rc-component/mutate-observer": ["@rc-component/mutate-observer@2.0.1", "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", { "dependencies": { "@rc-component/util": "^1.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w=="], + + "@rc-component/notification": ["@rc-component/notification@2.0.7", "https://registry.npmmirror.com/@rc-component/notification/-/notification-2.0.7.tgz", { "dependencies": { "@rc-component/motion": "^1.1.4", "@rc-component/util": "^1.11.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ=="], + + "@rc-component/overflow": ["@rc-component/overflow@1.0.1", "https://registry.npmmirror.com/@rc-component/overflow/-/overflow-1.0.1.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@rc-component/resize-observer": "^1.0.1", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA=="], + + "@rc-component/pagination": ["@rc-component/pagination@1.2.0", "https://registry.npmmirror.com/@rc-component/pagination/-/pagination-1.2.0.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw=="], + + "@rc-component/picker": ["@rc-component/picker@1.10.0", "https://registry.npmmirror.com/@rc-component/picker/-/picker-1.10.0.tgz", { "dependencies": { "@rc-component/overflow": "^1.0.0", "@rc-component/resize-observer": "^1.0.0", "@rc-component/trigger": "^3.6.15", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "date-fns": ">= 2.x", "dayjs": ">= 1.x", "luxon": ">= 3.x", "moment": ">= 2.x", "react": ">=16.9.0", "react-dom": ">=16.9.0" }, "optionalPeers": ["date-fns", "dayjs", "luxon", "moment"] }, "sha512-vVOXP2RVWozwpERGUFAehVH1Jz6o/uRrAb9qSZm1LC+iJs8rvEwFo1bzz2jlOYV+uWwu0dIuG86tnDui14Ea0w=="], + + "@rc-component/portal": ["@rc-component/portal@2.2.0", "https://registry.npmmirror.com/@rc-component/portal/-/portal-2.2.0.tgz", { "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ=="], + + "@rc-component/progress": ["@rc-component/progress@1.0.2", "https://registry.npmmirror.com/@rc-component/progress/-/progress-1.0.2.tgz", { "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ=="], + + "@rc-component/qrcode": ["@rc-component/qrcode@1.1.1", "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.1.1.tgz", { "dependencies": { "@babel/runtime": "^7.24.7" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA=="], + + "@rc-component/rate": ["@rc-component/rate@1.0.1", "https://registry.npmmirror.com/@rc-component/rate/-/rate-1.0.1.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw=="], + + "@rc-component/resize-observer": ["@rc-component/resize-observer@1.1.2", "https://registry.npmmirror.com/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", { "dependencies": { "@rc-component/util": "^1.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q=="], + + "@rc-component/segmented": ["@rc-component/segmented@1.3.0", "https://registry.npmmirror.com/@rc-component/segmented/-/segmented-1.3.0.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@rc-component/motion": "^1.1.4", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg=="], + + "@rc-component/select": ["@rc-component/select@1.6.15", "https://registry.npmmirror.com/@rc-component/select/-/select-1.6.15.tgz", { "dependencies": { "@rc-component/overflow": "^1.0.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.3.0", "@rc-component/virtual-list": "^1.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g=="], + + "@rc-component/slider": ["@rc-component/slider@1.0.1", "https://registry.npmmirror.com/@rc-component/slider/-/slider-1.0.1.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g=="], + + "@rc-component/steps": ["@rc-component/steps@1.2.2", "https://registry.npmmirror.com/@rc-component/steps/-/steps-1.2.2.tgz", { "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw=="], + + "@rc-component/switch": ["@rc-component/switch@1.0.3", "https://registry.npmmirror.com/@rc-component/switch/-/switch-1.0.3.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw=="], + + "@rc-component/table": ["@rc-component/table@1.10.0", "https://registry.npmmirror.com/@rc-component/table/-/table-1.10.0.tgz", { "dependencies": { "@rc-component/context": "^2.0.1", "@rc-component/resize-observer": "^1.0.0", "@rc-component/util": "^1.1.0", "@rc-component/virtual-list": "^1.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-SjtpcCf+rL7dDc62GKT3rXTdERjVuJvRiqjpU7g0Jc/ewCifXynHc7Nm3Em1XsD+WhGrgQtxNDScI/0+Lpfr0w=="], + + "@rc-component/tabs": ["@rc-component/tabs@1.9.0", "https://registry.npmmirror.com/@rc-component/tabs/-/tabs-1.9.0.tgz", { "dependencies": { "@rc-component/dropdown": "~1.0.0", "@rc-component/menu": "~1.3.0", "@rc-component/motion": "^1.1.3", "@rc-component/resize-observer": "^1.0.0", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-tn1slmbbaTyt8mgwyWJcT8jo/qNiYUs6u1H7OgGQt9faYO06BJIkU5cTmMqORzIrNmSEeeUY6pD5i+JlqSHYhg=="], + + "@rc-component/tooltip": ["@rc-component/tooltip@1.4.0", "https://registry.npmmirror.com/@rc-component/tooltip/-/tooltip-1.4.0.tgz", { "dependencies": { "@rc-component/trigger": "^3.7.1", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg=="], + + "@rc-component/tour": ["@rc-component/tour@2.4.0", "https://registry.npmmirror.com/@rc-component/tour/-/tour-2.4.0.tgz", { "dependencies": { "@rc-component/portal": "^2.2.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.7.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg=="], + + "@rc-component/tree": ["@rc-component/tree@1.3.1", "https://registry.npmmirror.com/@rc-component/tree/-/tree-1.3.1.tgz", { "dependencies": { "@rc-component/motion": "^1.0.0", "@rc-component/util": "^1.8.1", "@rc-component/virtual-list": "^1.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-zlL0PW0bTFlveTtLcA01VD/yMWKK73EywItFMgIZUY5sb6tMOAw7zV6qGzqldufqrV93ZWQB4H3NBNoTMCueJA=="], + + "@rc-component/tree-select": ["@rc-component/tree-select@1.9.0", "https://registry.npmmirror.com/@rc-component/tree-select/-/tree-select-1.9.0.tgz", { "dependencies": { "@rc-component/select": "~1.6.0", "@rc-component/tree": "~1.3.0", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-GXcFe15a+trUl1/J3OHWQhsVWFpwFpGFK2cqYWZ1sK22Zs3KZTvMwDpzr75PIo1s6QVioVxpE/pRwRopkeDQ6w=="], + + "@rc-component/trigger": ["@rc-component/trigger@3.9.0", "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-3.9.0.tgz", { "dependencies": { "@rc-component/motion": "^1.1.4", "@rc-component/portal": "^2.2.0", "@rc-component/resize-observer": "^1.1.1", "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg=="], + + "@rc-component/upload": ["@rc-component/upload@1.1.0", "https://registry.npmmirror.com/@rc-component/upload/-/upload-1.1.0.tgz", { "dependencies": { "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw=="], + + "@rc-component/util": ["@rc-component/util@1.10.1", "https://registry.npmmirror.com/@rc-component/util/-/util-1.10.1.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng=="], + + "@rc-component/virtual-list": ["@rc-component/virtual-list@1.0.2", "https://registry.npmmirror.com/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz", { "dependencies": { "@babel/runtime": "^7.20.0", "@rc-component/resize-observer": "^1.0.1", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "https://registry.npmmirror.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "https://registry.npmmirror.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.100.9", "", {}, "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.100.9", "", { "dependencies": { "@tanstack/query-core": "5.100.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A=="], + + "@ts-morph/common": ["@ts-morph/common@0.27.0", "https://registry.npmmirror.com/@ts-morph/common/-/common-0.27.0.tgz", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], + + "@types/debug": ["@types/debug@4.1.13", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/hast": ["@types/hast@2.3.10", "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "@types/mdast": ["@types/mdast@3.0.15", "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="], + + "@types/ms": ["@types/ms@2.1.0", "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="], + + "@types/parse-json": ["@types/parse-json@4.0.2", "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], + + "@types/set-cookie-parser": ["@types/set-cookie-parser@2.4.10", "https://registry.npmmirror.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw=="], + + "@types/statuses": ["@types/statuses@2.0.6", "https://registry.npmmirror.com/@types/statuses/-/statuses-2.0.6.tgz", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], + + "@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "https://registry.npmmirror.com/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], + + "@umijs/route-utils": ["@umijs/route-utils@4.0.3", "https://registry.npmmirror.com/@umijs/route-utils/-/route-utils-4.0.3.tgz", {}, "sha512-zPEcYhl1cSfkSRDzzGgoD1mDvGjxoOTJFvkn55srfgdQ3NZe2ZMCScCU6DEnOxuKP1XDVf8pqyqCDVd2+RCQIw=="], + + "@umijs/use-params": ["@umijs/use-params@1.0.9", "https://registry.npmmirror.com/@umijs/use-params/-/use-params-1.0.9.tgz", { "peerDependencies": { "react": "*" } }, "sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w=="], + + "accepts": ["accepts@2.0.0", "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "agent-base": ["agent-base@7.1.4", "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@8.20.0", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@3.0.1", "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "antd": ["antd@6.4.2", "https://registry.npmmirror.com/antd/-/antd-6.4.2.tgz", { "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.2", "@ant-design/cssinjs-utils": "^2.1.2", "@ant-design/fast-color": "^3.0.1", "@ant-design/icons": "^6.2.3", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.29.2", "@rc-component/cascader": "~1.15.0", "@rc-component/checkbox": "~2.0.0", "@rc-component/collapse": "~1.2.0", "@rc-component/color-picker": "~3.1.1", "@rc-component/dialog": "~1.9.0", "@rc-component/drawer": "~1.4.2", "@rc-component/dropdown": "~1.0.2", "@rc-component/form": "~1.8.1", "@rc-component/image": "~1.9.0", "@rc-component/input": "~1.3.0", "@rc-component/input-number": "~1.6.2", "@rc-component/mentions": "~1.9.0", "@rc-component/menu": "~1.3.0", "@rc-component/motion": "^1.3.2", "@rc-component/mutate-observer": "^2.0.1", "@rc-component/notification": "~2.0.6", "@rc-component/pagination": "~1.2.0", "@rc-component/picker": "~1.10.0", "@rc-component/progress": "~1.0.2", "@rc-component/qrcode": "~1.1.1", "@rc-component/rate": "~1.0.1", "@rc-component/resize-observer": "^1.1.2", "@rc-component/segmented": "~1.3.0", "@rc-component/select": "~1.6.15", "@rc-component/slider": "~1.0.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", "@rc-component/table": "~1.10.0", "@rc-component/tabs": "~1.9.0", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.4.0", "@rc-component/tree": "~1.3.1", "@rc-component/tree-select": "~1.9.0", "@rc-component/trigger": "^3.9.0", "@rc-component/upload": "~1.1.0", "@rc-component/util": "^1.10.1", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-PNJz8Vxc/mC3EsOg/h3e2YuaZduJ1RDp4RmySDuDmKPCxVgyp4Da4kB36o87p9hbLbOWdAWCKQlnyopsN8utKQ=="], + + "argparse": ["argparse@2.0.1", "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "ast-types": ["ast-types@0.16.1", "https://registry.npmmirror.com/ast-types/-/ast-types-0.16.1.tgz", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.16.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w=="], + + "babel-plugin-macros": ["babel-plugin-macros@3.1.0", "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="], + + "bail": ["bail@2.0.2", "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g=="], + + "body-parser": ["body-parser@2.2.2", "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + + "brace-expansion": ["brace-expansion@5.0.6", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "bundle-name": ["bundle-name@4.1.0", "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "bytes": ["bytes@3.1.2", "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], + + "chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "character-entities": ["character-entities@1.2.4", "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], + + "character-entities-legacy": ["character-entities-legacy@1.1.4", "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], + + "character-reference-invalid": ["character-reference-invalid@1.1.4", "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "classnames": ["classnames@2.5.1", "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], + + "cli-cursor": ["cli-cursor@5.0.0", "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-width": ["cli-width@4.1.0", "https://registry.npmmirror.com/cli-width/-/cli-width-4.1.0.tgz", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + + "cliui": ["cliui@8.0.1", "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "code-block-writer": ["code-block-writer@13.0.3", "https://registry.npmmirror.com/code-block-writer/-/code-block-writer-13.0.3.tgz", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], + + "color-convert": ["color-convert@2.0.1", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], + + "content-disposition": ["content-disposition@1.1.0", "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.1.0.tgz", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + + "content-type": ["content-type@1.0.5", "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "cookie-signature": ["cookie-signature@1.2.2", "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + + "cosmiconfig": ["cosmiconfig@9.0.1", "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssesc": ["cssesc@3.0.0", "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "dayjs": ["dayjs@1.11.20", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + + "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "dedent": ["dedent@1.7.2", "https://registry.npmmirror.com/dedent/-/dedent-1.7.2.tgz", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.5.0", "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.0.tgz", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "dequal": ["dequal@2.0.3", "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node-es": ["detect-node-es@1.1.0", "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "diff": ["diff@8.0.4", "https://registry.npmmirror.com/diff/-/diff-8.0.4.tgz", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dotenv": ["dotenv@17.4.2", "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eciesjs": ["eciesjs@0.4.18", "https://registry.npmmirror.com/eciesjs/-/eciesjs-0.4.18.tgz", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="], + + "ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.355", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.355.tgz", {}, "sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ=="], + + "emoji-regex": ["emoji-regex@10.6.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], + + "env-paths": ["env-paths@2.2.1", "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "error-ex": ["error-ex@1.3.4", "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "esprima": ["esprima@4.0.1", "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventsource": ["eventsource@3.0.7", "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.8", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.8.tgz", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + + "execa": ["execa@9.6.1", "https://registry.npmmirror.com/execa/-/execa-9.6.1.tgz", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + + "express": ["express@5.2.1", "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.5.2", "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.5.2.tgz", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="], + + "extend": ["extend@3.0.2", "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "https://registry.npmmirror.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "https://registry.npmmirror.com/fast-string-width/-/fast-string-width-3.0.2.tgz", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-uri": ["fast-uri@3.1.2", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.2.tgz", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "https://registry.npmmirror.com/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + + "fastq": ["fastq@1.20.1", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fault": ["fault@1.0.4", "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + + "fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "figures": ["figures@6.1.0", "https://registry.npmmirror.com/figures/-/figures-6.1.0.tgz", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "fill-range": ["fill-range@7.1.1", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "finalhandler": ["finalhandler@2.1.1", "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + + "find-root": ["find-root@1.1.0", "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "format": ["format@0.2.2", "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "forwarded": ["forwarded@0.2.0", "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "framer-motion": ["framer-motion@12.38.0", "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.38.0.tgz", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + + "fresh": ["fresh@2.0.0", "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fs-extra": ["fs-extra@11.3.5", "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.5.tgz", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "fuzzysort": ["fuzzysort@3.1.0", "https://registry.npmmirror.com/fuzzysort/-/fuzzysort-3.1.0.tgz", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="], + + "gensync": ["gensync@1.0.0-beta.2", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-nonce": ["get-nonce@1.0.1", "https://registry.npmmirror.com/get-nonce/-/get-nonce-1.0.1.tgz", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "https://registry.npmmirror.com/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@9.0.1", "https://registry.npmmirror.com/get-stream/-/get-stream-9.0.1.tgz", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphql": ["graphql@16.14.0", "https://registry.npmmirror.com/graphql/-/graphql-16.14.0.tgz", {}, "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + + "hast-util-whitespace": ["hast-util-whitespace@2.0.1", "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", {}, "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng=="], + + "hastscript": ["hastscript@6.0.0", "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], + + "headers-polyfill": ["headers-polyfill@5.0.1", "https://registry.npmmirror.com/headers-polyfill/-/headers-polyfill-5.0.1.tgz", { "dependencies": { "@types/set-cookie-parser": "^2.4.10", "set-cookie-parser": "^3.0.1" } }, "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA=="], + + "highlight.js": ["highlight.js@10.7.3", "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + + "hono": ["hono@4.12.18", "https://registry.npmmirror.com/hono/-/hono-4.12.18.tgz", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="], + + "http-errors": ["http-errors@2.0.1", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "human-signals": ["human-signals@8.0.1", "https://registry.npmmirror.com/human-signals/-/human-signals-8.0.1.tgz", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "ignore": ["ignore@5.3.2", "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + + "import-fresh": ["import-fresh@3.3.1", "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "inline-style-parser": ["inline-style-parser@0.1.1", "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="], + + "ip-address": ["ip-address@10.2.0", "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-alphabetical": ["is-alphabetical@1.0.4", "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + + "is-alphanumerical": ["is-alphanumerical@1.0.4", "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], + + "is-arrayish": ["is-arrayish@0.2.1", "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-buffer": ["is-buffer@2.0.5", "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], + + "is-core-module": ["is-core-module@2.16.2", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-decimal": ["is-decimal@1.0.4", "https://registry.npmmirror.com/is-decimal/-/is-decimal-1.0.4.tgz", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], + + "is-docker": ["is-docker@3.0.0", "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@1.0.4", "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + + "is-in-ssh": ["is-in-ssh@1.0.0", "https://registry.npmmirror.com/is-in-ssh/-/is-in-ssh-1.0.0.tgz", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-interactive": ["is-interactive@2.0.0", "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-mobile": ["is-mobile@5.0.0", "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="], + + "is-node-process": ["is-node-process@1.2.0", "https://registry.npmmirror.com/is-node-process/-/is-node-process-1.2.0.tgz", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], + + "is-number": ["is-number@7.0.0", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-obj": ["is-obj@3.0.0", "https://registry.npmmirror.com/is-obj/-/is-obj-3.0.0.tgz", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-promise": ["is-promise@4.0.0", "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "is-regexp": ["is-regexp@3.1.0", "https://registry.npmmirror.com/is-regexp/-/is-regexp-3.1.0.tgz", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="], + + "is-stream": ["is-stream@4.0.1", "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-wsl": ["is-wsl@3.1.1", "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isexe": ["isexe@3.1.5", "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "jose": ["jose@6.2.3", "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], + + "js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsesc": ["jsesc@3.1.0", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "json2mq": ["json2mq@0.2.0", "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", { "dependencies": { "string-convert": "^0.2.0" } }, "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA=="], + + "json5": ["json5@2.2.3", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.1", "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "kleur": ["kleur@4.1.5", "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], + + "lodash-es": ["lodash-es@4.18.1", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "log-symbols": ["log-symbols@6.0.0", "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "loose-envify": ["loose-envify@1.4.0", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lowlight": ["lowlight@1.20.0", "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + + "lru-cache": ["lru-cache@5.1.1", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@1.16.0", "https://registry.npmmirror.com/lucide-react/-/lucide-react-1.16.0.tgz", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdast-util-definitions": ["mdast-util-definitions@5.1.2", "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@12.3.0", "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-definitions": "^5.0.0", "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", "unist-util-generated": "^2.0.0", "unist-util-position": "^4.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw=="], + + "mdast-util-to-string": ["mdast-util-to-string@3.2.0", "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", { "dependencies": { "@types/mdast": "^3.0.0" } }, "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg=="], + + "media-typer": ["media-typer@1.1.0", "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "merge-stream": ["merge-stream@2.0.0", "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromark": ["micromark@3.2.0", "https://registry.npmmirror.com/micromark/-/micromark-3.2.0.tgz", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "micromark-core-commonmark": "^1.0.1", "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-combine-extensions": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="], + + "micromark-factory-destination": ["micromark-factory-destination@1.1.0", "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="], + + "micromark-factory-label": ["micromark-factory-label@1.1.0", "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="], + + "micromark-factory-space": ["micromark-factory-space@1.1.0", "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], + + "micromark-factory-title": ["micromark-factory-title@1.1.0", "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@1.1.0", "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ=="], + + "micromark-util-character": ["micromark-util-character@1.2.0", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark-util-chunked": ["micromark-util-chunked@1.1.0", "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@1.1.0", "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@1.1.0", "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ=="], + + "micromark-util-encode": ["micromark-util-encode@1.1.0", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@1.2.0", "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", {}, "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@1.1.0", "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@1.1.0", "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", { "dependencies": { "micromark-util-types": "^1.0.0" } }, "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@1.1.0", "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A=="], + + "micromark-util-symbol": ["micromark-util-symbol@1.1.0", "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="], + + "micromark-util-types": ["micromark-util-types@1.1.0", "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromatch": ["micromatch@4.0.8", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-function": ["mimic-function@5.0.1", "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@10.2.5", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minimist": ["minimist@1.2.8", "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "motion": ["motion@12.38.0", "https://registry.npmmirror.com/motion/-/motion-12.38.0.tgz", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], + + "motion-dom": ["motion-dom@12.38.0", "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.38.0.tgz", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], + + "motion-utils": ["motion-utils@12.36.0", "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.36.0.tgz", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], + + "mri": ["mri@1.2.0", "https://registry.npmmirror.com/mri/-/mri-1.2.0.tgz", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msw": ["msw@2.14.6", "https://registry.npmmirror.com/msw/-/msw-2.14.6.tgz", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.11", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg=="], + + "mute-stream": ["mute-stream@3.0.0", "https://registry.npmmirror.com/mute-stream/-/mute-stream-3.0.0.tgz", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "negotiator": ["negotiator@1.0.0", "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "next": ["next@16.2.3", "", { "dependencies": { "@next/env": "16.2.3", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.3", "@next/swc-darwin-x64": "16.2.3", "@next/swc-linux-arm64-gnu": "16.2.3", "@next/swc-linux-arm64-musl": "16.2.3", "@next/swc-linux-x64-gnu": "16.2.3", "@next/swc-linux-x64-musl": "16.2.3", "@next/swc-win32-arm64-msvc": "16.2.3", "@next/swc-win32-x64-msvc": "16.2.3", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA=="], + + "node-domexception": ["node-domexception@1.0.0", "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-releases": ["node-releases@2.0.44", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz", {}, "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ=="], + + "npm-run-path": ["npm-run-path@6.0.0", "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-6.0.0.tgz", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-treeify": ["object-treeify@1.1.33", "https://registry.npmmirror.com/object-treeify/-/object-treeify-1.1.33.tgz", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], + + "on-finished": ["on-finished@2.4.1", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@11.0.0", "https://registry.npmmirror.com/open/-/open-11.0.0.tgz", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + + "ora": ["ora@8.2.0", "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + + "outvariant": ["outvariant@1.4.3", "https://registry.npmmirror.com/outvariant/-/outvariant-1.4.3.tgz", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], + + "parent-module": ["parent-module@1.0.1", "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-entities": ["parse-entities@2.0.0", "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], + + "parse-json": ["parse-json@5.2.0", "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-ms": ["parse-ms@4.0.0", "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parseurl": ["parseurl@1.3.3", "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "path-type": ["path-type@4.0.0", "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + + "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "powershell-utils": ["powershell-utils@0.1.0", "https://registry.npmmirror.com/powershell-utils/-/powershell-utils-0.1.0.tgz", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "pretty-ms": ["pretty-ms@9.3.0", "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.3.0.tgz", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "prismjs": ["prismjs@1.30.0", "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "prompts": ["prompts@2.4.2", "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "prop-types": ["prop-types@15.8.1", "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + + "property-information": ["property-information@6.5.0", "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + + "proxy-addr": ["proxy-addr@2.0.7", "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "qs": ["qs@6.15.1", "https://registry.npmmirror.com/qs/-/qs-6.15.1.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "radix-ui": ["radix-ui@1.4.3", "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.4.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], + + "range-parser": ["range-parser@1.2.1", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "rc-field-form": ["rc-field-form@2.7.1", "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.1.tgz", { "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/async-validator": "^5.0.3", "rc-util": "^5.32.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A=="], + + "rc-footer": ["rc-footer@0.6.8", "https://registry.npmmirror.com/rc-footer/-/rc-footer-0.6.8.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg=="], + + "rc-resize-observer": ["rc-resize-observer@1.4.3", "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", { "dependencies": { "@babel/runtime": "^7.20.7", "classnames": "^2.2.1", "rc-util": "^5.44.1", "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ=="], + + "rc-steps": ["rc-steps@6.0.1", "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", { "dependencies": { "@babel/runtime": "^7.16.7", "classnames": "^2.2.3", "rc-util": "^5.16.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g=="], + + "rc-table": ["rc-table@7.55.1", "https://registry.npmmirror.com/rc-table/-/rc-table-7.55.1.tgz", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", "rc-util": "^5.44.3", "rc-virtual-list": "^3.14.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-mtZkOHQ+474hxw6usr5rrT6tTZvFlv7jRqao/oG4Y4mwOaXU7ZL1OZ3Vp7xO+zPhB2PPKLFnKr53EitryoLJyQ=="], + + "rc-util": ["rc-util@5.44.4", "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", { "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w=="], + + "rc-virtual-list": ["rc-virtual-list@3.19.2", "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", { "dependencies": { "@babel/runtime": "^7.20.0", "classnames": "^2.2.6", "rc-resize-observer": "^1.0.0", "rc-util": "^5.36.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA=="], + + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "react-draggable": ["react-draggable@4.5.0", "https://registry.npmmirror.com/react-draggable/-/react-draggable-4.5.0.tgz", { "dependencies": { "clsx": "^2.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.3.0", "react-dom": ">= 16.3.0" } }, "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw=="], + + "react-is": ["react-is@18.3.1", "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "react-layout-kit": ["react-layout-kit@1.9.2", "https://registry.npmmirror.com/react-layout-kit/-/react-layout-kit-1.9.2.tgz", { "dependencies": { "@babel/runtime": "^7", "@emotion/css": "^11" }, "peerDependencies": { "react": ">=18" } }, "sha512-fzmrwMBNGIAiDIrdFMV3NvJhUNl01QC9EMcI8SP7osg51N4j+z6w4tx9i2yWxEEXZ2armLV6EtkFd3KST8PYiA=="], + + "react-lazy-load": ["react-lazy-load@4.0.1", "https://registry.npmmirror.com/react-lazy-load/-/react-lazy-load-4.0.1.tgz", { "peerDependencies": { "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" } }, "sha512-TnXRr79X9rlC9UcmO6iyS28rOPHrgkHIP4+b8yZPfs1tw6k/Rp2DmFY8R20BqWR45ZWkpT+4dqV1f+yci+1ozg=="], + + "react-markdown": ["react-markdown@8.0.7", "https://registry.npmmirror.com/react-markdown/-/react-markdown-8.0.7.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/prop-types": "^15.0.0", "@types/unist": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^2.0.0", "prop-types": "^15.0.0", "property-information": "^6.0.0", "react-is": "^18.0.0", "remark-parse": "^10.0.0", "remark-rehype": "^10.0.0", "space-separated-tokens": "^2.0.0", "style-to-object": "^0.4.0", "unified": "^10.0.0", "unist-util-visit": "^4.0.0", "vfile": "^5.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "react-syntax-highlighter": ["react-syntax-highlighter@15.6.6", "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw=="], + + "recast": ["recast@0.23.11", "https://registry.npmmirror.com/recast/-/recast-0.23.11.tgz", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + + "refractor": ["refractor@3.6.0", "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + + "remark-parse": ["remark-parse@10.0.2", "https://registry.npmmirror.com/remark-parse/-/remark-parse-10.0.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "mdast-util-from-markdown": "^1.0.0", "unified": "^10.0.0" } }, "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw=="], + + "remark-rehype": ["remark-rehype@10.1.0", "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-to-hast": "^12.1.0", "unified": "^10.0.0" } }, "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw=="], + + "require-directory": ["require-directory@2.1.1", "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], + + "resolve": ["resolve@1.22.12", "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-from": ["resolve-from@4.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "restore-cursor": ["restore-cursor@5.1.0", "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "rettime": ["rettime@0.11.11", "https://registry.npmmirror.com/rettime/-/rettime-0.11.11.tgz", {}, "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ=="], + + "reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "router": ["router@2.2.0", "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "run-applescript": ["run-applescript@7.1.0", "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "sade": ["sade@1.8.1", "https://registry.npmmirror.com/sade/-/sade-1.8.1.tgz", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "send": ["send@1.2.1", "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serve-static": ["serve-static@2.2.1", "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "set-cookie-parser": ["set-cookie-parser@3.1.0", "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shadcn": ["shadcn@4.7.0", "https://registry.npmmirror.com/shadcn/-/shadcn-4.7.0.tgz", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-70fwnesNrY1GgeD7Kdzn+3SsYeyfibm8immsA5L68+OusoPTvYF01oWExl8/latKpMpvVXcbgdbbE6VFBJQ38w=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shiki-es": ["shiki-es@0.2.0", "https://registry.npmmirror.com/shiki-es/-/shiki-es-0.2.0.tgz", {}, "sha512-RbRMD+IuJJseSZljDdne9ThrUYrwBwJR04FvN4VXpfsU3MNID5VJGHLAD5je/HGThCyEKNgH+nEkSFEWKD7C3Q=="], + + "side-channel": ["side-channel@1.1.0", "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.1", "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sisteransi": ["sisteransi@1.0.5", "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "strict-event-emitter": ["strict-event-emitter@0.5.1", "https://registry.npmmirror.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], + + "string-convert": ["string-convert@0.2.1", "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="], + + "string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "stringify-object": ["stringify-object@5.0.0", "https://registry.npmmirror.com/stringify-object/-/stringify-object-5.0.0.tgz", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="], + + "strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-bom": ["strip-bom@3.0.0", "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "style-to-object": ["style-to-object@0.4.4", "https://registry.npmmirror.com/style-to-object/-/style-to-object-0.4.4.tgz", { "dependencies": { "inline-style-parser": "0.1.1" } }, "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg=="], + + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + + "stylis": ["stylis@4.4.0", "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "swr": ["swr@2.4.1", "https://registry.npmmirror.com/swr/-/swr-2.4.1.tgz", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="], + + "tagged-tag": ["tagged-tag@1.0.0", "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "tailwind-merge": ["tailwind-merge@3.6.0", "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.6.0.tgz", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], + + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "throttle-debounce": ["throttle-debounce@5.0.2", "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", {}, "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tldts": ["tldts@7.0.30", "https://registry.npmmirror.com/tldts/-/tldts-7.0.30.tgz", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], + + "tldts-core": ["tldts-core@7.0.30", "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.30.tgz", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], + + "to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tough-cookie": ["tough-cookie@6.0.1", "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.1.tgz", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], + + "trim-lines": ["trim-lines@3.0.1", "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-morph": ["ts-morph@26.0.0", "https://registry.npmmirror.com/ts-morph/-/ts-morph-26.0.0.tgz", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="], + + "tsconfig-paths": ["tsconfig-paths@4.2.0", "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "type-fest": ["type-fest@5.6.0", "https://registry.npmmirror.com/type-fest/-/type-fest-5.6.0.tgz", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + + "type-is": ["type-is@2.1.0", "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "unified": ["unified@10.1.2", "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "bail": "^2.0.0", "extend": "^3.0.0", "is-buffer": "^2.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^5.0.0" } }, "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q=="], + + "unist-util-generated": ["unist-util-generated@2.0.1", "https://registry.npmmirror.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz", {}, "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A=="], + + "unist-util-is": ["unist-util-is@5.2.1", "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "unist-util-position": ["unist-util-position@4.0.4", "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-4.0.4.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="], + + "unist-util-visit": ["unist-util-visit@4.1.2", "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "universalify": ["universalify@2.0.1", "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unpipe": ["unpipe@1.0.0", "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "until-async": ["until-async@3.0.2", "https://registry.npmmirror.com/until-async/-/until-async-3.0.2.tgz", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "https://registry.npmmirror.com/use-sidecar/-/use-sidecar-1.1.3.tgz", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uvu": ["uvu@0.5.6", "https://registry.npmmirror.com/uvu/-/uvu-0.5.6.tgz", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="], + + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + + "vary": ["vary@1.1.2", "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "vfile": ["vfile@5.3.7", "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", { "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "unist-util-stringify-position": "^3.0.0", "vfile-message": "^3.0.0" } }, "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g=="], + + "vfile-message": ["vfile-message@3.1.4", "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^3.0.0" } }, "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "which": ["which@4.0.0", "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "wsl-utils": ["wsl-utils@0.3.1", "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.3.1.tgz", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + + "xtend": ["xtend@4.0.2", "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@1.10.3", "https://registry.npmmirror.com/yaml/-/yaml-1.10.3.tgz", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], + + "yargs": ["yargs@17.7.2", "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-spinner": ["yocto-spinner@1.2.0", "https://registry.npmmirror.com/yocto-spinner/-/yocto-spinner-1.2.0.tgz", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="], + + "yoctocolors": ["yoctocolors@2.1.2", "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.2.tgz", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], + + "@ant-design/cssinjs-utils/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="], + + "@ant-design/pro-components/@ant-design/icons": ["@ant-design/icons@5.6.1", "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", { "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg=="], + + "@babel/core/semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@dotenvx/dotenvx/commander": ["commander@11.1.0", "https://registry.npmmirror.com/commander/-/commander-11.1.0.tgz", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "@dotenvx/dotenvx/execa": ["execa@5.1.1", "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@emotion/babel-plugin/@emotion/hash": ["@emotion/hash@0.9.2", "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], + + "@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], + + "@emotion/babel-plugin/source-map": ["source-map@0.5.7", "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + + "@emotion/babel-plugin/stylis": ["stylis@4.2.0", "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="], + + "@emotion/cache/stylis": ["stylis@4.2.0", "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="], + + "@emotion/serialize/@emotion/hash": ["@emotion/hash@0.9.2", "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], + + "@emotion/serialize/@emotion/unitless": ["@emotion/unitless@0.10.0", "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="], + + "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "https://registry.npmmirror.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], + + "@rc-component/menu/@rc-component/util": ["@rc-component/util@1.11.0", "https://registry.npmmirror.com/@rc-component/util/-/util-1.11.0.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jHG3/BYgUWiP5c7RZHiaUNToyw1L3nlPSKG2RPu+YoiD9b3ajiJwBWhsjO+ZELmCsKFAjNR5DelbKdlF0e2BDA=="], + + "@rc-component/notification/@rc-component/util": ["@rc-component/util@1.11.0", "https://registry.npmmirror.com/@rc-component/util/-/util-1.11.0.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jHG3/BYgUWiP5c7RZHiaUNToyw1L3nlPSKG2RPu+YoiD9b3ajiJwBWhsjO+ZELmCsKFAjNR5DelbKdlF0e2BDA=="], + + "@rc-component/table/@rc-component/context": ["@rc-component/context@2.0.1", "https://registry.npmmirror.com/@rc-component/context/-/context-2.0.1.tgz", { "dependencies": { "@rc-component/util": "^1.3.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw=="], + + "@rc-component/tabs/@rc-component/util": ["@rc-component/util@1.11.0", "https://registry.npmmirror.com/@rc-component/util/-/util-1.11.0.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jHG3/BYgUWiP5c7RZHiaUNToyw1L3nlPSKG2RPu+YoiD9b3ajiJwBWhsjO+ZELmCsKFAjNR5DelbKdlF0e2BDA=="], + + "@rc-component/tree/@rc-component/util": ["@rc-component/util@1.11.0", "https://registry.npmmirror.com/@rc-component/util/-/util-1.11.0.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jHG3/BYgUWiP5c7RZHiaUNToyw1L3nlPSKG2RPu+YoiD9b3ajiJwBWhsjO+ZELmCsKFAjNR5DelbKdlF0e2BDA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "accepts/mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "antd/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="], + + "antd/@ant-design/icons": ["@ant-design/icons@6.2.3", "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.2.3.tgz", { "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/icons-svg": "^4.4.2", "@rc-component/util": "^1.10.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-Pl3aoAtxQeKryYnt6VvDJtOxMOtA8wrRSACe/pTjOAIG3fdHrWm6Ivb4ku9tsFjYroSXBKirvuxG4QkwBXD9gg=="], + + "babel-plugin-macros/cosmiconfig": ["cosmiconfig@7.1.0", "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], + + "cliui/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cross-spawn/which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "express/cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "express/mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], + + "hastscript/property-information": ["property-information@5.6.0", "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], + + "hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "prompts/kleur": ["kleur@3.0.3", "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "prop-types/react-is": ["react-is@16.13.1", "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + + "refractor/prismjs": ["prismjs@1.27.0", "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + + "restore-cursor/onetime": ["onetime@7.0.0", "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "router/path-to-regexp": ["path-to-regexp@8.4.2", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + + "send/mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "type-is/content-type": ["content-type@2.0.0", "https://registry.npmmirror.com/content-type/-/content-type-2.0.0.tgz", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + + "type-is/mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "uvu/diff": ["diff@5.2.2", "https://registry.npmmirror.com/diff/-/diff-5.2.2.tgz", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], + + "wrap-ansi/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@ant-design/pro-components/@ant-design/icons/@ant-design/colors": ["@ant-design/colors@7.2.1", "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.1.tgz", { "dependencies": { "@ant-design/fast-color": "^2.0.6" } }, "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ=="], + + "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "accepts/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "express/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "send/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "type-is/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@ant-design/pro-components/@ant-design/icons/@ant-design/colors/@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..c5ebd12 --- /dev/null +++ b/web/components.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": { + "@magicui": "https://magicui.design/r/{name}" + } +} diff --git a/web/next-env.d.ts b/web/next-env.d.ts new file mode 100644 index 0000000..c4b7818 --- /dev/null +++ b/web/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/dev/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/web/next.config.ts b/web/next.config.ts new file mode 100644 index 0000000..61fd81b --- /dev/null +++ b/web/next.config.ts @@ -0,0 +1,21 @@ +import type { NextConfig } from "next"; +import { PHASE_DEVELOPMENT_SERVER } from "next/constants"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const apiBaseUrl = process.env.API_BASE_URL || "http://127.0.0.1:8080"; +const webDir = dirname(fileURLToPath(import.meta.url)); +const version = readFileSync(resolve(webDir, "../VERSION"), "utf8").trim() || "dev"; + +export default function nextConfig(phase: string): NextConfig { + const isDev = phase === PHASE_DEVELOPMENT_SERVER; + return { + env: { + NEXT_PUBLIC_APP_VERSION: version, + }, + async rewrites() { + return isDev ? [{ source: "/api/:path*", destination: `${apiBaseUrl}/api/:path*` }] : []; + }, + }; +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..2d05314 --- /dev/null +++ b/web/package.json @@ -0,0 +1,39 @@ +{ + "name": "infinite-canvas", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev --webpack -H 0.0.0.0", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@ant-design/icons": "^6.1.1", + "@ant-design/pro-components": "3.0.0-beta.3", + "@tanstack/react-query": "^5.100.9", + "antd": "^6.4.2", + "axios": "^1.16.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "localforage": "^1.10.0", + "lucide-react": "^1.16.0", + "motion": "^12.38.0", + "next": "16.2.3", + "radix-ui": "^1.4.3", + "react": "19.2.5", + "react-dom": "19.2.5", + "shadcn": "^4.7.0", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", + "typescript": "^5" + } +} diff --git a/web/postcss.config.mjs b/web/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/web/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 0000000..027ea5d --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/src/app/(admin)/admin/assets/page.tsx b/web/src/app/(admin)/admin/assets/page.tsx new file mode 100644 index 0000000..eb1f7a5 --- /dev/null +++ b/web/src/app/(admin)/admin/assets/page.tsx @@ -0,0 +1,162 @@ +"use client"; + +import { CopyOutlined, DeleteOutlined, EditOutlined, EyeOutlined, PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons"; +import { ProTable, type ProColumns } from "@ant-design/pro-components"; +import { App, Button, Card, Col, Flex, Form, Image, Input, Modal, Row, Select, Space, Tag, Tooltip, Typography } from "antd"; +import { useEffect, useState } from "react"; + +import type { AdminAsset } from "@/services/api/admin"; +import { useAdminAssets } from "../hooks/use-admin-assets"; + +type AssetFormValues = Partial & { tagText?: string }; + +const typeOptions = [ + { label: "全部类型", value: "" }, + { label: "文本", value: "text" }, + { label: "图片", value: "image" }, +]; + +const editTypeOptions = typeOptions.slice(1); + +export default function AdminAssetsPage() { + const { assets, tags, keyword, kind, tag, page, pageSize, total, isLoading, searchAssets, changeKind, changeTag, changePage, changePageSize, resetFilters, refreshAssets, saveAsset: saveAdminAsset, deleteAsset } = useAdminAssets(); + const { message } = App.useApp(); + const [form] = Form.useForm(); + const [editingAsset, setEditingAsset] = useState | null>(null); + const [detailAsset, setDetailAsset] = useState(null); + const [deletingAsset, setDeletingAsset] = useState(null); + const formType = Form.useWatch("type", form) || editingAsset?.type || "text"; + const tagOptions = tags.map((item) => ({ label: item, value: item })); + + useEffect(() => { + if (editingAsset) form.setFieldsValue({ ...editingAsset, tagText: editingAsset.tags?.join(", ") || "" }); + }, [editingAsset, form]); + + const copyValue = async (value: string) => { + await navigator.clipboard.writeText(value); + message.success("已复制"); + }; + + const saveAsset = async () => { + const value = await form.validateFields(); + const nextType = value.type || "text"; + await saveAdminAsset({ + ...editingAsset, + ...value, + type: nextType, + coverUrl: value.coverUrl || (nextType === "image" ? value.url : ""), + tags: (value.tagText || "").split(",").map((item) => item.trim()).filter(Boolean), + }); + setEditingAsset(null); + }; + + const columns: ProColumns[] = [ + { + title: "封面", + dataIndex: "coverUrl", + width: 88, + render: (_, item) => {item.title}, + }, + { + title: "标题", + dataIndex: "title", + width: 260, + render: (_, item) => setDetailAsset(item)}>{item.title}, + }, + { + title: "类型", + dataIndex: "type", + width: 84, + render: (_, item) => {item.type === "image" ? "图片" : "文本"}, + }, + { + title: "标签", + dataIndex: "tags", + width: 180, + render: (_, item) => {(item.tags || []).slice(0, 3).map((tag) => {tag})}, + }, + { + title: "分类", + dataIndex: "category", + width: 120, + render: (_, item) => {item.category || "未标注"}, + }, + { + title: "操作", + key: "actions", + width: 112, + align: "right", + render: (_, item) => ( + + + + + + + rowKey="id" + columns={columns} + dataSource={assets} + loading={isLoading} + search={false} + defaultSize="middle" + tableLayout="fixed" + cardProps={{ variant: "borderless" }} + headerTitle={素材列表{total} 条} + options={{ density: true, setting: true, reload: () => void refreshAssets() }} + toolBarRender={() => []} + pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [10, 20, 50, 100], showTotal: (value) => `共 ${value} 条`, onChange: (nextPage, nextPageSize) => nextPageSize !== pageSize ? changePageSize(nextPageSize) : changePage(nextPage) }} + /> + + + setEditingAsset(null)} onOk={() => void saveAsset()} okText="保存" cancelText="取消" destroyOnHidden> +
+ + + + + + {formType === "image" ? : } +
+
+ + setDetailAsset(null)} footer={}> + {detailAsset ? ( + + + {detailAsset.title} + + {detailAsset.title} + {detailAsset.type === "image" ? "图片" : "文本"}{detailAsset.category ? {detailAsset.category} : null}{(detailAsset.tags || []).map((tag) => {tag})} + + + {detailAsset.description ? {detailAsset.description} : null} + + + + ) : null} + + + setDeletingAsset(null)} onOk={async () => { if (!deletingAsset) return; await deleteAsset(deletingAsset.id); setDeletingAsset(null); }} okText="删除" okButtonProps={{ danger: true }} cancelText="取消"> + 确定删除「{deletingAsset?.title}」吗?删除后会从服务器素材库中移除。 + + + ); +} diff --git a/web/src/app/(admin)/admin/hooks/use-admin-assets.ts b/web/src/app/(admin)/admin/hooks/use-admin-assets.ts new file mode 100644 index 0000000..06dd24d --- /dev/null +++ b/web/src/app/(admin)/admin/hooks/use-admin-assets.ts @@ -0,0 +1,109 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { App } from "antd"; + +import { deleteAdminAsset, fetchAdminAssets, saveAdminAsset, type AdminAsset } from "@/services/api/admin"; +import { useUserStore } from "@/stores/use-user-store"; + +const defaultPageSize = 10; + +export function useAdminAssets() { + const { message } = App.useApp(); + const queryClient = useQueryClient(); + const token = useUserStore((state) => state.token); + const clearSession = useUserStore((state) => state.clearSession); + const [keyword, setKeyword] = useState(""); + const [type, setType] = useState(""); + const [tag, setTag] = useState([]); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(defaultPageSize); + + const query = useQuery({ + queryKey: ["admin", "assets", token, keyword, type, tag, page, pageSize], + queryFn: () => fetchAdminAssets(token, { keyword, type, tag, page, pageSize }), + enabled: Boolean(token), + retry: false, + }); + + const saveMutation = useMutation({ + mutationFn: (asset: Partial) => saveAdminAsset(token, asset), + onSuccess: async (_, asset) => { + await queryClient.invalidateQueries({ queryKey: ["admin", "assets"] }); + message.success(asset.id ? "素材已保存" : "素材已新增"); + }, + onError: (error) => { + message.error(error instanceof Error ? error.message : "保存失败"); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => deleteAdminAsset(token, id), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["admin", "assets"] }); + message.success("素材已删除"); + }, + onError: (error) => { + message.error(error instanceof Error ? error.message : "删除失败"); + }, + }); + + useEffect(() => { + if (query.isError) { + const errorMessage = query.error instanceof Error ? query.error.message : "读取素材失败"; + message.error(errorMessage); + if (errorMessage.includes("未登录") || errorMessage.includes("权限不足") || errorMessage.includes("登录状态无效")) { + clearSession(); + } + } + }, [clearSession, message, query.error, query.isError]); + + const updateFilters = (next: Partial<{ keyword: string; type: string; tag: string[]; page: number; pageSize: number }>) => { + const queryState = { keyword, type, tag, page, pageSize, ...next }; + if (next.keyword !== undefined || next.type !== undefined || next.tag !== undefined || next.pageSize !== undefined) { + queryState.page = 1; + } + setKeyword(queryState.keyword); + setType(queryState.type); + setTag(queryState.tag); + setPage(queryState.page); + setPageSize(queryState.pageSize); + }; + + const refreshAssets = async () => { + await query.refetch(); + }; + + const saveAsset = async (asset: Partial) => { + await saveMutation.mutateAsync(asset); + }; + + const deleteAsset = async (id: string) => { + await deleteMutation.mutateAsync(id); + }; + + const data = query.data; + const isLoading = query.isFetching || saveMutation.isPending || deleteMutation.isPending; + + return { + assets: data?.items || [], + tags: data?.tags || [], + keyword, + kind: type, + tag, + page, + pageSize, + total: data?.total || 0, + isLoading, + searchAssets: (value = keyword) => updateFilters({ keyword: value }), + changeKind: (value: string) => updateFilters({ type: value, tag: [] }), + changeTag: (value: string[]) => updateFilters({ tag: value }), + changePage: (value: number) => updateFilters({ page: value }), + changePageSize: (value: number) => updateFilters({ pageSize: value }), + resetFilters: () => updateFilters({ keyword: "", type: "", tag: [], page: 1, pageSize: defaultPageSize }), + refreshAssets, + saveAsset, + deleteAsset, + }; +} diff --git a/web/src/app/(admin)/admin/hooks/use-admin-prompts.ts b/web/src/app/(admin)/admin/hooks/use-admin-prompts.ts new file mode 100644 index 0000000..455e472 --- /dev/null +++ b/web/src/app/(admin)/admin/hooks/use-admin-prompts.ts @@ -0,0 +1,137 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { App } from "antd"; + +import { deleteAdminPrompt, fetchAdminPrompts, fetchAdminPromptCategories, saveAdminPrompt, syncAdminPromptCategory, type AdminPromptCategory } from "@/services/api/admin"; +import type { Prompt } from "@/services/api/prompts"; +import { useUserStore } from "@/stores/use-user-store"; + +const defaultPageSize = 10; + +export function useAdminPrompts() { + const { message } = App.useApp(); + const queryClient = useQueryClient(); + const token = useUserStore((state) => state.token); + const clearSession = useUserStore((state) => state.clearSession); + const [keyword, setKeyword] = useState(""); + const [category, setCategory] = useState(""); + const [tag, setTag] = useState([]); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(defaultPageSize); + + const categoriesQuery = useQuery({ + queryKey: ["admin", "prompt-categories", token], + queryFn: () => fetchAdminPromptCategories(token), + enabled: Boolean(token), + retry: false, + }); + + const promptsQuery = useQuery({ + queryKey: ["admin", "prompts", token, keyword, category, tag, page, pageSize], + queryFn: () => fetchAdminPrompts(token, { keyword, category, tag, page, pageSize }), + enabled: Boolean(token), + retry: false, + }); + + const syncMutation = useMutation({ + mutationFn: (category: string) => syncAdminPromptCategory(token, category), + onSuccess: async (categories) => { + queryClient.setQueryData(["admin", "prompt-categories", token], categories); + await queryClient.invalidateQueries({ queryKey: ["admin", "prompts"] }); + message.success("远程提示词源已同步"); + }, + onError: (error) => { + message.error(error instanceof Error ? error.message : "同步失败"); + }, + }); + + const saveMutation = useMutation({ + mutationFn: (prompt: Partial) => saveAdminPrompt(token, prompt), + onSuccess: async (_, prompt) => { + await queryClient.invalidateQueries({ queryKey: ["admin", "prompt-categories"] }); + await queryClient.invalidateQueries({ queryKey: ["admin", "prompts"] }); + message.success(prompt.id ? "提示词已保存" : "提示词已新增"); + }, + onError: (error) => { + message.error(error instanceof Error ? error.message : "保存失败"); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => deleteAdminPrompt(token, id), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["admin", "prompt-categories"] }); + await queryClient.invalidateQueries({ queryKey: ["admin", "prompts"] }); + message.success("提示词已删除"); + }, + onError: (error) => { + message.error(error instanceof Error ? error.message : "删除失败"); + }, + }); + + useEffect(() => { + if (categoriesQuery.isError) { + const errorMessage = categoriesQuery.error instanceof Error ? categoriesQuery.error.message : "读取提示词分类失败"; + message.error(errorMessage); + if (errorMessage.includes("未登录") || errorMessage.includes("权限不足") || errorMessage.includes("登录状态无效")) { + clearSession(); + } + } + }, [categoriesQuery.error, categoriesQuery.isError, clearSession, message]); + + useEffect(() => { + if (promptsQuery.isError) { + const errorMessage = promptsQuery.error instanceof Error ? promptsQuery.error.message : "读取提示词失败"; + message.error(errorMessage); + if (errorMessage.includes("未登录") || errorMessage.includes("权限不足") || errorMessage.includes("登录状态无效")) { + clearSession(); + } + } + }, [clearSession, message, promptsQuery.error, promptsQuery.isError]); + + const updateFilters = (next: Partial<{ keyword: string; category: string; tag: string[]; page: number; pageSize: number }>) => { + const queryState = { keyword, category, tag, page, pageSize, ...next }; + if (next.keyword !== undefined || next.category !== undefined || next.tag !== undefined || next.pageSize !== undefined) { + queryState.page = 1; + } + setKeyword(queryState.keyword); + setCategory(queryState.category); + setTag(queryState.tag); + setPage(queryState.page); + setPageSize(queryState.pageSize); + }; + + const refreshPrompts = async () => { + await categoriesQuery.refetch(); + await promptsQuery.refetch(); + }; + + const data = promptsQuery.data; + const isLoading = categoriesQuery.isFetching || promptsQuery.isFetching || saveMutation.isPending || deleteMutation.isPending; + + return { + categories: categoriesQuery.data || [], + prompts: data?.items || [], + tags: data?.tags || [], + keyword, + category, + tag, + page, + pageSize, + total: data?.total || 0, + isLoading, + isSyncing: syncMutation.isPending, + syncCategory: (category: string) => syncMutation.mutateAsync(category), + searchPrompts: (value = keyword) => updateFilters({ keyword: value }), + changeCategory: (value: string) => updateFilters({ category: value, tag: [] }), + changeTag: (value: string[]) => updateFilters({ tag: value }), + changePage: (value: number) => updateFilters({ page: value }), + changePageSize: (value: number) => updateFilters({ pageSize: value }), + resetFilters: () => updateFilters({ keyword: "", category: "", tag: [], page: 1, pageSize: defaultPageSize }), + refreshPrompts, + savePrompt: (prompt: Partial) => saveMutation.mutateAsync(prompt), + deletePrompt: (id: string) => deleteMutation.mutateAsync(id), + }; +} diff --git a/web/src/app/(admin)/admin/layout.tsx b/web/src/app/(admin)/admin/layout.tsx new file mode 100644 index 0000000..a40320b --- /dev/null +++ b/web/src/app/(admin)/admin/layout.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { FileTextOutlined, HomeOutlined, LogoutOutlined, PictureOutlined } from "@ant-design/icons"; +import { Button, Flex, Layout, Menu, Typography } from "antd"; +import { LogOut } from "lucide-react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import type { ReactNode } from "react"; +import { useEffect } from "react"; + +import { UserStatusActions } from "@/components/user-status-actions"; +import { useThemeStore } from "@/stores/use-theme-store"; +import { useUserStore } from "@/stores/use-user-store"; + +export default function AdminLayout({ children }: { children: ReactNode }) { + const router = useRouter(); + const pathname = usePathname(); + const token = useUserStore((state) => state.token); + const user = useUserStore((state) => state.user); + const isReady = useUserStore((state) => state.isReady); + const logout = useUserStore((state) => state.clearSession); + const colorTheme = useThemeStore((state) => state.theme); + const setTheme = useThemeStore((state) => state.setTheme); + const activeKey = pathname.startsWith("/admin/assets") ? "/admin/assets" : pathname.startsWith("/admin/prompts") ? "/admin/prompts" : ""; + const pageTitle = pathname.startsWith("/admin/assets") ? "素材库管理" : "提示词管理"; + const appVersion = process.env.NEXT_PUBLIC_APP_VERSION || "dev"; + + useEffect(() => { + if (!isReady) return; + if (!token) { + router.replace("/login?redirect=/admin"); + return; + } + if (user?.role !== "admin") { + router.replace("/"); + } + }, [isReady, router, token, user?.role]); + + if (!isReady || !token || user?.role !== "admin") { + return ( +
+ +
+ ); + } + + return ( + + + + + 无限画布 + + , label: 提示词管理 }, + { key: "/admin/assets", icon: , label: 素材库 }, + ]} + /> + + + + + + + + {pageTitle} + + , label: "退出登录", onClick: logout }]} + /> + + + {children} + + + ); +} diff --git a/web/src/app/(admin)/admin/page.tsx b/web/src/app/(admin)/admin/page.tsx new file mode 100644 index 0000000..320724e --- /dev/null +++ b/web/src/app/(admin)/admin/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function AdminPage() { + redirect("/admin/assets"); +} diff --git a/web/src/app/(admin)/admin/prompts/page.tsx b/web/src/app/(admin)/admin/prompts/page.tsx new file mode 100644 index 0000000..0d84058 --- /dev/null +++ b/web/src/app/(admin)/admin/prompts/page.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { CopyOutlined, DeleteOutlined, EditOutlined, ExportOutlined, EyeOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, SyncOutlined } from "@ant-design/icons"; +import { ProTable, type ProColumns } from "@ant-design/pro-components"; +import { App, Button, Card, Col, Flex, Form, Image, Input, Modal, Row, Select, Space, Table, Tag, Tooltip, Typography } from "antd"; +import { useEffect, useState } from "react"; + +import type { Prompt } from "@/services/api/prompts"; +import { useAdminPrompts } from "../hooks/use-admin-prompts"; + +export default function AdminPromptsPage() { + const { categories, prompts, tags, keyword, category, tag, page, pageSize, total, isLoading, isSyncing, searchPrompts, changeCategory, changeTag, changePage, changePageSize, resetFilters, refreshPrompts, syncCategory, savePrompt: saveAdminPrompt, deletePrompt } = useAdminPrompts(); + const { message } = App.useApp(); + const [form] = Form.useForm & { tagText?: string }>(); + const [editingPrompt, setEditingPrompt] = useState | null>(null); + const [detailPrompt, setDetailPrompt] = useState(null); + const [deletingPrompt, setDeletingPrompt] = useState(null); + const [isSyncOpen, setIsSyncOpen] = useState(false); + const defaultCategory = categories[0]?.category || ""; + const categoryName = (category: string) => categories.find((item) => item.category === category)?.name || category; + const categoryOptions = [{ label: "全部分类", value: "" }, ...categories.map((item) => ({ label: item.name, value: item.category }))]; + const tagOptions = tags.map((item) => ({ label: item, value: item })); + + useEffect(() => { + if (editingPrompt) form.setFieldsValue({ ...editingPrompt, tagText: editingPrompt.tags?.join(", ") || "" }); + }, [editingPrompt, form]); + + const copyPrompt = async (value: string) => { + await navigator.clipboard.writeText(value); + message.success("已复制"); + }; + + const savePrompt = async () => { + const value = await form.validateFields(); + await saveAdminPrompt({ ...editingPrompt, ...value, category: value.category || defaultCategory, tags: (value.tagText || "").split(",").map((item) => item.trim()).filter(Boolean) }); + setEditingPrompt(null); + }; + + const columns: ProColumns[] = [ + { + title: "封面", + dataIndex: "coverUrl", + width: 88, + render: (_, item) => {item.title}, + }, + { + title: "标题", + dataIndex: "title", + width: 260, + render: (_, item) => setDetailPrompt(item)}>{item.title}, + }, + { + title: "分类", + dataIndex: "category", + width: 150, + render: (_, item) => {categoryName(item.category)}, + }, + { + title: "标签", + dataIndex: "tags", + width: 180, + render: (_, item) => {(item.tags || []).slice(0, 3).map((tag) => {tag})}, + }, + { + title: "操作", + key: "actions", + width: 112, + align: "right", + render: (_, item) => ( + + + + + + + rowKey="id" + columns={columns} + dataSource={prompts} + loading={isLoading} + search={false} + defaultSize="middle" + tableLayout="fixed" + cardProps={{ variant: "borderless" }} + headerTitle={提示词列表{total} 条} + options={{ density: true, setting: true, reload: () => void refreshPrompts() }} + toolBarRender={() => [, ]} + pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [10, 20, 50, 100], showTotal: (value) => `共 ${value} 条`, onChange: (nextPage, nextPageSize) => nextPageSize !== pageSize ? changePageSize(nextPageSize) : changePage(nextPage) }} + /> + + + setEditingPrompt(null)} onOk={() => void savePrompt()} okText="保存" cancelText="取消" destroyOnHidden> +
+ + + + +
+
+ + setDetailPrompt(null)} footer={}> + {detailPrompt ? ( + + + {detailPrompt.title} + + {detailPrompt.title} + {categoryName(detailPrompt.category)}{(detailPrompt.tags || []).map((tag) => {tag})} + + + {detailPrompt.preview ? {detailPrompt.preview} : null} + + + + {detailPrompt.githubUrl ? : null} + + + ) : null} + + + !isSyncing && setIsSyncOpen(false)} maskClosable={!isSyncing} footer={}> + item.remote)} + pagination={false} + columns={[ + { title: "远程源", dataIndex: "name", render: (_, item) => {item.name}{item.githubUrl ? : null} }, + { title: "", key: "sync", width: 96, align: "right", render: (_, item) => }, + ]} + /> + + + setDeletingPrompt(null)} onOk={async () => { if (!deletingPrompt) return; await deletePrompt(deletingPrompt.id); setDeletingPrompt(null); }} okText="删除" okButtonProps={{ danger: true }} cancelText="取消"> + 确定删除「{deletingPrompt?.title}」吗?删除后会从当前分类中删除。 + + + ); +} diff --git a/web/src/app/(user)/asset-library/page.tsx b/web/src/app/(user)/asset-library/page.tsx new file mode 100644 index 0000000..e0026b8 --- /dev/null +++ b/web/src/app/(user)/asset-library/page.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { Copy, FolderPlus, Search } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { App, Button, Card, Drawer, Empty, Image, Input, Pagination, Spin, Tag, Typography } from "antd"; +import axios from "axios"; + +import { cn } from "@/lib/utils"; +import { useAssetStore } from "@/stores/use-asset-store"; +import { fetchAssetLibrary, type AssetLibraryItem } from "@/services/api/assets"; +import { uploadImage } from "@/services/image-storage"; + +const PAGE_SIZE = 12; + +export default function AssetLibraryPage() { + const { message } = App.useApp(); + const [keyword, setKeyword] = useState(""); + const [selectedType, setSelectedType] = useState(""); + const [selectedTags, setSelectedTags] = useState([]); + const [page, setPage] = useState(1); + const [selectedAsset, setSelectedAsset] = useState(null); + const addAsset = useAssetStore((state) => state.addAsset); + + const query = useQuery({ + queryKey: ["asset-library", keyword, selectedType, selectedTags, page], + queryFn: () => fetchAssetLibrary({ keyword, type: selectedType, tag: selectedTags, page, pageSize: PAGE_SIZE }), + retry: false, + }); + + useEffect(() => { + if (query.isError) { + message.error(query.error instanceof Error ? query.error.message : "获取素材库失败"); + } + }, [message, query.error, query.isError]); + + const isReady = query.isFetched || query.isError; + const items = query.data?.items || []; + const availableTags = query.data?.tags || []; + const total = query.data?.total || 0; + + const toggleTag = (tag: string) => { + setSelectedTags((items) => items.includes(tag) ? items.filter((item) => item !== tag) : [...items, tag]); + }; + + const saveToMyAssets = async (asset: AssetLibraryItem) => { + try { + if (asset.type === "image") { + const dataUrl = await remoteImageToDataUrl(asset.url); + const image = await uploadImage(dataUrl); + addAsset({ + kind: "image", + title: asset.title, + coverUrl: asset.coverUrl, + tags: asset.tags, + source: asset.category, + note: asset.description, + data: { dataUrl: image.url, storageKey: image.storageKey, width: image.width, height: image.height, bytes: image.bytes, mimeType: image.mimeType }, + metadata: { source: "asset-library", assetId: asset.id }, + }); + } else { + addAsset({ + kind: "text", + title: asset.title, + coverUrl: asset.coverUrl, + tags: asset.tags, + source: asset.category, + note: asset.description, + data: { content: asset.content }, + metadata: { source: "asset-library", assetId: asset.id }, + }); + } + message.success("已加入我的素材"); + } catch { + message.error("加入失败"); + } + }; + + const copyText = async (value: string) => { + await navigator.clipboard.writeText(value); + message.success("已复制"); + }; + + if (!isReady) { + return ( +
+ +
+ ); + } + + return ( +
+
+
+
+

素材库

+

挑选团队素材,加入我的素材后继续编辑和使用。

+
+
+ } value={keyword} placeholder="按标题查询" onChange={(event) => { setPage(1); setKeyword(event.target.value); }} /> +
+
+
+
类型
+
+ {[ + { label: "全部", value: "" }, + { label: "文本", value: "text" }, + { label: "图片", value: "image" }, + ].map((item) => ( + { setPage(1); setSelectedType(item.value); }}> + {item.label} + + ))} +
+
+
+
标签
+
+ { setPage(1); setSelectedTags([]); }}> + 全部 + + {availableTags.map((tag) => ( + { setPage(1); toggleTag(tag); }}> + {tag} + + ))} +
+
+
+
+ +
+
+ {items.map((asset) => ( + setSelectedAsset(asset)} onAdd={() => void saveToMyAssets(asset)} /> + ))} +
+ + {!items.length ? : null} + +
+ setPage(nextPage)} /> +
+
+
+ + setSelectedAsset(null)}> + {selectedAsset ? ( +
+ {selectedAsset.coverUrl ? :
{selectedAsset.content || "暂无封面"}
} +
+ {selectedAsset.title} +
+ {selectedAsset.type === "image" ? "图片" : "文本"} + {selectedAsset.tags.map((tag) => {tag})} +
+
+
+ 内容 + {selectedAsset.type === "text" ? {selectedAsset.content} : {selectedAsset.url}} +
+ {selectedAsset.description ? {selectedAsset.description} : null} +
+ {selectedAsset.type === "text" ? : null} + {selectedAsset.type === "image" ? : null} + +
+
+ ) : null} +
+
+ ); +} + +function LibraryCard({ asset, onOpen, onAdd }: { asset: AssetLibraryItem; onOpen: () => void; onAdd: () => void }) { + const cover = asset.coverUrl; + return ( + {cover ? {asset.title} :
{asset.content || "暂无封面"}
}}> + +
+ + +
+
+ ); +} + +async function remoteImageToDataUrl(url: string) { + const response = await axios.get(url, { responseType: "blob" }); + const blob = response.data as Blob; + return await blobToDataUrl(blob); +} + +function blobToDataUrl(blob: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result || "")); + reader.onerror = () => reject(new Error("读取图片失败")); + reader.readAsDataURL(blob); + }); +} diff --git a/web/src/app/(user)/assets/page.tsx b/web/src/app/(user)/assets/page.tsx new file mode 100644 index 0000000..0ea1bbd --- /dev/null +++ b/web/src/app/(user)/assets/page.tsx @@ -0,0 +1,382 @@ +"use client"; + +import { Copy, Download, PencilLine, Search, Trash2, Upload } from "lucide-react"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { App, Button, Card, Drawer, Empty, Form, Image, Input, Modal, Pagination, Select, Space, Tag, Typography } from "antd"; + +import { formatBytes, readFileAsDataUrl } from "@/lib/image-utils"; +import { uploadImage } from "@/services/image-storage"; +import { cn } from "@/lib/utils"; +import { useAssetStore, type Asset, type AssetKind, type ImageAsset } from "@/stores/use-asset-store"; + +type AssetFormValues = { + kind: AssetKind; + title: string; + coverUrl: string; + tags: string[]; + source?: string; + note?: string; + content?: string; +}; + +type ImageDraft = ImageAsset["data"] | null; + +const kindOptions = [ + { label: "全部", value: "all" }, + { label: "文本", value: "text" }, + { label: "图片", value: "image" }, +]; + +export default function AssetsPage() { + const { message } = App.useApp(); + const [form] = Form.useForm(); + const coverInputRef = useRef(null); + const imageInputRef = useRef(null); + const assets = useAssetStore((state) => state.assets); + const addAsset = useAssetStore((state) => state.addAsset); + const updateAsset = useAssetStore((state) => state.updateAsset); + const removeAsset = useAssetStore((state) => state.removeAsset); + const [keyword, setKeyword] = useState(""); + const [kindFilter, setKindFilter] = useState("all"); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [editingAsset, setEditingAsset] = useState(null); + const [isAssetOpen, setIsAssetOpen] = useState(false); + const [previewAsset, setPreviewAsset] = useState(null); + const [deletingAsset, setDeletingAsset] = useState(null); + const [formKind, setFormKind] = useState("text"); + const [imageDraft, setImageDraft] = useState(null); + const coverUrl = Form.useWatch("coverUrl", form) || ""; + const title = Form.useWatch("title", form) || ""; + const tags = Form.useWatch("tags", form) || []; + const content = Form.useWatch("content", form) || ""; + const validAssets = useMemo(() => assets.filter((asset) => asset.kind === "text" || asset.kind === "image"), [assets]); + + const filteredAssets = useMemo(() => { + const query = keyword.trim().toLowerCase(); + return validAssets.filter((asset) => { + if (kindFilter !== "all" && asset.kind !== kindFilter) return false; + if (!query) return true; + return assetSearchText(asset).includes(query); + }); + }, [validAssets, keyword, kindFilter]); + + const visibleAssets = useMemo(() => { + const start = (page - 1) * pageSize; + return filteredAssets.slice(start, start + pageSize); + }, [filteredAssets, page, pageSize]); + + useEffect(() => { + const maxPage = Math.max(1, Math.ceil(filteredAssets.length / pageSize)); + setPage((value) => Math.min(value, maxPage)); + }, [filteredAssets.length, pageSize]); + + const openCreate = () => { + setEditingAsset(null); + setImageDraft(null); + setFormKind("text"); + form.setFieldsValue({ kind: "text", title: "", coverUrl: "", tags: [], source: "手动添加", note: "", content: "" }); + setIsAssetOpen(true); + }; + + const openEdit = (asset: Asset) => { + setEditingAsset(asset); + setFormKind(asset.kind); + setImageDraft(asset.kind === "image" ? asset.data : null); + form.setFieldsValue({ + kind: asset.kind, + title: asset.title, + coverUrl: asset.coverUrl, + tags: asset.tags || [], + source: asset.source, + note: asset.note, + content: asset.kind === "text" ? asset.data.content : "", + }); + setIsAssetOpen(true); + }; + + const saveAsset = async () => { + const values = await form.validateFields(); + const base = { + title: values.title.trim(), + coverUrl: values.coverUrl?.trim() || (values.kind === "image" && imageDraft ? imageDraft.dataUrl : ""), + tags: values.tags || [], + source: values.source?.trim(), + note: values.note?.trim(), + metadata: editingAsset?.metadata || { source: "manual" }, + }; + + if (values.kind === "text") { + const asset = { ...base, kind: "text" as const, data: { content: (values.content || "").trim() } }; + editingAsset ? updateAsset(editingAsset.id, asset) : addAsset(asset); + } else { + if (!imageDraft) { + message.error("请选择图片文件"); + return; + } + const asset = { ...base, kind: "image" as const, data: imageDraft }; + editingAsset ? updateAsset(editingAsset.id, asset) : addAsset(asset); + } + + message.success(editingAsset ? "素材已更新" : "素材已保存"); + setIsAssetOpen(false); + }; + + const readCoverFile = async (file?: File) => { + if (!file) return; + const dataUrl = await readFileAsDataUrl(file); + form.setFieldValue("coverUrl", dataUrl); + }; + + const readImageFile = async (file?: File) => { + if (!file || !file.type.startsWith("image/")) return; + const image = await uploadImage(file); + const draft = { dataUrl: image.url, storageKey: image.storageKey, width: image.width, height: image.height, bytes: image.bytes, mimeType: image.mimeType }; + setImageDraft(draft); + if (!form.getFieldValue("coverUrl")) form.setFieldValue("coverUrl", draft.dataUrl); + if (!form.getFieldValue("title")) form.setFieldValue("title", file.name); + }; + + const copyText = async (asset: Asset) => { + if (asset.kind !== "text") return; + await navigator.clipboard.writeText(asset.data.content); + message.success("文本已复制"); + }; + + const downloadImage = (asset: Asset) => { + if (asset.kind !== "image") return; + const link = document.createElement("a"); + link.href = asset.data.dataUrl; + link.download = `${asset.title || "asset"}.${asset.data.mimeType.split("/")[1] || "png"}`; + link.click(); + }; + + const confirmDelete = () => { + if (!deletingAsset) return; + removeAsset(deletingAsset.id); + message.success("素材已删除"); + setDeletingAsset(null); + }; + + return ( +
+
+
+
+

我的素材

+

收藏常用文本和图片,按类型、标题和标签快速查找。

+
+ +
+ } value={keyword} placeholder="搜索标题、内容、标签或来源" onChange={(event) => { setPage(1); setKeyword(event.target.value); }} onSearch={(value) => { setPage(1); setKeyword(value); }} /> +
+ +
+
+
+
类型
+
+ {kindOptions.map((option) => ( + { setPage(1); setKindFilter(option.value as AssetKind | "all"); }}> + {option.label} + + ))} +
+
+ +
+
+
+ +
+
+ {visibleAssets.map((asset) => ( + setPreviewAsset(asset)} + onEdit={() => openEdit(asset)} + onCopy={copyText} + onDownload={downloadImage} + onDelete={() => setDeletingAsset(asset)} + /> + ))} +
+ + {!visibleAssets.length ? : null} + +
+ { + setPage(nextPage); + setPageSize(nextPageSize); + }} + /> +
+
+
+ + setIsAssetOpen(false)} onOk={() => void saveAsset()} okText="保存" cancelText="取消" destroyOnHidden> +
+
+ + + + + + + + + + + + + + + +
+ {formKind === "text" ? ( + + + + ) : ( + +
+ + {imageDraft ? {imageDraft.width}x{imageDraft.height} · {formatBytes(imageDraft.bytes)} : 未选择图片} +
+
+ )} + +
+ 预览 +
+ {coverUrl || imageDraft?.dataUrl ? :
{content || "暂无封面"}
} +
+ {title || "未命名素材"} +
+ {tags.length ? tags.map((tag) => {tag}) : 未打标签} +
+
+
+
+
+ { + void readCoverFile(event.target.files?.[0]); + event.target.value = ""; + }} /> + { + void readImageFile(event.target.files?.[0]); + event.target.value = ""; + }} /> + + + setPreviewAsset(null)} onCopy={copyText} onDownload={downloadImage} /> + + setDeletingAsset(null)} onOk={confirmDelete} okText="删除" okButtonProps={{ danger: true }} cancelText="取消"> + 确定删除「{deletingAsset?.title}」吗?删除后会从我的素材中移除。 + + + ); +} + +function AssetCard({ asset, onOpen, onEdit, onCopy, onDownload, onDelete }: { asset: Asset; onOpen: () => void; onEdit: () => void; onCopy: (asset: Asset) => void; onDownload: (asset: Asset) => void; onDelete: () => void }) { + const cover = asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : ""); + const summary = assetSummary(asset); + return ( + + {cover ? {asset.title} :
{asset.kind === "text" ? asset.data.content : "暂无封面"}
} + + } + > + +
+ + + {asset.kind === "text" ? : null} + {asset.kind === "image" ? : null} + +
+
+ ); +} + +function AssetDrawer({ asset, onClose, onCopy, onDownload }: { asset: Asset | null; onClose: () => void; onCopy: (asset: Asset) => void; onDownload: (asset: Asset) => void }) { + const cover = asset ? asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : "") : ""; + return ( + + {asset ? ( +
+ {cover ? :
{asset.kind === "text" ? asset.data.content : "暂无封面"}
} +
+ {asset.title} + + {asset.kind === "image" ? "图片" : "文本"} + {(asset.tags || []).map((tag) => {tag})} + +
+
+ 内容 + {asset.kind === "text" ? {asset.data.content} : {asset.data.width}x{asset.data.height} · {formatBytes(asset.data.bytes)} · {asset.data.mimeType}} +
+ {asset.note ?
备注{asset.note}
: null} + + {asset.kind === "text" ? : null} + {asset.kind === "image" ? : null} + +
+ ) : null} +
+ ); +} + +function assetSummary(asset: Asset) { + if (asset.kind === "text") return asset.data.content; + return `${asset.data.width}x${asset.data.height} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`; +} + +function assetSearchText(asset: Asset) { + return [ + asset.title, + asset.source || "", + asset.note || "", + (asset.tags || []).join(" "), + asset.kind === "text" ? asset.data.content : asset.data.mimeType, + ].join(" ").toLowerCase(); +} diff --git a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx new file mode 100644 index 0000000..7a7841a --- /dev/null +++ b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx @@ -0,0 +1,2387 @@ +"use client"; + +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import type { ChangeEvent as ReactChangeEvent, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { Home, ImageIcon, Images, Keyboard, List, LogOut, Menu, MessageSquare, Plus, Redo2, Settings2, Trash2, Undo2, Upload } from "lucide-react"; + +import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image"; +import { resolveImageUrl, uploadImage, type UploadedImage } from "@/services/image-storage"; +import { createId } from "@/lib/id"; +import { getDataUrlByteSize, readImageMeta } from "@/lib/image-utils"; +import { useAiConfigStore } from "@/stores/use-ai-config-store"; +import { useConfigDialogStore } from "@/stores/use-config-dialog-store"; +import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme"; +import { useThemeStore } from "@/stores/use-theme-store"; +import { useAssetStore } from "@/stores/use-asset-store"; +import { useUserStore } from "@/stores/use-user-store"; +import { UserStatusActions } from "@/components/user-status-actions"; +import { cropDataUrl } from "../utils/canvas-image-data"; +import { App, Button, Dropdown, Modal } from "antd"; +import { defaultConfig, type AiConfig } from "@/lib/ai-config"; +import { NODE_DEFAULT_SIZE, getNodeSpec } from "../constants"; +import { ActiveConnectionPath, ConnectionPath } from "../components/canvas-connections"; +import { CanvasConfigNodePanel } from "../components/canvas-config-node-panel"; +import { CanvasAssistantPanel } from "../components/canvas-assistant-panel"; +import { CanvasNodeContextMenu } from "../components/canvas-context-menu"; +import { CanvasNodeAngleDialog, type CanvasImageAngleParams } from "../components/canvas-node-angle-dialog"; +import { CanvasNodeCropDialog, type CanvasImageCropRect } from "../components/canvas-node-crop-dialog"; +import { buildNodeChatMessages, buildNodeGenerationContext, buildNodeGenerationInputs, hydrateNodeGenerationContext, type NodeGenerationInput } from "../components/canvas-node-generation"; +import { CanvasNodeHoverToolbar, CanvasNodeInfoModal } from "../components/canvas-node-hover-toolbar"; +import { InfiniteCanvas } from "../components/infinite-canvas"; +import { Minimap } from "../components/canvas-mini-map"; +import { CanvasNode } from "../components/canvas-node"; +import { CanvasNodePromptPanel, type CanvasNodeGenerationMode } from "../components/canvas-node-prompt-panel"; +import { CanvasToolbar } from "../components/canvas-toolbar"; +import { AssetPickerModal, type AssetPickerTab, type InsertAssetPayload } from "../components/asset-picker-modal"; +import { CanvasZoomControls } from "../components/canvas-zoom-controls"; +import { useCanvasStore } from "../stores/use-canvas-store"; +import { CanvasNodeType, type CanvasAssistantImage, type CanvasAssistantSession, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ConnectionHandle, type ContextMenuState, type Position, type SelectionBox, type ViewportTransform } from "../types"; + +type CanvasClipboard = { + nodes: CanvasNodeData[]; + connections: CanvasConnection[]; +}; + +type PendingConnectionCreate = { + connection: ConnectionHandle; + position: Position; +}; + +type CanvasHistoryEntry = Pick & { + chatSessions: CanvasAssistantSession[]; + activeChatId: string | null; + backgroundMode: CanvasBackgroundMode; +}; + +const UPLOADED_IMAGE_MAX_SIDE = 640; +const NODE_STATUS_LOADING = "loading" as const; +const NODE_STATUS_SUCCESS = "success" as const; +const NODE_STATUS_ERROR = "error" as const; + +function createCanvasNode(type: CanvasNodeType, position: Position, metadata?: CanvasNodeMetadata): CanvasNodeData { + const spec = getNodeSpec(type); + const id = `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; + + return { + id, + type, + title: spec.title, + position: { + x: position.x - spec.width / 2, + y: position.y - spec.height / 2, + }, + width: spec.width, + height: spec.height, + metadata: { ...spec.metadata, ...metadata }, + }; +} + +export default function CanvasPage() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return ; + + return ; +} + +function CanvasRefreshShell() { + return ( +
+
+ +
+ ); +} + +function ConnectionCreateMenu({ + pending, + onCreate, + onClose, +}: { + pending: PendingConnectionCreate; + onCreate: (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Config) => void; + onClose: () => void; +}) { + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + return ( +
event.stopPropagation()} + onPointerDown={(event) => event.stopPropagation()} + > +
+ 引用该节点生成 + +
+
+ } title="文本生成" description="脚本、广告词、品牌文案" onClick={() => onCreate(CanvasNodeType.Text)} /> + } title="图片生成" onClick={() => onCreate(CanvasNodeType.Image)} /> + } title="配置节点" description="模型、尺寸、数量和输入顺序" onClick={() => onCreate(CanvasNodeType.Config)} /> +
+
+ ); +} + +function ConnectionCreateOption({ + icon, + title, + description, + onClick, +}: { + icon: React.ReactNode; + title: string; + description?: string; + onClick?: () => void; +}) { + return ( + + ); +} + +function InfiniteCanvasPage() { + const { message } = App.useApp(); + const params = useParams<{ id: string }>(); + const router = useRouter(); + const projectId = params.id; + const containerRef = useRef(null); + const imageInputRef = useRef(null); + const uploadTargetRef = useRef<{ nodeId?: string; position?: Position } | null>(null); + const clipboardRef = useRef(null); + const historyRef = useRef<{ past: CanvasHistoryEntry[]; future: CanvasHistoryEntry[] }>({ past: [], future: [] }); + const lastHistoryRef = useRef(null); + const historyCommitTimerRef = useRef | null>(null); + const viewportSaveTimerRef = useRef | null>(null); + const applyingHistoryRef = useRef(false); + const historyPausedRef = useRef(false); + const didInitialCenterRef = useRef(false); + const rafRef = useRef(null); + const toolbarHideTimerRef = useRef | null>(null); + const nodeDraggingRef = useRef(false); + const dragRef = useRef<{ + isDraggingNode: boolean; + hasMoved: boolean; + startX: number; + startY: number; + initialSelectedNodes: { id: string; x: number; y: number }[]; + }>({ + isDraggingNode: false, + hasMoved: false, + startX: 0, + startY: 0, + initialSelectedNodes: [], + }); + + const config = useAiConfigStore((state) => state.config); + const openConfigDialog = useConfigDialogStore((state) => state.openConfigDialog); + const addAsset = useAssetStore((state) => state.addAsset); + const cleanupAssetImages = useAssetStore((state) => state.cleanupImages); + const hydrated = useCanvasStore((state) => state.hydrated); + const createProject = useCanvasStore((state) => state.createProject); + const openProject = useCanvasStore((state) => state.openProject); + const updateProject = useCanvasStore((state) => state.updateProject); + const renameProject = useCanvasStore((state) => state.renameProject); + const deleteProjects = useCanvasStore((state) => state.deleteProjects); + const currentProject = useCanvasStore((state) => state.projects.find((project) => project.id === projectId)); + const user = useUserStore((state) => state.user); + const logout = useUserStore((state) => state.clearSession); + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const [nodes, setNodes] = useState([]); + const [connections, setConnections] = useState([]); + const [chatSessions, setChatSessions] = useState([]); + const [activeChatId, setActiveChatId] = useState(null); + const [viewport, setViewport] = useState({ x: 0, y: 0, k: 1 }); + const [size, setSize] = useState({ width: 1200, height: 720 }); + const [selectedNodeIds, setSelectedNodeIds] = useState>(new Set()); + const [selectedConnectionId, setSelectedConnectionId] = useState(null); + const [hoveredNodeId, setHoveredNodeId] = useState(null); + const [connectingParams, setConnectingParams] = useState(null); + const [connectionTargetNodeId, setConnectionTargetNodeId] = useState(null); + const [pendingConnectionCreate, setPendingConnectionCreate] = useState(null); + const [mouseWorld, setMouseWorld] = useState({ x: 0, y: 0 }); + const [selectionBox, setSelectionBox] = useState(null); + const [contextMenu, setContextMenu] = useState(null); + const [runningNodeId, setRunningNodeId] = useState(null); + const [isMiniMapOpen, setIsMiniMapOpen] = useState(false); + const [backgroundMode, setBackgroundMode] = useState("lines"); + const [clearConfirmOpen, setClearConfirmOpen] = useState(false); + const [assetPickerOpen, setAssetPickerOpen] = useState(false); + const [assetPickerTab, setAssetPickerTab] = useState("my-assets"); + const [projectLoaded, setProjectLoaded] = useState(false); + const [toolbarNodeId, setToolbarNodeId] = useState(null); + const [dialogNodeId, setDialogNodeId] = useState(null); + const [editingNodeId, setEditingNodeId] = useState(null); + const [editRequestNonce, setEditRequestNonce] = useState(0); + const [infoNodeId, setInfoNodeId] = useState(null); + const [cropNodeId, setCropNodeId] = useState(null); + const [angleNodeId, setAngleNodeId] = useState(null); + const [assistantCollapsed, setAssistantCollapsed] = useState(true); + const [assistantMounted, setAssistantMounted] = useState(false); + const [titleEditing, setTitleEditing] = useState(false); + const [titleDraft, setTitleDraft] = useState(""); + const [historyState, setHistoryState] = useState({ canUndo: false, canRedo: false }); + const [collapsingBatchIds, setCollapsingBatchIds] = useState>(new Set()); + const [openingBatchIds, setOpeningBatchIds] = useState>(new Set()); + const [isNodeDragging, setIsNodeDragging] = useState(false); + + const nodesRef = useRef(nodes); + const connectionsRef = useRef(connections); + const selectedNodeIdsRef = useRef(selectedNodeIds); + const viewportRef = useRef(viewport); + const connectingParamsRef = useRef(connectingParams); + const connectionTargetNodeIdRef = useRef(connectionTargetNodeId); + const selectionBoxRef = useRef(selectionBox); + const pendingConnectionCreateRef = useRef(pendingConnectionCreate); + + const createHistoryEntry = useCallback((): CanvasHistoryEntry => ({ + nodes: nodesRef.current, + connections: connectionsRef.current, + chatSessions, + activeChatId, + backgroundMode, + }), [activeChatId, backgroundMode, chatSessions]); + + useEffect(() => { + if (!hydrated) return; + setProjectLoaded(false); + const project = openProject(projectId); + if (!project) { + router.replace("/canvas"); + return; + } + + const restore = async () => { + const restoredNodes = await hydrateCanvasImages(resetInterruptedGeneration(project.nodes)); + const restoredSessions = await hydrateAssistantImages(project.chatSessions || []); + setNodes(restoredNodes); + setConnections(project.connections); + setChatSessions(restoredSessions); + setActiveChatId(project.activeChatId || null); + setBackgroundMode(project.backgroundMode); + setViewport(project.viewport); + historyRef.current = { past: [], future: [] }; + if (historyCommitTimerRef.current) { + clearTimeout(historyCommitTimerRef.current); + historyCommitTimerRef.current = null; + } + lastHistoryRef.current = { + nodes: restoredNodes, + connections: project.connections, + chatSessions: restoredSessions, + activeChatId: project.activeChatId || null, + backgroundMode: project.backgroundMode, + }; + setHistoryState({ canUndo: false, canRedo: false }); + setProjectLoaded(true); + }; + void restore(); + }, [hydrated, openProject, projectId, router]); + + useEffect(() => { + if (!projectLoaded || applyingHistoryRef.current || historyPausedRef.current) return; + const next = createHistoryEntry(); + const previous = lastHistoryRef.current; + if ( + previous?.nodes === next.nodes && + previous.connections === next.connections && + previous.chatSessions === next.chatSessions && + previous.activeChatId === next.activeChatId && + previous.backgroundMode === next.backgroundMode + ) return; + + if (historyCommitTimerRef.current) clearTimeout(historyCommitTimerRef.current); + historyCommitTimerRef.current = setTimeout(() => { + const current = createHistoryEntry(); + const last = lastHistoryRef.current; + if (!last) return; + historyRef.current.past = [...historyRef.current.past.slice(-49), last]; + historyRef.current.future = []; + setHistoryState({ canUndo: true, canRedo: false }); + lastHistoryRef.current = current; + historyCommitTimerRef.current = null; + }, 180); + + return () => { + if (historyCommitTimerRef.current) { + clearTimeout(historyCommitTimerRef.current); + historyCommitTimerRef.current = null; + } + }; + }, [activeChatId, backgroundMode, chatSessions, connections, createHistoryEntry, nodes, projectLoaded]); + + useEffect(() => { + if (!projectLoaded || historyPausedRef.current) return; + updateProject(projectId, { nodes, connections, chatSessions, activeChatId, backgroundMode }); + }, [activeChatId, backgroundMode, chatSessions, connections, nodes, projectId, projectLoaded, updateProject]); + + useEffect(() => { + if (!projectLoaded) return; + if (viewportSaveTimerRef.current) clearTimeout(viewportSaveTimerRef.current); + viewportSaveTimerRef.current = setTimeout(() => { + updateProject(projectId, { viewport: viewportRef.current }); + viewportSaveTimerRef.current = null; + }, 500); + return () => { + if (viewportSaveTimerRef.current) clearTimeout(viewportSaveTimerRef.current); + }; + }, [projectId, projectLoaded, updateProject, viewport]); + + useLayoutEffect(() => { + nodesRef.current = nodes; + connectionsRef.current = connections; + selectedNodeIdsRef.current = selectedNodeIds; + viewportRef.current = viewport; + connectingParamsRef.current = connectingParams; + connectionTargetNodeIdRef.current = connectionTargetNodeId; + pendingConnectionCreateRef.current = pendingConnectionCreate; + }, [nodes, connections, selectedNodeIds, viewport, connectingParams, connectionTargetNodeId, pendingConnectionCreate]); + + useLayoutEffect(() => { + selectionBoxRef.current = selectionBox; + }, [selectionBox]); + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + const updateSize = () => { + const rect = el.getBoundingClientRect(); + setSize({ width: rect.width, height: rect.height }); + if (!didInitialCenterRef.current) { + didInitialCenterRef.current = true; + setViewport({ x: rect.width / 2, y: rect.height / 2, k: 1 }); + } + }; + + updateSize(); + const resizeObserver = new ResizeObserver(updateSize); + resizeObserver.observe(el); + return () => resizeObserver.disconnect(); + }, []); + + const screenToCanvas = useCallback((clientX: number, clientY: number) => { + const rect = containerRef.current?.getBoundingClientRect(); + const currentViewport = viewportRef.current; + const localX = clientX - (rect?.left || 0); + const localY = clientY - (rect?.top || 0); + + return { + x: (localX - currentViewport.x) / currentViewport.k, + y: (localY - currentViewport.y) / currentViewport.k, + }; + }, []); + + const getCanvasCenter = useCallback(() => { + const rect = containerRef.current?.getBoundingClientRect(); + return screenToCanvas((rect?.left || 0) + (rect?.width || size.width) / 2, (rect?.top || 0) + (rect?.height || size.height) / 2); + }, [screenToCanvas, size.height, size.width]); + + const setConnecting = useCallback((next: ConnectionHandle | null) => { + connectingParamsRef.current = next; + setConnectingParams(next); + if (!next) { + connectionTargetNodeIdRef.current = null; + setConnectionTargetNodeId(null); + } + }, []); + + const keepNodeToolbar = useCallback((nodeId: string) => { + if (nodeDraggingRef.current) return; + if (toolbarHideTimerRef.current) { + clearTimeout(toolbarHideTimerRef.current); + toolbarHideTimerRef.current = null; + } + setToolbarNodeId(nodeId); + }, []); + + const hideNodeToolbar = useCallback(() => { + if (toolbarHideTimerRef.current) clearTimeout(toolbarHideTimerRef.current); + toolbarHideTimerRef.current = setTimeout(() => { + setToolbarNodeId(null); + toolbarHideTimerRef.current = null; + }, 120); + }, []); + + const connectNodes = useCallback((current: ConnectionHandle, targetNodeId: string) => { + if (current.nodeId === targetNodeId) return; + + const connection = normalizeConnection(current.nodeId, targetNodeId, nodesRef.current, current.handleType); + if (!connection) { + message.warning("配置节点之间不能连接"); + return; + } + const { fromNodeId, toNodeId } = connection; + const exists = connectionsRef.current.some((conn) => conn.fromNodeId === fromNodeId && conn.toNodeId === toNodeId); + if (!exists) { + setConnections((prev) => [...prev, { id: `conn-${Date.now()}`, fromNodeId, toNodeId }]); + } + setContextMenu(null); + }, [message]); + + const createConnectedNode = useCallback((type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Config, pending: PendingConnectionCreate) => { + const metadata = type === CanvasNodeType.Config ? { model: config.imageModel || config.model, size: config.size, count: 3 } : undefined; + const newNode = createCanvasNode(type, pending.position, metadata); + const connection = normalizeConnection(pending.connection.nodeId, newNode.id, [...nodesRef.current, newNode], pending.connection.handleType); + if (!connection) { + message.warning("配置节点之间不能连接"); + return; + } + setNodes((prev) => [...prev, newNode]); + setConnections((prev) => [...prev, { id: createId(), ...connection }]); + setSelectedNodeIds(new Set([newNode.id])); + setSelectedConnectionId(null); + setDialogNodeId(newNode.id); + setPendingConnectionCreate(null); + setConnecting(null); + }, [config.imageModel, config.model, config.size, message, setConnecting]); + + const cancelPendingConnectionCreate = useCallback(() => { + setPendingConnectionCreate(null); + setConnecting(null); + }, [setConnecting]); + + const getConnectableNodeAtPoint = useCallback((clientX: number, clientY: number, current: ConnectionHandle) => { + const world = screenToCanvas(clientX, clientY); + return [...nodesRef.current] + .filter((node) => !isHiddenBatchChild(node, nodesRef.current)) + .reverse() + .find((node) => ( + node.id !== current.nodeId && + Boolean(normalizeConnection(current.nodeId, node.id, nodesRef.current, current.handleType)) && + world.x >= node.position.x && + world.x <= node.position.x + node.width && + world.y >= node.position.y && + world.y <= node.position.y + node.height + ))?.id || null; + }, [screenToCanvas]); + + const visibleNodes = useMemo(() => { + const padding = 280; + const rect = containerRef.current?.getBoundingClientRect(); + const width = rect?.width || size.width; + const height = rect?.height || size.height; + const viewLeft = -viewport.x / viewport.k - padding; + const viewTop = -viewport.y / viewport.k - padding; + const viewRight = viewLeft + width / viewport.k + padding * 2; + const viewBottom = viewTop + height / viewport.k + padding * 2; + + return nodes.filter((node) => ( + !isHiddenBatchChild(node, nodes, collapsingBatchIds) && + node.position.x + node.width > viewLeft && + node.position.x < viewRight && + node.position.y + node.height > viewTop && + node.position.y < viewBottom + )); + }, [collapsingBatchIds, nodes, size.height, size.width, viewport.k, viewport.x, viewport.y]); + + const nodeById = useMemo(() => new Map(nodes.map((node) => [node.id, node])), [nodes]); + const toolbarNode = toolbarNodeId ? nodeById.get(toolbarNodeId) || null : null; + const infoNode = infoNodeId ? nodeById.get(infoNodeId) || null : null; + const cropNode = cropNodeId ? nodeById.get(cropNodeId) || null : null; + const angleNode = angleNodeId ? nodeById.get(angleNodeId) || null : null; + const hasMultipleSelectedNodes = selectedNodeIds.size > 1; + const activeNodeId = hasMultipleSelectedNodes ? null : hoveredNodeId || (selectedNodeIds.size === 1 ? Array.from(selectedNodeIds)[0] : null); + const batchChildCountById = useMemo(() => { + const map = new Map(); + nodes.forEach((node) => { + if (node.metadata?.isBatchRoot) map.set(node.id, node.metadata.batchChildIds?.length || 0); + }); + return map; + }, [nodes]); + const batchMotionById = useMemo(() => { + const map = new Map(); + nodes.forEach((node) => { + const rootId = node.metadata?.batchRootId; + if (!rootId) return; + const root = nodeById.get(rootId); + const index = root?.metadata?.batchChildIds?.indexOf(node.id) ?? 0; + const stackX = root ? root.position.x + 34 + index * 14 : node.position.x; + const stackY = root ? root.position.y + 14 + index * 8 : node.position.y; + map.set(node.id, { x: stackX - node.position.x, y: stackY - node.position.y, index: Math.max(index, 0) }); + }); + return map; + }, [nodeById, nodes]); + const relatedHighlight = useMemo(() => { + const nodeIds = new Set(); + const connectionIds = new Set(); + + if (!activeNodeId) return { nodeIds, connectionIds }; + + nodeIds.add(activeNodeId); + connections.forEach((connection) => { + if (connection.fromNodeId !== activeNodeId && connection.toNodeId !== activeNodeId) return; + connectionIds.add(connection.id); + nodeIds.add(connection.fromNodeId); + nodeIds.add(connection.toNodeId); + }); + + return { nodeIds, connectionIds }; + }, [activeNodeId, connections]); + + const configInputsById = useMemo(() => { + const map = new Map(); + nodes.forEach((node) => { + if (node.type !== CanvasNodeType.Config) return; + map.set(node.id, buildNodeGenerationInputs(node.id, nodes, connections)); + }); + return map; + }, [connections, nodes]); + + const createNode = useCallback( + (type: CanvasNodeType, position?: Position) => { + const targetPosition = position || getCanvasCenter(); + const configMetadata = type === CanvasNodeType.Config ? { + model: config.imageModel || config.model, + size: config.size, + count: 3, + } : undefined; + const newNode = createCanvasNode(type, targetPosition, configMetadata); + + setNodes((prev) => [...prev, newNode]); + setSelectedNodeIds(new Set([newNode.id])); + setSelectedConnectionId(null); + setDialogNodeId(newNode.id); + }, + [config.imageModel, config.model, config.size, getCanvasCenter], + ); + + const deleteNodes = useCallback((ids: Set) => { + if (!ids.size) return; + const allIds = new Set(ids); + nodesRef.current.forEach((node) => { + if (ids.has(node.id)) node.metadata?.batchChildIds?.forEach((childId) => allIds.add(childId)); + }); + setNodes((prev) => { + const next = prev.filter((node) => !allIds.has(node.id)); + return next.map((node) => { + const childIds = node.metadata?.batchChildIds?.filter((childId) => !allIds.has(childId)); + if (!node.metadata?.isBatchRoot || childIds?.length === node.metadata.batchChildIds?.length) return node; + const primaryImageId = childIds?.includes(node.metadata.primaryImageId || "") ? node.metadata.primaryImageId : childIds?.[0]; + const primaryNode = next.find((item) => item.id === primaryImageId); + return { ...node, metadata: { ...node.metadata, batchChildIds: childIds, primaryImageId, content: primaryNode?.metadata?.content || node.metadata.content, naturalWidth: primaryNode?.metadata?.naturalWidth || node.metadata.naturalWidth, naturalHeight: primaryNode?.metadata?.naturalHeight || node.metadata.naturalHeight } }; + }); + }); + setConnections((prev) => prev.filter((conn) => !allIds.has(conn.fromNodeId) && !allIds.has(conn.toNodeId))); + setSelectedNodeIds(new Set()); + setSelectedConnectionId(null); + setHoveredNodeId((current) => current && allIds.has(current) ? null : current); + setToolbarNodeId((current) => current && allIds.has(current) ? null : current); + setDialogNodeId((current) => current && allIds.has(current) ? null : current); + setEditingNodeId((current) => current && allIds.has(current) ? null : current); + setInfoNodeId((current) => current && allIds.has(current) ? null : current); + setCropNodeId((current) => current && allIds.has(current) ? null : current); + setAngleNodeId((current) => current && allIds.has(current) ? null : current); + setRunningNodeId((current) => current && allIds.has(current) ? null : current); + setContextMenu((current) => current && allIds.has(current.nodeId) ? null : current); + cleanupAssetImages({ projectId, nodes: nodesRef.current.filter((node) => !allIds.has(node.id)), chatSessions }); + }, [chatSessions, cleanupAssetImages, projectId]); + + const deselectCanvas = useCallback(() => { + cancelPendingConnectionCreate(); + setSelectedNodeIds(new Set()); + setSelectedConnectionId(null); + setContextMenu(null); + setSelectionBox(null); + setHoveredNodeId(null); + setToolbarNodeId(null); + setDialogNodeId(null); + setEditingNodeId(null); + }, [cancelPendingConnectionCreate]); + + const clearCanvas = useCallback(() => { + setNodes([]); + setConnections([]); + setInfoNodeId(null); + setCropNodeId(null); + setAngleNodeId(null); + setRunningNodeId(null); + deselectCanvas(); + setClearConfirmOpen(false); + cleanupAssetImages({ projectId, nodes: [], chatSessions: [] }); + }, [cleanupAssetImages, deselectCanvas, projectId]); + + const duplicateNode = useCallback((nodeId: string) => { + const source = nodesRef.current.find((node) => node.id === nodeId); + if (!source) return; + + const id = `${source.type}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; + const next: CanvasNodeData = { + ...source, + id, + title: `${source.title} Copy`, + position: { x: source.position.x + 36, y: source.position.y + 36 }, + }; + + setNodes((prev) => [...prev, next]); + setSelectedNodeIds(new Set([id])); + setSelectedConnectionId(null); + setDialogNodeId(id); + }, []); + + const copySelectedNodes = useCallback(() => { + const selectedIds = selectedNodeIdsRef.current; + if (!selectedIds.size) return; + + const copiedNodes = nodesRef.current + .filter((node) => selectedIds.has(node.id)) + .map((node) => ({ + ...node, + position: { ...node.position }, + metadata: node.metadata ? { ...node.metadata } : undefined, + })); + + if (!copiedNodes.length) return; + + clipboardRef.current = { + nodes: copiedNodes, + connections: connectionsRef.current + .filter((connection) => selectedIds.has(connection.fromNodeId) && selectedIds.has(connection.toNodeId)) + .map((connection) => ({ ...connection })), + }; + }, []); + + const pasteCopiedNodes = useCallback(() => { + const clipboard = clipboardRef.current; + if (!clipboard?.nodes.length) return false; + + const center = getCanvasCenter(); + const bounds = clipboard.nodes.reduce( + (acc, node) => ({ + left: Math.min(acc.left, node.position.x), + top: Math.min(acc.top, node.position.y), + right: Math.max(acc.right, node.position.x + node.width), + bottom: Math.max(acc.bottom, node.position.y + node.height), + }), + { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity }, + ); + const dx = center.x - (bounds.left + bounds.right) / 2; + const dy = center.y - (bounds.top + bounds.bottom) / 2; + const idMap = new Map(); + const nextNodes = clipboard.nodes.map((node, index) => { + const id = `${node.type}-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 7)}`; + idMap.set(node.id, id); + return { + ...node, + id, + title: node.title.endsWith(" Copy") ? node.title : `${node.title} Copy`, + position: { + x: node.position.x + dx, + y: node.position.y + dy, + }, + metadata: node.metadata ? { ...node.metadata } : undefined, + }; + }); + + const nextConnections = clipboard.connections.flatMap((connection, index) => { + const fromNodeId = idMap.get(connection.fromNodeId); + const toNodeId = idMap.get(connection.toNodeId); + if (!fromNodeId || !toNodeId) return []; + return [{ + ...connection, + id: `conn-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 7)}`, + fromNodeId, + toNodeId, + }]; + }); + + setNodes((prev) => [...prev, ...nextNodes]); + setConnections((prev) => [...prev, ...nextConnections]); + setSelectedNodeIds(new Set(nextNodes.map((node) => node.id))); + setSelectedConnectionId(null); + setContextMenu(null); + setDialogNodeId(nextNodes[0]?.id || null); + return true; + }, [getCanvasCenter]); + + const resetViewport = useCallback(() => { + setViewport({ x: size.width / 2, y: size.height / 2, k: 1 }); + setContextMenu(null); + }, [size.height, size.width]); + + const setZoomScale = useCallback((scale: number) => { + const nextScale = Math.min(Math.max(scale, 0.05), 5); + setViewport((prev) => ({ + x: size.width / 2 - ((size.width / 2 - prev.x) / prev.k) * nextScale, + y: size.height / 2 - ((size.height / 2 - prev.y) / prev.k) * nextScale, + k: nextScale, + })); + setContextMenu(null); + }, [size.height, size.width]); + + const applyHistory = useCallback((entry: CanvasHistoryEntry) => { + if (historyCommitTimerRef.current) { + clearTimeout(historyCommitTimerRef.current); + historyCommitTimerRef.current = null; + } + applyingHistoryRef.current = true; + setNodes(entry.nodes); + setConnections(entry.connections); + setChatSessions(entry.chatSessions); + setActiveChatId(entry.activeChatId); + setBackgroundMode(entry.backgroundMode); + setSelectedNodeIds(new Set()); + setSelectedConnectionId(null); + setContextMenu(null); + setTimeout(() => { + lastHistoryRef.current = entry; + applyingHistoryRef.current = false; + setHistoryState({ canUndo: historyRef.current.past.length > 0, canRedo: historyRef.current.future.length > 0 }); + }); + }, []); + + const undoCanvas = useCallback(() => { + const previous = historyRef.current.past.pop(); + const current = lastHistoryRef.current; + if (!previous || !current) return; + historyRef.current.future.push(current); + applyHistory(previous); + }, [applyHistory]); + + const redoCanvas = useCallback(() => { + const next = historyRef.current.future.pop(); + const current = lastHistoryRef.current; + if (!next || !current) return; + historyRef.current.past.push(current); + applyHistory(next); + }, [applyHistory]); + + const createAndOpenProject = useCallback(() => { + const id = createProject(`无限画布 ${useCanvasStore.getState().projects.length + 1}`); + router.push(`/canvas/${id}`); + }, [createProject, router]); + + const deleteCurrentProject = useCallback(() => { + deleteProjects([projectId]); + cleanupAssetImages(); + router.push("/canvas"); + }, [cleanupAssetImages, deleteProjects, projectId, router]); + + const handleCanvasMouseDown = useCallback( + (event: ReactPointerEvent) => { + setContextMenu(null); + if (pendingConnectionCreateRef.current) cancelPendingConnectionCreate(); + if (event.button !== 0) return; + + if (!event.ctrlKey && !event.metaKey) { + setSelectionBox(null); + setSelectedNodeIds(new Set()); + setSelectedConnectionId(null); + return; + } + + const world = screenToCanvas(event.clientX, event.clientY); + const nextSelectionBox = { + startWorldX: world.x, + startWorldY: world.y, + currentWorldX: world.x, + currentWorldY: world.y, + additive: event.shiftKey, + initialSelectedNodeIds: event.shiftKey ? Array.from(selectedNodeIdsRef.current) : [], + }; + selectionBoxRef.current = nextSelectionBox; + setSelectionBox(nextSelectionBox); + if (!event.shiftKey) { + setSelectedNodeIds(new Set()); + } + + setSelectedConnectionId(null); + }, + [cancelPendingConnectionCreate, screenToCanvas], + ); + + const handleNodeMouseDown = useCallback((event: ReactMouseEvent, nodeId: string) => { + event.stopPropagation(); + setContextMenu(null); + setHoveredNodeId(null); + setToolbarNodeId(null); + setSelectedConnectionId(null); + + const currentSelected = selectedNodeIdsRef.current; + const currentNodes = nodesRef.current; + const nextSelected = new Set(currentSelected); + + if (event.shiftKey || event.metaKey || event.ctrlKey) { + if (nextSelected.has(nodeId)) { + nextSelected.delete(nodeId); + } else { + nextSelected.add(nodeId); + } + } else if (!nextSelected.has(nodeId)) { + nextSelected.clear(); + nextSelected.add(nodeId); + } + + setSelectedNodeIds(nextSelected); + const dragIds = new Set(nextSelected); + currentNodes.forEach((node) => { + if (nextSelected.has(node.id)) node.metadata?.batchChildIds?.forEach((childId) => dragIds.add(childId)); + }); + dragRef.current = { + isDraggingNode: true, + hasMoved: false, + startX: event.clientX, + startY: event.clientY, + initialSelectedNodes: currentNodes + .filter((node) => dragIds.has(node.id)) + .map((node) => ({ id: node.id, x: node.position.x, y: node.position.y })), + }; + historyPausedRef.current = true; + nodeDraggingRef.current = true; + setIsNodeDragging(true); + }, []); + + const finishNodeDrag = useCallback((clientX?: number, clientY?: number) => { + if (rafRef.current) { + cancelAnimationFrame(rafRef.current); + rafRef.current = null; + } + if (!dragRef.current.isDraggingNode) return; + + const wasClick = !dragRef.current.hasMoved && dragRef.current.initialSelectedNodes.length === 1; + const clickedNodeId = dragRef.current.initialSelectedNodes[0]?.id; + const currentViewport = viewportRef.current; + const dx = clientX == null ? 0 : (clientX - dragRef.current.startX) / currentViewport.k; + const dy = clientY == null ? 0 : (clientY - dragRef.current.startY) / currentViewport.k; + const initialPositions = dragRef.current.initialSelectedNodes; + + historyPausedRef.current = false; + nodeDraggingRef.current = false; + setIsNodeDragging(false); + if (dragRef.current.hasMoved && clientX != null && clientY != null) { + setNodes((prev) => + prev.map((node) => { + const initial = initialPositions.find((item) => item.id === node.id); + if (!initial) return node; + return { ...node, position: { x: initial.x + dx, y: initial.y + dy } }; + }), + ); + } + + dragRef.current.isDraggingNode = false; + dragRef.current.hasMoved = false; + dragRef.current.initialSelectedNodes = []; + if (wasClick && clickedNodeId) { + const clickedNode = nodesRef.current.find((node) => node.id === clickedNodeId); + if (clickedNode?.type === CanvasNodeType.Text) { + setDialogNodeId((current) => current === clickedNodeId ? current : null); + } else { + setDialogNodeId(clickedNodeId); + } + } + }, []); + + const handleGlobalMouseMove = useCallback( + (event: MouseEvent) => { + const currentViewport = viewportRef.current; + + if (dragRef.current.isDraggingNode) { + const dx = (event.clientX - dragRef.current.startX) / currentViewport.k; + const dy = (event.clientY - dragRef.current.startY) / currentViewport.k; + const initialPositions = dragRef.current.initialSelectedNodes; + if (Math.abs(event.clientX - dragRef.current.startX) > 3 || Math.abs(event.clientY - dragRef.current.startY) > 3) { + dragRef.current.hasMoved = true; + } + + if (rafRef.current) cancelAnimationFrame(rafRef.current); + rafRef.current = requestAnimationFrame(() => { + setNodes((prev) => prev.map((node) => { + const initial = initialPositions.find((item) => item.id === node.id); + return initial ? { ...node, position: { x: initial.x + dx, y: initial.y + dy } } : node; + })); + rafRef.current = null; + }); + return; + } + + if (connectingParamsRef.current && !pendingConnectionCreateRef.current) { + const targetNodeId = getConnectableNodeAtPoint(event.clientX, event.clientY, connectingParamsRef.current); + connectionTargetNodeIdRef.current = targetNodeId; + setConnectionTargetNodeId(targetNodeId); + setMouseWorld(screenToCanvas(event.clientX, event.clientY)); + } + }, + [finishNodeDrag, getConnectableNodeAtPoint, screenToCanvas], + ); + + const handleGlobalPointerMove = useCallback( + (event: PointerEvent) => { + const currentSelection = selectionBoxRef.current; + if (!currentSelection) return; + + if (event.buttons === 0) { + selectionBoxRef.current = null; + setSelectionBox(null); + return; + } + + const world = screenToCanvas(event.clientX, event.clientY); + const rectX = Math.min(currentSelection.startWorldX, world.x); + const rectY = Math.min(currentSelection.startWorldY, world.y); + const rectW = Math.abs(world.x - currentSelection.startWorldX); + const rectH = Math.abs(world.y - currentSelection.startWorldY); + const nextSelected = new Set(currentSelection.additive ? currentSelection.initialSelectedNodeIds : []); + + nodesRef.current.filter((node) => !isHiddenBatchChild(node, nodesRef.current)).forEach((node) => { + const intersects = + rectX < node.position.x + node.width && + rectX + rectW > node.position.x && + rectY < node.position.y + node.height && + rectY + rectH > node.position.y; + + if (intersects) nextSelected.add(node.id); + }); + + const nextSelectionBox = { ...currentSelection, currentWorldX: world.x, currentWorldY: world.y }; + selectionBoxRef.current = nextSelectionBox; + setSelectionBox(nextSelectionBox); + setSelectedNodeIds(nextSelected); + }, + [screenToCanvas], + ); + + const handleGlobalMouseUp = useCallback( + (event: MouseEvent) => { + finishNodeDrag(event.clientX, event.clientY); + + selectionBoxRef.current = null; + setSelectionBox(null); + + if (pendingConnectionCreateRef.current) return; + + const currentConnection = connectingParamsRef.current; + if (currentConnection) { + const targetNodeId = getConnectableNodeAtPoint(event.clientX, event.clientY, currentConnection) || connectionTargetNodeIdRef.current; + if (targetNodeId) { + connectNodes(currentConnection, targetNodeId); + setConnecting(null); + } else { + setMouseWorld(screenToCanvas(event.clientX, event.clientY)); + setPendingConnectionCreate({ connection: currentConnection, position: screenToCanvas(event.clientX, event.clientY) }); + } + } + }, + [connectNodes, finishNodeDrag, getConnectableNodeAtPoint, screenToCanvas, setConnecting], + ); + + useEffect(() => { + const handlePointerUp = (event: PointerEvent) => finishNodeDrag(event.clientX, event.clientY); + const cancelNodeDrag = () => finishNodeDrag(); + window.addEventListener("mousemove", handleGlobalMouseMove); + window.addEventListener("mouseup", handleGlobalMouseUp); + window.addEventListener("pointerup", handlePointerUp); + window.addEventListener("pointercancel", cancelNodeDrag); + window.addEventListener("blur", cancelNodeDrag); + window.addEventListener("pointermove", handleGlobalPointerMove); + return () => { + window.removeEventListener("mousemove", handleGlobalMouseMove); + window.removeEventListener("mouseup", handleGlobalMouseUp); + window.removeEventListener("pointerup", handlePointerUp); + window.removeEventListener("pointercancel", cancelNodeDrag); + window.removeEventListener("blur", cancelNodeDrag); + window.removeEventListener("pointermove", handleGlobalPointerMove); + }; + }, [finishNodeDrag, handleGlobalMouseMove, handleGlobalMouseUp, handleGlobalPointerMove]); + + const createImageFileNode = useCallback(async (file: File, position: Position) => { + const image = await uploadImage(file); + const size = fitImageNodeSize(image.width, image.height); + const id = `image-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; + const newNode: CanvasNodeData = { + id, + type: CanvasNodeType.Image, + title: file.name, + position: { x: position.x - size.width / 2, y: position.y - size.height / 2 }, + width: size.width, + height: size.height, + metadata: imageMetadata(image), + }; + + setNodes((prev) => [...prev, newNode]); + setSelectedNodeIds(new Set([id])); + setSelectedConnectionId(null); + setDialogNodeId(id); + }, []); + + const createTextNodeFromClipboard = useCallback((text: string) => { + const trimmed = text.trim(); + if (!trimmed) return false; + + const node = { + ...createCanvasNode(CanvasNodeType.Text, getCanvasCenter(), { content: trimmed, status: NODE_STATUS_SUCCESS }), + title: trimmed.slice(0, 32) || "剪切板文本", + }; + + setNodes((prev) => [...prev, node]); + setSelectedNodeIds(new Set([node.id])); + setSelectedConnectionId(null); + setContextMenu(null); + setDialogNodeId(node.id); + return true; + }, [getCanvasCenter]); + + const pasteSystemClipboard = useCallback(async () => { + if (!navigator.clipboard) return; + + const items = await navigator.clipboard.read(); + const imageItem = items.find((item) => item.types.some((type) => type.startsWith("image/"))); + if (imageItem) { + const imageType = imageItem.types.find((type) => type.startsWith("image/")); + if (!imageType) return; + const blob = await imageItem.getType(imageType); + const file = new File([blob], "clipboard-image.png", { type: imageType }); + void createImageFileNode(file, getCanvasCenter()); + message.success("已从剪切板添加图片"); + return; + } + + const text = await navigator.clipboard.readText(); + if (createTextNodeFromClipboard(text)) message.success("已从剪切板添加文本"); + }, [createImageFileNode, createTextNodeFromClipboard, getCanvasCenter, message]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement) return; + + const key = event.key.toLowerCase(); + const isModifierShortcut = event.metaKey || event.ctrlKey; + + if (isModifierShortcut && !event.altKey && key === "z") { + event.preventDefault(); + if (event.shiftKey) redoCanvas(); + else undoCanvas(); + return; + } + + if (isModifierShortcut && !event.altKey && key === "y") { + event.preventDefault(); + redoCanvas(); + return; + } + + if (isModifierShortcut && !event.altKey && key === "a") { + event.preventDefault(); + setSelectedNodeIds(new Set(nodesRef.current.map((node) => node.id))); + setSelectedConnectionId(null); + setContextMenu(null); + setSelectionBox(null); + return; + } + + if (isModifierShortcut && !event.altKey && key === "c") { + event.preventDefault(); + copySelectedNodes(); + return; + } + + if (isModifierShortcut && !event.altKey && key === "v") { + event.preventDefault(); + if (!pasteCopiedNodes()) void pasteSystemClipboard(); + return; + } + + if (event.key === "Delete" || event.key === "Backspace") { + if (selectedNodeIdsRef.current.size) { + deleteNodes(new Set(selectedNodeIdsRef.current)); + } else if (selectedConnectionId) { + setConnections((prev) => prev.filter((conn) => conn.id !== selectedConnectionId)); + setSelectedConnectionId(null); + } + } + + if (event.key === "Escape") { + setSelectedNodeIds(new Set()); + setSelectedConnectionId(null); + setContextMenu(null); + setSelectionBox(null); + setConnecting(null); + setHoveredNodeId(null); + setToolbarNodeId(null); + setDialogNodeId(null); + setEditingNodeId(null); + setInfoNodeId(null); + setCropNodeId(null); + setPendingConnectionCreate(null); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [copySelectedNodes, deleteNodes, pasteCopiedNodes, pasteSystemClipboard, redoCanvas, selectedConnectionId, setConnecting, undoCanvas]); + + const handleConnectStart = useCallback( + (event: ReactMouseEvent, nodeId: string, handleType: "source" | "target") => { + event.stopPropagation(); + setMouseWorld(screenToCanvas(event.clientX, event.clientY)); + setConnecting({ nodeId, handleType }); + connectionTargetNodeIdRef.current = null; + setConnectionTargetNodeId(null); + setSelectedConnectionId(null); + }, + [screenToCanvas, setConnecting], + ); + + const handleNodeResize = useCallback((nodeId: string, width: number, height: number, position?: Position) => { + setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, width, height, position: position || node.position } : node))); + }, []); + + const toggleNodeFreeResize = useCallback((nodeId: string) => { + setNodes((prev) => prev.map((node) => { + if (node.id !== nodeId) return node; + const freeResize = !node.metadata?.freeResize; + if (freeResize || node.type !== CanvasNodeType.Image) return { ...node, metadata: { ...node.metadata, freeResize } }; + const ratio = (node.metadata?.naturalWidth || node.width) / (node.metadata?.naturalHeight || node.height || 1); + const height = node.width / ratio; + return { ...node, height, position: { x: node.position.x, y: node.position.y + node.height / 2 - height / 2 }, metadata: { ...node.metadata, freeResize } }; + })); + }, []); + + const handleNodeContentChange = useCallback((nodeId: string, content: string) => { + setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, content } } : node))); + }, []); + + const toggleBatchExpanded = useCallback((nodeId: string) => { + const isExpanded = Boolean(nodesRef.current.find((node) => node.id === nodeId)?.metadata?.imageBatchExpanded); + if (isExpanded) { + setCollapsingBatchIds((prev) => new Set(prev).add(nodeId)); + window.setTimeout(() => { + setCollapsingBatchIds((prev) => { + const next = new Set(prev); + next.delete(nodeId); + return next; + }); + }, 320); + } else { + setOpeningBatchIds((prev) => new Set(prev).add(nodeId)); + window.setTimeout(() => { + setOpeningBatchIds((prev) => { + const next = new Set(prev); + next.delete(nodeId); + return next; + }); + }, 260); + } + setNodes((prev) => prev.map((node) => { + if (node.id !== nodeId) return node; + return { ...node, metadata: { ...node.metadata, imageBatchExpanded: !node.metadata?.imageBatchExpanded } }; + })); + }, []); + + const setBatchPrimary = useCallback((child: CanvasNodeData) => { + const rootId = child.metadata?.batchRootId; + if (!rootId || !child.metadata?.content) return; + setNodes((prev) => prev.map((node) => node.id === rootId ? { + ...node, + width: child.width, + height: child.height, + metadata: { + ...node.metadata, + content: child.metadata?.content, + primaryImageId: child.id, + naturalWidth: child.metadata?.naturalWidth, + naturalHeight: child.metadata?.naturalHeight, + freeResize: child.metadata?.freeResize, + }, + } : node)); + }, []); + + const openTextEditor = useCallback((node: CanvasNodeData) => { + if (node.type !== CanvasNodeType.Text) return; + setSelectedNodeIds(new Set([node.id])); + setSelectedConnectionId(null); + setDialogNodeId(node.id); + setEditingNodeId(node.id); + setEditRequestNonce((value) => value + 1); + }, []); + + const handleNodePromptChange = useCallback((nodeId: string, prompt: string) => { + setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt } } : node))); + }, []); + + const handleConfigNodeChange = useCallback((nodeId: string, patch: Partial) => { + setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, ...(patch || {}) } } : node))); + }, []); + + const downloadNodeImage = useCallback((node: CanvasNodeData) => { + if (node.type !== CanvasNodeType.Image || !node.metadata?.content) return; + const link = document.createElement("a"); + link.href = node.metadata.content; + link.download = `canvas-image-${node.id}.${imageExtension(node.metadata.content)}`; + link.click(); + }, []); + + const saveNodeAsset = useCallback(async (node: CanvasNodeData) => { + if (node.type === CanvasNodeType.Text) { + const content = node.metadata?.content?.trim(); + if (!content) return message.error("没有可保存的文本"); + addAsset({ kind: "text", title: node.metadata?.prompt?.slice(0, 24) || "画布文本", coverUrl: "", tags: [], source: "Canvas", data: { content }, metadata: { source: "canvas", nodeId: node.id } }); + message.success("已加入我的素材"); + return; + } + if (!node.metadata?.content) return message.error("没有可保存的图片"); + const dataUrl = node.metadata.storageKey ? "" : node.metadata.content; + addAsset({ kind: "image", title: node.metadata?.prompt?.slice(0, 24) || "画布图片", coverUrl: node.metadata.content, tags: [], source: "Canvas", data: { dataUrl, storageKey: node.metadata.storageKey, width: node.metadata.naturalWidth || node.width, height: node.metadata.naturalHeight || node.height, bytes: node.metadata.bytes || getDataUrlByteSize(dataUrl), mimeType: node.metadata.mimeType || "image/png" }, metadata: { source: "canvas", nodeId: node.id, prompt: node.metadata?.prompt } }); + message.success("已加入我的素材"); + }, [addAsset, message]); + + const cropImageNode = useCallback(async (node: CanvasNodeData, crop: CanvasImageCropRect) => { + if (!node.metadata?.content) return; + const cropped = await cropDataUrl(node.metadata.content, crop); + const image = await uploadImage(cropped); + const width = Math.min(node.width, Math.max(220, image.width)); + const childId = createId(); + const child: CanvasNodeData = { + id: childId, + type: CanvasNodeType.Image, + title: "Cropped Image", + position: { x: node.position.x + node.width + 96, y: node.position.y }, + width, + height: width * (image.height / image.width), + metadata: { + ...imageMetadata(image), + prompt: node.metadata?.prompt, + }, + }; + setNodes((prev) => [...prev, child]); + setConnections((prev) => [...prev, { id: createId(), fromNodeId: node.id, toNodeId: childId }]); + setSelectedNodeIds(new Set([childId])); + setDialogNodeId(childId); + setCropNodeId(null); + }, []); + + const generateAngleNode = useCallback(async (node: CanvasNodeData, params: CanvasImageAngleParams) => { + if (!node.metadata?.content) return; + const generationConfig = { ...buildGenerationConfig(config, node, "image"), count: "1" }; + if (!generationConfig.baseUrl.trim() || !generationConfig.model.trim() || !generationConfig.apiKey.trim()) { + openConfigDialog(true); + return; + } + const childId = createId(); + const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image]; + const title = buildAngleLabel(params); + const prompt = buildAnglePrompt(params); + setAngleNodeId(null); + setRunningNodeId(childId); + setNodes((prev) => [...prev, { + id: childId, + type: CanvasNodeType.Image, + title, + position: { x: node.position.x + node.width + 96, y: node.position.y }, + width: imageConfig.width, + height: imageConfig.height, + metadata: { prompt, status: NODE_STATUS_LOADING }, + }]); + setConnections((prev) => [...prev, { id: createId(), fromNodeId: node.id, toNodeId: childId }]); + setSelectedNodeIds(new Set([childId])); + setDialogNodeId(childId); + try { + const image = await requestEdit(generationConfig, prompt, [{ id: node.id, name: `${node.title || node.id}.png`, type: node.metadata.mimeType || "image/png", dataUrl: node.metadata.content, storageKey: node.metadata.storageKey }]).then((items) => items[0]); + const uploaded = await uploadImage(image.dataUrl); + const size = fitImageNodeSize(uploaded.width, uploaded.height, imageConfig.width, imageConfig.height); + setNodes((prev) => prev.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...imageMetadata(uploaded), prompt } } : item)); + } catch (error) { + const errorDetails = error instanceof Error ? error.message : "生成失败"; + setNodes((prev) => prev.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails } } : item)); + } finally { + setRunningNodeId(null); + } + }, [config, openConfigDialog]); + + const handleFontSizeChange = useCallback((nodeId: string, fontSize: number) => { + setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, fontSize } } : node))); + }, []); + + const handleUploadRequest = useCallback((nodeId?: string, position?: Position) => { + uploadTargetRef.current = { nodeId, position }; + imageInputRef.current?.click(); + }, []); + + const handleImageInputChange = useCallback( + async (event: ReactChangeEvent) => { + const file = event.target.files?.[0]; + const target = uploadTargetRef.current; + if (!file || !file.type.startsWith("image/")) return; + + if (target?.nodeId) { + const image = await uploadImage(file); + const size = fitImageNodeSize(image.width, image.height); + setNodes((prev) => + prev.map((node) => + node.id === target.nodeId + ? { ...node, type: CanvasNodeType.Image, title: file.name, width: size.width, height: size.height, metadata: { ...node.metadata, ...imageMetadata(image), errorDetails: undefined, freeResize: false, isBatchRoot: undefined, batchRootId: undefined, batchChildIds: undefined, primaryImageId: undefined, imageBatchExpanded: undefined } } + : node, + ), + ); + setSelectedNodeIds(new Set([target.nodeId])); + setSelectedConnectionId(null); + setDialogNodeId(target.nodeId); + } else { + const position = target?.position || screenToCanvas( + (containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2, + (containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2, + ); + void createImageFileNode(file, position); + } + + uploadTargetRef.current = null; + event.target.value = ""; + }, + [createImageFileNode, screenToCanvas, size.height, size.width], + ); + + const handleDrop = useCallback( + (event: ReactDragEvent) => { + event.preventDefault(); + const file = Array.from(event.dataTransfer.files).find((item) => item.type.startsWith("image/")); + if (!file) return; + + const pos = screenToCanvas(event.clientX, event.clientY); + void createImageFileNode(file, pos); + }, + [createImageFileNode, screenToCanvas], + ); + + const pasteAssistantImage = useCallback((file: File) => { + const position = screenToCanvas( + (containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2, + (containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2, + ); + void createImageFileNode(file, position); + message.success("已从剪切板添加图片"); + }, [createImageFileNode, message, screenToCanvas, size.height, size.width]); + + const handleAssistantSessionsChange = useCallback((sessions: CanvasAssistantSession[], activeId: string | null) => { + setChatSessions(sessions); + setActiveChatId(activeId); + }, []); + + const startTitleEditing = useCallback(() => { + setTitleDraft(currentProject?.title || "未命名画布"); + setTitleEditing(true); + }, [currentProject?.title]); + + const finishTitleEditing = useCallback(() => { + const nextTitle = titleDraft.trim(); + if (nextTitle) renameProject(projectId, nextTitle); + setTitleEditing(false); + }, [projectId, renameProject, titleDraft]); + + const preventCanvasContextMenu = useCallback((event: ReactMouseEvent) => { + if ((event.target as HTMLElement).closest("[data-node-id]")) return; + event.preventDefault(); + setContextMenu(null); + }, []); + + const handleGenerateNode = useCallback( + async (nodeId: string, mode: CanvasNodeGenerationMode, prompt: string) => { + const sourceNode = nodesRef.current.find((node) => node.id === nodeId); + const generationConfig = buildGenerationConfig(config, sourceNode, mode); + if (!generationConfig.baseUrl.trim() || !generationConfig.model.trim() || !generationConfig.apiKey.trim()) { + openConfigDialog(true); + return; + } + + setRunningNodeId(nodeId); + const sourceTextContent = sourceNode?.type === CanvasNodeType.Text ? sourceNode.metadata?.content?.trim() || "" : ""; + const editingTextNode = mode === "text" && Boolean(sourceTextContent); + const generationContext = await hydrateNodeGenerationContext(buildNodeGenerationContext(nodeId, nodesRef.current, connectionsRef.current, editingTextNode ? `请根据要求修改以下文本。\n\n原文:\n${sourceTextContent}\n\n修改要求:\n${prompt}` : prompt)); + const effectivePrompt = generationContext.prompt.trim(); + const markSourceStatus = sourceNode?.type !== CanvasNodeType.Image && !editingTextNode; + if (!effectivePrompt && mode === "text") { + setRunningNodeId(null); + return; + } + let pendingChildIds: string[] = []; + if (markSourceStatus) setNodes((prev) => prev.map((node) => node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node)); + + try { + if (mode === "image") { + const count = getGenerationCount(generationConfig.count); + const isConfigNode = sourceNode?.type === CanvasNodeType.Config; + const isImageNode = sourceNode?.type === CanvasNodeType.Image; + const sourceReference = isImageNode && sourceNode?.metadata?.content + ? [{ id: sourceNode.id, name: `${sourceNode.title || sourceNode.id}.png`, type: sourceNode.metadata.mimeType || "image/png", dataUrl: sourceNode.metadata.content, storageKey: sourceNode.metadata.storageKey }] + : []; + const referenceImages = [...sourceReference, ...generationContext.referenceImages]; + const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : isImageNode ? CanvasNodeType.Image : CanvasNodeType.Text]; + const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image]; + const parentPosition = sourceNode?.position || { x: 0, y: 0 }; + const gap = 96; + const rowGap = 36; + const rootId = createId(); + const childIds = count > 1 ? Array.from({ length: count }, () => createId()) : []; + const targetIds = count > 1 ? childIds : [rootId]; + pendingChildIds = [rootId, ...childIds]; + const rootNode: CanvasNodeData = { + id: rootId, + type: CanvasNodeType.Image, + title: effectivePrompt.slice(0, 32) || "Generated Image", + position: { + x: parentPosition.x + parentConfig.width + gap, + y: parentPosition.y + parentConfig.height / 2 - imageConfig.height / 2, + }, + width: imageConfig.width, + height: imageConfig.height, + metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, isBatchRoot: count > 1, batchChildIds: count > 1 ? childIds : undefined, imageBatchExpanded: count > 1 ? true : undefined }, + }; + const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({ + id, + type: CanvasNodeType.Image, + title: effectivePrompt.slice(0, 32) || "Generated Image", + position: { + x: rootNode.position.x + rootNode.width + 120 + (index % 2) * (imageConfig.width + 36), + y: rootNode.position.y + Math.floor(index / 2) * (imageConfig.height + rowGap), + }, + width: imageConfig.width, + height: imageConfig.height, + metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, batchRootId: count > 1 ? rootId : undefined }, + })); + const batchConnections = [{ id: createId(), fromNodeId: nodeId, toNodeId: rootId }, ...childIds.map((childId) => ({ id: createId(), fromNodeId: rootId, toNodeId: childId }))]; + + setNodes((prev) => [ + ...prev.map((node) => + node.id === nodeId + ? isConfigNode ? { + ...node, + metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined }, + } : isImageNode ? { + ...node, + metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS, errorDetails: undefined }, + } : { + ...node, + type: CanvasNodeType.Text, + title: prompt.slice(0, 32) || "Prompt", + width: parentConfig.width, + height: parentConfig.height, + metadata: { ...node.metadata, content: prompt, prompt, status: NODE_STATUS_SUCCESS, fontSize: 14, errorDetails: undefined }, + } + : node, + ), + rootNode, + ...childNodes, + ]); + setConnections((prev) => [...prev, ...batchConnections]); + setSelectedNodeIds(new Set([nodeId])); + setSelectedConnectionId(null); + setDialogNodeId(nodeId); + + let hasSuccess = false; + await Promise.all(targetIds.map(async (targetId) => { + try { + const image = referenceImages.length + ? await requestEdit({ ...generationConfig, count: "1" }, effectivePrompt, referenceImages).then((items) => items[0]) + : await requestGeneration({ ...generationConfig, count: "1" }, effectivePrompt).then((items) => items[0]); + const uploaded = await uploadImage(image.dataUrl); + const imageSize = fitImageNodeSize(uploaded.width, uploaded.height, imageConfig.width, imageConfig.height); + setNodes((prev) => { + const root = prev.find((node) => node.id === rootId); + return prev.map((node) => { + if (node.id !== targetId && node.id !== rootId) return node; + const center = { x: node.position.x + node.width / 2, y: node.position.y + node.height / 2 }; + if (node.id === rootId && (targetId === rootId || !root?.metadata?.primaryImageId)) return { ...node, position: { x: center.x - imageSize.width / 2, y: center.y - imageSize.height / 2 }, width: imageSize.width, height: imageSize.height, metadata: { ...node.metadata, ...imageMetadata(uploaded), primaryImageId: targetId } }; + if (node.id === targetId) return { ...node, position: { x: center.x - imageSize.width / 2, y: center.y - imageSize.height / 2 }, width: imageSize.width, height: imageSize.height, metadata: { ...node.metadata, ...imageMetadata(uploaded) } }; + return node; + }); + }); + hasSuccess = true; + if (isConfigNode) setNodes((prev) => prev.map((node) => node.id === nodeId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS, errorDetails: undefined } } : node)); + return true; + } catch (error) { + const errorDetails = error instanceof Error ? error.message : "生成失败"; + setNodes((prev) => prev.map((node) => node.id === targetId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails } } : node)); + return false; + } + })); + setNodes((prev) => prev.map((node) => + node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, status: hasSuccess ? NODE_STATUS_SUCCESS : NODE_STATUS_ERROR, errorDetails: hasSuccess ? undefined : "全部图片生成失败" } } + : node.id === rootId && !hasSuccess ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails: "全部图片生成失败" } } + : node, + )); + return; + } + + let streamed = ""; + const isConfigNode = sourceNode?.type === CanvasNodeType.Config; + const textCount = isConfigNode ? getGenerationCount(generationConfig.count) : 1; + const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : CanvasNodeType.Text]; + const textConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Text]; + const parentPosition = sourceNode?.position || { x: 0, y: 0 }; + const childIds = isConfigNode || editingTextNode ? Array.from({ length: textCount }, () => createId()) : []; + pendingChildIds = childIds; + if (isConfigNode || editingTextNode) { + const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({ + id, + type: CanvasNodeType.Text, + title: prompt.slice(0, 32) || "Generated Text", + position: { + x: parentPosition.x + parentConfig.width + 96, + y: parentPosition.y + parentConfig.height / 2 - textConfig.height / 2 + (index - (textCount - 1) / 2) * (textConfig.height + 36), + }, + width: textConfig.width, + height: textConfig.height, + metadata: { prompt, status: NODE_STATUS_LOADING, fontSize: 14 }, + })); + setNodes((prev) => [...prev.map((node) => node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node), ...childNodes]); + setConnections((prev) => [...prev, ...childIds.map((childId) => ({ id: createId(), fromNodeId: nodeId, toNodeId: childId }))]); + } + + const answers = await Promise.all( + (childIds.length ? childIds : [nodeId]).map((targetNodeId) => { + let localStreamed = ""; + return requestImageQuestion(generationConfig, buildNodeChatMessages({ ...generationContext, prompt: effectivePrompt }), (text) => { + localStreamed = text; + streamed = text; + if (isConfigNode) return; + setNodes((prev) => prev.map((node) => node.id === targetNodeId ? { ...node, type: CanvasNodeType.Text, metadata: { ...node.metadata, content: text, status: NODE_STATUS_LOADING } } : node)); + }).then((answer) => ({ nodeId: targetNodeId, content: answer || localStreamed })); + }), + ); + const answerByNodeId = new Map(answers.map((item) => [item.nodeId, item.content])); + setNodes((prev) => + prev.map((node) => + childIds.includes(node.id) ? { ...node, metadata: { ...node.metadata, content: answerByNodeId.get(node.id) || streamed, status: NODE_STATUS_SUCCESS } } + : node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS } } + : node.id === nodeId && !editingTextNode ? { ...node, type: CanvasNodeType.Text, title: prompt.slice(0, 32) || "Generated Text", metadata: { ...node.metadata, content: answerByNodeId.get(node.id) || streamed, status: NODE_STATUS_SUCCESS } } + : node, + ), + ); + } catch (error) { + const errorDetails = error instanceof Error ? error.message : "生成失败"; + setNodes((prev) => + prev.map((node) => + node.id === nodeId || pendingChildIds.includes(node.id) + ? node.id === nodeId && !markSourceStatus ? node : { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails } } + : node, + ), + ); + } finally { + setRunningNodeId(null); + } + }, + [config, openConfigDialog], + ); + + const handleRetryNode = useCallback( + async (node: CanvasNodeData) => { + const sourceNode = findRetrySourceNode(node.id, nodesRef.current, connectionsRef.current) || node; + const generationConfig = { ...buildGenerationConfig(config, sourceNode, node.type === CanvasNodeType.Text ? "text" : "image"), count: "1" }; + if (!generationConfig.baseUrl.trim() || !generationConfig.model.trim() || !generationConfig.apiKey.trim()) { + openConfigDialog(true); + return; + } + + const context = await hydrateNodeGenerationContext(buildNodeGenerationContext(sourceNode.id, nodesRef.current, connectionsRef.current, sourceNode.metadata?.prompt || node.metadata?.prompt || "")); + const prompt = context.prompt.trim(); + if (!prompt) { + message.warning("找不到提示词,无法重试"); + return; + } + + setRunningNodeId(node.id); + setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_LOADING, errorDetails: undefined } } : item)); + + try { + if (node.type === CanvasNodeType.Text) { + let streamed = ""; + const answer = await requestImageQuestion(generationConfig, buildNodeChatMessages({ ...context, prompt }), (text) => { + streamed = text; + setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, type: CanvasNodeType.Text, metadata: { ...item.metadata, content: text, status: NODE_STATUS_LOADING } } : item)); + }); + setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, type: CanvasNodeType.Text, metadata: { ...item.metadata, content: answer || streamed, prompt, status: NODE_STATUS_SUCCESS } } : item)); + return; + } + + const image = context.referenceImages.length + ? await requestEdit(generationConfig, prompt, context.referenceImages).then((items) => items[0]) + : await requestGeneration(generationConfig, prompt).then((items) => items[0]); + const uploadedImage = await uploadImage(image.dataUrl); + const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image]; + const imageSize = fitImageNodeSize(uploadedImage.width, uploadedImage.height, imageConfig.width, imageConfig.height); + setNodes((prev) => prev.map((item) => item.id === node.id ? { + ...item, + type: CanvasNodeType.Image, + width: imageSize.width, + height: imageSize.height, + metadata: { ...item.metadata, ...imageMetadata(uploadedImage), prompt }, + } : item)); + } catch (error) { + const errorDetails = error instanceof Error ? error.message : "生成失败"; + setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails } } : item)); + } finally { + setRunningNodeId(null); + } + }, + [config, message, openConfigDialog], + ); + + const generateImageFromTextNode = useCallback((node: CanvasNodeData) => { + const prompt = (node.metadata?.content || node.metadata?.prompt || "").trim(); + if (!prompt) { + message.warning("文本节点为空,无法生图"); + return; + } + const sourceNode = nodesRef.current.find((item) => item.id === node.id); + if (!sourceNode) return; + const nodeSize = getNodeSpec(CanvasNodeType.Config); + const configNode = createCanvasNode(CanvasNodeType.Config, { + x: sourceNode.position.x + sourceNode.width + 96 + nodeSize.width / 2, + y: sourceNode.position.y + sourceNode.height / 2, + }, { + prompt: "", + model: config.imageModel || config.model, + size: config.size, + count: 3, + }); + const connection = { id: createId(), fromNodeId: sourceNode.id, toNodeId: configNode.id }; + const nextNodes = nodesRef.current.map((item) => item.id === sourceNode.id ? { ...item, metadata: { ...item.metadata, content: prompt, prompt, status: NODE_STATUS_SUCCESS } } : item).concat(configNode); + const nextConnections = [...connectionsRef.current, connection]; + nodesRef.current = nextNodes; + connectionsRef.current = nextConnections; + setNodes(nextNodes); + setConnections(nextConnections); + setSelectedNodeIds(new Set([configNode.id])); + setSelectedConnectionId(null); + setDialogNodeId(configNode.id); + }, [config.imageModel, config.model, config.size, message]); + + const insertAssistantImage = useCallback(async (image: CanvasAssistantImage) => { + const storedImage = image.storageKey + ? { url: image.dataUrl, storageKey: image.storageKey, width: 1, height: 1, bytes: 0, mimeType: "image/png" } + : await uploadImage(image.dataUrl); + const meta = storedImage.width === 1 && storedImage.height === 1 ? await readImageMeta(storedImage.url) : storedImage; + const config = fitImageNodeSize(meta.width, meta.height); + const center = screenToCanvas( + (containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2, + (containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2, + ); + const id = `image-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; + const node: CanvasNodeData = { + id, + type: CanvasNodeType.Image, + title: image.prompt.slice(0, 32) || "Generated Image", + position: { x: center.x - config.width / 2, y: center.y - config.height / 2 }, + width: config.width, + height: config.height, + metadata: { ...imageMetadata({ ...storedImage, width: meta.width, height: meta.height }), prompt: image.prompt }, + }; + + setNodes((prev) => [...prev, node]); + setSelectedNodeIds(new Set([id])); + setSelectedConnectionId(null); + setDialogNodeId(id); + }, [screenToCanvas, size.height, size.width]); + + const insertAssistantText = useCallback((text: string) => { + const center = screenToCanvas( + (containerRef.current?.getBoundingClientRect().left || 0) + size.width / 2, + (containerRef.current?.getBoundingClientRect().top || 0) + size.height / 2, + ); + const node = { + ...createCanvasNode(CanvasNodeType.Text, center, { content: text, status: NODE_STATUS_SUCCESS }), + title: text.slice(0, 32) || "Assistant Text", + }; + + setNodes((prev) => [...prev, node]); + setSelectedNodeIds(new Set([node.id])); + setSelectedConnectionId(null); + setDialogNodeId(node.id); + }, [screenToCanvas, size.height, size.width]); + + const handleAssetInsert = useCallback((payload: InsertAssetPayload) => { + if (payload.kind === "text") { + insertAssistantText(payload.content); + } else { + insertAssistantImage({ id: `asset-${Date.now()}`, prompt: payload.title, dataUrl: payload.dataUrl, storageKey: payload.storageKey }); + } + setAssetPickerOpen(false); + }, [insertAssistantImage, insertAssistantText]); + + if (!projectLoaded) return ; + + return ( +
+
+ setTitleEditing(false)} + canUndo={historyState.canUndo} + canRedo={historyState.canRedo} + onHome={() => router.push("/")} + onProjects={() => router.push("/canvas")} + onCreateProject={createAndOpenProject} + onDeleteProject={deleteCurrentProject} + onImportImage={() => handleUploadRequest()} + onUndo={undoCanvas} + onRedo={redoCanvas} + onOpenConfig={() => openConfigDialog(false)} + onLogout={logout} + assistantCollapsed={assistantCollapsed} + onExpandAssistant={() => { + setAssistantMounted(true); + setAssistantCollapsed(false); + }} + /> + + { + setViewport(next); + setContextMenu(null); + }} + onCanvasMouseDown={handleCanvasMouseDown} + onCanvasDeselect={deselectCanvas} + onContextMenu={preventCanvasContextMenu} + onDrop={handleDrop} + > + + {connections.filter((connection) => { + const from = nodeById.get(connection.fromNodeId); + const to = nodeById.get(connection.toNodeId); + return Boolean(from && to && !isHiddenBatchConnectionEndpoint(from, nodes) && !isHiddenBatchConnectionEndpoint(to, nodes)); + }).map((connection) => { + const from = nodeById.get(connection.fromNodeId); + const to = nodeById.get(connection.toNodeId); + if (!from || !to) return null; + + return ( + { + setSelectedConnectionId(connection.id); + setSelectedNodeIds(new Set()); + setContextMenu(null); + }} + /> + ); + })} + {connectingParams ? : null} + + + {visibleNodes.map((node) => ( + ( + + )} + renderNodeContent={(contentNode) => ( + { + const target = nodesRef.current.find((item) => item.id === nodeId); + void handleGenerateNode(nodeId, target?.metadata?.generationMode || "image", target?.metadata?.prompt || ""); + }} + /> + )} + onMouseDown={handleNodeMouseDown} + onHoverStart={(nodeId) => { + if (nodeDraggingRef.current) return; + setHoveredNodeId(nodeId); + keepNodeToolbar(nodeId); + }} + onHoverEnd={(nodeId) => { + setHoveredNodeId((current) => current === nodeId ? null : current); + hideNodeToolbar(); + }} + onConnectStart={handleConnectStart} + onResize={handleNodeResize} + onContentChange={handleNodeContentChange} + onToggleBatch={toggleBatchExpanded} + onSetBatchPrimary={setBatchPrimary} + onRetry={(node) => void handleRetryNode(node)} + onGenerateImage={generateImageFromTextNode} + onContextMenu={(event, id) => { + event.preventDefault(); + event.stopPropagation(); + setContextMenu({ type: "node", x: event.clientX, y: event.clientY, nodeId: id }); + }} + /> + ))} + + {selectionBox ? ( +
+ ) : null} + {pendingConnectionCreate ? ( + createConnectedNode(type, pendingConnectionCreate)} + onClose={cancelPendingConnectionCreate} + /> + ) : null} + + + setInfoNodeId(node.id)} + onEditText={openTextEditor} + onDecreaseFont={(node) => handleFontSizeChange(node.id, Math.max(10, (node.metadata?.fontSize || 14) - 2))} + onIncreaseFont={(node) => handleFontSizeChange(node.id, Math.min(32, (node.metadata?.fontSize || 14) + 2))} + onToggleDialog={(node) => setDialogNodeId((current) => current === node.id ? null : node.id)} + onGenerateImage={generateImageFromTextNode} + onUpload={(node) => handleUploadRequest(node.id)} + onDownload={downloadNodeImage} + onSaveAsset={(node) => void saveNodeAsset(node)} + onCrop={(node) => setCropNodeId(node.id)} + onAngle={(node) => setAngleNodeId(node.id)} + onRetry={(node) => void handleRetryNode(node)} + onToggleFreeResize={(node) => toggleNodeFreeResize(node.id)} + onDelete={(node) => deleteNodes(new Set([node.id]))} + /> + + createNode(CanvasNodeType.Image)} + onAddText={() => createNode(CanvasNodeType.Text)} + onAddConfig={() => createNode(CanvasNodeType.Config)} + onUndo={undoCanvas} + onRedo={redoCanvas} + onUpload={() => handleUploadRequest()} + onDelete={() => deleteNodes(new Set(selectedNodeIds))} + onClear={() => setClearConfirmOpen(true)} + onDeselect={deselectCanvas} + onBackgroundModeChange={setBackgroundMode} + onOpenAssetLibrary={() => { setAssetPickerTab("library"); setAssetPickerOpen(true); }} + onOpenMyAssets={() => { setAssetPickerTab("my-assets"); setAssetPickerOpen(true); }} + /> + + {isMiniMapOpen ? : null} + + setIsMiniMapOpen((value) => !value)} + /> + + {contextMenu ? ( + setContextMenu(null)} + onDuplicate={() => { + duplicateNode(contextMenu.nodeId); + setContextMenu(null); + }} + onDelete={() => { + deleteNodes(new Set([contextMenu.nodeId])); + setContextMenu(null); + }} + /> + ) : null} + + + + setInfoNodeId(null)} /> + + {cropNode?.metadata?.content ? ( + setCropNodeId(null)} + onConfirm={(crop) => void cropImageNode(cropNode!, crop)} + /> + ) : null} + + {angleNode?.metadata?.content ? ( + setAngleNodeId(null)} + onConfirm={(params) => void generateAngleNode(angleNode!, params)} + /> + ) : null} + + setClearConfirmOpen(false)} + footer={ + <> + + + + } + > +

这会删除当前画布上的所有节点和连线。

+
+ + setAssetPickerOpen(false)} + /> +
+ {assistantMounted ? ( + setAssistantCollapsed(true)} + onCollapse={() => setAssistantMounted(false)} + /> + ) : null} +
+ ); +} + +function CanvasTopBar({ + title, + titleDraft, + isTitleEditing, + userName, + onTitleDraftChange, + onStartTitleEditing, + onFinishTitleEditing, + onCancelTitleEditing, + canUndo, + canRedo, + onHome, + onProjects, + onCreateProject, + onDeleteProject, + onImportImage, + onUndo, + onRedo, + onOpenConfig, + onLogout, + assistantCollapsed, + onExpandAssistant, +}: { + title: string; + titleDraft: string; + isTitleEditing: boolean; + userName: string; + onTitleDraftChange: (value: string) => void; + onStartTitleEditing: () => void; + onFinishTitleEditing: () => void; + onCancelTitleEditing: () => void; + canUndo: boolean; + canRedo: boolean; + onHome: () => void; + onProjects: () => void; + onCreateProject: () => void; + onDeleteProject: () => void; + onImportImage: () => void; + onUndo: () => void; + onRedo: () => void; + onOpenConfig: () => void; + onLogout: () => void; + assistantCollapsed: boolean; + onExpandAssistant: () => void; +}) { + const colorTheme = useThemeStore((state) => state.theme); + const setTheme = useThemeStore((state) => state.setTheme); + const theme = canvasThemes[colorTheme]; + const appVersion = process.env.NEXT_PUBLIC_APP_VERSION || "dev"; + const initial = (userName.trim()[0] || "U").toUpperCase(); + const titleRef = useRef(null); + const accountRef = useRef(null); + const [shortcutsOpen, setShortcutsOpen] = useState(false); + const [accountOpen, setAccountOpen] = useState(false); + + useEffect(() => { + if (!isTitleEditing) return; + const close = (event: PointerEvent) => { + if (!titleRef.current?.contains(event.target as Node)) onFinishTitleEditing(); + }; + document.addEventListener("pointerdown", close, true); + return () => document.removeEventListener("pointerdown", close, true); + }, [isTitleEditing, onFinishTitleEditing]); + + useEffect(() => { + if (!accountOpen) return; + const close = (event: PointerEvent) => { + if (!accountRef.current?.contains(event.target as Node)) setAccountOpen(false); + }; + document.addEventListener("pointerdown", close, true); + return () => document.removeEventListener("pointerdown", close, true); + }, [accountOpen]); + + return ( + <> +
+
+ , label: "主页", onClick: onHome }, + { key: "projects", icon: , label: "我的画布", onClick: onProjects }, + { type: "divider" }, + { key: "new", icon: , label: "新建画布", onClick: onCreateProject }, + { key: "delete", danger: true, icon: , label: "删除当前画布", onClick: onDeleteProject }, + { type: "divider" }, + { key: "import", icon: , label: "导入图片", onClick: onImportImage }, + { type: "divider" }, + { key: "undo", disabled: !canUndo, icon: , label: , onClick: onUndo }, + { key: "redo", disabled: !canRedo, icon: , label: , onClick: onRedo }, + ], + }} + > + + + +
+ {isTitleEditing ? ( + onTitleDraftChange(event.target.value)} + onBlur={onFinishTitleEditing} + onKeyDown={(event) => { + if (event.key === "Enter") onFinishTitleEditing(); + if (event.key === "Escape") onCancelTitleEditing(); + }} + className="max-w-[280px] bg-transparent p-0 text-left text-lg font-semibold tracking-normal outline-none" + style={{ color: theme.node.text }} + /> + ) : ( + + )} +
+
+ +
+ node.parentElement || document.body} + iconStyle={{ color: theme.node.text }} + gitHubClassName="size-11 text-base" + gitHubStyle={{ color: theme.node.text }} + avatarStyle={{ borderColor: theme.toolbar.border, color: theme.node.text }} + userLabel={initial} + menuItems={[ + { key: "user", disabled: true, label: {userName} }, + { type: "divider" }, + { key: "shortcuts", icon: , label: "快捷键", onClick: () => { setShortcutsOpen(true); setAccountOpen(false); } }, + { type: "divider" }, + { key: "logout", icon: , label: "退出登录", onClick: () => { setAccountOpen(false); onLogout(); } }, + ]} + /> + {assistantCollapsed ? ( + <> + + + + ) : null} +
+
+ setShortcutsOpen(false)} footer={null} centered> +
+ + + + + + + + + + + + + +
+
+ + ); +} + +function MenuLabel({ text, shortcut }: { text: string; shortcut: string }) { + return ( + + {text} + {shortcut} + + ); +} + +function Shortcut({ keys, value }: { keys: string[]; value: string }) { + return ( +
+ + {keys.map((key, index) => ( + + {index ? + : null} + {key} + + ))} + + {value} +
+ ); +} + +function imageExtension(dataUrl: string) { + return dataUrl.match(/^data:image[/]([^;]+)/)?.[1] || dataUrl.match(/image[/]([^;]+)/)?.[1] || "png"; +} + +function imageMetadata(image: UploadedImage): CanvasNodeMetadata { + return { content: image.url, storageKey: image.storageKey, status: "success", naturalWidth: image.width, naturalHeight: image.height, bytes: image.bytes, mimeType: image.mimeType }; +} + +async function hydrateCanvasImages(nodes: CanvasNodeData[]) { + return Promise.all(nodes.map(async (node) => { + const content = node.metadata?.content; + if (node.type !== CanvasNodeType.Image || !content) return node; + if (node.metadata?.storageKey) return { ...node, metadata: { ...node.metadata, content: await resolveImageUrl(node.metadata.storageKey, content) } }; + if (!content.startsWith("data:image/")) return node; + return { ...node, metadata: { ...node.metadata, ...imageMetadata(await uploadImage(content)) } }; + })); +} + +async function hydrateAssistantImages(sessions: CanvasAssistantSession[]) { + const hydrateItem = async (item: T) => { + if (item.storageKey) return { ...item, dataUrl: await resolveImageUrl(item.storageKey, item.dataUrl) }; + if (item.dataUrl?.startsWith("data:image/")) { + const image = await uploadImage(item.dataUrl); + return { ...item, dataUrl: image.url, storageKey: image.storageKey }; + } + return item; + }; + return Promise.all(sessions.map(async (session) => ({ + ...session, + messages: await Promise.all(session.messages.map(async (message) => ({ + ...message, + references: await Promise.all((message.references || []).map(hydrateItem)), + images: await Promise.all((message.images || []).map(hydrateItem)), + }))), + }))); +} + +function fitImageNodeSize(width: number, height: number, maxWidth = UPLOADED_IMAGE_MAX_SIDE, maxHeight = UPLOADED_IMAGE_MAX_SIDE) { + const safeWidth = Math.max(1, width); + const safeHeight = Math.max(1, height); + const scale = Math.min(1, maxWidth / safeWidth, maxHeight / safeHeight); + + return { + width: safeWidth * scale, + height: safeHeight * scale, + }; +} + +function getGenerationCount(count: string) { + return Math.max(1, Math.min(15, Math.floor(Math.abs(Number(count)) || 1))); +} + +function normalizeConnection(firstNodeId: string, secondNodeId: string, nodes: CanvasNodeData[], firstHandleType: "source" | "target") { + const first = nodes.find((node) => node.id === firstNodeId); + const second = nodes.find((node) => node.id === secondNodeId); + if (!first || !second || first.id === second.id) return null; + if (first.type === CanvasNodeType.Config && second.type === CanvasNodeType.Config) return null; + if (second.type === CanvasNodeType.Config) return { fromNodeId: first.id, toNodeId: second.id }; + if (first.type === CanvasNodeType.Config && firstHandleType === "target") return { fromNodeId: second.id, toNodeId: first.id }; + if (first.type === CanvasNodeType.Config) return { fromNodeId: first.id, toNodeId: second.id }; + return { fromNodeId: first.id, toNodeId: second.id }; +} + +function getInputSummary(inputs: NodeGenerationInput[]) { + return { + textCount: inputs.filter((input) => input.type === "text").length, + imageCount: inputs.filter((input) => input.type === "image").length, + }; +} + +function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | undefined, mode: CanvasNodeGenerationMode): AiConfig { + const defaultModel = mode === "image" ? config.imageModel : config.textModel; + return { + ...config, + model: node?.metadata?.model || defaultModel || config.model || defaultConfig.model, + quality: config.quality || defaultConfig.quality, + size: node?.metadata?.size || config.size || defaultConfig.size, + count: String(node?.metadata?.count || config.count || defaultConfig.count), + }; +} + +function resetInterruptedGeneration(nodes: CanvasNodeData[]) { + return nodes.map((node) => node.metadata?.status === "loading" + ? { ...node, metadata: { ...node.metadata, status: "error" as const, errorDetails: "页面刷新后生成已中断,请重新生成。" } } + : node); +} + +function findRetrySourceNode(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) { + const queue = connections.filter((connection) => connection.toNodeId === nodeId).map((connection) => connection.fromNodeId); + const visited = new Set(); + while (queue.length) { + const id = queue.shift()!; + if (visited.has(id)) continue; + visited.add(id); + const node = nodes.find((item) => item.id === id); + if (node?.type === CanvasNodeType.Config) return node; + connections.filter((connection) => connection.toNodeId === id).forEach((connection) => queue.push(connection.fromNodeId)); + } + return null; +} + +function isHiddenBatchChild(node: CanvasNodeData, nodes: CanvasNodeData[], collapsingBatchIds?: Set) { + const rootId = node.metadata?.batchRootId; + if (!rootId) return false; + const root = nodes.find((item) => item.id === rootId); + if (root && collapsingBatchIds?.has(rootId)) return false; + return Boolean(root && !root.metadata?.imageBatchExpanded); +} + +function isHiddenBatchConnectionEndpoint(node: CanvasNodeData, nodes: CanvasNodeData[]) { + const rootId = node.metadata?.batchRootId; + if (!rootId) return false; + const root = nodes.find((item) => item.id === rootId); + return Boolean(root && !root.metadata?.imageBatchExpanded); +} + +function buildAngleLabel(params: CanvasImageAngleParams) { + const horizontal = params.horizontalAngle === 0 ? "正面视角" : params.horizontalAngle > 0 ? `向右旋转 ${params.horizontalAngle} 度` : `向左旋转 ${Math.abs(params.horizontalAngle)} 度`; + const pitch = params.pitchAngle === 0 ? "水平视角" : params.pitchAngle > 0 ? `俯视 ${params.pitchAngle} 度` : `仰视 ${Math.abs(params.pitchAngle)} 度`; + return `AI 多角度:${horizontal},${pitch},镜头距离 ${params.cameraDistance.toFixed(1)},${params.wideAngle ? "广角" : "标准"}镜头`; +} + +function buildAnglePrompt(params: CanvasImageAngleParams) { + return `基于参考图重新生成同一主体的新视角,保持主体、颜色、材质和画面风格一致,不要只做透视变形。${buildAngleLabel(params)}。`; +} diff --git a/web/src/app/(user)/canvas/[id]/page.tsx b/web/src/app/(user)/canvas/[id]/page.tsx new file mode 100644 index 0000000..fd57769 --- /dev/null +++ b/web/src/app/(user)/canvas/[id]/page.tsx @@ -0,0 +1,5 @@ +import CanvasClientPage from "./canvas-client-page"; + +export default function CanvasPage() { + return ; +} diff --git a/web/src/app/(user)/canvas/components/asset-picker-modal.tsx b/web/src/app/(user)/canvas/components/asset-picker-modal.tsx new file mode 100644 index 0000000..b0d640c --- /dev/null +++ b/web/src/app/(user)/canvas/components/asset-picker-modal.tsx @@ -0,0 +1,265 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { App, Empty, Input, Modal, Pagination, Spin, Tabs, Tag } from "antd"; +import { Search } from "lucide-react"; +import axios from "axios"; + +import { cn } from "@/lib/utils"; +import { useAssetStore, type Asset } from "@/stores/use-asset-store"; +import { fetchAssetLibrary, type AssetLibraryItem } from "@/services/api/assets"; + +export type AssetPickerTab = "my-assets" | "library"; + +export type InsertAssetPayload = + | { kind: "text"; content: string; title: string } + | { kind: "image"; dataUrl: string; title: string; storageKey?: string }; + +type Props = { + open: boolean; + defaultTab?: AssetPickerTab; + onInsert: (payload: InsertAssetPayload) => void; + onClose: () => void; +}; + +export function AssetPickerModal({ open, defaultTab = "my-assets", onInsert, onClose }: Props) { + const [activeTab, setActiveTab] = useState(defaultTab); + + useEffect(() => { + if (open) setActiveTab(defaultTab); + }, [open, defaultTab]); + + return ( + + setActiveTab(key as AssetPickerTab)} + items={[ + { key: "my-assets", label: "我的素材", children: }, + { key: "library", label: "素材库", children: }, + ]} + /> + + ); +} + +const PAGE_SIZE = 8; + +const kindOptions = [ + { label: "全部", value: "all" }, + { label: "文本", value: "text" }, + { label: "图片", value: "image" }, +]; + +function LibraryTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => void }) { + const { message } = App.useApp(); + const [keyword, setKeyword] = useState(""); + const [kindFilter, setKindFilter] = useState(""); + const [page, setPage] = useState(1); + const [inserting, setInserting] = useState(null); + + const query = useQuery({ + queryKey: ["asset-picker-library", keyword, kindFilter, page], + queryFn: () => fetchAssetLibrary({ keyword, type: kindFilter, page, pageSize: PAGE_SIZE }), + retry: false, + }); + + const items = query.data?.items || []; + const total = query.data?.total || 0; + + const handleInsert = async (asset: AssetLibraryItem) => { + try { + setInserting(asset.id); + if (asset.type === "text") { + onInsert({ kind: "text", content: asset.content, title: asset.title }); + } else { + const dataUrl = await remoteImageToDataUrl(asset.url); + onInsert({ kind: "image", dataUrl, title: asset.title }); + } + } catch { + message.error("插入失败"); + } finally { + setInserting(null); + } + }; + + return ( +
+
+ } + placeholder="搜索素材" + value={keyword} + allowClear + onChange={(e) => { setPage(1); setKeyword(e.target.value); }} + /> +
+ {[{ label: "全部", value: "" }, { label: "文本", value: "text" }, { label: "图片", value: "image" }].map((opt) => ( + { setPage(1); setKindFilter(opt.value); }} + > + {opt.label} + + ))} +
+
+ + {query.isLoading ? ( +
+ ) : items.length ? ( +
+ {items.map((asset) => ( + void handleInsert(asset)} + /> + ))} +
+ ) : ( + + )} + + {total > PAGE_SIZE && ( +
+ +
+ )} +
+ ); +} + +function PickerCard({ title, kind, cover, loading, onClick }: { title: string; kind: string; cover: string; loading?: boolean; onClick: () => void }) { + return ( + + ); +} + +async function remoteImageToDataUrl(url: string) { + const response = await axios.get(url, { responseType: "blob" }); + const blob = response.data as Blob; + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result || "")); + reader.onerror = () => reject(new Error("读取图片失败")); + reader.readAsDataURL(blob); + }); +} + +function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => void }) { + const assets = useAssetStore((state) => state.assets); + const [keyword, setKeyword] = useState(""); + const [kindFilter, setKindFilter] = useState("all"); + const [page, setPage] = useState(1); + + const filtered = useMemo(() => { + const query = keyword.trim().toLowerCase(); + return assets + .filter((a) => a.kind === "text" || a.kind === "image") + .filter((a) => kindFilter === "all" || a.kind === kindFilter) + .filter((a) => !query || [a.title, ...(a.tags || [])].join(" ").toLowerCase().includes(query)); + }, [assets, keyword, kindFilter]); + + const visible = useMemo(() => filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [filtered, page]); + + useEffect(() => { + const maxPage = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); + setPage((v) => Math.min(v, maxPage)); + }, [filtered.length]); + + const handleInsert = (asset: Asset) => { + if (asset.kind === "text") { + onInsert({ kind: "text", content: asset.data.content, title: asset.title }); + } else { + onInsert({ kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title }); + } + }; + + return ( +
+
+ } + placeholder="搜索素材" + value={keyword} + allowClear + onChange={(e) => { setPage(1); setKeyword(e.target.value); }} + /> +
+ {kindOptions.map((opt) => ( + { setPage(1); setKindFilter(opt.value); }} + > + {opt.label} + + ))} +
+
+ + {visible.length ? ( +
+ {visible.map((asset) => ( + handleInsert(asset)} /> + ))} +
+ ) : ( + + )} + + {filtered.length > PAGE_SIZE && ( +
+ +
+ )} +
+ ); +} diff --git a/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx b/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx new file mode 100644 index 0000000..de2de24 --- /dev/null +++ b/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx @@ -0,0 +1,621 @@ +"use client"; + +import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { ArrowUp, History, ImageIcon, LoaderCircle, MessageSquare, PanelRightClose, Plus, RotateCcw, Settings2, Sparkles, Trash2, X } from "lucide-react"; +import { Button, ConfigProvider, Input, InputNumber, Modal, Popover, Segmented, Tooltip } from "antd"; +import { motion } from "motion/react"; + +import { ImageGenerationPending } from "@/components/image-generation-pending"; +import { ModelPicker } from "@/components/model-picker"; +import type { AiConfig } from "@/lib/ai-config"; +import { canvasThemes } from "@/lib/canvas-theme"; +import { createId } from "@/lib/id"; +import { cn } from "@/lib/utils"; +import { requestEdit, requestGeneration, requestImageQuestion, type ChatCompletionMessage } from "@/services/api/image"; +import { imageToDataUrl, uploadImage } from "@/services/image-storage"; +import { useAiConfigStore } from "@/stores/use-ai-config-store"; +import { useAssetStore } from "@/stores/use-asset-store"; +import { useConfigDialogStore } from "@/stores/use-config-dialog-store"; +import { useThemeStore } from "@/stores/use-theme-store"; +import type { ReferenceImage } from "@/types/image"; +import { DiaTextReveal } from "@/components/ui/dia-text-reveal"; +import { CanvasPromptLibrary } from "./canvas-prompt-library"; +import { CanvasNodeType, type CanvasAssistantImage, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "../types"; + +type AssistantMode = "ask" | "image"; +const PANEL_MOTION_MS = 500; +const PANEL_MOTION_SECONDS = PANEL_MOTION_MS / 1000; + +type CanvasAssistantPanelProps = { + nodes: CanvasNodeData[]; + selectedNodeIds: Set; + sessions: CanvasAssistantSession[]; + activeSessionId: string | null; + onSelectNodeIds: (ids: Set) => void; + onSessionsChange: (sessions: CanvasAssistantSession[], activeSessionId: string | null) => void; + onInsertImage: (image: CanvasAssistantImage) => void; + onInsertText: (text: string) => void; + onPasteImage: (file: File) => void; + onCollapseStart: () => void; + onCollapse: () => void; +}; + +export function CanvasAssistantPanel({ nodes, selectedNodeIds, sessions, activeSessionId, onSelectNodeIds, onSessionsChange, onInsertImage, onInsertText, onPasteImage, onCollapseStart, onCollapse }: CanvasAssistantPanelProps) { + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const config = useAiConfigStore((state) => state.config); + const cleanupImages = useAssetStore((state) => state.cleanupImages); + const updateConfig = useAiConfigStore((state) => state.updateConfig); + const openConfigDialog = useConfigDialogStore((state) => state.openConfigDialog); + const [width, setWidth] = useState(390); + const [view, setView] = useState<"chat" | "history">("chat"); + const [mode, setMode] = useState("image"); + const [prompt, setPrompt] = useState(""); + const [isRunning, setIsRunning] = useState(false); + const [checkedChatIds, setCheckedChatIds] = useState([]); + const [deleteChatIds, setDeleteChatIds] = useState([]); + const [closing, setClosing] = useState(false); + const [resizing, setResizing] = useState(false); + const [removedReferenceIds, setRemovedReferenceIds] = useState>(new Set()); + const [localSessions, setLocalSessions] = useState(() => sessions.length ? sessions : [createSession()]); + const [localActiveSessionId, setLocalActiveSessionId] = useState(activeSessionId); + + useEffect(() => { + if (!sessions.length) return; + setLocalSessions(sessions); + setLocalActiveSessionId(activeSessionId); + }, [activeSessionId, sessions]); + + useEffect(() => { + onSessionsChange(localSessions, localActiveSessionId); + }, [localActiveSessionId, localSessions, onSessionsChange]); + + const safeSessions = localSessions.length ? localSessions : [createSession()]; + const activeSession = useMemo(() => safeSessions.find((session) => session.id === localActiveSessionId) || safeSessions[0] || null, [localActiveSessionId, safeSessions]); + const historySessions = safeSessions.filter((session) => session.messages.length > 0); + const messages = activeSession?.messages || []; + const hasMessages = messages.length > 0; + const selectedNodeKey = useMemo(() => Array.from(selectedNodeIds).sort().join(","), [selectedNodeIds]); + const allSelectedReferences = useMemo(() => buildAssistantReferences(nodes, selectedNodeIds), [nodes, selectedNodeIds]); + const selectedReferences = useMemo(() => allSelectedReferences.filter((item) => !removedReferenceIds.has(item.id)), [allSelectedReferences, removedReferenceIds]); + const iconButtonStyle = { color: theme.node.muted }; + + useEffect(() => { + setRemovedReferenceIds(new Set()); + }, [selectedNodeKey]); + + const updateSession = (sessionId: string, updater: (session: CanvasAssistantSession) => CanvasAssistantSession) => { + setLocalSessions((prev) => prev.map((session) => session.id === sessionId ? updater(session) : session)); + }; + + const appendMessage = (sessionId: string, message: CanvasAssistantMessage) => { + updateSession(sessionId, (session) => ({ + ...session, + title: session.messages.length ? session.title : message.text.slice(0, 18) || "新对话", + messages: [...session.messages, message], + updatedAt: new Date().toISOString(), + })); + }; + + const updateMessage = (sessionId: string, messageId: string, patch: Partial) => { + updateSession(sessionId, (session) => ({ + ...session, + messages: session.messages.map((message) => message.id === messageId ? { ...message, ...patch } : message), + updatedAt: new Date().toISOString(), + })); + }; + + const startChatSession = () => { + if (activeSession && activeSession.messages.length === 0) { + setLocalActiveSessionId(activeSession.id); + return; + } + const session = createSession(); + setLocalSessions((prev) => [session, ...prev]); + setLocalActiveSessionId(session.id); + }; + + const removeSessions = (ids: string[]) => { + const next = safeSessions.filter((session) => !ids.includes(session.id)); + if (!next.length) { + const session = createSession(); + setLocalSessions([session]); + setLocalActiveSessionId(session.id); + } else { + setLocalSessions(next); + setLocalActiveSessionId(localActiveSessionId && ids.includes(localActiveSessionId) ? next[0].id : localActiveSessionId); + } + cleanupImages({ sessions: next }); + setCheckedChatIds((prev) => prev.filter((id) => !ids.includes(id))); + }; + + const clearSessions = () => { + const session = createSession(); + setLocalSessions([session]); + setLocalActiveSessionId(session.id); + setCheckedChatIds([]); + cleanupImages({ sessions: [session] }); + }; + + const sendMessage = async (text: string, nextMode: AssistantMode, history: CanvasAssistantMessage[], savedReferences?: CanvasAssistantReference[]) => { + const requestConfig = { ...config, model: nextMode === "image" ? config.imageModel || config.model : config.textModel || config.model }; + if (!requestConfig.baseUrl.trim() || !requestConfig.model.trim() || !requestConfig.apiKey.trim()) { + openConfigDialog(true); + return; + } + + const session = activeSession || createSession(); + if (!activeSession) { + setLocalSessions([session]); + setLocalActiveSessionId(session.id); + } + + const refs = savedReferences || selectedReferences; + const userMessage: CanvasAssistantMessage = { id: createId(), role: "user", mode: nextMode, text, references: refs }; + const assistantId = createId(); + appendMessage(session.id, userMessage); + appendMessage(session.id, { id: assistantId, role: "assistant", mode: nextMode, text: nextMode === "image" ? "正在生成图片" : "正在回答", isLoading: true }); + setPrompt(""); + setIsRunning(true); + + try { + if (nextMode === "image") { + const referenceImages: ReferenceImage[] = await Promise.all(refs.filter((item) => item.dataUrl).map(async (item) => ({ id: item.id, name: `${item.title}.png`, type: "image/png", dataUrl: await imageToDataUrl(item), storageKey: item.storageKey }))); + const images = referenceImages.length ? await requestEdit(requestConfig, text, referenceImages) : await requestGeneration(requestConfig, text); + const storedImages = await Promise.all(images.map((image) => uploadImage(image.dataUrl))); + updateMessage(session.id, assistantId, { + text: `生成了 ${storedImages.length} 张图片`, + images: storedImages.map((image, index) => ({ id: images[index].id, dataUrl: image.url, storageKey: image.storageKey, prompt: text })), + isLoading: false, + }); + return; + } + + const answer = await requestImageQuestion(requestConfig, await buildChatMessages([...history, userMessage]), (streamed) => { + updateMessage(session.id, assistantId, { text: streamed, isLoading: false }); + }); + updateMessage(session.id, assistantId, { text: answer, isLoading: false }); + } catch (error) { + updateMessage(session.id, assistantId, { text: error instanceof Error ? error.message : "操作失败", isLoading: false }); + } finally { + setIsRunning(false); + } + }; + + const submit = async () => { + const text = prompt.trim(); + if (!text || isRunning) return; + await sendMessage(text, mode, messages); + }; + + const retryMessage = (message: CanvasAssistantMessage) => { + const index = messages.findIndex((item) => item.id === message.id); + const userIndex = messages.slice(0, index).findLastIndex((item) => item.role === "user"); + const user = messages[userIndex]; + if (user) void sendMessage(user.text, user.mode, messages.slice(0, userIndex), user.references); + }; + + const startResize = () => { + const move = (event: MouseEvent) => setWidth(Math.min(760, Math.max(320, window.innerWidth - event.clientX))); + const stop = () => { + setResizing(false); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + document.removeEventListener("mousemove", move); + document.removeEventListener("mouseup", stop); + }; + setResizing(true); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", move); + document.addEventListener("mouseup", stop); + }; + + const collapse = () => { + setClosing(true); + onCollapseStart(); + window.setTimeout(onCollapse, PANEL_MOTION_MS); + }; + + return ( + + + + + } + > +

将删除 {deleteChatIds.length} 条对话记录,此操作不可撤销。

+ +
+
+ ); +} + +function AssistantComposer({ + mode, + prompt, + isRunning, + references, + config, + onModeChange, + onPromptChange, + onSubmit, + onConfigChange, + onMissingConfig, + onRemoveReference, + onPasteImage, +}: { + mode: AssistantMode; + prompt: string; + isRunning: boolean; + references: CanvasAssistantReference[]; + config: AiConfig; + onModeChange: (mode: AssistantMode) => void; + onPromptChange: (prompt: string) => void; + onSubmit: () => void; + onConfigChange: (key: keyof AiConfig, value: string) => void; + onMissingConfig: () => void; + onRemoveReference: (id: string) => void; + onPasteImage: (file: File) => void; +}) { + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + + return ( +
event.stopPropagation()}> + {references.length ? ( +
+ {references.map((item) => onRemoveReference(item.id)} />)} +
+ ) : null} +
+