开发 AI 应用的无敌配方,半小时手搓学英语利器

💰 点进来就是赚到知识点!本文手把手教你用 Web 和 AI 去实现灵感和创意点赞收藏评论更能促进消化吸收!

🚀 想解锁更多 Web AI 的强大能力吗?快来订阅专栏「Web AI 进化录」!

为什么要做一款解词插件

需求场景

我最近在学英语,会强迫自己读一些英文资料,比如 GitHub 上的文档、X 上的资讯、arxiv 上的论文等等,但我词汇量太少,所以常常遇到生词。我先后尝试了好多翻译工具,有道词典、Chrome 自带翻译、MacOS 系统词典、沉浸式翻译插件...... 这些工具要么需要中断阅读、切换到其他界面;要么翻译得不够贴切,需要我自己去适配当前的语境。

所以后来我发现,我需要的是一个能够原地给出解释,而且是结合语境用英文解释的工具,这样就能保持专注,不被打断;用英文解释也是很多学英语方法里提到的,用简单基础的词汇去理解高级词汇,咱们中国老话也说,原汤化原食嘛。

于是我的需求已经很明确了,这时候我有两个选择,一个是去找市面上符合我需求的工具,另一个就是遵循咱们程序员的本能,自己造一个轮子。我简单评估了一下,发现自己开发一个会更快,因为我手握两件法宝。

两件法宝

Web 技术和 Chrome 内置 AI,这两样法宝都是灵活强大而简单好用,我甚至感觉这两种技术就是为了帮我学英语而发明的,因为太水到渠成了------

首先我看英文内容基本都是在浏览器里看在线文档,所以把解词工具做成浏览器扩展是再适合不过的,而开发浏览器扩展用 Web 技术就够了。

其次是解词功能,我们很容易想到用大语言模型来做。而 Prompt API 就是 Chrome 浏览器内置大模型的配套 API,让你连服务端代码和第三方 API 都省了,非常方便。

效果演示

这个工具我已经做出来投入使用了,下面我来给大家演示一下效果。

假设我现在正在读一些英文资料,读着读着遇到一个不懂的词,这时候我只需要打开解词工具,然后双击选中生词,解词工具会自动捕获选中的词,而且会抓取这个词的上下文,然后用小学生都能看懂的英文给出解释。全程只需要几秒钟,我就可以继续阅读了。怎么样,是不是很丝滑?

这个插件的完整代码我也放到 GitHub 上了,大家可以把代码拉下来自己试着玩玩。

代码仓库里有两个文件夹,full-version 是刚刚演示的扩展;template 是我为咱们稍后代码实操准备的模板。

本地安装

由于这个扩展还没有上架 Chrome Web Store,所以我们可以在本地导入源码进行安装。

首先我们打开 Chrome 浏览器(最好是最新版),然后访问 chrome://extensions

接着,在右上角打开开发者模式,再到左上角点击导入按钮,选中本地代码仓库的 full-version 目录,这样就安装好了,你会看到扩展列表里这个橘黄色的 icon。

点击 icon 会打开侧边栏,因为大家是首次运行,所以可能会看到这几种状态:

如果是红色的不可用,那你可能需要换其他电脑试试;如果是黄色的下载进度,那就等模型下载完。如果是绿色,那就万事俱备了。

什么是 Prompt API

今年六月份开始,Google 开始在 Chrome 里装载 Gemini Nano 这个大模型。

Gemini 是 Google 推出的多模态模型家族,前不久发布的 Gemini 2 效果非常惊艳。而 Nano 则是家族中的轻量模型,参量有 1.8B 和 3.25B 两个版本,非常小巧,适合用在部署在用户端设备上,也就是端侧智能的概念。而把 Nano 部署到 Chrome 里,让 AI 本地化又迈进了一大步。

有了 Gemini Nano 和其他的一些专家模型,内置 AI 不仅可以给浏览器提供推理能力,而且还有配套的 API,让开发者用简洁的 JavaScript 代码就能把 AI 能力装载到自己的 Web 应用中去。

内置 AI 目前还处于实验阶段,对开发者和少部分用户开放了内测。这是目前支持的 API 的推进进展

我们看到它在网页和浏览器扩展中都可以使用。其中 EPP 指的是 Early Preview Program,是一种面向开发者的内测群;而 OT 则是 Origin Trials,是一种面向用户开启实验功能的方式。我们今天演示的扩展就是用了 OT token。

在这么多处理特定任务的 API 中,我们今天用到和着重介绍的就是 Prompt API

Prompt 意思是提示词,我们和大模型交互,就是通过提示词的形式,而 Prompt API 就是让我们能用自然语言和大模型来交互。比如我们可以基于 Prompt API 开发 Chatbot,用 topK、temperature 等参数去调控生成内容,也可以通过限定系统提示词、角色、示例,来完成特定任务。

这套 API 提供两个方法:

  • capabilities() 方法来检测可用性;

  • create() 方法来创建会话 session,我们与模型的交互就是和 session 交互。create 方法接收一些配置参数,让我们可以定制角色、初始状态(initialPrompt、systemPrompt、topK、temperature),还能对过程进行控制(monitor、signal)。

创建出的 session 有一些 token 相关的属性和方法,有两个接收提示词、生成文本的方法,有复制和销毁的方法。

看起来有点复杂,但我们今天只需要用到图中白色高亮的这几个,就足够了。

从零搭建

现在正式进入动手写代码环节。

当然我们不会一个字符一个字符地手搓代码。在 GitHub 仓库中,我把核心逻辑拆解成了一个个模块。我们会以搭积木的形式把解词插件拼装起来,在拼装过程中拆解 Prompt API 的应用技巧。

我们还是打开刚刚 pull 到本地的代码仓库,进入 template 目录。在这里你会看到一个浏览器扩展的模板结构:

  • manifest.json:配置文件
  • background.js:浏览器层面的高级逻辑
  • side-panel/html、js:侧边栏页面的代码文件

manifest.json 中,有两个字段需要我们特别注意一下。

key

key 是每个浏览器扩展的唯一 ID。但更常见的是一个短字符串的形式,比如我们打开 chrome://extensions 就可以看到每个扩展都有一个短 ID。但由于我们是通过导入文件夹的形式安装的,所以这个 ID 在你电脑上和在我电脑上是不一样的。为了把这个 ID 固定住,我们可以把代码文件夹打一个压缩包,上传到浏览器扩展的开发者后台,就会生成 key 这个身份标识。把 key 写到 manifest.json 中,就把 ID 固定住了,在你们电脑上和在我电脑上的 ID 就保持一致了。

那让 ID 保持一致有什么用呢?这就要说到第二个字段 trial_tokens 了。

trial_tokens

这个 token 能够让浏览器开启实验性功能。为了得到这个 token,我们需要登录 Google 的 OT 平台,用浏览器扩展的 ID 注册 Prompt API。

所以大家明白了吧,如果不固定住 ID,那么我注册好的 token 在你电脑上就会失效。

如果大家发布自己的浏览器扩展,需要自己去注册 ID 和 token。

搭建步骤 1:UI

我们先来实现「点击 icon 打开侧边栏」这个功能。

background.js 中添加以下代码:

JavaScript 复制代码
// 点击 icon 打开或关闭侧边栏
chrome.sidePanel
    .setPanelBehavior({ openPanelOnActionClick: true })

然后,我们给解词插件添加一个最简的 UI 界面。

side-panel/index.html<body> 标签中添加如下代码:

html 复制代码
<div class="label">输入</div>
<div class="input-box">
    <textarea id="input-area"></textarea>
</div>

<div class="label">输出</div>
<div class="output-box">
    <textarea id="output-area" rows="10"></textarea>
</div>

side-panel/index.js 中添加如下代码:

JavaScript 复制代码
const inputArea = document.getElementById('input-area');
const outputArea = document.getElementById('output-area');

保存后,我们仍然是通过本地导入的方式安装扩展,安装后你会看到一个亮绿色的 icon:

点击 icon,你会看到 Chrome 右侧展开了侧边栏区域:

自动捕获选中文本

接着,我们让侧边栏能够自动捕获到用户在左侧页面选中的文本。

background.js 中添加如下代码:

JavaScript 复制代码
chrome.tabs.onActivated.addListener((activeInfo) => {
    chrome.tabs.get(activeInfo.tabId, tab => {
        if (tab.url?.startsWith('chrome://')) return;
        // 给左侧页面注入脚本
        chrome.scripting.executeScript({
            target: { tabId: activeInfo.tabId },
            function: listenSelection,
        });
    });
});

function listenSelection() {
    document.addEventListener('selectionchange', () => {
        const selection = window.getSelection();
        if (!selection?.toString?.()) return;
        
        const selectedText = selection.toString().trim();
        
        // 获取选中文本所在的元素
        const container = selection.anchorNode?.parentElement;
        // 获取上下文
        const fullText = container?.textContent || '';
        let beforeText = '';
        let afterText = '';
        if (fullText) {
            const selectionStart = fullText?.indexOf?.(selectedText);
        
            if (selectionStart === -1) return;
            // 向前向后各取 20 个单词
            const halfLength = 20;
            beforeText = fullText.slice(0, selectionStart).split(/\s+/).slice(-halfLength).join(' ');
            afterText = fullText.slice(selectionStart + selectedText.length).split(/\s+/).slice(0, halfLength).join(' ');
        };
        
        // 把选中的文本和上下文发送给 extension
        chrome.runtime.sendMessage({ 
            type: 'textSelected',
            text: selectedText,
            context: {
                before: beforeText,
                after: afterText
            }
        });
    });
}

这段代码在侧边栏打开时向左侧页面注入了 listenSelection 这段逻辑,用来监听文本选中事件。当用户选中一段文本,注入脚本会自动抓取文本和前后的上下文,然后通过消息机制发送给侧边栏。

sendMessage 中,我们自定义了一个 textSelected 消息类型,接着,我们在侧边栏接收这个消息。

