您的浏览器会记住您打开过的每一个页面,但它并不了解您为何要打开这些页面。
您可能会花费三天时间,在十几个标签页中对比各种笔记本电脑的信息,然后分心离开。一周后回来时,浏览历史记录中只会显示一系列时间戳和页面标题,而完全看不出这些访问行为其实是与某个特定目标相关的、您开始但尚未完成的一系列操作。
在这个教程中,您将制作出openloops——这款开源的、以本地数据为主处理的Chrome扩展程序。它通过扫描您的浏览历史记录,并将其分类为“意图主题”来解决这个问题。这些“意图主题”指的是那些您反复访问、与之相关联的决策、研究内容或未解决的问题。此外,该扩展程序还会根据每个主题的活跃程度对其进行评分。另外,它还可以利用Claude技术用通俗的语言对这些主题进行标注,建议下一步应该采取什么行动,甚至还能为您提供一个聊天助手,让您能够询问“这周我应该关闭哪些页面?”
完成这个教程后,您将掌握以下技能:
-
一款基于Manifest V3规范的Chrome扩展程序,其中包含服务工作者功能以及用于展示所有浏览信息的界面。
-
一个本地处理流程,能够完全利用IndexedDB数据库来捕获、清洗、整理并分类您的浏览历史记录。
-
一种经过优化和调试的聚类算法,可以有效地处理实际中的复杂浏览数据。
-
一个利用Claude技术实现的人工智能标注系统,其中还包含了从context.dev获取品牌相关数据的步骤。
-
一个设计精良的界面,其中包含入门指南、设计规范以及用于显示处理流程状态的工具。
一个能够分析您所有的浏览记录并告诉您下一步该做什么的聊天助手。
所有这些功能都在设备本地运行,而唯一需要通过网络进行的数据传输也是可选的,并且您可以使用自己的API密钥来控制这些传输操作。
目录
你将构建什么
在首次运行时,openloops会以一个居中的欢迎界面来引导你完成三个步骤:

一旦你完成了扫描使用记录、创建会话以及构建意图图这些步骤,你的浏览内容将会被重新组织成按状态分类的线程:活动中的、暂停的以及休眠的。每个线程都会显示一个置信度评分、简洁的说明、下一步该执行的操作,以及一个继续按钮,点击该按钮可以重新打开你之前停下的页面。右侧列还会显示一个基于你自身浏览记录的聊天助手:

这个聊天助手会综合分析用户实际的浏览记录,根据这些记录的完成难度以及还需要做出多少决策来对它们进行排序。同时,它还会解释为什么需要采取某些行动——这是该功能中最新颖的部分,其具体实现方式取决于你在本教程后续环节中添加的context.dev相关设置。
先决条件
要顺利完成这些步骤,你需要具备以下条件:
-
Node 18+版本及以上的操作系统,以及基于Chromium浏览器的软件(如Chrome、Brave、Edge等)。
-
需要对TypeScript和React有一定的了解。你不需要成为这些技术的专家,但应该能够熟练阅读与它们相关的代码以及理解async/await等概念。
-
对IndexedDB有基本的了解会很有帮助,但并不是必须的——因为在学习过程中你会逐渐掌握相关知识。
本教程中的两个部分是可选的,使用它们需要你自己的API密钥,不过这两个部分都提供免费试用版本:
-
一个Anthropic API密钥(来自platform.claude.com),用于AI标签生成和聊天助手功能。
-
一个context.dev API密钥(来自context.dev),用于品牌相关功能的实现。
即使没有这些API密钥,你也可以构建并使用整个核心流程,包括数据捕获、聚类分析以及评分等功能——因为这两个API密钥只是为某些高级功能提供的额外支持而已。
openloops的架构结构
在开始编写任何代码之前,了解整个系统的架构结构会很有帮助。openloops的每一个阶段都会从同一个IndexedDB存储中读取数据,并将处理后的结果写入到下一个阶段:
chrome.history (backfill) ──┐
chrome.tabs.onUpdated (live)─┴─→ raw_events
│ 噪音过滤
▼
sessions
│ 环境检测 + 聚类分析 + 评分
▼
intent_threads
│
▼
React控制面板
│ 可选,需主动启用
├──→ 品牌信息补充 (context.dev)
└──→ AI标签生成 + 后续操作 (Claude)
│
▼ 可选,需主动启用
AI聊天助手 (Claude)
每个阶段都是`src/pipeline/`目录下的独立模块,且都可以被单独查看:你可以打开Chrome开发者工具,在“应用程序”标签页中直接查看`raw_events`、`sessions`或`intent_threads`这些数据,而且可以重新构建其中任何一个阶段,而不会影响到其他阶段。
共享类型
每个阶段都会使用并生成相同的一组TypeScript接口,这些接口在`src/types.ts`文件中定义了一次:
// openloops处理流程中使用的共享TypeScript接口。
// 流程中的每一个阶段都会使用并生成这些类型。
export interface RawEvent {
id: string;
url: string;
domain: string;
title: string;
visitedAt: number; // 以毫秒为单位的时间戳
source: "backfill" | "live";
}
export interface Session {
id: string;
events: RawEvent[];
startedAt: number;
endedAt: number;
domains: string[];
keywords: string[];
}
export interface IntentThread {
id: string;
title: string;
summary?: string;
nextStep?: string; // 用于推动流程前进的具体操作
sessions: Session[];
type: "buying" | "research" | "planning" | "learning" | "unclassified";
confidence: number; // 0-1之间的数值
status: "active" | "stalled" | "dormant";
firstSeen: number;
lastSeen: number;
distinctDays: number;
signals: string[];
}
export interface Brand {
domain: string;
name: string;
description: string;
industry: string;
logoUrl: string;
brandColor: string;
}
在`IntentThread`结构中,`confidence`、`status`、`signals`以及`distinctDays`这些字段的值,会在本指南后面的部分通过本地算法来填充;而`summary`和`nextStep`这两个字段则会在后续的AI标注步骤中才会被赋值。
正是这种设计模式使得整个项目能够正常运行:核心数据模型本身就可以独立使用,而AI技术则为这些数据模型增添了更多的功能。
清单文件
openloops是一个符合Manifest V3规范的扩展程序,它拥有三项权限以及三项主机相关权限:
{
"manifest_version": 3,
"name": "openloops",
"version": "0.0.1",
"description": "将你的浏览历史重新整理成一份由AI标注的意图线程清单:包括正在进行的操作、停滞的研究内容以及未解决的问题。所有处理过程都在本地完成。",
"permissions": ["history", "tabs", "storage"],
"host_permissions": [
"https://api.anthropic.com/*",
"https://api.context.dev/*",
"https://logos.context.dev/*"
],
"background": {
"service_worker": "src/background.ts",
"type": "module"
},
"options_page": "src/dashboard/index.html",
"icons": {
"16": "icons/icon16.png",
"32": "icons'icon32.png",
"48": "icons ICON48.png",
"128": "icons'icon128.png"
},
"action": {
"default_title": "openloops",
"default_icon": {
"16": "icons/icon16.png",
"32": "icons'icon32.png"
}
}
}
这些权限、主机权限以及options_page设置各自具有不同的作用:
-
permissions: ["history", "tabs", "storage"]是核心流程所必需的权限。history用于读取用户的浏览历史记录,tabs使服务工作者能够监控新页面的加载情况,并允许“恢复”功能重新打开标签页,而storage则用于存储API密钥和用户偏好设置。 -
host_permissions是另外一组权限,只有在使用可选的AI功能时才会需要它们。这些权限使得仪表板能够正常地向Anthropic和context.dev发送fetch()请求,而不会遇到CORS错误。 -
options_page用于指定仪表板的显示位置。如果将其设置为这个值,而不是使用default_popup,那么点击工具栏图标时,仪表板会以完整的浏览器标签页的形式打开,而不会以弹窗的形式出现。这对于那些具有多列布局、状态分组卡片以及聊天功能的仪表板来说非常重要。
如何构建扩展程序
首先使用Vite和CRXJS插件,该插件能够生成支持热模块重载的Manifest V3格式扩展程序:
npm create vite@latest openloops -- --template react-ts
cd openloops
npm install @crxjs/vite-plugin idb react-markdown
你的vite.config.ts文件会将CRXJS插件与manifest.json文件连接起来,之后Vite会负责将src/background.ts文件编译成Chrome能够加载的真正的.js文件。如果我在manifest文件中直接使用原始的.ts文件路径,注册扩展程序时会遇到错误,我们会在下一节中解决这个问题。
仪表板的入口点是一个标准的React 18根组件:
<!DOCTYPE html>
<html lang="en">
openloops
</div>