基于 IdSet 与 IdMap 的 Yjs 内容归属(Content Attribution):从 Google Docs 式版本追踪到高效编码
数据同步前端【免费下载链接】yjsShared data types for building collaborative software项目地址https://gitcode.com/GitHub_Trending/yj/yjs点击查看免费下载本篇文章以 Yjs 仓库中的 attributing-content.md 为骨架系统讲解 Yjs 用于实现内容归属Attribution的两大核心数据结构——IdSet与IdMap以及它们如何支撑起类似 Google Docs 的变更标注能力。读完本文你将掌握如何用IdSet/IdMap高效表示 ID 区间、如何通过 diff/merge/intersect 等集合运算分离出某个版本之后发生了哪些插入与删除如何将变更归属Attribution映射到具体用户并借助渲染器Renderer输出带标注的 Delta以及这些数据最终如何在网络传输中被压缩编码。为什么需要内容归属Google Docs 式版本追踪在协同编辑场景中仅仅知道文档当前长什么样往往不够。当用户在 Google Docs 中点击某个历史版本时界面会展示类似这样的注解式变更——谁在何时插入了什么、删除了什么、修改了哪些格式# 例如Bob 在上一版本 hello 之后追加了 world [{ insert: hello }, { insert: world, color: blue, creator: Bob, when: yesterday }] # 例如Bob 从上一版本 hello world 中删除了 world [{ insert: hello }, { insert: world, backgroundColor: red, creator: Bob, when: yesterday }]Yjs 采用完全不同的思路来实现这一目标所有变更在 Yjs 中都可以通过 ID 唯一标识ID 由client与clock构成见 src/utils/ID.js。因此我们可以把一段内容抽象成一组 ID 区间把某个用户改了哪些内容抽象成从 ID 区间到属性Attribution的映射。渲染时toString()、toDelta()这类方法对无归属的内容原样输出而对有归属的内容则附带额外信息如creator、when由编辑器决定如何用背景色等视觉效果呈现。这套机制在仓库中的正式名称为Content Attribution内容归属其官方文档正是 attributing-content.md底层实现分布在 src/utils/ids.js、src/utils/Renderer.js 与 src/utils/renderer-helpers.js 中。两大基础数据结构IdSet 与 IdMapIdSetID 区间的紧凑表示IdSet是 Yjs 中用于高效表示一组 ID 区间的数据结构其前身名为DeleteSet历史上用于表示已删除内容。在 Yjs 中一切内容都由 ID 标识因此任何内容集合——例如文档里所有插入的内容或文档里所有被删除的内容——都可以表达为若干条(client, clock, length)形式的 ID 区间。从源码结构看src/utils/ids.js 中的IdSet围绕客户端 IDclient组织区间列表内部为每个 client 维护一组排序合并后的IdRange相邻区间会被合并因此同样的内容集合占用空间极小。createIdSet()ids.js用于创建空集insertIntoIdSetids.js负责插入/合并区间。IdMap从 ID 到属性的映射IdMap是相对较新的数据结构用于将 ID 区间映射到属性列表Attribution。它与IdSet的结构类似但每个区间额外携带一个ArrayContentAttribute。一个ContentAttribute就是一条形如{ name: insert, val: Bob }的记录由createContentAttribute(name, val)ids.js创建用于表达这段区间被谁以什么方式修改。通用集合运算diff、merge、intersectIdSet与IdMap均支持全套标准集合运算这正是归属功能的基础。文档明确指出We can perform all usual set operations onIdMaps andIdSets: diff, merge, intersect.对应到 src/utils/ids.js 中的实现运算IdSet 版本IdMap 版本语义差集diffdiffIdSetL721diffIdMapL1666从集合 A 中剔除集合 B 覆盖的区间并集mergemergeIdSetsL576mergeIdMapsL1421合并多个集合属性按区间拼接去重交集intersectintersectSetsL773intersectMapsL1673取两集合共同覆盖的区间此外还有createIdSetFromIdMapL1485可以把一个IdMap的覆盖范围提取为IdSet供快速intersects/covers查询使用。从文档中提取变更三类关键工厂函数要为一组变更打上归属第一步是从文档结构中提取变更集。文档与源码提供了三个相互配合的工厂函数Y.createInsertSetFromStructStore(store, filterDeleted)ids.js遍历某个文档的 StructStore返回其中所有插入内容的IdSet。第二个参数为false时不做删除过滤。Y.createDeleteSetFromStructStore(store)ids.js返回该文档中所有已删除内容的IdSet。Y.diffIdSet(a, b)计算a - b用于排除掉某个基准版本已有的内容从而得到从这个版本到那个版本之间新增的变更。例如若要找出ydoc相对于ydocVersion0的所有插入与删除文档给出的核心模式是const insertionSet Y.createInsertSetFromStructStore(ydoc.store, false) const deleteSet Y.createDeleteSetFromStructStore(ydoc.store) // exclude the changes from ydocVersion0 const insertionSetDiff Y.diffIdSet(insertionSet, Y.createInsertSetFromStructStore(ydocVersion0.store, false)) const deleteSetDiff Y.diffIdSet(deleteSet, Y.createDeleteSetFromStructStore(ydocVersion0.store))实战为一段文本变更添加归属文档给出了一个完整可运行的端到端示例我们在此完整继承并逐步讲解。第一步构造基准文档与变更文档// We create some initial content Hello World!. Then we create another // document that will have a bunch of changes (make Hell italic, replace World // with attributions). const ydocVersion0 new Y.Doc({ gc: false }) ydocVersion0.get().insert(0, Hello World!) const ydoc new Y.Doc({ gc: false }) Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocVersion0)) const ytext ydoc.get() ytext.applyDelta(delta.create().retain(4, { italic: true }).retain(2).delete(5).insert(attributions).done())要点说明gc: false至关重要。归属功能需要读取已被删除的内容例如渲染被删除的 World如果开启垃圾回收这些内容会被 GC 掉渲染器将无法还原。这一点在 Renderer.js 的AttributionsRenderer文档注释中也有明确要求Restoring deleted content requiresgc: falseon the doc。ytext.applyDelta依次执行retain(4, { italic: true })前 4 个字符 Hell 加斜体、retain(2)跳过 o 、delete(5)删除 World、insert(attributions)插入新词。这里delta.create()是来自lib0/delta的 Delta 构建器在 tests/attribution.tests.js 中同样通过import * as delta from lib0/delta使用Y.applyUpdate/Y.encodeStateAsUpdate是 Yjs 的标准更新同步 API。第二步计算基准版本与当前版本的变更差集// this represents all insertions of ydoc const insertionSet Y.createInsertSetFromStructStore(ydoc.store, false) const deleteSet Y.createDeleteSetFromStructStore(ydoc.store) // exclude the changes from ydocVersion0 const insertionSetDiff Y.diffIdSet(insertionSet, Y.createInsertSetFromStructStore(ydocVersion0.store, false)) const deleteSetDiff Y.diffIdSet(deleteSet, Y.createDeleteSetFromStructStore(ydocVersion0.store))此时insertionSetDiff中只包含 attributions 这个新插入的区间以及格式变更相关的区间deleteSetDiff中只包含被删除的 World 区间——基准版本原有的 Hello World! 都被diffIdSet排除掉了。第三步将差集映射为归属Attribution// assign attributes to the diff const attributedInsertions Y.createIdMapFromIdSet(insertionSetDiff, [Y.createContentAttribute(insert, Bob)]) const attributedDeletions Y.createIdMapFromIdSet(deleteSetDiff, [Y.createContentAttribute(delete, Bob)])Y.createIdMapFromIdSet(idset, attrs)ids.js把一份IdSet整体包装成IdMap并给其中的每个区间都附上同一组属性。这里我们用createContentAttribute(insert, Bob)表示这些内容是 Bob 插入的用createContentAttribute(delete, Bob)表示这些内容是 Bob 删除的。第四步用渲染器输出带归属的 Delta// now we can define a renderer that maps these changes to output. One of the // implementations is the TwosetRenderer const renderer new Y.TwosetRenderer(attributedInsertions, attributedDeletions) // we render the attributed content with the renderer const attributedContent ytext.toDelta({ renderer }) console.log(JSON.stringify(attributedContent.toJSON(), null, 2)) const expectedContent delta.create().insert(Hell, { italic: true }, { format: { italic: [Bob] } }).insert(o ).insert(World, {}, { delete: [Bob] }).insert(attributions, {}, { insert: [Bob] }).insert(!) t.assert(attributedContent.equals(expectedContent))ytext.toDelta({ renderer })让渲染器介入 Delta 生成过程。从 renderer-helpers.js 的attributionJsonSchemaL7可以确认归属信息的标准 JSON 形态Delta 操作可以附带insert字符串数组表示插入者、delete字符串数组表示删除者、format{ 格式名: [归属者列表] }等字段这正是文档示例输出中attribution对象的来源。需要说明的是文档中提到的TwosetRenderer属于示例性质的渲染器之一在当前仓库中AbstractRenderer的具体实现由 src/utils/Renderer.js 提供包括AttributionsRendererL63将{ inserts, deletes }两份归属图合并渲染、DiffRendererL359比较两个文档并归属两者之间的差异、SnapshotRendererL613面向快照场景。它们输出的归属格式与文档示例保持一致——这一点由 tests/attribution.tests.js 中的testAttributionSession1L143等用例直接断言验证例如期望insert(a, null, { insert: [0] })、insert(b, null, { delete: [0] })这样的输出形态。第五步解读渲染结果上述代码会输出如下 JSON文档原样给出{ type: delta, children: [ { type: insert, insert: Hell, format: { italic: true }, attribution: { // no insert attribution: the insertion Hell is not attributed to anyone format: { italic: [ // the formatting attribute italic was added by Bob Bob ] } } }, { type: insert, insert: o // the insertion o has no attributions }, { type: insert, insert: World, attribution: { // the insertion World was deleted by Bob delete: [ Bob ] } }, { type: insert, insert: attributions, // the insertion attributions was inserted by Bob attribution: { insert: [ Bob ] } }, { type: insert, insert: ! // the insertion ! has no attributions } ] }这段输出与 Google Docs 的版本注解语义高度一致Hell这个插入本身不归属于任何人但它携带的italic格式是 Bob 添加的因此attribution.format.italic [Bob]o 与!是基准版本就存在且未被修改的内容没有任何归属World是被 Bob 删除的内容归属标记为attribution.delete [Bob]attributions是 Bob 插入的新内容归属标记为attribution.insert [Bob]。渲染器只负责把这些归属信息翻译进 Delta至于最终用背景色如红色高亮删除、蓝色高亮插入等视觉效果呈现则是编辑器层的职责——这正是文档所说的 It will be the job of the editor to render those changes with background-color etc.。多用户归属与按用户筛选内容当然可以归属于多个用户。文档给出的示例是为同一段删除同时附加两条属性const attributedDeletions Y.createIdMapFromIdSet(deleteSetDiff, [Y.createContentAttribute(insert, Bob), Y.createContentAttribute(insert, OpenAI o3)])在真实协同系统中更常见的做法是监听每个用户的 update 事件动态累积全局归属表。仓库测试 testAttributionSession1 给出了这一模式的完整实现const globalAttributions Y.createContentMap() // 见 src/utils/meta.js#L103 users.forEach(user user.on(update, (update, _, ydoc, tr) { if (!tr.local) return const userid ydoc.clientID.toString() const contentIds Y.createContentIdsFromUpdate(update) Y.insertIntoIdMap(globalAttributions.inserts, Y.createIdMapFromIdSet(contentIds.inserts, [Y.createContentAttribute(insert, userid)])) Y.insertIntoIdMap(globalAttributions.deletes, Y.createIdMapFromIdSet(contentIds.deletes, [Y.createContentAttribute(delete, userid)])) }))这里的Y.createContentMap()meta.js返回{ inserts, deletes }两份IdMap的包装结构是归属渲染器的标准输入格式createContentIdsFromUpdate从一次网络同步的 update 中提取插入/删除 ID 区间。测试随后通过Y.filterIdMap(...)筛选出仅属于某个用户的归属构造出只看 Bob 的改动的专属渲染器再用Y.undoContentIds撤销特定用户的改动——这些都是 src/utils/meta.js 提供的高层辅助能力。测试 testYdocDiff 还验证了Y.diffDocsToDelta(ydocStart, ydocUpdated)这一高层 API它直接比较两个文档并输出带{ insert: [] }/{ delete: [] }归属标记的 Delta空数组表示有变更但未命名归属者。AbstractRenderer 与自定义高亮渲染AbstractRenderer是渲染器的抽象基类负责把归属信息映射到内容上。它在 src/utils/renderer-helpers.js 中定义核心契约有三个hasItem(item)判断某个 Item 是否命中归属集合。只有被hasItem认领的内容才会走渲染器路径其余内容走通用快速路径原样渲染见 Renderer.js 中AttributionsRenderer.hasItem的实现。readContent(...)把内容按归属区间切分逐个产出AttributedContentrenderer-helpers.js携带content、clock、deleted、attrs与渲染行为标记。contentLength(item)计算归属内容渲染后的长度供遍历迭代器使用。文档明确指出通过这种抽象It is possible to highlight arbitrary content with this approach——即任何内容片段都可以被任意高亮/标注而不仅限于插入与删除。rendererContentLengthrenderer-helpers.js提供了通用长度规则存活可计数内容全量渲染其余为 0除非渲染器认领供各具体渲染器复用。渲染器还可以通过ObservableV2派发change事件在归属发生变化后通知上层增量更新当前内容的归属标注。从归属输出计算真实 Diff由于归属输出本身就是结构化的 Delta文档指出可以直接用它计算纯 diff只包含删除与插入、去掉归属信息You could use the same output to calculate a real diff as well (consisting of deletions and insertions only, without Attributions).换言之同一份渲染结果既能驱动版本注解视图展示谁改了哪里也能驱动普通的差异视图只展示改了什么。这是toDelta({ renderer })输出模型带来的直接红利也解释了为什么仓库测试如 testAttributedEvents会反复断言insert(world, null, { delete: [] })这类形态归属存在与否、归属为空与否都在同一份 Delta 结构中表达。高效编码RLE 与属性去重归属数据最终需要与文档更新一起在网络上传输因此编码效率至关重要。文档给出了两个硬性结论ID 采用游程编码run-length encoding相邻的 ID 区间合并后以(client, clock, length)三元组紧凑表达属性去重、只编码一次相同的属性如insert、Bob不会重复出现。实测结果上述示例整体仅编码为 27 字节。在仓库实现中这一高效编码由 src/utils/BlockSet.js 承担。writeBlockSetL87按 client 分组写出块引用GC/Skip/ItemreadBlockSetL25对称地读回BlockSet.toIdSet()L118可把块集合还原为IdSetexclude()L144则把被排除的区间替换为Skip占位块以保持编码紧凑。区间排序合并的逻辑位于 ids.js 的AttrRanges.getIds()L1118中先按clock排序再对重叠区间切分、对相邻同属性区间合并这正是 RLE 压缩的算法基础。测试验证与可靠性归属功能并非纸面设计仓库通过 tests/attribution.tests.js 提供了系统性的验证可作为读者理解行为预期的活文档testAttributionSession1L143多用户协同下的全局归属累积、按用户筛选、撤销单用户改动testAttributedEventsL38与testAttributionEventL180在事件回调中通过event.getDelta({ renderer })获取带归属的变更 Delta验证删除段落时子内容也会获得归属testAttributionChangeL203渲染器change事件在归属变化时触发并提供三态null 清除归属更新testInsertionsMindingAttributedContentL62与testInsertionsIntoAttributedContentL80在带归属渲染器下继续插入内容文档实际状态依然正确归属视图与真实状态互不干扰testRdtDeltaAttributionSanityL755维护中的增量 Delta 缓存与全新深渲染结果保持一致。小结Yjs 的内容归属机制是一条完整的技术链路以IdSet紧凑表示 ID 区间以IdMap将区间映射到归属属性通过diff/merge/intersect 集合运算分离版本间变更借助AbstractRenderer家族把归属渲染进 Delta最后以RLE 属性去重的方式压缩编码。这套设计让 Yjs 在保持 CRDT 数据模型不变的前提下原生支持谁在何时改了什么的版本注解能力且与快照、撤销、多用户协同等既有能力自然衔接。无论是实现一个 Google Docs 风格的版本历史面板、一个审阅/建议模式还是一个按用户着色的高亮层上述数据结构与渲染器都是可以直接落地的核心构件。赞分享数据同步前端【免费下载链接】yjsShared data types for building collaborative software项目地址https://gitcode.com/GitHub_Trending/yj/yjs点击查看免费下载相关推荐OneUptime AI 编程助手可观测性基于 OpenTelemetry 追踪代码助手的用量、成本与员工归属OneUptime AI 编程助手可观测性基于 OpenTelemetry 追踪代码助手的用量、成本与员工归属 当团队中的工程师开始使用 Claude Cod可观测性后端运维前端云原生微服务AI AgentYjs v14 Attribution 特性实战指南用 Renderer 为 Delta 渲染作者归属、删除痕迹与变更高亮Yjs v14 Attribution 特性实战指南用 Renderer 为 Delta 渲染作者归属、删除痕迹与变更高亮 导读 Attribution归属数据同步前端Nuxt Content 内容编辑全指南从可视化到代码编辑Nuxt Content 内容编辑全指南从可视化到代码编辑 前言 在现代内容管理系统中内容编辑体验直接影响着开发者和内容创作者的效率。Nuxt Conten前端CMS上一篇阴阳师自动化脚本终极指南如何用AI解放双手轻松获取游戏资源下一篇mistral.rs 中 MCP 客户端实战三种传输方式接入外部工具并自动注册到模型对话创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

