如何写一个支持多种类型数据输入输出的对话框组件 | 豆包MarsCode AI 刷题

366 阅读3分钟

在前端小项目中,要求组件需要支持用户输入输出对话,且对话内容支持图片、PDF等多媒体格式;LLM返回内容可能为文本、图片等,需要正确展示这些格式内容;代码需要显示copy按键,支持用户一键复制。所以这篇文章是一篇关于如何写一个支持多种类型数据输入输出的对话框组件的实践文章。

构建对话框组件

首先,我们需要构建一个对话框组件(还是使用我们前端开发的老三件):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对话框组件</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            padding: 20px;
        }
        .dialogue-box {
            background: white;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 20px;
            max-width: 600px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            overflow: hidden;
            margin-bottom: 20px;
        }
        .message {
            margin: 10px 0;
            line-height: 1.5;
        }
    </style>
</head>
<body>

<div class="dialogue-box" id="dialogueBox">
    <div id="messages"></div>
</div>


<script>
</script>

</body>
</html>

这部分相当于搭建了一个简单的编写框架,依旧是HTML负责页面包含的内容,CSS负责样式,JavaScript负责逻辑。

构建输入框组件

即在页面中加入input_area,包含输入区域以及发送按钮两个部分,代码如下。

<div class="input-area"> 
    <input type="text" id="userInput" placeholder="输入您的消息..."> 
    <button onclick="sendMessage()">发送</button> 
</div>

同时编写简单样式:

.input-area { 
    display: flex; 
    margin-top: 20px; 
} 
.input-area input { 
    flex: 1; 
    padding: 10px; 
    border: 1px solid #ccc; 
    border-radius: 5px; 
} 
.input-area button { 
    padding: 10px; 
    margin-left: 10px; 
    background-color: #28a745; 
    color: white; 
    border: none; 
    border-radius: 5px;
}

由于本篇文章并不探讨LLM模型如何引入,所以sendMessage()函数仅仅作为一个上传消息和处理消息获得显示内容的接口保留。

支持文件输入

上面的输入框仅仅支持text的输入,我们希望它能够进一步支持图片/PDF等类型文件的输入,我们可以在input_area中,再单独编写一个文件输入框和按钮:

<input type="file" id="fileInput" accept="image/*,application/pdf"> 
<button onclick="uploadFile()">上传文件</button>

同时为它编写样式:

.input-area input[type="file"] { 
    margin-left: 10px; 
}

输出逻辑的编写

接下来,我们来讨论这个输入及显示的逻辑过程,我们目前已经设定了两个函数接口分别是sendMessage和uploadFile,都可以在用户点击相应的按钮的时候触发,完成将用户输入传递给处理器并捕获处理器输出显示给用户的功能。

function sendMessage() { 
    const userInput = document.getElementById('userInput'); 
    const message = userInput.value; 
    if (message) { 
        addMessage(message); // 用户消息 
        userInput.value = ''; // 清空输入框 
        // 模拟LLM返回 
        setTimeout(() => { 
            const response = simulateLLMResponse(message); // 根据输入返回内容 
            addMessage(response, false); // LLM消息 
        }, 1000); 
    } 
} 

function uploadFile() { 
    const fileInput = document.getElementById('fileInput'); 
    const file = fileInput.files[0]; 
    if (file) { 
           addMessage(file); // 添加文件消息 
           fileInput.value = ''; // 清空文件输入框 
           // 模拟LLM返回 
           setTimeout(() => { 
               const response = simulateLLMResponse('上传的文件'); // 根据输入返回内容 
               addMessage(response, false); // LLM消息 
           }, 1000); 
     } else { alert('请先选择文件。'); } }

这里我们将处理器的模拟单独使用一个simulateLLMResponse的函数写出来,将输出使用一个addMessage函数写出来。

const messagesContainer = document.getElementById('messages');

function addMessage(content, isUser = true) {
    const messageElement = document.createElement('div');
    messageElement.className = 'message';

    if (typeof content === 'string') {
        messageElement.innerHTML = content;
    } else if (content instanceof HTMLImageElement) {
        messageElement.appendChild(content);
    } else if (content instanceof Blob) {
        const url = URL.createObjectURL(content);
        const link = document.createElement('a');
        link.href = url;
        link.textContent = '查看PDF';
        link.target = '_blank';
        messageElement.appendChild(link);
    }
}

function simulateLLMResponse(message) {
    // 根据用户输入返回不同格式的内容
    if (message.includes('图片')) {
        const img = document.createElement('img');
        img.src = 'https://via.placeholder.com/150'; // 示例图片URL
        img.className = 'media';
        return img;
    } else if (message.includes('PDF')) {
        return new Blob(['PDF内容示例'], { type: 'application/pdf' });
    } else {
        return `您说的是: ${message}`; // 简单文本响应
    }
}

注:由于本文内容并不涉及到前后端的文件传输存储,上面函数仅作为一个前端实现的思路提示,并不具有实际应用意义。实际应根据文件类型判断具体的处理方式。

实现复制功能

复制按钮仅在特定的情况下显示,而不是一直出现,所以我们可以在addMessage时动态加入copy按钮。

function addMessage(content, type) {
    // 此处省略
    
    const copyButton = document.createElement('button');
    copyButton.className = 'copy-button';
    copyButton.textContent = '复制';
    copyButton.onclick = () => copyToClipboard(content, type);
    messageElement.appendChild(copyButton);
    messagesContainer.appendChild(messageElement);
}

function copyToClipboard(content, type) {
    let textToCopy;

    if (type === 'text') {
        textToCopy = content;
    } else if (type === 'pdf') {
        textToCopy = '查看PDF'; // 复制为文本描述
    } else {
        textToCopy = ''; // 对于图片不复制任何内容
    }

    navigator.clipboard.writeText(textToCopy).then(() => {
        alert('内容已复制!');
    });
}