ChatGPT Graph Navigator
仓库链接: https://github.com/Robbings/chatgpt-graph-navigator
希望能够得到您的Star
一、为什么我们需要非线性对话?
相比于Gemini,ChatGPT支持通过修改提问或者回答,产生新的对话分支,并且每一个分支都可以被保存和重新访问。
我个人非常喜欢这个功能。因为复杂问题的解决包含了不断地假设,试错和分支探索 ,而这个功能让多分支的图谱式对话成为可能。
举个栗子,比如搞科研或者开发一个项目的时候让GPT生成方案,它会提供多个选择,针对每一个方案我需要并行地讨论,然后确认如何选择。如果纯线性对话,一方面后续很难定位和复盘,另一方面,假设我最终选定一套方案,其他方案的大量对话就会变成无用的上下文,干扰模型思考和后续的复盘。而方案开始实施后我也会遇到很多问题需要处理,这些问题有些彼此相关,有些彼此无关,这时我也会使用分支功能,每个分支解决一个独立的问题。
所以在进行复杂对话的时候,如果采用线性对话,会有诸多弊端,比如:
- 📉 “上下文污染”问题: 当你在同一个对话流中按顺序尝试不同方案时,无关的上下文和失败的尝试会不断堆积。这种“噪音”不仅消耗 Token 配额,还会干扰模型的注意力,使其难以针对你当前的策略提供最精准的分析。
- 🔀 “并行探索”的刚需: 为了获取最佳结果,你往往需要对对话进行“分叉”——通过修改 Prompt 或重新生成回复来测试不同的路径。在线性界面中,管理这些“平行宇宙”简直是一场灾难。你很容易忘记思路是在哪里分岔的,也记不清哪个分支产出了最佳结果。
- 🧠 逻辑混乱,定位困难: 试图在脑海中复盘 20 分钟前的 Prompt 与刚刚写好的新变体之间的逻辑关系,是一件极度消耗精力的事情。
ChatGPT Graph Navigator 专为解决此问题而生。 我们将你的分支可视化,帮助你隔离上下文以获取更纯净的模型输出,同时让你原本复杂的推理结构变得井井有条。
二、功能介绍
核心能力一览:
- 🎨 两种界面: 选择 侧边栏 (Sidebar) 享受常驻的沉浸式工作流,或使用 悬浮窗 (Floating Window) 进行随叫随到的轻量化查看。
- 👁️ 两个可视化视图:
- 图谱视图 (Graph View): 采用思维导图结构,助你一眼掌握对话“全局”与逻辑脉络。
- 时间线树 (Timeline Tree): 采用 Git 风格的垂直树状图,精准追踪每一次细微的修改与分支。
- ⚡ 导航: 点击任意节点即可 直接跳转 至对应分支的具体消息,瞬间还原历史上下文。
- 🔍 搜索: 在整个对话树中快速定位特定的 Prompt 或 AI 回复,不再迷失在长对话中。
- 🛠️ 实用工具: 内置长消息自动折叠功能,并计划持续集成更多效率工具(如导出、格式化等)。
更多功能展示
1. 侧边栏

侧边栏专为提升效率而生,提供两种模式以契合您的工作流:
a. 图谱模式 (Graph Mode)
掌控结构与上下文跳转的最佳选择。
- 空间掌控: 支持自由缩放与平移,瞬间掌握对话主题的完整拓扑结构。
- 一键跳转: 点击图谱中的任意消息节点,即可 瞬间跳转 到任意分支的任意对话,并立即恢复当时的上下文环境。
b. 时间线模式 (Timeline Mode)
精准定位与内容检索的利器。
- 专注筛选: 信息噪音太多?切换过滤器以显示 全部问答、仅问题 (Prompts) 或 **仅回答 **。非常适合快速回顾您的 Prompt 迭代历史。同时也支持点击跳转。
- 即时搜索: 使用内置搜索栏快速定位关键词,快速定位消息,回车直接跳转。
2. 悬浮窗
- 🚀 拖拽与缩放: 在屏幕任意位置访问完整的图谱/时间线视图。
- 👻 穿透模式: 点击穿透按钮,直接与悬浮窗背后的页面进行交互,互不干扰。
- 📌 固定与融合: 支持窗口 置顶 并自由调节 透明度。
3. 长消息折叠
- 📂 消息自动折叠:长回复/代码块可自动或手动折叠,界面更清爽。
- 支持自定义折叠提问或回答,可以设置折叠阈值,也可以手动折叠。
三、安装指南
🚧 敬请期待: 我们正在进行 Chrome 应用商店的审核流程,审核通过后将第一时间在此更新安装链接!
- 下载: 在Github下载最新的 Release 版本,或者在本贴下载压缩包。
- 打开扩展页: 在浏览器地址栏输入
chrome://extensions/ 并回车。
- 开启开发者模式: 打开页面右上角的“开发者模式”开关。
- 加载: 点击左上角的 “加载未打包的扩展程序” 按钮,选择下载的文件;或者直接将压缩包拖拽进入浏览器。
四、后续计划
现在插件还在快速开发迭代的阶段,后续的计划主要包括:
- 节点和分支的高亮,收藏,分类等功能。
- 更多工具的集成,目前计划添加:对话或者消息级别的导出,其他功能如果需要欢迎issue。
- 图谱编辑: 删除不需要的分支或节点,以及手动编辑图谱结构,比如删除,添加节点间的连线,从而令图谱不再局限于消息结构,让逻辑更加清晰。
- 全局知识图谱: 实现针对项目或者自定义跨对话的更复杂的知识图谱构建和管理。
- 个人知识库的管理: 基于图谱构建个人的知识库,并支持知识库的管理,检索,导出,在对话中导入等功能。
五、写在最后
最后的最后,非常欢迎感兴趣的朋友们试用本插件! 目前项目还处于早期阶段,还有诸多 Bug 和不足,非常欢迎大家在 Issues 里反馈。如果觉得这个小工具对你有帮助,十分希望能得到您的一个 Star,这对我是莫大的鼓励。
如果您有新的想法非常欢迎Fork我们的项目,并提交PR!
最后最后的最后,再贴一下项目中文README的链接,项目和完整的介绍请参见:https://github.com/Robbings/chatgpt-graph-navigator/blob/master/README_ZH.md
附录:关键代码
完整代码已经上传到附件并开源在了github。
[b]#### 1. findScrollContainer - Find Scrollable Ancestor
function findScrollContainer(element) {
let el = element.parentElement;
while (el) {
const style = window.getComputedStyle(el);
const overflowY = style.overflowY;
const isScrollable = (overflowY === 'auto' || overflowY === 'scroll')
&& el.scrollHeight > el.clientHeight;
if (isScrollable) {
return el;
}
el = el.parentElement;
}
return document.documentElement;
}
[b]#### 2. isElementNearViewportCenter - Check Position
function isElementNearViewportCenter(element) {
const rect = element.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const elementCenter = rect.top + rect.height / 2;
const centerZoneTop = viewportHeight * 0.25;
const centerZoneBottom = viewportHeight * 0.75;
return elementCenter >= centerZoneTop && elementCenter <= centerZoneBottom;
}
[b]#### 3. waitForScrollToStop - Detect Scroll End
function waitForScrollToStop(container, timeout = 1500) {
return new Promise((resolve) => {
let lastScrollTop = container.scrollTop;
let stableCount = 0;
const STABLE_THRESHOLD = 3;
const startTime = Date.now();
const check = () => {
const currentScrollTop = container.scrollTop;
if (Math.abs(currentScrollTop - lastScrollTop) < 1) {
stableCount++;
if (stableCount >= STABLE_THRESHOLD) { resolve(); return; }
} else {
stableCount = 0;
lastScrollTop = currentScrollTop;
}
if (Date.now() - startTime > timeout) { resolve(); return; }
requestAnimationFrame(check);
};
requestAnimationFrame(check);
});
}
[b]#### 4. scrollUntilVisible - Main Scroll Logic
async function scrollUntilVisible(element) {
const MAX_ATTEMPTS = 10;
const DOM_WAIT_INITIAL = 100;
const DOM_WAIT_MAX = 1500;
const STUCK_THRESHOLD = 3;
const SCROLL_TOLERANCE = 5;
const scrollContainer = findScrollContainer(element);
let lastScrollTop = scrollContainer.scrollTop;
let domWaitTime = DOM_WAIT_INITIAL;
let stuckCount = 0;
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
// 1. Check if already in center
if (isElementNearViewportCenter(element)) {
highlightElement(element);
return true;
}
// 2. Scroll
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 3. Wait for scroll animation
await waitForScrollToStop(scrollContainer);
// 4. Wait for DOM changes (lazy load)
await waitForDOMChangeOrTimeout(scrollContainer, domWaitTime);
// 5. Detect stuck
const currentScrollTop = scrollContainer.scrollTop;
const scrollDelta = Math.abs(currentScrollTop - lastScrollTop);
if (scrollDelta < SCROLL_TOLERANCE) {
stuckCount++;
domWaitTime = Math.min(Math.round(domWaitTime * 1.5), DOM_WAIT_MAX);
if (stuckCount >= STUCK_THRESHOLD) {
highlightElement(element);
return false;
}
} else {
stuckCount = 0;
domWaitTime = DOM_WAIT_INITIAL;
}
lastScrollTop = currentScrollTop;
}
return false;
}
[b]#### Flow
scrollToMessage(messageId)
│
├─► findMessageElement(messageId) // Find target by data-message-id or data-turn-id
│
└─► scrollUntilVisible(element)
│
├─► findScrollContainer(element) // Find scrollable ancestor
│
└─► Loop (max 10 attempts):
│
├─► isElementNearViewportCenter() → Done if true
│
├─► element.scrollIntoView({ block: 'center' })
│
├─► waitForScrollToStop()
│
├─► waitForDOMChangeOrTimeout() // Handle lazy load
│
└─► Check stuck (scrollDelta < 5px)
│
└─► 3 consecutive stuck → Give up