ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

smol developer 全程序生成实战:从 shared_deps.md 共享依赖契约解读 AI 生成的 Pong 游戏架构

smol developer 全程序生成实战:从 shared_deps.md 共享依赖契约解读 AI 生成的 Pong 游戏架构
  • AI Agent
  • 代码智能体

【免费下载链接】developer

the first library to let you embed a developer agent in your own app!

项目地址:https://gitcode.com/gh_mirrors/de/developer
点击查看免费下载

shared_deps.md是 smol developer(当前仓库即其 v1 版本)在多文件程序生成管线中产出的关键中间产物:一份用 Markdown 写成的“共享依赖契约”。本文以仓库中 examples/v1_pong_game/shared_deps.md 为骨架,逐文件拆解这份契约如何描述一个由单条提示词生成的 Canvas Pong 游戏,并结合 index.html、style.css、main.js、ai.js 的真实源码,说明“变量清单、DOM id、函数命名”等一致性约束如何在生成阶段约束每个文件的代码,最后给出在本地复现整个生成过程的完整命令。读完本文,你将理解 smol developer 保证“整个程序全局一致”的核心机制,并掌握用一条提示词生成并运行一个多文件前端应用的完整方法。

一、shared_deps.md 是什么:多文件程序的一致性契约

1.1 全程序合成的痛点:跨文件间接依赖

smol developer 的目标是“whole program synthesis”——给定产品规格(prompt),直接生成一整个代码库。但多文件程序(尤其是 Chrome 扩展、前后端分离这类应用)存在大量跨文件的间接依赖:index.html里的元素 id 要被main.js用getElementById引用,CSS 选择器要和 HTML 结构对应,多个 JS 文件之间共享函数名与数据格式。只要模型在某个文件里“幻觉”出一个不一致的名字,整个程序就会报错。

仓库根目录的 readme.md 在 “innovations and insights” 一节明确记录了这一解法:

We solved this by adding an intermediate step asking GPT to think throughshared_dependencies.md, and then insisting on using that in generating each file. This basically means GPT is able to talk to itself...

也就是说:先生成一份描述“文件之间共享什么”的清单,再让模型在生成每个文件时严格遵守这份清单,从而让“模型与自己对话”,达成跨文件一致性。shared_deps.md就是这个中间步骤的产物。

1.2 在生成管线中的位置:plan → specify_file_paths → generate_code

smol developer v1 的生成管线是一个清晰的三段式流程,主控逻辑见 smol_dev/main.py:

  1. plan(规划):调用plan(prompt, ...)让模型输出整份代码的规划,直接以 Markdown 形式实时写入输出目录的shared_deps.md文件。对应的核心实现是 smol_dev/prompts.py 中的plan()。
  2. specify_file_paths(定文件清单):调用specify_file_paths(prompt, shared_deps),通过 OpenAI Function Calling 强制模型返回一个 JSON 文件路径数组,实现见 smol_dev/prompts.py。
  3. generate_code(逐文件生成):遍历文件路径数组,对每个文件调用generate_code_sync(prompt, shared_deps, file_path),把“规划”和“当前要生成的文件名”一起交给模型,生成单一文件的代码,实现见 smol_dev/prompts.py。

关键点在于第 3 步的系统提示词会要求模型“不得偏离我们已经商定的文件名和规划”(原文:do not stray from the names of the files and the plan we have decided on)。因此shared_deps.md不只是给人类看的文档,更是约束每次代码生成的事实来源。

1.3 plan() 提示词规定了 shared_deps.md 的内容结构

为什么shared_deps.md里恰好是“变量、数据 schema、DOM id、函数名”这些内容?因为plan()的系统提示词(smol_dev/prompts.py)明确要求:

In this plan, please name and briefly describe the structure of code that will be generated, including, for each file we are generating,what variables they export, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.

对照本示例的 shared_deps.md,可以看到它正是按这一模板组织的:先是 4 个文件的职责描述,随后是main.js的变量清单(playerPaddle、aiPaddle、ball、score)、函数清单(startGame、updateGame、drawGame、playerMove、aiMove、checkCollision、updateScore、increaseBallSpeed),以及ai.js的参数与函数(aiSpeed、aiError、aiDecision)。

