资讯详情

资讯详情

DeepChat 侧边栏工作区注册:让空工作区可见、可归档、可一键开聊的实现解析

DeepChat 侧边栏工作区注册让空工作区可见、可归档、可一键开聊的实现解析【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat本文基于 DeepChat 仓库的规格文档 docs/features/sidebar-workspace-registration/spec.md 与配套实施计划 docs/features/sidebar-workspace-registration/plan.md 展开深入讲解主侧边栏如何合并 Project工作区投影与 Session会话投影实现空工作区的注册展示、归档确认与空行直达新对话。读完后你将掌握DeepChat 的版本化 Project 快照事件机制、new_environment_preferences表的排序语义、渲染端快照栅栏fencing防陈旧读取的写法以及“路径即身份”的侧边栏合并规则。一、问题背景Session 驱动的侧边栏会“吞掉”新选的工作区DeepChat 的主侧边栏WindowSideBar.vue原本只从 Session 分组出发渲染哪些目录出现过会话才会在 Workspace 区块出现对应的分组行。而 Project 域已经在独立地持久化“用户选中过的目录”environment preferenceprojectStore.environments只会影响已存在分组的排序。这就形成了一个断裂的交互流用户通过目录选择器选中一个目录Project 域持久化并发布活跃 environment该目录下还没有任何非草稿 Session由于侧边栏由 Session 派生列表里没有这一行——操作看起来“失败了”。另一个被明确否决的方案是在行旁渲染全局会话数。规格文档指出EnvironmentSummary.sessionCount是全局持久计数而侧边栏展示的是“当前已加载的分页 搜索命中 agent 过滤 置顶分区”之后的子集。把全局数字贴在过滤列表旁边会让可见的层级关系自相矛盾。二、目标与术语边界规格给出的目标是让用户在“浏览工作区的地方”就能注册工作区在没有第一条 Session 之前活跃的管理工作区就应当可见并且可以从空工作区发起作用域正确的新草稿——同时不改变既有的目录生命周期与 Session 发现行为。文档定义了四个必须区分清楚的术语它们是整个合并视图的地基Managed workspace管理工作区由src/main/project/持有、通过projectStore.environments投影出来的活跃EnvironmentSummary。Chat workspacedefaultChatWorkspacePath指向的内置对话工作区只渲染在专门的 Chat 区块绝不在 Workspace 下重复出现。Session group按projectDir分组的“当前已加载且经过过滤”的 Session。True empty workspace真空工作区sessionCount 0且没有可见 Session 的活跃管理工作区。仅仅因为 agent 过滤、置顶、搜索或分页导致“看起来空了”的分组不算真空。职责划分同样被明确Project 是工作区身份、活跃顺序、状态、存在性与持久计数的唯一事实源Session 是可见会话成员关系的事实源侧边栏只拥有合并后的视图模型文件系统的读写仍归 Workspace 域。三、用户交互设计3.1 布局变化BeforeWorkspace [Group] project-a [] [...] Session A project-b [] [...] Session BAfterWorkspace [Add] [Group] new-project Empty [] [...] project-a [] [...] Session A project-b [] [...] Session B [...] Move to Top Move Up Move Down Move to Bottom ---------------- Archive关键交互约束Add是一个紧凑的folder-plus图标按钮带可访问的 “Add workspace” 名称与 tooltip它在展开的侧边栏中、日期分组和项目分组两种模式下都出现原有的分组切换仍是表头最后一个动作。空行与有内容的行共用同一套 folder 标识与持久化位置空行显示弱化样式的Empty标签而不是误导性的会话计数其“新建对话”按钮始终可见而不是 hover 才出现让下一步操作一目了然。在仓库源码中这些行为已落地于 WindowSideBar.vue表头按钮带有:disabledisAddingWorkspace忙锁并绑定handleAddWorkspace约 L371-L374空行判定由isTrueEmptyWorkspaceGroup(group)在渲染期派生约 L455、L507。3.2 添加工作区流程Add Workspace Flow用户激活Add workspace打开既有的、带类型契约的 Project 目录选择器。动作在结束前保持 disabled避免同一侧边栏打开两个互相竞争的选择器取消不改变任何选中项、搜索词、分组模式或持久化状态成功选中时通过project.selectDirectory注册或重新激活该路径把新激活的路径移动到持久化活跃顺序的顶部不扫描所有已管理路径并等待带版本的 Project 快照侧边栏清空 Session 搜索、确定性地切换到项目分组模式、滚动让新行进入视口并把焦点还给该行该动作不创建草稿也不替换当前 New Thread 的项目选择——注册与开始工作始终是两次显式用户动作。边界情况的收敛规则路径已是活跃状态不产生重复行、不打乱其显式顺序只展示并聚焦既有行路径处于归档/已删除状态走既有 Project 路由重新激活到顶部并清掉生命周期墓碑tombstone路径是内置 Chat 工作区聚焦 Chat 区块而不是创建重复的 Workspace 行选择器或快照失败列表保持原样走既有的破坏性操作通知通道注册成功是持久的即使后续的“揭示/聚焦”步骤失败也不回滚。3.3 归档工作区流程Archive Workspace Flow每个活跃管理工作区都暴露省略号菜单——哪怕它只有单行、或因重排暂时不可用而无法移动移动动作保留既有门槛分隔线之后放置Archive归档打开与目录 Settings 相同的确认契约说明文字声明“Session、消息和真实文件夹都会被保留”确认后调用既有的projectStore.archiveEnvironment(path)并等待精确的 mutation 版本被 Project 快照提交渲染端绝不直接编辑 Session 行或文件系统内容成功归档后零 Session 的工作区从 Workspace 消失有历史的工作区保留为“历史分组”historical group不再参与重排和新建对话失败时保持确认框打开、保持活跃行完整、报告本地化错误请求在途时禁用取消与重复确认。内置 Chat 区块与历史分组永不暴露 ArchiveRestore 与 Remove 仍留在 Settings 中管理。3.4 空工作区流程Empty Workspace Flow激活一个“真空且存在”的工作区主行即发起一次以该路径为显式projectDir的新对话行上常驻的加号按钮执行同一操作侧边栏必须走既有的统一startNewConversation({ projectDir })路径不得直接创建 Session。一次性项目意图one-shot intent的存在是为了保证即使当前活跃 Session 随后关闭agent 默认值或全局默认值也不能覆盖用户点击的工作区DeepChat 延续“首次提交时才持久化 Session”的语义ACP 可以确保其既有的草稿 Session 存在但草稿行仍然被排除在侧边栏和 environment 使用计数之外无论“第一条非草稿 Session”与“Project 快照”谁先到达行都保持同一 path 身份获得 Session、去掉 Empty 标签、进入正常折叠行为。细节约束真空行没有折叠态有内容的行保留点击折叠与既有新建按钮持久计数非零但当前加载子项为空的工作区仍是“有内容行”绝不能把表头点击变成隐式的新对话。四、生命周期与可见性矩阵这张矩阵是验收口径的核心规定了每种状态下侧边栏的唯一正确行为状态侧边栏行为项目分组、无搜索将每个符合条件的活跃管理工作区与已加载 Session 分组合并日期分组日期分组保持不变Add 成功后切换到项目分组以揭示结果Session 搜索激活只显示包含命中 Session 的分组不合成空行Add 成功先清搜索再揭示agent 过滤、仅置顶子项、未加载分页保留活跃工作区行只挂当前可见的 Session真空由持久计数判定活跃且存在展示、可重排、可新建对话、可归档活跃但路径缺失展示不可用指示保留顺序与归档但禁用新建对话已归档且有 Session 历史保留历史分组可被发现不合成空行、不允许新建对话已归档且无 Session 历史从侧边栏隐藏到 Settings 中管理已删除removed永不合成行残留的 ACP Session 发现沿用既有投影但不获得添加/重排/新建入口内置 Chat 路径只渲染在 Chat永不在 Workspace 重复五、业务规则15 条规格文档列出的 15 条业务规则中以下数条是理解实现的关键路径是稳定的分组身份。显示名可以冲突因此合并、折叠、重排、草稿意图一律以 path 为准绝不用名字。活跃顺序以 Project 为准。ProjectService.selectDirectory()在选中使路径“新激活”时执行前置prepend渲染端只消费已提交的顺序不得自行前置插入也不能从 Session 活跃度推导顺序。空行只存在于“项目分组且无 Session 搜索”时搜索保持 Session 搜索语义不悄悄扩展成工作区名搜索。显式 Project 顺序在重复选中时存活重复选中同一活跃路径对成员关系是幂等的只有不存在显式顺序时才可能由回退的“最近使用”改变。缺失、归档、已删除的路径都不能从侧边栏发起新对话。内置 Chat 路径即使出现在活跃 Project 快照中也固定在重排范围之外。第一条 Session 会原地替换空态呈现——同一路径绝不允许出现一条合成行加一条 Session 派生行。渲染端本地乐观态只能揭示模式或焦点变化只有已提交的版本化 Project 快照才能新增、重新激活或移除工作区行。归档的可用性与重排的可用性相互独立单行、搜索、加载或动画期间重排可以禁用活跃行仍可归档。侧边栏归档是既有的、可逆的 Project 生命周期变更必须先确认且不得删除 Session、消息或文件系统内容。Project 快照就绪是 Project store 的状态同一份快照携带内置 Chat 工作区身份因此启动引导与 Project 刷新竞态期间侧边栏无法合成重复行。工作区身份比较只去掉尾部分隔符保留 POSIX 根/与 Windows 盘符根不做大小写折叠也不改写分隔符风格。归档或移除“手动选中的 New Thread 工作区”会使该选择失效下一次草稿不得复用非活跃路径。分组模式持久化是串行化的失败时可见模式回滚到最近一次成功持久化的模式所有调用方都能观察到它所等待的那次写入失败。目录注册把 recent-project 与 active-order 状态写进同一个数据库事务重新激活/新注册用一次 preference 表写入置顶选择已活跃路径则保持其顺序。六、源码级解析主进程的排序与快照版本机制6.1activateAtTop单条 upsert 完成“置顶或保持”排序语义落在 newEnvironmentPreferences.ts 的activateAtTop()L71-L111。表结构为CREATE TABLE IF NOT EXISTS new_environment_preferences ( path TEXT PRIMARY KEY, status TEXT NOT NULL DEFAULT active CHECK (status IN (active, archived, removed)), sort_order INTEGER NOT NULL DEFAULT 2147483647, archived_at INTEGER, removed_at INTEGER, updated_at INTEGER NOT NULL );核心 SQL 是一次带冲突处理的 upsertINSERT INTO new_environment_preferences (...) VALUES (?, active, COALESCE(( SELECT MIN(sort_order) - 1 FROM new_environment_preferences WHERE status active AND sort_order 2147483647 ), 0), NULL, NULL, ?) ON CONFLICT(path) DO UPDATE SET status active, sort_order CASE WHEN new_environment_preferences.status active THEN new_environment_preferences.sort_order -- 已活跃保持原顺序 ELSE excluded.sort_order -- 新激活插入到当前最小显式顺序之前 END, archived_at NULL, removed_at NULL, updated_at excluded.updated_at其中DEFAULT_ENVIRONMENT_SORT_ORDER 2147483647作为“无显式顺序”的哨兵值新路径拿到MIN(sort_order) - 1没有任何显式顺序时退化为0从而实现“一次写入即置顶”已活跃路径则原样保留sort_order同时把archived_at/removed_at墓碑清空——这正是业务规则 4重复选中幂等与“重新激活清除墓碑”的落地。6.2selectDirectory一个事务不带无关 IOProjectService.selectDirectory() 的实现与计划文档完全一致const result await this.deviceService.selectDirectory() if (result.canceled || result.filePaths.length 0) { return { path: null, version: this.snapshotVersion } } // ... this.sqlitePresenter.getDatabase().transaction(() { this.sqlitePresenter.newProjectsTable.upsert(dirPath, dirName) this.sqlitePresenter.newEnvironmentPreferencesTable.activateAtTop(dirPath) })() const version this.bumpSnapshotVersion() return { path: dirPath, version }三个要点取消直接返回当前版本无副作用recent-project upsert 与 preference 激活在一个事务内完成且不调用getEnvironments()、不做任何针对无关路径的同步存在性检查约束条款要求目录选择路径保持轻量最后自增并持久化快照版本号bumpSnapshotVersion()把projectSnapshotVersion写入 settings返回精确版本供调用方等待。6.3 版本化快照与 stale-read fencingProjectService.getSnapshot()index.ts把 preferences、new_environments使用量与 new_projects 联合投影为单一快照由于paths集合是 usage 表与 preference 表的并集一个零 Session 的选中路径也会以sessionCount: 0出现在environments中——这是空行得以存在的持久化前提。快照还携带defaultChatWorkspacePath因此侧边栏不需要维护第二个本地就绪标志。活跃环境的排序比较器compareEnvironmentSummaries()同样编码了顺序语义L353-L375显式顺序sortOrder DEFAULT_ENVIRONMENT_SORT_ORDER优先其次按lastUsedAt降序最后按路径字典序兜底。路由层 routes.ts 中project.selectDirectory在成功时发布publishEnvironmentsChanged(select, path, version)project.archiveEnvironment则把服务层返回的精确版本原样带回渲染端L98-L106这支撑了“对话框必须等这个版本提交后才能关闭”的契约。契约本身定义在 project.routes.tsprojectSelectDirectoryRoute输出{ path: string | null, version }projectArchiveEnvironmentRoute输出{ updated: boolean, version }均为 zod 强类型校验。渲染端的栅栏在 stores/ui/project.tsrefreshProjectSnapshot(minVersion)L139-L176是唯一刷新负责人用requestedSnapshotVersion单调抬升目标版本do/while循环里若读到的快照版本落后于目标就重试若事件先于其快照投影到达读到“成功但不完整”的旧版本不空转、也不发布陈旧失败保留最后提交状态等待后续刷新——即计划文档所说的“事件与路由响应在同一 owner 下合并”requireProjectSnapshot(version)等待精确版本提交未提交则抛错openFolderPicker(options)L307-L348正是计划中新增的select选项默认select: true保持 New Thread 语义select: false时只注册不改变selectedProjectPath快照校验失败会回滚已发生的选中并重新抛出让调用方能通知用户——“返回null仅表示用户取消了原生选择器”。archiveEnvironment(path)L256-L275还做了一层投影断言提交后若该路径既不在environments也不在archivedEnvironments中抛出 “Archived environment is missing from the committed project snapshot”保证确认框只在精确版本提交且生命周期投影为 archived 后才可关闭。七、侧边栏视图模型合并投影与揭示策略7.1 本地合并类型合并类型刻意保留在WindowSideBar.vue内部除非测试证明存在第二个真实消费者type SidebarWorkspaceGroup SessionGroup { environment?: EnvironmentSummary }真空判定、生命周期、可用性与动作状态都在渲染期从这个可选environment派生不进入共享契约。7.2 项目模式合并算法7 步像今天一样构建过滤后的 Session 分组搜索激活时只保留至少含一条命中 Session 的分组在合并前先摘出 Chat/无项目分组为 active、archived、removed 三类 Project 摘要建立路径身份映射比较时去掉普通路径的尾部分隔符、保留 POSIX 与 Windows 盘符根不在渲染端做跨平台大小写折叠或分隔符改写身份比较复用 src/shared/utils/filesystem.ts 的normalizeWorkspacePath见 project.ts 的导入当 Project 元数据就绪且 Session 搜索为空时按持久顺序遍历活跃 environments跳过defaultChatWorkspacePath挂上匹配的可见 Session 分组或[]零 Session 行用environment.name命名仅当sessionCount 0 sessions.length 0才置isTrueEmpty仅对“活跃且存在”的路径置canStartConversation按 path 标记已被消费的 Session 分组再把剩余 Session 派生分组按既有稳定顺序追加把已知的 archived/removed 路径归类为 historical 并禁用重排/新建以 Project store 的“已提交快照就绪”作为唯一元数据就绪信号未就绪时回退到当前纯 Session 渲染并保持重排禁用——绝不为失败的快照捏造活跃元数据Session 搜索期间不合成“无匹配子项”的活跃行既有活跃匹配仍走 Project 顺序历史匹配排在活跃匹配之后。日期分组保持纯 Session 语义Add 成功处理器先切换到项目分组再尝试聚焦结果行。7.3 排序与拖拽按 Project 顺序遍历活跃 environments而不是对 Session 派生子集重新排序Chat 工作区留在可拖拽列表之外发送完整重排载荷时保持其隐藏位置真空行与“活跃但缺失”行都参与活跃重排历史分组永远排在活跃分组之后且不参与重排Session 搜索、初始加载、置顶动画、拖拽期间重排保持禁用与既有行为一致折叠态以 path 为键在重排与“空→有内容”转换中保持不丢失。7.4 头动作的九步实现契约handleAddWorkspace的完整时序① 不捕获任何选择器之前的分组/选中假设② 调用projectStore.openFolderPicker({ select: false })③ 返回null则无任何副作用地返回④ 成功后清空sessionSearchQuery⑤ 等待sessionStore.setGroupMode(project)⑥ 等待响应式渲染、按 path 找到行并scrollIntoView({ block: nearest })⑦ 聚焦该行并施加一个简短、对减少动态设置友好的揭示态⑧ 若路径是defaultChatWorkspacePath改为揭示/聚焦 Chat⑨ 选择器、快照或分组失败都发通知且无论如何释放忙锁。其中setGroupMode(mode)被设计为幂等的确定性动作toggleGroupMode()只是它的 UI 包装器模态选择器之后不能用“选择器打开前的陈旧状态”去做 toggle否则用户或另一个窗口在弹窗期间改过设置时就会切错方向写请求串行化、单独记录“最近成功持久化的模式”、同目标调用方复用同一在途 Promise失败的写入只把最近一次可见请求回滚到最后成功持久化的模式且合并进同一写入的调用方会观察到真实失败而非假成功。7.5 行状态与安全覆盖真空且存在省略aria-expanded主行激活与常驻加号都调用handleNewChatForProject(path)有内容的活跃行保留点击折叠与 hover/focus 加号活跃但缺失显示警告图标与本地化的“不可用”tooltip加号隐藏/禁用行激活不解释为草稿请求历史分组保留折叠与 Session 访问隐藏新建与重排入口省略号菜单对所有活跃管理分组可见与重排门槛解耦移动项经canMoveProjectGroup保持禁用Archive 放在分隔线之后安全覆盖当 Session 事件先于更新后的 Project 快照到达时用sessions.length 0作为覆盖条件防止暂时陈旧的sessionCount 0把“有内容的表头”误变成新建对话动作——这保证了 Session 先到与 Project 先到两种事件顺序都收敛到同一行。八、边界与兼容性矩阵计划文档枚举的边界场景中几个值得注意的实现结果重复活跃选择一行path 键控显式顺序存活聚焦既有行选择归档/已删除路径activateAtTop清掉生命周期状态选择变更把重新激活的路径放到第一Remove 期间被清空的常规 Session 保持未分配状态选择默认 Chat不产生 Workspace 重复行揭示 Chat注册后路径缺失已提交的活跃行保持可见并处于不可用态新建对话在路径重新存在前被阻断但归档仍可用单一活跃工作区归档可用四个移动项全部禁用分页活跃表头可以早于其 Session 页渲染滚动加载仍由既有容器驱动表头不得触发重复 Session 抓取多窗口每个渲染端都收到版本化 Project 事件但只有发起侧边栏改变自己的本地搜索、分组、滚动与焦点。九、约束与非目标约束保证改动面最小原生目录选择保持在既有带类型的 Project 路由与上下文隔离的 bridge 之后不新增数据库表或 schema 迁移——当前 environment preferences 已经能持久化零 Session 的选中路径表中new_environment_preferences早在 schema 版本 32 即存在见 newEnvironmentPreferences.ts保留版本化 Project 快照/事件的 owner 与其 stale-read fencing目录选择路径上不做针对无关路径的同步存在性检查使用 vue-i18n 与既有DcButton、tooltip、通知、下拉、拖拽原语保留侧边栏既有分页、滚动恢复、快捷键徽标、置顶动画与拖拽门槛。非目标不在过滤/分页侧边栏中展示全局 Session 数不绕过原生选择器创建文件系统目录不在选择目录后自动创建对话不在 Workspace 表头加生命周期管理也不给行加 Restore、Remove、重命名、默认工作区或批量归档不搜索工作区名、不做嵌套层级或批量操作不改动 Chat 专区块与 Settings 的目录管理面。十、测试策略与验证测试策略分五层与仓库的 Vitest 组织方式对应Project Store选择器取消返回null且不改变选中/快照状态侧边栏注册模式select: false返回路径但不改selectedProjectPathNew Thread 默认模式仍选中路径事件先于路由响应到达时合并到所请求的快照版本失败在设置 store error 后重新抛出重复/归档/已删除选择收敛到同一活跃快照行。Session StoresetGroupMode(project)幂等且只持久化一次并发 set/toggle 串行化且最后请求的模式胜出持久化失败只回滚对应请求连续失败写回滚到最后持久化的模式同目标调用方观察到在途失败。Sidebar 组件头动作的可访问性/忙锁/取消/失败/成功从日期模式与激活搜索中成功后揭示并聚焦所选行零 Session 活跃环境渲染而无 Session 派生分组默认 Chat 不重复精确 path 身份保留同名工作区搜索隐藏空行agent 过滤/置顶/分页不把非空路径误标为空空行与加号派发精确的一次性projectDir流缺失活跃与历史分组不能新建对话空分组参与持久化重排单活跃分组仍可归档而移动项禁用归档确认派发精确路径、防重复提交、失败保留行/对话框归档行按 Session 历史收敛为隐藏或有内容的历史呈现Session 先到与 Project 先到都产生唯一有内容行并保持折叠身份/与 Windows 盘符根保持为工作区身份。Project 主进程选择目录不做环境投影或无关存在性检查recent 与 preference 激活在同一事务新/重激活路径排最前而重复选择保持显式顺序归档响应暴露精确的已提交版本。集成零使用量的选中目录以sessionCount: 0出现在活跃快照重复选择不产生重复行也不重置显式顺序首个常规/ACP 非草稿 Session 推进环境投影渲染层集成测试覆盖事件驱动的空行出现且不重挂侧边栏。验证命令交接前执行pnpm run format pnpm run i18n pnpm run lint pnpm run typecheck外加在至少 Windows 与一个 POSIX 平台上手工验证原生选择器取消/成功、键盘焦点、从日期/搜索模式添加、重复选择、缺失路径恢复、活跃 Session 拆除以及 DeepChat/ACP 的首条 Session 转换。十一、回滚策略该特性不引入任何 schema 或持久化的工作区实体移除表头动作与合并的渲染端投影即可恢复旧 UI特性启用期间选中的目录仍作为 Settings 与 New Thread 中的合法管理工作区存在其最新持久化顺序原样保留。小结DeepChat 的侧边栏工作区注册是一个典型的“投影合并”问题与其修改 Session 发现逻辑不如让侧边栏同时消费两个事实源——版本化的 Project 快照提供身份、顺序与生命周期Session store 提供可见成员——并以“path 即身份 精确版本栅栏”保证任何事件顺序下都收敛到唯一、正确的行呈现。核心落地点包括new_environment_preferences 表 的单 upsert 置顶语义、ProjectService 的事务化selectDirectory与版本化快照、Project store 的单 owner 刷新与select选项、以及 WindowSideBar.vue 的渲染期派生判定。全程零新表、零新路由、零新 i18n 键边界与回滚成本极低。【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →