16.3 项目十六:Gradio应用拓展为Website Widget

除了在Discord、Slack中使用Gradio App,还可以将Gradio应用作为聊天插件添加到网站中,比如Intercom等常见的客服工具。适合场景:①为文档页面添加AI助手;②在作品集或产品官网上提供互动帮助;③为Gradio应用创建自定义聊天界面。

工作原理。在已建成的网站加入聊天插件(Website Widget),它会以浮动按钮形式显示在网站角落。用户点击后,将通过JavaScript客户端API接口与Gradio应用进行实时交互,所有问答对话都将在弹出窗口中直接完成。

在开始之前,做好准备工作,其前置条件为:
(1)某个已建成网站,任意包含完整<head>和<body>的可运行HTML文件。
(2) 确保已部署可运行的Gradio应用(可运行于本地或Spaces)。本示例将使用与16.1节相同的Gradio Playground Bot。

16.3.1 HTML和CSS创建并定制聊天插件

首先在示例网页的部分加入HTML和CSS代码,如代码16-6所示:

代码16-6
<div id="chat-widget" class="chat-widget">
    <button id="chat-toggle" class="chat-toggle">💬</button>
    <div id="chat-container" class="chat-container hidden">
        <div id="chat-header">
            <h3>Gradio Assistant</h3>
            <button id="close-chat">×</button>
        </div>
        <div id="chat-messages"></div>
        <div id="chat-input-area">
            <input type="text" id="chat-input" placeholder="Ask a question...">
            <button id="send-message">Send</button>
        </div>
    </div>
</div>

<style>
.chat-widget {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 1000;
}
...
</style>

上述代码包含网页中的HTML和CSS代码,解读如下:
(1)在网页放置div分区,id为chat-widget,其CSS格式表示它位于底部、置前显示(z-index>0)且格式固定。它的内容包括一个聊天切换键chat-toggle和一个隐藏的聊天界面chat-container。
(2)在聊天容器中,首先,在聊天标题chat-header中定义聊天标题名和关闭键close-chat;然后,定义聊天信息显示区域chat-messages;最后,定义聊天输入区域chat-input-area,包括输入文本框和发送键。
(3)在格式style中,为上面定义的各个部分渲染格式,省略部分,请查看线上源码。CSS代码能力强的读者可根据自己需求更改。

16.3.2 嵌入JavaScript代码实现交互

JavaScript代码实现主体功能,它引入聊天机器人并与之交互。在网站的头部加入以下JavaScript代码,其中Gradio的JavaScript客户端通过Space网址与Space建立连接,而非Space ID,注意区别。如代码16-7所示:

代码16-7
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script type="module">
    import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
    async function initChatWidget() {
        const client = await Client.connect("https://abidlabs-gradio-playground-bot.hf.space");
        const chatToggle = document.getElementById('chat-toggle');
        const chatContainer = document.getElementById('chat-container');
        const closeChat = document.getElementById('close-chat');
        const chatInput = document.getElementById('chat-input');
        const sendButton = document.getElementById('send-message');
        const messagesContainer = document.getElementById('chat-messages');
        chatToggle.addEventListener('click', () => {
            chatContainer.classList.remove('hidden');
        });
        closeChat.addEventListener('click', () => {
            chatContainer.classList.add('hidden');
        });
    
        async function sendMessage() {
            const userMessage = chatInput.value.trim();
            if (!userMessage) return;
            appendMessage(userMessage, 'user');
            chatInput.value = '';
            try {
                const result = await client.predict("/chat", {
                    message: {"text": userMessage, "files": []}
                });
                const message = result.data[0];
                console.log(result.data[0]);
                const botMessage = result.data[0].join('\n');
                appendMessage(botMessage, 'bot');
            } catch (error) {
                console.error('Error:', error);
                appendMessage('Sorry, an error when processing your request.', 'bot');
            }
        }
    
        function appendMessage(text, sender) {
            const messageDiv = document.createElement('div');
            messageDiv.className = `message ${sender}-message`;
            if (sender === 'bot') {
                messageDiv.innerHTML = marked.parse(text);
            } else {
                messageDiv.textContent = text;
            }
            messagesContainer.appendChild(messageDiv);
            messagesContainer.scrollTop = messagesContainer.scrollHeight;
        }
    
        sendButton.addEventListener('click', sendMessage);
        chatInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') sendMessage();
        });
    }
    initChatWidget();
</script>

作为主体功能实现部分,代码稍显复杂,解读如下:
(1)通过CDN方式引入marked.js库。这是一个将Markdown转换为HTML的JavaScript库,可以直接在浏览器中使用该库功能来解析和渲染Markdown内容。
(2)通过CDN方式并使用ES模块语法(script type=“module”)导入Gradio客户端的JavaScript库,这是为了在浏览器中与Gradio服务端进行交互。两者都使用了jsDelivr CDN服务,这是一个可靠的公共CDN服务。这种代码组合用于创建一个既能处理Markdown内容,又能与Gradio后端服务交互的Web应用。
(3)定义主体函数initChatWidget。首先定义各种常量,其中比较重要的是客户端通过URL连接Hugging Face上的应用,以及各种需要操作的网页组件;然后添加事件监听器,在点击和关闭窗口时去除和添加隐藏属性。
(4)定义发送消息函数sendMessage。函数中首先清理用户发送的消息并以身份“user”追加到历史聊天记录messageDiv.textContent;然后通过客户端API接口/chat,向Gradio Playground Bot发送消息并获取回复;最后将回复内容整理后添加到日志,并以身份“bot”将回复添加到消息messageDiv.innerHTML中。
(5)定义追加消息函数appendMessage。首先定义显示消息分区messageDiv,根据不同消息类型将消息内容加入到messageDiv的不同部分;然后添加到消息容器中messagesContainer;最后将滚动条位置设置为容器总高度,即自动滚动到底部‌。
(6)添加事件监听器,实现两种发送消息的方式。一是通过点击发送按钮,二是通过回车键发送消息。

16.3.3 操作网页插件与自定义设置

大功告成!现在已经有网页版的插件Gradio Bot,如何使用及扩展它呢?
与插件交互。作者使用任意浏览器打开保存的website-widget.html文件,稍等片刻,便会加载完成,如图16-24所示:
在这里插入图片描述

图16-24

可以看到,网页右下角出现了添加的网页插件图标。单击它,在输入框输入:make a simple gradio app,会得到一个简单Gradio应用,如图16-25所示:
在这里插入图片描述

图16-25

现在网站已经成功接入Gradio应用!用户只需点击聊天按钮,就能打开窗口与Gradio应用实时互动。还可以通过修改CSS来个性化组件外观,例如:①更改配色以匹配网站主题;②调整组件尺寸和显示位置;③添加图片等文件;④添加展开/收起动画效果;⑤自定义消息气泡样式等。

本书讲解Gradio几乎所有基础及高级技术,为用户奠定了开发复杂AI应用的基础。Gradio虽然入门简单,但麻雀虽小五脏俱全,它可与各类文字、图像、音频和视频大模型结合展示,并引入实时流、智能体和MCP等当前流行技术,还可拓展到Discord、Slack及网页。Gradio版本仍在快速迭代中,在修正Bug同时,引入更多紧跟技术前沿的实用功能,未来会更贴合当前智能领域的应用,为用户带来更精彩的体验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