仓库中另一个同类产物 v0/exampleChromeExtension/shared_dependencies.md(Chrome 扩展示例)采用了完全一致的契约格式:DOM 元素 id 列表、消息名、函数名、数据 schema。这说明shared_deps.md是 smol developer 在 v0/v1 全系列中一以贯之的核心机制,Pong 游戏只是它的一次具体落地。

二、Pong 示例的文件全景

shared_deps.md 将整个应用划分为 4 个文件,职责清晰:

文件职责关键元素 / 接口
index.html页面骨架:画布 + 挡板 div + 脚本装载id="gameArea"、id="playerPaddle"、id="aiPaddle"
style.css样式:画布、挡板、球的视觉契约400×400 黑色画布、100px 黄色挡板、红色小球
main.js主逻辑:鼠标控制、AI 跟随、碰撞检测、计分4 个全局对象 + 8 个函数
ai.js简单 AI 算法:带误差概率的决策aiSpeed、aiError、aiDecision()

四者通过index.html底部的两个<script>标签链接在一起:先加载main.js,再加载ai.js。两个 JS 文件均不使用import/export关键字,完全通过全局函数与 DOM API 协作——这是提示词中明确要求的 Chrome 兼容约束,下文会详细展开。

三、index.html:页面骨架与 DOM 契约

shared_deps.md 对它的描述是:包含游戏画布和两个挡板 div 的主 HTML 文件。真实源码(examples/v1_pong_game/index.html)仅 15 行:

<!DOCTYPE html> <html> <head> <title>Pong Game</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="gameArea" width="400" height="400"></canvas> <div id="playerPaddle" style="height:100px; width:10px; background-color:yellow;"></div> <div id="aiPaddle" style="height:100px; width:10px; background-color:yellow;"></div> <script type="text/javascript" src="main.js"></script> <script type="text/javascript" src="ai.js"></script> </body> </html>

这份文件定义了全程序最重要的跨文件契约——三个 DOM id:

DOM id用途被谁引用
gameArea400×400 画布,游戏渲染区域main.js通过getElementById("gameArea")获取并绘制
playerPaddle玩家挡板 divstyle.css定位与着色
aiPaddleAI 挡板 divstyle.css定位与着色

shared_deps.md要求所有 JS 函数“only uses the DOM API to interact with the HTML elements based on their id names”,而 main.js 的第一行就兑现了这一约定:const canvas = document.getElementById("gameArea")。

值得注意的细节:两个挡板 div 带有与 CSS 一致的内联样式(height:100px; width:10px; background-color:yellow)。从源码看,main.js并未通过getElementById获取这两个 div,游戏画面中的挡板实际由 canvas 绘制(见第五节drawGame()),div 更多承担了样式层的占位职责——这种“计划与实现之间的轻微出入”在 AI 生成代码中很常见,我们会在第九节再讨论。

四、style.css:画布与挡板的样式契约

shared_deps.md 对样式的描述是:画布为 400×400 黑色方块并在页面居中;挡板 100px 长、黄色;球小而红。真实实现(examples/v1_pong_game/style.css)逐条对应:

body { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: black; } #gameArea { background-color: black; height: 400px; width: 400px; position: relative; } #playerPaddle, #aiPaddle { position: absolute; background-color: yellow; width: 10px; height: 100px; } #playerPaddle { left: 0; } #aiPaddle { right: 0; } .ball { position: absolute; background-color: red; height: 10px; width: 10px; border-radius: 50%; }

可以看到契约的完整落地路径:

  • “400×400 黑色”同时出现在 HTML 的width/height属性和 CSS 的#gameArea中,双重保险;
  • “居中”由body的 flex 布局实现;
  • “挡板 100px、黄色”由#playerPaddle, #aiPaddle规则落实,并借助position: absolute分别贴左(left: 0)贴右(right: 0);
  • “球红色”由.ball类给出 10×10 红色圆形样式。

这里的“样式契约”与main.js的绘制逻辑也有呼应:main.js绘制挡板时用的是fillStyle = "yellow"、绘制球时用fillStyle = "red",与 CSS 中的颜色值完全一致——这正是shared_deps.md所承诺的“视觉方案全局统一”。

五、main.js:主循环与 8 个核心函数

shared_deps.md 将 main.js 定位为“控制游戏功能的主 JavaScript 文件”,并列出 4 个变量对象和 8 个函数。下面逐项对照真实源码展开。

5.1 全局变量:4 个游戏状态对象

const canvas = document.getElementById("gameArea"); const ctx = canvas.getContext("2d"); let playerPaddle = { x: 0, y: 200, width: 10, height: 100 }; let aiPaddle = { x: 390, y: 200, width: 10, height: 100 }; let ball = { x: 200, y: 200, radius: 5, dx: 2, dy: 2 }; let score = { player: 0, ai: 0 };

与shared_deps.md的清单一一对应:

  • playerPaddle:玩家挡板,位于x: 0(左侧),初始纵向居中y: 200,尺寸 10×100;
  • aiPaddle:AI 挡板,位于x: 390(右侧),尺寸相同;
  • ball:小球,半径 5,初始速度dx: 2, dy: 2(每帧水平、垂直各移动 2 像素);
  • score:双方计分对象,player与ai两个字段。

注意这里挡板的width: 10 / height: 100与 CSS、HTML 中的挡板尺寸完全一致,画布 400×400 也与 HTML 属性一致——尺寸契约在多文件间保持了统一。

5.2 startGame():初始化游戏状态

function startGame() { playerPaddle.y = 200; aiPaddle.y = 200; ball.x = 200; ball.y = 200; ball.dx = 2; ball.dy = 2; score.player = 0; score.ai = 0; }

负责把挡板复位到中线、球放回画布中心、速度恢复初值、比分清零。它在脚本末尾被调用一次(startGame();),并在每次得分后由updateScore()再次调用以重置对局。

5.3 playerMove(event):鼠标驱动的玩家挡板

canvas.addEventListener("mousemove", playerMove); function playerMove(event) { let rect = canvas.getBoundingClientRect(); playerPaddle.y = event.clientY - rect.top - playerPaddle.height / 2; }

通过监听画布的mousemove事件,把鼠标在页面中的clientY换算成画布坐标系内的y(减去rect.top),再减去挡板高度的一半,使挡板中心跟随鼠标纵坐标。这正是shared_deps.md中“player's paddle following the mouse”的实现。

5.4 aiMove():AI 挡板追踪小球

function aiMove() { let targetY = ball.y - (aiPaddle.height - ball.radius) / 2; aiPaddle.y += (targetY - aiPaddle.y) * 0.1; }

AI 的“慢速跟随”在这里实现:先算出理想位置targetY(让挡板中心对准球心),然后每帧只移动剩余距离的 10%(系数 0.1)。这是一个平滑的追赶算法——越接近目标移动越慢,天然形成“slowly moves the paddle toward the ball”的观感。

5.5 checkCollision():碰撞检测

