- AI Agent
- 人工智能
- 代码智能体
- 交互助手
【免费下载链接】openchamber
Agentic Development Environment based on OpenCode AI agent
Session Sidebar(会话侧边栏)是 OpenChamber(基于 OpenCode AI Agent 的 Agentic 开发环境)桌面端(Web / Electron)与 VS Code 扩展中共用的会话管理核心组件,负责 Chats、Recent、项目(projects)、分组(groups)、文件夹(folders)与会话行的投影、搜索、活动状态、拖拽与批量操作。本文基于仓库中 packages/ui/src/components/session/sidebar/DOCUMENTATION.md 的完整技术说明,结合源码与测试展开,帮助读者理解侧边栏的模块归属、全局会话缓存与目录引导(bootstrap demand)机制、Timeline 与 Search 模式、加载规则以及虚拟化与粘性头实现。读完本文,你将能够定位任意侧边栏行为对应的源码模块,并理解其底层数据流与约束。
模块划分:按业务对象归属的目录结构
侧边栏代码按照“它拥有的业务对象”来组织,共享契约放在根目录的 types.ts 与 utils.tsx 中。types.ts定义了SessionNode、SessionGroup、SessionSidebarRow、SessionSidebarRowModel等核心类型,是理解全部子模块的入口。
| 目录 | 职责 |
|---|---|
shell/ | 侧边栏外壳(chrome)、导航、搜索、确认对话框与切换器(switcher)效果 |
list/ | 全局优先的会话集合、目录引导需求(bootstrap demand)、布局持有的同步、权威清理(authoritative cleanup)、邻近会话预取(prefetch) |
projects/ | 项目分区(zones)、分组、排序、滚动器行为、项目视图状态、仓库状态与 worktree 呈现 |
sessions/ | 会话行、行操作、展开、所有权(ownership)与活动指示器 |
recent/ | Recent 与受管 Chats 的活动投影 |
folders/ | 文件夹拖拽、批量操作、归档文件夹与文件夹 UI |
其中有两个文件处于架构中心:
- sessionSidebarRowModel.ts 持有“有序、模式无关”的行投影(projection),统一负责 Chats、Recent、项目、分组、文件夹、会话、状态通知、空状态与 reveal 控件。它导出
SessionSidebarViewMode(projects | timeline)与SessionSidebarRenderContext(project | recent | timeline | timeline-chat)等类型(见 types.ts)。 - SessionSidebarRows.tsx 是桌面 Web、Electron 与 VS Code 共享的行渲染器。普通模式与已提交搜索(committed-search)模式使用同一个模型与同一个
@tanstack/react-virtual实例,即搜索不会引入第二套虚拟化状态。
// packages/ui/src/components/session/sidebar/types.ts#L46-L54 export type SessionSidebarRow = | (RowBase & { kind: 'activity-header'; activityKey: SessionSidebarActivityKey; collapsed: boolean; forceExpanded: boolean }) | (RowBase & { kind: 'project-header'; section: ProjectSection; collapsed: boolean; forceExpanded: boolean }) | (RowBase & { kind: 'group-header'; group: SessionGroup; ... }) | (RowBase & { kind: 'folder-header'; group: SessionGroup; folder: SessionFolder; ... }) | (RowBase & { kind: 'session'; node: SessionNode; depth: number; ... }) | (RowBase & { kind: 'empty'; emptyKind: 'sidebar' | 'search' | 'group' | 'archived'; ... }) | (RowBase & { kind: 'status'; status: SessionSidebarGroupStatus; group: SessionGroup; groupKey: string }) | (RowBase & { kind: 'show-control'; control: 'more' | 'fewer'; containerKey: string; ... });该联合类型说明侧边栏渲染的每一种“行”都是模型里的一种判别式(discriminated union)成员:activity-header、project-header、group-header、folder-header、session、empty、status、show-control。任何 UI 层的新行类型都必须先落在这个模型中,渲染层才有对应的分支。
活动指示器:折叠组的单点汇总
sessions/拥有会话行活动指示器,折叠的分组或文件夹只显示一个指示器,汇总其隐藏会话:待定权限(shield)> 待定问题(question)> 运行中回合(running turn)> 未读(unread)。优先级说明:
- 待定请求从跨目录的
global-blocking-requests索引读取,因此本次启动从未打开的项目也能显示它们; - 运行中与未读来自全局状态索引与通知 store。
实现上SessionActivityIndicator用于项目行、时间线(timeline)行、头部标签、切换器与折叠聚合(见 collapsedActivityIndicator.tsx 与 collapsedActivityState.ts)。运行中用 info 色,未读用 success 色。本地 Appearance 偏好animatedActivityIndicators默认关闭;开启后即使系统请求减少动画(reduced motion),运行中的圆点也会换成步进 spinner。权限/问题徽章与逐会话计时器保持原有优先级与行为。显示 store 保持版本 8:缺失偏好在水合(hydration)时继承默认值,而显式保存的选择在重载后存活。
数据同步:全局会话缓存与目录引导需求
useSessionListSync:唯一的引导需求持有者
MainLayout与VSCodeLayout无条件调用 list/useSessionListSync.ts 中的useSessionListSync({ isVSCode })。该 hook 是唯一的 bootstrap demand 持有者,只发布当前目录与所选会话的目录,同时负责:
- 刷新新增拓扑(topology);
- 合并(coalesce)控制事件;
- 执行权威清理(authoritative cleanup)。
// packages/ui/src/components/session/sidebar/list/useSessionListSync.ts#L41-L47 React.useEffect(() => { childStores.setBootstrapDemand(bootstrapDemandOwner, buildSessionBootstrapDemands({ currentDirectory, currentSessionDirectory, })); return () => childStores.clearBootstrapDemand(bootstrapDemandOwner); }, [bootstrapDemandOwner, childStores, currentDirectory, currentSessionDirectory]);关键约束:已知项目根目录与 worktree 是拓扑(topology),不是引导需求。行与会话来自全局会话列表,活动来自全局状态索引与宿主状态种子。这是因为每次目录级读取都会让 OpenCode 创建并初始化一个 location,若启动时把整个拓扑都设为需求,每个目录就会各产生一个 OpenCode 实例(源码注释原话:initializing them only created one OpenCode instance per directory at startup)。
根级useGlobalSessionsPolling仍是唯一初始 + 45 秒间隔的全局轮询器(带受限启动恢复)。useSessionListSync不得创建第二个全局轮询生命周期——它订阅 OpenChamber 控制事件流(scheduled-task-ran触发全量刷新、session-created按目录刷新,500ms 去抖合并,见 useSessionListSync.ts)。
全局缓存是权威源
全局会话缓存(global sessions cache)是激活与归档覆盖的完整来源。已初始化的目录 store 只补充缓存中缺失的会话。实时 busy 与 retry 状态来自global-session-status,从不来自全局缓存或持久化历史。全局或目录拉取失败时保留既有数据,绝不视为权威空列表(it is never treated as an authoritative empty list)。
useSidebarGroupStatus:分组状态的订阅规则
list/useSidebarGroupStatus.ts 同时订阅项目目录与独立 Chats 目录,Chats 使用同一activity:chats身份做状态与行投影(包括纯 Chats 侧边栏):
- 未解析的全局列表让未打开的分组保持 loading;
- 全局失败暴露 Retry,直接作用于全局加载器,不会引导(bootstrap)任何目录;
- 完整全局快照或完整激活目录覆盖会独立于初始化停止 loading;
- 归档分组要求全局覆盖;
- 目录失败保留各自的 retry/access 操作。
其状态机由 types.ts 定义:'ready' | 'loading' | 'load-failed' | 'initialization-failed' | 'permission-denied',并携带directory与canGrantAccess两个字段。
会话位置解析与 worktree 索引
recent/sessionLocation.ts是“会话的项目、目录、worktree 与分支标签”的唯一所有者,供 Recent 与 Timeline 读取行元数据:
- 先通过会话所有权索引解析项目(受管 worktree 位于项目路径之外,路径前缀匹配永远找不到它们的项目);
- 回退到路径前缀匹配;
- 用最长前缀查找包含的 worktree,使
<worktree>/sub中的会话仍保留该 worktree 的分支与 PR key(见 resolveSidebarSessionLocations)。
分支标签是live-first:实时 git 状态优先于已发现的 worktree 元数据(resolveBranchLiveFirst,见 worktreeIndex.ts)。Recent 隐藏与项目标签相同的分支;Timeline 每行都显示分支。
worktreeIndex.ts 是共享的精确 worktree 索引,供 Recent/Timeline、项目分组与会话切换器复用,其不变式:归一化 key、排除项目根、first-wins 去重(重复路径只保留第一条,避免拓扑重复发布翻转所有权)。findPrefixWorktreeEntry用最长前缀匹配<worktree>/sub场景。
Timeline 视图:扁平的活动时间线
sidebarViewMode(profile 作用域、按 surface 区分)在桌面与 Web 的projects与timeline之间切换;VS Code 无开关、始终渲染projects。
Timeline 的关键规则(对应文档 “Timeline view” 一节):
- 保留受管 Chats 分区,初始 reveal 为 3(而非通常的 Chats 上限);置顶(pinned)chats 总是显示且不占用该额度,Show more/Show fewer 只统计未置顶行。Chats 行以
renderContext: 'timeline-chat'渲染:单行、无左侧 gutter、置顶与状态点在右侧时间旁。折叠分区头会重置其 Show more 状态。 - 分区头在 projects 视图粘性、在 timeline 永不粘性,且无用户开关;Timeline 分区头去掉前导图标、使用更高的色带。
- Chats 之下渲染一个
timeline活动头(粘性分区头,同chats、active-now),随后是所有项目与 worktree 的全部非归档根会话的单一扁平列表,按共享生命周期顺序排列、置顶会话在前。没有 reveal 上限:列表完全虚拟化。 - Timeline 行携带
renderContext: 'timeline'、depth 0、children 为空:永不展开、无 chevron、无文件夹、无项目头、无 worktree 分组、无 Recent 投影。由于文件夹不投影,行菜单隐藏 “Move to folder”;但归档/删除操作仍覆盖完整子树(collectSessionSubtreeIds在动作时从全局缓存解析后代)。 - 搜索用与 Recent 相同的规则过滤 Timeline:精确
ses_id 否则标题包含;每个列出的行计一次匹配。
常量佐证(sessionSidebarRowModel.ts):SESSION_ESTIMATE = 32、TIMELINE_SESSION_ESTIMATE = 64、TIMELINE_CHATS_INITIAL_LIMIT = 3、TIMELINE_CHATS_INCREMENT = 7——行高估算直接参与虚拟化测量。
搜索:只在 Enter 提交,精确 ID 优先
侧边栏、移动会话列表与归档列表中的专用搜索框只在 Enter 提交。SessionSearchInput本地持有草稿文本,列表所有者只接收已提交查询,因此输入不会使会话树失效;清空字段立即重置已应用查询。IME 确认与长按 Enter 不提交;Escape 先清文本,再在为空时关闭侧边栏搜索。会话行只收到一个稳定的 reset 动作,而非瞬态的 search-open 或草稿状态;关闭保留中的移动端搜索会丢弃未提交文本。
ID 匹配规则:以ses_开头的查询只匹配完整会话 ID(大小写不敏感、忽略周边空白);部分 ID 与拼写错误返回无匹配,不回退到标题、目录、分组标签或文件夹名。匹配节点保留其子树用于渲染与子树操作,祖先仍作为树上下文保留。只有精确 ID 匹配计入结果总数。ID 搜索不包含归档会话;ArchiveView对其归档列表应用同一精确 ID 规则。
搜索改变的是模型输入而非渲染器所有权:它强制项目/分组/文件夹/活动行打开,却不改变普通模式的折叠或 show-more 状态,因此关闭搜索精确恢复之前的 Chats、Recent、项目、分组、文件夹投影。搜索不获取会话、不扩宽列表成员(Search does not fetch sessions or broaden list membership)。
加载规则:引导、失败与可视性的约束清单
文档 “Loading rules” 一节总结了决定侧边栏稳定性的关键规则,以下摘录最核心的几条并标注源码依据:
- 引导需求只发布当前目录与所选会话目录。目录需求与刷新请求在分隔符与盘符归一化后保留路径大小写;大小写不敏感的侧边栏成员 key 留在集合投影内,把 key 当路径发送会创建重复目录 store,在大小写敏感文件系统上可能寻址到不同目录。
- 未引导的目录只有完整全局快照后才 ready。此前分组显示全局 loading/failure,Retry 重载全局列表;已加载分组在后台轮询期间保持 ready。目录失败与拒绝的文件夹访问仍对受影响目录使用强制引导或原生访问恢复。
- 同步调度器负责去重、提升、重试与限流;侧边栏组件不得用 mount effects 复刻该生命周期。
- 隐藏投机工作:侧边栏/聊天 surface 隐藏时,消息预取、Git/PR 富化与订阅、搜索监听、粘性头观察、归档文件夹派生全部停止;会话行树卸载,使行拥有的状态/权限/unseen/viewport 订阅不做后台工作。外层侧边栏保持挂载以保留 UI 状态与权威目录刷新,可见性恢复时从当前状态重跑延迟的派生工作(见 useSessionPrefetch.ts 与 useAuthoritativeSessionCleanup.ts)。
- 父展开完全手动:选择或导航到子会话从不自动展开父级。project/worktree 与
recent树使用独立持久化 context key 与独立稳定投影;持久化存储 key 保持v3,旧状态混用 context 且不迁移进此契约(见 useExpandedParents.ts)。 - 空列表、未解析加载与失败是三种独立 UI 状态:失败分组暴露 Retry 并保留旧数据。列表加载与工作区初始化分离:spinner 只跟随列表队列,config/MCP/LSP/实时状态恢复不能让已成功的空列表继续转圈。
- 目录权限失败在保留陈旧会话时仍可见:扁平分组检查每个代表的根/worktree 目录;桌面本地可为精确失败目录打开原生选择器,其他运行时保留普通 Retry。
- Shift 选择与 Ctrl/Cmd+A 消费模型的逻辑行序:API 会话 ID 只在动作边界去重(隐藏后代已纳入后)。运行时切换与确认的会话删除会清除选择。批量破坏性动作在动作时从模型当前会话记录分类归档状态,绝不使用挂载 DOM 或选择时元数据。
Move to worktree:整棵子树的迁移语义
根会话右键与溢出菜单暴露Move to worktree子菜单:列出规范主 worktree 与关联 worktree 目标,当前目标禁用,另有独立New worktree...动作。打开子菜单会刷新 worktree 拓扑(见 sessionWorktreeMenu.ts)。
移动语义的关键保证:
- 移动转移完整空闲子树。干净与非 Git 源仅移动会话;脏 Git 源提示三选一:仅移动会话 / 移动全部源改动 / 取消。
- 后代先移动且不带改动,若之后某后代失败则仅回滚会话部分;根最后移动且只携带一次源改动,防止回滚把已转移的补丁重放进源。
- 失败清理:为移动创建的 worktree 只在明确失败后移除;携带改动的请求失败且未确认结果时,该 worktree保留(可能持有用户改动的唯一副本),两个目录都做权威刷新(会话可能已在服务端移动),toast 指向目标位置。既有目标从不移除,同样给出指引。
- 打开子菜单强制刷新所属项目的 worktree 拓扑,外部创建的 worktree 无需完整重载即出现;刷新期间菜单保留最后已知的主/关联拓扑,刷新失败则陈旧拓扑保留且加载失败状态保持显式。
worktree 拓扑发现是事件驱动的:包括session-created与服务端worktree-changed控制事件,无空闲轮询。服务端在自身 worktree 创建/移除、或在状态/列表请求注意到某仓库 worktree 集合变化后发送worktree-changed(见 packages/web/server/lib/git/DOCUMENTATION.md);事件点名该仓库服务端见过的每个目录,侧边栏对其中每个已注册项目各刷新一次,绕过 30 秒列表缓存。宿主移动端与桌面 mini chat 通过 lib/worktrees/worktreeTopologyRefresh.ts 处理同一控制事件;VS Code 有意排除 worktree 拓扑。
虚拟化与粘性头:单一 scroller、交叉淡化分区头
侧边栏虚拟化(sessionSidebarVirtualization.ts)的关键常数与函数:
const INITIAL_ROW_LIMIT = 24; export const getInitialSessionSidebarRowIndexes = (rowCount: number): number[] => Array.from({ length: Math.min(rowCount, INITIAL_ROW_LIMIT) }, (_, index) => index);既有ScrollableOverlay是唯一滚动所有者。共享行渲染器测量变高行、使用稳定 occurrence key、保持受限的预初始化窗口,并在 range extractor 中钉住编辑/聚焦/打开菜单的 occurrence。scroller 通过 callback-backed state 发布其 DOM 元素,使虚拟化在每次挂载后激活,无需等待无关渲染。归档分组不得添加嵌套 virtualizer。
SessionTreeItem(sessions/SessionTreeItem.tsx)用比较器做 memoization,比较它实际读取的 props:行列表在滚动每一帧与每次模型重建时重渲染,向每行散布共享 props bag 与全新renderExtras对象,身份比较永远无法命中。会话与次级元数据按值比较,因此滚动只渲染进入视口的行,模型重建只渲染会话发生变化的行。模型把父子会话展平为 occurrence-keyed 行;SessionTreeItem每行渲染renderChildren={false},绝不在共享 scroller 中递归挂载后代。单个 preorder ID 池加索引区间为子树选择提供隐藏后代,而不为每个祖先复制后代数组(见 sessionSidebarRowModel.ts 的indexNodes)。
粘性项目/活动头来自模型头描述符与首个可见虚拟索引:resolveSessionSidebarStickyHeader从描述符列表中取“rowIndex 不超过首个可见索引”的最后一个(sessionSidebarRowModel.ts),使当前与相邻头行保持挂载。CrossfadeZoneHeaders(projects/CrossfadeZoneHeaders.tsx)使用其缓存的虚拟布局偏移做视觉交接:
- 活跃头在虚拟行占位符与原生 scroller 内静止层之间移动时保持一个 portal host,保留其控件与菜单状态;
- 行/头尺寸或虚拟起点变化刷新缓存边界;滚动只比较偏移并在分区交接时改 DOM;
- 离开的头做 inert、accessibility-hidden 快照,在进入的头之上 150ms 淡出;reduced motion 跳过淡出;
- 项目拖拽临时把头送回各自分区而不重挂载控件;重排用包含虚拟定位但排除 sortable transforms 的布局偏移刷新边界,使归位动画不会留下陈旧头位置。
会话树操作、所有权与预取
子树归档/删除
归档或删除会话在每个 surface都带走其整个激活子树,因为服务端不级联time.archived。Recent 与受管 Chats 用 list/sessionCollection.ts 的buildActiveSessionNode构建行;移动会话 sheet 用getDescendantIds在完整激活列表上解析同一谱系。sessions/sessionSubtreeActions.ts 拥有单发/批发 store 调用与全部结果 toast;collectSessionSubtreeIds在动作时用全局激活 + 归档缓存的遍历扩展 surface 自身的后代列表:归档中间节点之下的激活子代理仍被归档(归档跳过已归档中间节点;删除包含它)。把树展平为一层的投影会静默遗留孙代激活。
所有权解析与回退
完整应用的激活记录在其目录不再属于已知拓扑时仍保留在集合中(如已删除的 worktree)。分组先使用精确配置的项目/worktree 所有权;仅当某项目的规范 worktree 映射到已配置项目根时,才回退使用权威 OpenCode 项目元数据。该回退只改显示所有权,不改路由用的会话真实目录。无解析所有者的记录没有保证的项目分组。VS Code 保持精确工作区目录作用域、不使用回退;移动 sheet、Recent 与 Timeline 使用同一所有权解析器,行保留会话自身目录,同时从索引取显示所有权(project id、labels)(见 sessions/sessionOwnership.ts)。
邻近会话预取
list/useSessionPrefetch.ts 实现相邻会话预取,服务“选中会话旁的会话更快可读”的目标;与文档“目录需求只覆盖正在工作的目录”呼应:显示、展开或恢复项目从不引导它;行挂载不得启动引导工作。选择与活动订阅保持会话作用域,结构性列表更新不会让每行都观察无关的流式更新。
跨 surface 差异:Web/桌面、移动端与 VS Code
- Web 与桌面:显示受管 Chats(位于可选 Recent 活动之前,Recent 默认关闭——timeline 视图存在后由显示菜单切换)。Chats 使用共享受管根建文件夹、永不暴露 worktree 动作。项目显示可为全部项目或单选一个项目。
- 移动端:宿主移动端与 Capacitor 使用独立
MobileSessionsSheet渲染器(apps/MobileSessionsSheet.tsx),通过partitionSidebarSessions以同样方式分区:Chats 作为项目树之上的可折叠区段列出,无 Recent 投影。共享目录缓存规则适用,但此侧边栏虚拟化器不适用。 - VS Code:排除 worktree 与受管 Chats,保留工作区作用域的归组列表与行内归档桶。无 timeline 开关。VS Code 在
alwaysShowActions下也在行右缘 hover-reveal 操作,因此其徽章保持淡出(selectRowBadgeVisibilityClass,见 sessions/sessionNodeItemUtils.ts)。
项目动作指示器:目录级终端活动
SidebarTerminalActivity(list/SidebarTerminalActivity.tsx)与动作头、终端面板共享终端发现:一次服务端列表覆盖所有目录(含折叠项目)。侧边栏只在已知有项目动作运行期间保持该循环;无运行中时挂载列表一次(拾取另一客户端启动的运行)后保持安静——空闲侧边栏零轮询。它保留比列表更新的本地变更,失败时保留已知状态。终端发现与 OpenCode 会话引导分离。
DirectoryActionIndicator(sessions/DirectoryActionIndicator.tsx)只读自己目录的终端元数据;输出块与无关目录不会重渲染它。活动项目动作(含自动发现命令)显示status.info中的静态pulse图标;持久化的空闲标签页与普通交互终端不指示活动——这是进程活动而非服务端就绪状态。
分组视图在项目根与 worktree 头上显示图标;扁平项目视图在项目根头与关联 worktree 会话上显示;Recent 在自身目录有活动动作的每个会话上显示;归档桶不显示。指示器留在既有行/头操作 padding 边界内,hover、键盘焦点与 always-visible 操作按钮把它们左移而不隐藏。
命名与交互细节速查
- AI 重命名:会话菜单与头部标签、单会话头共享
SessionAiRenameMenuItem;AI 重命名使用与 worktree 移动相同的前导 spinner,待定操作在关闭菜单或切换会话后仍存活;资格仅在菜单打开时加载。上下文选择与变更守卫见 packages/ui/src/sync/DOCUMENTATION.md 的 AI session titles 一节。 - 手动重命名:输入框与头、移动列表共享 components/session/sessionRenameKeyboard.ts;Enter 在 keydown 时显式提交所属表单,Escape 取消;IME 组合键保持文本输入行为,按住 Enter 不重复提交。
- Run fusion 资格:来自 lib/multirun/identity.ts,仅未标记的遗留会话解析标题;行 memoization 比较同一语义,元数据级成员变化即更新菜单。源选择与 fork 规则见 lib/multirun/DOCUMENTATION.md。
- Recent 成员规则:激活根会话立即进入 Recent 成员,即使其最后提交的
time.updated超出 48 小时窗口;子与归档会话仍排除,非激活根仍基于时间戳。激活 ID 订阅在侧边栏隐藏时禁用并忽略 retry/status 细节变化,避免流式频率重渲染。 - 置顶与文件夹赋值:不从不含置顶/文件夹的首启快照或乐观变更中修剪;确认的本地删除与路由的外部删除立即清理;基线确立后的权威遗漏覆盖错过的外部删除事件。
结语:读源码的地图
回到起点——DOCUMENTATION.md 的目录划分就是这份地图的图例:行为归属决定代码位置,共享契约沉淀在根级types.ts/utils.tsx。调试侧边栏问题时,先问“这是谁的业务对象”:
- 会话行、徽章、动作 →
sessions/; - 列表来源、引导、清理、预取 →
list/; - 分区、粘性头、排序 →
projects/; - Recent/Timeline 行投影与位置 →
recent/+sessionLocation.ts; - 文件夹拖拽/批量 →
folders/; - 行模型与虚拟化契约 →
sessionSidebarRowModel.ts+SessionSidebarRows.tsx。
行为测试散落在各目录(如 SessionSidebarRows.test.ts、SessionTreeItem.behavior.test.tsx、sessionFolderDnd.behavior.test.tsx),与本文描述的规则一一对应,是验证语义最直接的依据。
- AI Agent
- 人工智能
- 代码智能体
- 交互助手
【免费下载链接】openchamber
Agentic Development Environment based on OpenCode AI agent
相关推荐
Apache Maka Session Navigation:桌面端会话侧栏的功能边界设计与实现解析
Apache Maka Session Navigation:桌面端会话侧栏的功能边界设计与实现解析 Session Navigation 是 Apache M
人工智能AI Agent自主智能体工具调用交互助手AI 评测AO Session Rename Surfaces 设计解析:侧边栏与终端会话标签的内联重命名实现指南
AO Session Rename Surfaces 设计解析:侧边栏与终端会话标签的内联重命名实现指南 导读 本文围绕 AO(agent orchestrat
如何在PC上完美运行Switch游戏:yuzu模拟器终极性能优化指南
如何在PC上完美运行Switch游戏:yuzu模拟器终极性能优化指南 想要在电脑上畅玩Switch游戏却遇到卡顿、闪退、兼容性等问题?yuzu模拟器作为目前最强
虚拟化桌面应用图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考