黄白助手 第 111 个开关:启用快捷查询账号的位置、验证方法与风险边界

黄白助手 第 111 个开关:启用快捷查询账号的位置、验证方法与风险边界

🔥 个人主页: 杨利杰YJlio ❄️ 个人专栏: 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》 《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》 《超简单:用Python让Excel飞起来》…

2026/10/1 2:36:01 阅读更多 →
MacBook玩大型游戏的三大技术路径:串流、虚拟机与Metal兼容层

MacBook玩大型游戏的三大技术路径:串流、虚拟机与Metal兼容层

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 2:36:01 阅读更多 →
防火墙源代码深度拆解:包过滤、状态检测与规则引擎实战

防火墙源代码深度拆解:包过滤、状态检测与规则引擎实战

简介:一款名为“费尔防火墙1.0”的源代码压缩包,面向网络安全开发者、运维人员及安全爱好者,用于研究防火墙的工作原理与源码实现。包体约529KB,内含源码主体、功能说明文档及社区入口链接,文件数量与类型明细暂未标注…

2026/10/1 2:36:01 阅读更多 →

最新新闻

Selenium自动化测试实战:核心逻辑、环境搭建与工程化方案

Selenium自动化测试实战:核心逻辑、环境搭建与工程化方案

如果你准备进入自动化测试领域,Selenium几乎是绕不开的第一个工具。无论是刚转行的测试新人,还是已经在功能测试岗位上做了几年的老手,简历上只要写上"Selenium",面试官通常都会默认你具备 UI 自动化能力。它的知名度高…