侧边栏接收消息

side-panel/index.js 中添加如下代码:

JavaScript 复制代码
chrome.runtime.onMessage.addListener(message => {
    if (message.type === 'textSelected') {
        const { text, context } = message;
        inputArea.value = text;
        explain(text, context);
    }
});

在这段代码中,我们以 textSelected 为暗号,取得了注入脚本发来的密电,从中提取了数据,显示在 UI 的输入框内。

保存代码并刷新扩展,你会看到自动捕获的效果。

检测 API 是否可用

由于内置 AI 还是实验性功能,并非所有的浏览器都支持,所以我们要检测可用性,保证优雅降级。

side-panel/index.js 中添加如下代码:

JavaScript 复制代码
await check();

async function check() {
    if (!chrome?.aiOriginTrial?.languageModel?.capabilities) return Promise.reject('no capabilities');
    
    const capabilities = await chrome.aiOriginTrial.languageModel.capabilities();
    const { available } = capabilities;
    console.log('available', available); // no, after-download, readily
    
    if (available === 'no') return Promise.reject('no available');
    return available;
}

在上述代码中,我们先判断了 API 在用户运行环境中是否存在;如果存在,再去调用 capabilities() 方法,方法返回的 available 有三种取值:

  • no:不可用
  • after-download:需要等模型下载完
  • readily:可用

创建 session

如果一切顺利,我们就可以开始和内置大模型交互了。我们首先来创建一个会话。

side-panel/index.js 中添加如下代码:

JavaScript 复制代码
let session = null;

await createSession();

async function createSession() {
    const config = {
        initialPrompts: [
            {
                role: 'system',
                content: `You are a language expert who explains words and phrases in their proper context. When given a text and its surrounding context, explain the meaning while considering how the context affects the interpretation. If there is no context, explain the word or phrase in isolation. Use simple terms, relatable examples, and engaging analogies. If you don't understand the content, say so and provide suggestions for finding the answer. Format output in markdown and keep responses under 50 words.`
            }
        ],
    }
    session = await chrome.aiOriginTrial.languageModel.create(config);
}

我们首先需要在 initialPrompts 中设置系统提示词,然后把配置参数传入 create(),这样就生成了一个专门用来解词的 session。

生成解释

side-panel/index.js 中添加如下代码:

JavaScript 复制代码
async function explain(input, context) {
    if (!input) return;
    const prompt = `
        Here is the text that needs to be explained:
        ${input}

        Here is the context where this text appears:
        ${context.before} [${input}] ${context.after}

        Please explain the text above using simple terms, relatable examples, and engaging analogies. Keep your explanation under 30 words and format the response in plain text, not markdown.
    `
    const stream = await session.promptStreaming(prompt);

    for await (const chunk of stream) {
        outputArea.textContent = chunk;
    }
}

这里我们实现了 explain() 方法,我们在「侧边栏接收消息」的部分调用了它。

我们首先组装了一下提示词,把捕获到的词语和上下文嵌入到提示词,告诉大模型如何处理。

然后我们把提示词传入 promptStreaming() 方法,大模型会以数据流的形式输出词语解释,显示在输出文本框中。

保存后刷新扩展,在左侧页面选中文字后,侧边栏会自动生成解释,并显示在输出区域。

于是,我们成功地开发出了基于内置 AI 的浏览器扩展!撒花!

结语

今天我们实践了 Chrome 内置的 Prompt API,用它来开发了一款 AI 解词插件。Web 技术和内置 AI 联手,让开发者能以最低成本、最高效率实现自己的灵感和创意,大家快快玩起来!

📣 我是 Jax,在畅游 Web 技术海

相关推荐
A XMan.8 分钟前
JSON结构快捷转XML结构API集成指南
xml·java·前端·json·php
小林爱14 分钟前
【Compose multiplatform教程06】用IDEA编译Compose Multiplatform常见问题
android·java·前端·kotlin·intellij-idea·compose·多平台
蜗牛快跑2133 小时前
前端正在被“锈”化
前端·代码规范
Jet_closer_burning5 小时前
微信小程序中遇到过的问题
前端·微信小程序·小程序
掘金酱6 小时前
稀土掘金社区2024年度影响力榜单正式公布
android·前端·后端
Keven__Java6 小时前
Java开发-后端请求成功,前端显示失败
java·开发语言·前端
轻口味7 小时前
【每日学点鸿蒙知识】渐变效果、Web组件注册对象报错、深拷贝list、loadContent数据共享、半屏弹窗
前端·list·harmonyos
老K(郭云开)7 小时前
最新版Chrome浏览器加载ActiveX控件技术——alWebPlugin中间件V2.0.28-迎春版发布
前端·chrome·中间件
轻口味7 小时前
【每日学点鸿蒙知识】子窗口方向、RichEdit不居中、本地资源缓存给web、Json转对象丢失方法、监听状态变量数组中内容改变
前端·缓存·harmonyos
我是苏苏7 小时前
Web开发:ORM框架之使用Freesql的分表分页写法
前端·数据库·sql