function checkCollision() { // Ball and player paddle if (ball.y + ball.radius > playerPaddle.y && ball.y - ball.radius < playerPaddle.y + playerPaddle.height && ball.dx < 0) { if (ball.x - ball.radius < playerPaddle.x + playerPaddle.width) { ball.dx *= -1; increaseBallSpeed(); } } // Ball and AI paddle if (ball.y + ball.radius > aiPaddle.y && ball.y - ball.radius < aiPaddle.y + aiPaddle.height && ball.dx > 0) { if (ball.x + ball.radius > aiPaddle.x) { ball.dx *= -1; increaseBallSpeed(); } } // Ball and top or bottom if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) { ball.dy *= -1; } }

三段逻辑对应三类碰撞:

  1. 玩家挡板:球纵向范围与挡板重叠(ball.y ± radius落在playerPaddle.y ~ y+height内)且球向左飞(dx < 0)时,若球左缘越过挡板右缘(ball.x - radius < playerPaddle.x + width),则dx取反——球反弹;
  2. AI 挡板:对称逻辑,条件为球向右飞(dx > 0)且球右缘越过 AI 挡板左缘;
  3. 上下边界:球碰到画布顶部或底部时dy取反。

每次击中挡板都会调用increaseBallSpeed(),落实“每次弹板加速”的需求。

5.6 updateScore():得分与对局重置

function updateScore() { if (ball.x + ball.radius > canvas.width) { score.player++; startGame(); } else if (ball.x - ball.radius < 0) { score.ai++; startGame(); } }

球完全越过右边界(ball.x + radius > 400)时玩家得一分;越过左边界时 AI 得一分。得分后立即调用startGame()重置对局状态。

5.7 increaseBallSpeed():弹板加速

function increaseBallSpeed() { ball.dx *= 1.1; ball.dy *= 1.1; }

每次球击中挡板,水平与垂直速度同时乘以 1.1——速度按约 10% 的比率递增,游戏随时间推进而愈发激烈,这正是提示词中“Every time the ball bounces off a paddle, the ball should move faster”的直接实现。

5.8 updateGame() 与 requestAnimationFrame 主循环

function updateGame() { ball.x += ball.dx; // 移动球 ball.y += ball.dy; aiMove(); // 移动 AI 挡板 checkCollision(); // 碰撞检测 updateScore(); // 更新比分 drawGame(); // 渲染画面 requestAnimationFrame(updateGame); // 调度下一帧 }

这是整个游戏的心脏:每一帧依次执行“移动球 → 移动 AI → 碰撞检测 → 计分 → 渲染”,最后通过requestAnimationFrame(updateGame)递归调度自身,形成 60fps 的游戏循环。shared_deps.md描述的“Updates the game state at every frame”正是此函数。

5.9 drawGame():Canvas 渲染

function drawGame() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清屏 ctx.fillStyle = "yellow"; // 绘制挡板 ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height); ctx.fillRect(aiPaddle.x, aiPaddle.y, aiPaddle.width, aiPaddle.height); ctx.beginPath(); // 绘制球 ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2); ctx.fillStyle = "red"; ctx.fill(); ctx.font = "30px Arial"; // 绘制比分 ctx.fillText(score.player, 50, 50); ctx.fillText(score.ai, canvas.width - 50, 50); }

渲染层逐项落实视觉契约:黑色画布上先清屏,然后以黄色fillRect绘制左右挡板、以红色圆形arc绘制小球,最后在画布顶部左右各画一个比分数字。shared_deps.md所说的“Renders the game state on the canvas”即此。

六、ai.js:带误差概率的简单 AI

ai.js 全文仅 22 行,是“简单 AI 算法”的独立载体:

// Define AI's paddle speed var aiSpeed = 2; // Define AI's error rate var aiError = 0.05; /** * Function to make a decision on the direction to move the AI paddle based on the ball's position and the error factor. * @param {object} ball - The ball object * @param {object} aiPaddle - The AI paddle object */ function aiDecision(ball, aiPaddle) { // If ball is above the AI paddle and random number is greater than error rate, move the paddle up if (ball.y < aiPaddle.y && Math.random() > aiError) { aiPaddle.y -= aiSpeed; } // If ball is below the AI paddle and random number is greater than error rate, move the paddle down else if (ball.y > aiPaddle.y && Math.random() > aiError) { aiPaddle.y += aiSpeed; } }

对照shared_deps.md的变量说明:

  • aiSpeed(= 2):AI 挡板每次决策的移动步长(像素/帧);
  • aiError(= 0.05):AI 的失误概率。每次决策都会重新生成一个Math.random(),只有随机数大于 0.05 时才真正移动,因此约有 5% 的帧 AI 会“漏接”一次移动指令,让对手有可乘之机;
  • aiDecision(ball, aiPaddle):核心决策函数——球在挡板上方则上移、下方则下移,方向判断加上随机误差因子。

一个值得指出的源码级观察:从当前仓库的代码结构看,aiDecision()并没有被main.js调用(main.js全文中不存在对ai.js函数的引用),实际生效的 AI 行为来自main.js中的aiMove()。ai.js定义了“带概率误差的决策算法”以满足提示词中的规格描述,但游戏主循环走的是另一条更平滑的路径。这是“计划中的接口”与“最终装配”之间轻微不一致的又一个例子——shared_deps.md中的函数名aiDecision被如实生成,但其接线取决于生成main.js时的模型选择。

七、一致性契约如何约束代码生成

7.1 DOM id 契约贯穿三个文件

整个 Pong 示例最典型的一致性示范是gameArea:它在index.html中作为<canvas>的 id 被定义,在style.css中被#gameArea选择器赋予 400×400 与黑色背景,在main.js中被getElementById("gameArea")获取并作为 2D 绘图上下文。playerPaddle/aiPaddle同样横跨 HTML 与 CSS。这一“一处定义、多处引用”的模式,正是plan()提示词要求列出“every DOM elements that javascript functions will use”的原因——id 是跨文件引用的最强耦合点,必须先全局商定。

7.2 函数命名契约

shared_deps.md列出的 8 个函数名(startGame、updateGame、drawGame、playerMove、aiMove、checkCollision、updateScore、increaseBallSpeed)与main.js中的实际定义逐字一致。在生成阶段,generate_code的系统提示词要求“do not stray from the names of the files and the plan we have decided on”,即每个文件的代码必须使用契约中的命名,这从机制上杜绝了“main.js调用了一个不存在于ai.js的函数”这类跨文件幻觉。

7.3 兼容性约束:无 import/export、仅 Chrome 特性

shared_deps.md末尾特别说明:JS 文件不写import/export,只使用 Chrome 浏览器支持的特性,每个 JS 函数通过 DOM API 按 id 与 HTML 元素交互。这一约束来源于生成该示例的提示词(见下一节),并在源码中得到了贯彻:

  • 文件间通信依靠全局作用域函数与getElementById,而非 ES Module;
  • 使用的 API(requestAnimationFrame、addEventListener、CanvasRenderingContext2D、Math.random)全部属于浏览器原生能力;
  • 没有任何 Node.js 专有 API。

这意味着生成的index.html可以直接用 Chrome 打开运行,无需构建工具——这是“可在 Chrome 直接运行的前端小游戏”这一产品规格的最低门槛实现。

7.4 契约并非强约束:计划与实现的细微出入

需要客观指出的是,shared_deps.md是一份“指南”而非“编译器检查”。对比契约与最终代码,可以观察到三处出入:

  1. HTML 中的playerPaddle/aiPaddlediv 从未被main.js引用,实际挡板渲染完全走 canvas 绘制路径;
  2. style.css中定义了.ball类,但 HTML 中并没有class="ball"的元素,球同样由 canvas 绘制;
  3. ai.js的aiDecision()未接入主循环,实际 AI 逻辑是main.js的aiMove()。

这些出入不影响游戏可玩性,但印证了 readme.md 中作者自己的坦诚:shared_dependencies.md有时并不完备(“is sometimes not comprehensive in understanding what are hard dependencies between files”)。因此,shared_deps.md的实战定位是:生成阶段的命名与结构约束源、以及人类 review 生成的程序的入口文档,而非不可违背的规格书。

八、亲手重现:从一条提示词生成这个 Pong 游戏

examples/v1_pong_game/并非手写的示例,而是 smol developer 的真实输出产物。用于生成它的提示词就内嵌在仓库入口 main.py 中,原文如下:

a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG. The left paddle is controlled by the player, following where the mouse goes. The right paddle is controlled by a simple AI algorithm, which slowly moves the paddle toward the ball at every frame, with some probability of error. Make the canvas a 400 x 400 black square and center it in the app. Make the paddles 100px long, yellow and the ball small and red. Make sure to render the paddles and name them so they can controlled in javascript. Implement the collision detection and scoring as well. Every time the ball bouncess off a paddle, the ball should move faster. It is meant to run in Chrome browser, so dont use anything that is not supported by Chrome, and don't use the import and export keywords.

对照可见:shared_deps.md 中的每一个要素(canvas 400×400、挡板 100px 黄色、球小且红、AI 带误差概率、弹板加速、Chrome 兼容、不用 import/export)都能在这条提示词中找到出处。

8.1 环境准备

在本地克隆该仓库后,用 Poetry 安装依赖(见 readme.md 的 Basic Usage):

poetry install # 需要 poetry,依赖声明见 pyproject.toml

运行前还需要可用的 OpenAI API Key(openai库通过环境变量OPENAI_API_KEY读取)。

8.2 直接复现(不传参数即使用内嵌 Pong 提示词)

根目录的 main.py 在无命令行参数时会使用上述内嵌的 Pong 提示词。指定输出目录与调试模式即可复现:

python main.py --generate_folder_path pong --debug True

main.py的注释中也保留了等价写法(main.py):

python main.py --prompt "a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG..." --generate_folder_path "generated" --debug True

执行后,pong/(或generated/)目录下将出现与examples/v1_pong_game/结构一致的文件集,其中就包括新生成的pong/shared_deps.md——也就是本文剖析的这份契约文档的“孪生兄弟”。

8.3 关键命令行参数

根目录入口 main.py 支持以下参数:

参数默认值说明
--prompt内嵌 Pong 提示词应用规格;若以.md结尾且长度小于 100,则按文件读取提示词内容
--modelgpt-4-0613生成模型,也可切换为gpt-3.5-turbo-0613
--generate_folder_pathgenerated生成代码的输出目录
--debugFalse开启后实时打印流式输出、字符速率与各阶段内容

若想把长提示词沉淀为文件,参考 prompt.md(Chrome 扩展示例)的做法,将提示词写入 Markdown 后执行:

python main.py --prompt prompt.md --model gpt-3.5-turbo-0613

8.4 运行生成的游戏

生成完成后,直接用 Chrome 打开输出目录中的index.html即可试玩:鼠标上下移动控制左侧挡板,右侧 AI 挡板自动追踪小球,球每击中挡板加速一次,任一方丢球则对方得分并重置对局。

8.5 生成过程内部视角(源码级)

如果想知道生成过程中shared_deps.md是如何落盘的,可以阅读 smol_dev/main.py:main()先调用generate_folder()创建/清空输出目录,随后以open(f"{generate_folder_path}/shared_deps.md", "wb")打开文件,把plan()的流式输出 chunk 实时写入;规划完成后,specify_file_paths()通过 Function Calling 拿到文件路径数组(smol_dev/prompts.py),再逐个调用generate_code_sync()生成文件并落盘(smol_dev/main.py)。因此shared_deps.md既是第一步规划的输出,也是第二步起每个文件生成时的上下文输入——全程序一致性由此闭环。

九、局限与注意事项

  • 契约覆盖有限:如第七节所述,shared_deps.md并不总能穷尽文件间的硬依赖(例如aiDecision与aiMove的分叉)。作者在 readme.md 中也承认这一点,并补充过“在提示词中直接指定具体name”这一兜底手段。因此把它当作“人类 reviewer 的检查清单 + 模型生成的结构约束”是更稳妥的定位。
  • 输出目录会被清空重建:generate_folder()(smol_dev/utils.py)在目录已存在时会先shutil.rmtree再重建,因此重复运行会覆盖上一次的全部生成结果。若要保留多版本输出,请为每次运行指定不同的--generate_folder_path。
  • 模型选择影响质量:默认模型为gpt-4-0613(见 main.py),提示词级生成质量与模型能力强相关;示例代码中也可切换到gpt-3.5-turbo-0613以降低成本,但一致性表现通常更弱。
  • 依赖与版本前提:仓库的 pyproject.toml 声明python >=3.10,<4.0.0,并依赖openai ^0.27.8、openai-function-call ^0.0.5、tenacity ^8.2.2等库。复现时请以当前仓库声明为准。

结语

shared_deps.md是 smol developer“让模型与自己对话”这一核心思想的物质载体。透过 Pong 游戏这份仅 36 行的契约文档,我们完整看到了它如何规定 DOM id、变量、函数名与视觉方案,如何约束index.html、style.css、main.js、ai.js四个文件的生成,也看到了契约与实现之间真实存在的缝隙。理解这份文档,就等于理解了 smol developer 从“一条提示词”到“一个可运行的多文件程序”之间的那座桥——它既是生成机制的一部分,也是人类介入审查生成结果的第一个入口。

  • AI Agent
  • 代码智能体

【免费下载链接】developer

the first library to let you embed a developer agent in your own app!

项目地址:https://gitcode.com/gh_mirrors/de/developer
点击查看免费下载
上一篇:nix-update:Nix包更新的终极瑞士军刀,让依赖管理化繁为简
下一篇:DeepMesh震撼发布:革命性AI网格生成技术如何通过强化学习重塑3D创作?

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进