2026/10/1 3:53:42 阅读更多 →
基于Python爬虫的豆瓣电影音乐图书数据分析系统实战

基于Python爬虫的豆瓣电影音乐图书数据分析系统实战

豆瓣这个网站在爬虫练习圈子里一直是个绕不开的标本。它同时拥有电影、音乐、图书三种内容形态,页面结构清晰,字段完整,反爬强度也恰好卡在“需要认真对待但还不至于直接劝退”的位置。这个属性让它成了课程设计和毕业设计的高频选题——如果…

2026/10/1 3:53:42 阅读更多 →
Flutter在OpenHarmony上构建交互式文档应用:布局、滚动与通道实战

Flutter在OpenHarmony上构建交互式文档应用:布局、滚动与通道实战

说实话,刚接到“用 Flutter 在 OpenHarmony 上做交互式文档应用”这个需求时,我心里是有点打鼓的。文档类应用表面看不复杂,无非是文章、目录、代码块、搜索定位,但一旦加上“交互式”三个字,事情就完全变味了。你要处…

2026/10/1 3:53:42 阅读更多 →
Flutter鸿蒙网络请求桥接实践:MethodChannel与踩坑全记录

Flutter鸿蒙网络请求桥接实践:MethodChannel与踩坑全记录

你要是接过鸿蒙设备上的 Flutter 网络请求,应该能懂那种憋屈感。明明 Dart 侧封装好的 Dio 请求拿到 Android 和 iOS 上跑得干干净净,一到开源鸿蒙(OpenHarmony)上,要么连不上、要么超时时间失效、要么证书校验直接卡死…

2026/10/1 3:53:42 阅读更多 →
eval命令全解析:动态执行原理、安全风险与替代方案

eval命令全解析:动态执行原理、安全风险与替代方案

很多开发者第一次接触 eval 命令时,都会觉得它不过是一个“把字符串变成命令”的小工具。我在刚开始写 shell 脚本时也这么想,直到有一次线上脚本因为一行 eval,把原本正常的变量展开搞得面目全非,我才意识到:这个看似…

2026/10/1 3:53:42 阅读更多 →
手语识别毕设实战:PyTorch+CNN-LSTM+PyQt5全流程落地

手语识别毕设实战:PyTorch+CNN-LSTM+PyQt5全流程落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 3:52:42 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →