> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/playcanvas-plugin/chat-overlay.md).

# 聊天覆盖层

让我们添加一个聊天窗口，以增强用户交互和沉浸感。创建一个名为 Convai Chat 的新实体。

### Convai Chat 脚本

\
该 `ConvaiChat` 脚本负责管理聊天界面，并显示用户与由 AI 驱动的虚拟角色之间的对话。它处理用户消息和 AI 回复的渲染、维护聊天历史，并确保聊天容器内平滑的滚动行为。

```javascript
// 创建一个名为 'convaiChat' 的新脚本
var ConvaiChat = pc.createScript('convaiChat');

// 添加一个属性用于存储 CSS 资源
ConvaiChat.attributes.add('css', { type: 'asset', assetType: 'css', title: 'CSS Asset' });

// 添加一个属性用于存储 HTML 资源
ConvaiChat.attributes.add('html', { type: 'asset', assetType: 'html', title: 'HTML Asset' });

// 每个实体只调用一次的初始化代码
ConvaiChat.prototype.initialize = function() {
    // 为聊天框容器创建一个新的 div 元素
    this.htmlEl = document.createElement('div');
    this.htmlEl.classList.add('chatbox__container');
    this.htmlEl.setAttribute("id", "convai-chat");
    document.body.appendChild(this.htmlEl);

    // 设置聊天框容器的 HTML 内容
    this.htmlEl.innerHTML = this.html.resource || '';

    // 为 CSS 创建一个 STYLE 元素
    this.cssEl = document.createElement('style');
    document.head.appendChild(this.cssEl);
    this.cssEl.innerHTML = this.css.resource || '';

    // 初始化变量
    this.asset = null;
    this.assetId = 0;
    this.currentNpcText = "";
    this.currentUserText = "";
    this.ChatHistory = [];
    this.createNewUserChatEl = true;
    this.createNewNpcChatEl = true;
    this.userChatEl = null;
    this.npcChatEl = null;
};

// 向聊天历史中添加一段对话的函数
ConvaiChat.prototype.addToChatHistory = function(userQuery, npcResponse) {
    // 创建一个表示该对话的对象
    const conversation = {
        userQuery: userQuery.trim(),
        response: npcResponse.trim(),
    };

    // 将对话对象推送到聊天历史数组中
    this.ChatHistory.push(conversation);
};

// 向聊天容器中添加用户消息的函数
ConvaiChat.prototype.addUserMessage = function(message) {
    const chatContainer = document.getElementById("chatbot__activeChat");
    if (this.createNewUserChatEl) {
        const userMessage = document.createElement("div");
        userMessage.classList.add("user_message");
        this.userChatEl = userMessage;
        chatContainer.appendChild(this.userChatEl);
    }
    this.userChatEl.textContent = message;
};

// 向聊天容器中添加 AI 回复的函数
ConvaiChat.prototype.addNpcMessage = function(message) {
    const chatContainer = document.getElementById("chatbot__activeChat");
    if (this.createNewNpcChatEl) {
        const convaiMessage = document.createElement("div");
        convaiMessage.classList.add("convai_message");
        this.npcChatEl = chatContainer.appendChild(convaiMessage);
    }
    this.npcChatEl.textContent = message;
};

// 处理聊天更新的函数
ConvaiChat.prototype.handleChat = function() {
    // 当对话处于活动状态时设置 userText
    if (window.conversationActive && (window.userTextStream !== this.currentUserText) && window.userTextStream !== "") {
        this.currentUserText = window.userTextStream;
        // 当前用户聊天的 UI（在 UI 中也进行截断）
        this.addUserMessage(this.currentUserText);
        this.createNewUserChatEl = false;
    }

    // 当对话处于活动状态时设置 npcText
    if (window.conversationActive && (window.npcTextStream !== this.currentNpcText) && window.npcTextStream !== "") {
        this.currentNpcText = window.npcTextStream;
        // 当前 NPC 聊天的 UI（在 UI 中也进行截断）
        this.addNpcMessage(this.currentNpcText);
        this.createNewNpcChatEl = false;
    }

    // 当对话结束时，将其添加到聊天历史中
    if (!window.conversationActive && this.currentUserText !== "" && this.currentNpcText !== "") {
        this.addToChatHistory(this.currentUserText, this.currentNpcText);
        this.currentUserText = "";
        this.currentNpcText = "";
        this.createNewUserChatEl = true;
        this.createNewNpcChatEl = true;
    }

    // 滚动到聊天容器底部
    this.scrollTop();
};

// 滚动到聊天容器底部的函数
ConvaiChat.prototype.scrollTop = function(dt) {
    if (window.conversationActive) {
        var chatBox = document.getElementById("chatbot__activeChat");
        chatBox.scrollTop = chatBox.scrollHeight - chatBox.clientHeight;
    }
};

// 每帧调用的更新代码
ConvaiChat.prototype.update = function(dt) {
    this.handleChat();
};
```

在解析脚本后，将以下文件作为附件添加到 convaiChat 脚本中。

### HTML

```html
<div class="chatbot__activeChat" id="chatbot__activeChat">    
</div>
<form id="convai-form">
      <input
        type="text"
        placeholder="按住 [T] 交谈"
        class="convai_input"
        id="convai-input"
        name="convai-input"
      />
      <button type="submit" class="convai_chat_submit send-button">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          height="35px"
          viewBox="0 0 24 24"
          width="35px"
          fill="#e5e5e5"
        >
          <path d="M0 0h24v24H0z" fill="none" />
          <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
        </svg>
      </button>
</form>
```

### CSS

```css
*{
  box-sizing: border-box;
}

.chatbox__container{
    position: fixed;
    bottom: 1vw;
    left: 1vw;
    background-color: rgba(0, 0, 0, 0.7);
    border-radius: 16px;
    width: 30vw;
    z-index: 10;
}
.chatbot__activeChat{
  width: 100%;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  min-height: 20vh;
  max-height: 60vh;
  padding: 2%;
}
/* 宽度 */
::-webkit-scrollbar {
  width: 0px;
}
/* 轨道 */
::-webkit-scrollbar-track {
  background: transparent;
}

/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
  background: transparent;
}

/* 悬停时的滑块 */
::-webkit-scrollbar-thumb:hover {
  background: transparent;
}

.user_message, .convai_message {
    margin-bottom: 8px;
    padding: 8px;
    color: white;
    background: rgba(255, 255, 255, 0.07);
    backdrop-filter: blur( 2px );
    -webkit-backdrop-filter: blur( 2px );
    border-radius: 16px;
}
.user_message{
  color: #e5e5e5;
  align-self: flex-start;
  max-width: 80%;
}
.convai_message {
  background-color: rgba(14, 114, 75, 0.4);
  color: #e5e5e5;
  align-self: flex-end;
  max-width: 80%;
}
@media only screen and (min-device-width: 0px) {
  #convai-form {
    box-sizing: border-box;
    display: flex;
    position: relative;
    padding: 2%;
    height: auto;
    bottom: 0;
    width: 100%;
    max-width: 100vw;
    column-gap: 2%;
    justify-content: space-between;
    align-items: center;
    background-color: transparent;
  }
  #convai-input {
    width: 90%;
    background: rgba(255, 255, 255, 0.07);
    border-radius: 30px;
    padding: 2%;
    color: #e5e5e5;
    outline: none;
    box-sizing: border-box;
    transition: 0.3s;
    border: none;
  }
  #convai-input::placeholder{
    color: #e5e5e5;
  }

  .active_input {
    width: 70%;
    border: 1px solid #ca7f1c;
    box-shadow: 0 0 10px rgba(255, 225, 137, 0.6);
    transition: 0.4s;
  }
}

/* 表单按钮  */

.convai_chat_submit {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 100%;
  height: 100%;
  aspect-ratio: 1/1;
  color: #e5e5e5;
  border: 2px solid #e5e5e5;
  background-color: transparent;
  font-weight: 500;
}
.send-button {
  width: 30px;
  height: 30px;
  box-sizing: border-box;
  display: flex;
  border: 2px solid #e5e5e5;
  transition: 0.4s;
  cursor: pointer;
}

.convai-logo{
  position: fixed;
  bottom: 1vw;
  right: 1vw;
  height: 5%;
  object-fit: contain;
  width: auto;
}
```

<figure><img src="/files/9e46793453148dbdccc63caa0f07cab0619669e1" alt=""><figcaption><p>聊天集成</p></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/playcanvas-plugin/chat-overlay.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
