当前位置:AIGC资讯 > AIGC > 正文

一款基于百度文心一言的商品评论智能回复Chrome插件

一、引言

在上次小弟发布了一款基于openai大模型的Chrome网页插件后,有许多朋友向我提意见,表示这个插件需要翻墙,对于真正的小白还是有些不友好。因此这次我花了两个通宵,完成了一款基于百度文心一言大模型的网页插件的创作。本文将以作为一款商品评论的智能回复工具为例,详细介绍插件的功能及使用方法,大家可以根据自己的需求魔改插件,实现不同的目标,例如文稿助手、聊天助手等。本插件完全开源,也欢迎大家一起学习,或作为课设毕设使用。

二、功能展示

本文以作为商品评论智能回复工具的目标为例,详细展示插件各功能。

在普通模式下,用户可以将消费者的评论输入进插件,插件将会调用百度api进行智能回复。

而在高级模式下,用户可以选择四种情绪模式、三种回复策略来让插件作答,如图所示,每种模式将会有不同的表达,这样能让商家对于不同类型的买家评论做出最恰当且快速的回复。

三、插件部署

对于插件本身来说,包含这几个文件:

1.manifest.json

{
    "manifest_version": 3,
    "name": "Wenxin Chat reply",
    "version": "1.0",
    "description": "A Chrome extension to chat with Baidu Wenxin",
    "permissions": [
        "activeTab",
        "storage"
    ],
    "background": {
        "service_worker": "background.js"
    },
    "action": {
        "default_popup": "popup.html",
        "default_icon": {
            "16": "icons/icon16.png",
            "48": "icons/icon48.png",
            "128": "icons/icon128.png"
        }
    },
    "icons": {
        "16": "icons/icon16.png",
        "48": "icons/icon48.png",
        "128": "icons/icon128.png"
    }
}

2.popup.html(插件前端页面)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文心一言商家智能回复</title>
    <style>
        html, body {
            width: 500px;  /* 调整宽度 */
            height: 100%;
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
    </style>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
    <link rel="stylesheet" href="https://blog.csdn.net/MickeyRay0624/article/details/style.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.7.2/font/bootstrap-icons.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>

<body>

    <div class="container mt-4">

        <h1 class="text-center mb-4">文心一言商品评价回复</h1>

        <div class="form-check form-switch mb-3">
            <span id="mode-text">智能回复(高级模式)</span>
            <input   type="checkbox" role="switch" id="model-selector">
            <label   for="model-selector"></label>
        </div>

        <p style="font-size: 17px;">消费者的评论</p>

        <div class="form mb-3">
            <textarea     placeholder="请输入......" rows="4"></textarea>
        </div>

        <div   class="d-none">
            <div class="mb-3">
                <p>选择情绪模式:</p>
                <div   role="group">
                    <input type="radio"   name="emotionStrategy"   autocomplete="off" checked>
                    <label   for="acknowledgement">认可</label>

                    <input type="radio"   name="emotionStrategy"   autocomplete="off">
                    <label   for="informal">随和</label>

                    <input type="radio"   name="emotionStrategy"   autocomplete="off">
                    <label   for="attentiveness">关注</label>

                    <input type="radio"   name="emotionStrategy"   autocomplete="off">
                    <label   for="encouragement">鼓励</label>
                </div>
            </div>

            <div class="mb-3">
                <p>选择回复策略:</p>
                <div   role="group">
                    <input type="radio"   name="rationalStrategy"   autocomplete="off" checked>
                    <label   for="explanation">解释</label>

                    <input type="radio"   name="rationalStrategy"   autocomplete="off">
                    <label   for="redress">补偿</label>

                    <input type="radio"   name="rationalStrategy"   autocomplete="off">
                    <label   for="facilitation">改进</label>
                </div>
            </div>
        </div>

        <div class="d-grid gap-2 mb-4">
            <button   class="btn btn-primary">提交</button>
        </div>

        <div     style="min-height: 100px;"></div>

    </div>

    <script src="https://blog.csdn.net/MickeyRay0624/article/details/popup.js"></script>

</body>

</html>

3.popup.js

document.getElementById('model-selector').addEventListener('change', function() {
    const reviewSection = document.getElementById('review-section');
    if (this.checked) {
        reviewSection.classList.remove('d-none');
    } else {
        reviewSection.classList.add('d-none');
    }
});

document.getElementById('send-button').addEventListener('click', function () {
    const userInput = document.getElementById('user-input').value;
    const responseElement = document.getElementById('response');

    const isBasicMode = document.getElementById('model-selector').checked;
    const emotionStrategy = isBasicMode ? document.querySelector('input[name="emotionStrategy"]:checked').id : null;
    const rationalStrategy = isBasicMode ? document.querySelector('input[name="rationalStrategy"]:checked').id : null;

    responseElement.innerText = 'Loading...';

    chrome.runtime.sendMessage({ action: 'getWenxinResponse', content: userInput, emotionStrategy, rationalStrategy, isBasicMode }, function (response) {
        if (chrome.runtime.lastError) {
            responseElement.innerText = 'Error: ' + chrome.runtime.lastError.message;
        } else {
            responseElement.innerText = response;
        }
    });
});

4. background.js

const serverUrl = 'http://127.0.0.1:3000';  // 代理服务器的URL

function getAccessToken() {
    return new Promise((resolve, reject) => {
        fetch(`${serverUrl}/getAccessToken`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({})
        })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            if (data.access_token) {
                resolve(data.access_token);
            } else {
                throw new Error('Failed to retrieve access token');
            }
        })
        .catch(error => {
            console.error('Error fetching access token:', error);
            reject(error);
        });
    });
}

function getPrompt(userMessage, emotionStrategy, rationalStrategy, isBasicMode) {
    if (!isBasicMode) {
        return `我是一名商家,需要回复消费者对自己商品的评价:${userMessage}`;
    }

    let emotionPrompt = '';
    let rationalPrompt = '';

    switch (emotionStrategy) {
        case 'acknowledgement':
            emotionPrompt = '请用一种理解客户情绪的方式进行回答';
            break;
        case 'informal':
            emotionPrompt = '请用一种轻松、友好的语气与客户沟通';
            break;
        case 'attentiveness':
            emotionPrompt = '请表示你会关注客户的问题,并尽力解决';
            break;
        case 'encouragement':
            emotionPrompt = '请表达出你会改善的强烈意愿';
            break;
    }

    switch (rationalStrategy) {
        case 'explanation':
            rationalPrompt = '请解释客户投诉的原因';
            break;
        case 'redress':
            rationalPrompt = '请提供合适的补救措施来弥补';
            break;
        case 'facilitation':
            rationalPrompt = '请提出改进措施';
            break;
    }

    return `我是一名商家,需要回复消费者对自己商品的评价。${emotionPrompt}。${rationalPrompt}:${userMessage}`;
}

function getWenxinResponse(accessToken, userMessage, emotionStrategy, rationalStrategy, isBasicMode) {
    const prompt = getPrompt(userMessage, emotionStrategy, rationalStrategy, isBasicMode);
    return new Promise((resolve, reject) => {
        fetch(`${serverUrl}/getWenxinResponse`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ accessToken, prompt })
        })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            if (data.result) {
                resolve(data.result);
            } else {
                throw new Error('Failed to retrieve Wenxin response');
            }
        })
        .catch(error => {
            console.error('Error fetching Wenxin response:', error);
            reject(error);
        });
    });
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'getWenxinResponse') {
        getAccessToken()
            .then(accessToken => getWenxinResponse(accessToken, message.content, message.emotionStrategy, message.rationalStrategy, message.isBasicMode))
            .then(response => sendResponse(response))
            .catch(error => sendResponse('Error: ' + error.message));
        return true;  // Will respond asynchronously.
    }
});

5.style.css

body {
    font-family: Arial, sans-serif;
    background-color: #f7f7f7;
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

.container {
    padding: 20px;
    max-width: 480px; /* 调整宽度 */
    margin: auto;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.text-center {
    font-size: 24px;
    margin-bottom: 20px;
    color: #333;
}

.form-control {
    font-size: 16px;
    padding: 10px;
    border-radius: 4px;
    border: 1px solid #ddd;
    resize: none;
}

.btn-primary {
    background-color: #4CAF50;
    border-color: #4CAF50;
    border-radius: 4px;
    font-size: 16px;
    transition: background-color 0.3s ease;
}

.btn-primary:hover {
    background-color: #45a049;
}

.bg-light {
    background-color: #f1f1f1;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
    white-space: pre-wrap;
}

.btn-outline-primary {
    color: #007bff;
    border-color: #007bff;
}

.btn-outline-primary:hover {
    background-color: #007bff;
    color: #ffffff;
}

.btn-check:checked + .btn-outline-primary {
    background-color: #007bff;
    border-color: #007bff;
    color: #ffffff;
}

.btn-outline-success {
    color: #28a745;
    border-color: #28a745;
}

.btn-outline-success:hover {
    background-color: #28a745;
    color: #ffffff;
}

.btn-check:checked + .btn-outline-success {
    background-color: #28a745;
    border-color: #28a745;
    color: #ffffff;
}

最后,大家需要在文件里面创建一个icons文件夹,如果不会调代码,请往里面存放命名为icon16、icon48、icon128的图片。

四、服务器部署

CORS(跨域资源共享)策略限制问题足足硬控了笔者一天,浏览器会阻止从插件的背景脚本直接发送跨域请求到第三方API。为了解决这个问题,笔者先通过修改background.js权限、使用CORS代理服务等方法进行尝试,但都以失败告终。无奈之下,只好通过部署了一个本地服务器,在服务器上创建一个代理,将请求转发到百度API,从而解决CORS问题。

创建一个新python项目,命名为wenxin_proxy_pythonreply(举例)

创建一个名为server.py的文件

from flask import Flask, request, jsonify
from flask_cors import CORS
import requests

app = Flask(__name__)
CORS(app)  # 启用CORS

CLIENT_ID = '*****'  # 替换为你的API Key
CLIENT_SECRET = '****'  # 替换为你的Secret Key

@app.route('/getAccessToken', methods=['POST'])
def get_access_token():
    url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}"
    headers = {
        'Content-Type': 'application/json'
    }
    response = requests.post(url, headers=headers, json={})
    if response.status_code == 200:
        return jsonify(response.json())
    else:
        return jsonify({'error': 'Failed to retrieve access token'}), response.status_code

@app.route('/getWenxinResponse', methods=['POST'])
def get_wenxin_response():
    data = request.get_json()
    access_token = data['accessToken']
    prompt = data['prompt']
    url = f"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-4.0-8k-0329?access_token={access_token}"
    payload = {
        "messages": [
            {
                "role": "user",
                "content": prompt
            }
        ]
    }
    headers = {
        'Content-Type': 'application/json'
    }
    response = requests.post(url, headers=headers, json=payload)
    if response.status_code == 200:
        return jsonify(response.json())
    else:
        return jsonify({'error': 'Failed to retrieve Wenxin response'}), response.status_code

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)

复制后,在对应位置填入API Key和Secret Key,并在终端分别运行这两段代码

pip install flask requests
pip install flask-cors

然后运行server.py

如果没有报错,就将之前插件的文件夹在chrome://extensions/里打开,选择开发者模式并点击“加载已解压的拓展程序”。

最后,点击插件,输入内容,如果能够成功运行,那么恭喜你大功告成。

五、总结

一款即插即用的多功能网页插件,能根据个人需求魔改成各种工具,欢迎大家交流学习!如有不解之处,请仔细阅读API调用文档https://cloud.baidu.com/doc/WENXINWORKSHOP/s/xlvlzz84k,或与我联系,我将尽我所能。

总结

**基于百度文心一言的Chrome插件创作与功能展示**
本文详细介绍了一款基于百度文心一言大模型的Chrome网页插件的创作过程与功能。插件的目的是为了方便用户在浏览网页时能直接使用文心一言的智能回复功能,提升用户的在线交互体验。
在文章开始,作者提到了此次创作的起因是应朋友之需,因为之前的openai插件存在翻墙问题,不便于所有用户使用。为此,作者决定采用百度文心一言大模型来创建一个更适合国内用户使用的插件。这款插件不仅适用于商品评论的智能回复,还可以根据用户需求进行定制,例如作为文稿助手、聊天助手等。更重要的是,插件完全开源,便于学习交流和二次开发。
接下来是插件的功能展示部分。在普通模式下,插件可以接收消费者的评论并通过百度api进行智能回复。高级模式则提供了更多的选择,用户可以根据自己的需要选择四种情绪模式和三种回复策略,这使得插件能够根据不同情境提供更加个性化和恰当的回复。
文章的第三部分详细介绍了插件的部署文件和步骤。这包括manifest.json配置文件、popup.html前端页面文件、popup.js和background.js脚本文件,以及style.css样式表文件。作者还贴心地提到了需要在文件内创建一个icons文件夹,并放入相应尺寸的图标文件。
随后,文章转向服务器部署的介绍。由于CORS策略的限制,作者通过部署一个本地服务器并创建代理,成功将请求转发到百度API,从而绕过了CORS问题。作者提供了一个简单的Flask应用程序代码作为服务器端的实现示例,包括如何获取访问令牌和转发请求到文心一言API的细节。
最后,作者对此次创作进行了总结,并鼓励读者根据个人需求对插件进行定制和改造,同时也提供了API调用文档链接以便进一步学习和探索。
总的来说,本文提供了一个详尽的教程,从创作动机、功能展示到技术实现等各个方面对基于百度文心一言的Chrome插件做了全面的介绍。这不仅是一个实用的工具开发教程,也是对大模型应用实践的一个有益探索。

更新时间 2024-07-13