给自己网站增加一个免费的AI助手,纯HTML

发布于:2025-06-29 ⋅ 阅读:(23) ⋅ 点赞:(0)

助手效果图

看完这篇文章,你将免费拥有你自己的Ai助手,全程干货,先到先得
在这里插入图片描述

获取免费的AI大模型接口

访问这个地址 生成key https://openrouter.ai/mistralai/mistral-small-3.2-24b-instruct:free/api
或者调用其他的免费大模型,这个根据自己的需求更改,要先注册这个网站
在这里插入图片描述

修改默认的参数

最主要的就是你申请生成的key
在这里插入图片描述

助手源码

纯HTML的源码,嘎嘎够劲

<!DOCTYPE html>
<html lang="zh-CN">

<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;
            margin: 0;
            padding: 0;
            height: 100vh;
            user-select: none;
            overflow: hidden;
        }

        #chat-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 300px;
            height: 400px;
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            z-index: 1000;
            resize: both;
            min-width: 300px;
            min-height: 400px;
            transition: transform 0.2s ease, opacity 0.2s ease;
            transform-origin: bottom right;
        }

        #chat-container.minimized {
            transform: scale(0);
            opacity: 0;
            pointer-events: none;
        }

        #chat-header {
            background-color: #4a6bdf;
            color: white;
            padding: 12px 15px;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        #chat-title {
            font-weight: bold;
            font-size: 16px;
        }

        #minimize-btn, #restore-btn {
            background: none;
            border: none;
            color: white;
            font-size: 18px;
            cursor: pointer;
            padding: 0;
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #minimized-chat {
            position: fixed;
            width: 50px;
            height: 50px;
            border-radius: 10px;
            background-color: #4a6bdf;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
            z-index: 1000;
            display: none;
            transition: transform 0.1s ease;
        }

        #minimized-chat:active {
            transform: scale(0.95);
        }

        #restore-btn {
            position: absolute;
            width: 100%;
            height: 100%;
            font-size: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: move;
            pointer-events: auto;
        }

        #chat-messages {
            flex: 1;
            padding: 15px;
            overflow-y: auto;
            background-color: #f9f9f9;
        }

        .message {
            margin-bottom: 12px;
            max-width: 80%;
            padding: 8px 12px;
            border-radius: 12px;
            line-height: 1.4;
            word-wrap: break-word;
        }

        .user-message {
            background-color: #e3effd;
            margin-left: auto;
            border-bottom-right-radius: 4px;
        }

        .ai-message {
            background-color: white;
            margin-right: auto;
            border-bottom-left-radius: 4px;
            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
        }

        #chat-input-area {
            display: flex;
            padding: 10px;
            border-top: 1px solid #eee;
            background-color: white;
        }

        #chat-input {
            flex: 1;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 20px;
            outline: none;
            resize: none;
            height: 40px;
            max-height: 100px;
            font-family: inherit;
        }

        #send-btn {
            margin-left: 10px;
            padding: 0 15px;
            background-color: #4a6bdf;
            color: white;
            border: none;
            border-radius: 20px;
            cursor: pointer;
            transition: background-color 0.2s;
        }

        #send-btn.stop {
            background-color: #ff4d4d;
        }

        #send-btn:hover {
            background-color: #3a5bcf;
        }

        #send-btn.stop:hover {
            background-color: #e63c3c;
        }

        .typing-indicator {
            display: inline-block;
            margin-left: 5px;
        }

        .typing-dot {
            display: inline-block;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background-color: #999;
            margin-right: 3px;
            animation: typingAnimation 1.4s infinite ease-in-out;
        }

        .typing-dot:nth-child(1) {
            animation-delay: 0s;
        }

        .typing-dot:nth-child(2) {
            animation-delay: 0.2s;
        }

        .typing-dot:nth-child(3) {
            animation-delay: 0.4s;
        }

        @keyframes typingAnimation {
            0%, 60%, 100% {
                transform: translateY(0);
            }
            30% {
                transform: translateY(-5px);
            }
        }

        .stopped-message {
            color: #888;
            font-style: italic;
        }
    </style>
</head>

<body>
    <div id="chat-container">
        <div id="chat-header">
            <div id="chat-title">AI助手</div>
            <button id="minimize-btn"></button>
        </div>
        <div id="chat-messages"></div>
        <div id="chat-input-area">
            <textarea id="chat-input" placeholder="输入消息..." rows="1"></textarea>
            <button id="send-btn">发送</button>
        </div>
    </div>

    <div id="minimized-chat">
        <button id="restore-btn">+</button>
    </div>

    <script>
        // 获取DOM元素
        const chatContainer = document.getElementById('chat-container');
        const chatHeader = document.getElementById('chat-header');
        const minimizedChat = document.getElementById('minimized-chat');
        const minimizeBtn = document.getElementById('minimize-btn');
        const restoreBtn = document.getElementById('restore-btn');
        const chatInput = document.getElementById('chat-input');
        const sendBtn = document.getElementById('send-btn');
        const chatMessages = document.getElementById('chat-messages');

        // 全局变量
        let isDragging = false;
        let isMinimizedDragging = false;
        let offsetX, offsetY;
        let startX, startY;
        let restoreBtnClicked = false;
        let abortController = null; // 用于中止fetch请求
        let isWaitingForResponse = false; // 是否正在等待响应
        let isTypingEffectActive = false; // 是否正在打字效果中
        let typingTimeoutId = null; // 打字效果的timeout ID

        // 限制元素在窗口范围内
        function constrainToWindow(element, x, y) {
            const rect = element.getBoundingClientRect();
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;
            
            const maxX = windowWidth - rect.width;
            const maxY = windowHeight - rect.height;
            
            x = Math.max(0, Math.min(x, maxX));
            y = Math.max(0, Math.min(y, maxY));
            
            return { x, y };
        }

        // 主窗口拖动功能
        chatHeader.addEventListener('mousedown', (e) => {
            if (e.target.id !== 'chat-header' && e.target.id !== 'chat-title') return;

            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            
            const rect = chatContainer.getBoundingClientRect();
            offsetX = startX - rect.left;
            offsetY = startY - rect.top;
            
            chatContainer.style.cursor = 'grabbing';
            chatContainer.style.transition = 'none';
            e.preventDefault();
        });

        // 恢复按钮拖动功能
        restoreBtn.addEventListener('mousedown', (e) => {
            isMinimizedDragging = true;
            restoreBtnClicked = false;
            startX = e.clientX;
            startY = e.clientY;
            
            const rect = minimizedChat.getBoundingClientRect();
            offsetX = startX - rect.left;
            offsetY = startY - rect.top;
            
            minimizedChat.style.cursor = 'grabbing';
            minimizedChat.style.transition = 'none';
            e.preventDefault();
            e.stopPropagation();
        });

        // 恢复按钮点击功能
        restoreBtn.addEventListener('dblclick', (e) => {
            if (!isMinimizedDragging && !restoreBtnClicked) {
                restoreBtnClicked = true;
                restoreChatWindow();
            }
            e.stopPropagation();
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                let x = e.clientX - offsetX;
                let y = e.clientY - offsetY;
                
                const constrained = constrainToWindow(chatContainer, x, y);
                x = constrained.x;
                y = constrained.y;
                
                chatContainer.style.left = `${x}px`;
                chatContainer.style.top = `${y}px`;
                chatContainer.style.right = 'auto';
                chatContainer.style.bottom = 'auto';
            }
            
            if (isMinimizedDragging) {
                let x = e.clientX - offsetX;
                let y = e.clientY - offsetY;
                
                const constrained = constrainToWindow(minimizedChat, x, y);
                x = constrained.x;
                y = constrained.y;
                
                minimizedChat.style.left = `${x}px`;
                minimizedChat.style.top = `${y}px`;
                minimizedChat.style.right = 'auto';
                minimizedChat.style.bottom = 'auto';
            }
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                chatContainer.style.cursor = 'default';
                chatContainer.style.transition = 'transform 0.2s ease, opacity 0.2s ease';
            }
            
            if (isMinimizedDragging) {
                isMinimizedDragging = false;
                minimizedChat.style.cursor = 'move';
                minimizedChat.style.transition = 'transform 0.1s ease';
            }
        });

        // 缩小/恢复功能
        minimizeBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            
            const rect = chatContainer.getBoundingClientRect();
            minimizedChat.style.left = `${rect.left}px`;
            minimizedChat.style.top = `${rect.top}px`;
            minimizedChat.style.right = 'auto';
            minimizedChat.style.bottom = 'auto';
            
            const constrained = constrainToWindow(
                minimizedChat, 
                parseFloat(minimizedChat.style.left || 0),
                parseFloat(minimizedChat.style.top || 0)
            );
            
            minimizedChat.style.left = `${constrained.x}px`;
            minimizedChat.style.top = `${constrained.y}px`;
            
            chatContainer.classList.add('minimized');
            
            setTimeout(() => {
                minimizedChat.style.display = 'block';
            }, 200);
        });

        function restoreChatWindow() {
            chatContainer.style.left = 'auto';
            chatContainer.style.top = 'auto';
            chatContainer.style.right = '20px';
            chatContainer.style.bottom = '20px';
            
            chatContainer.classList.remove('minimized');
            
            minimizedChat.style.display = 'none';
        }

        // 聊天功能
        chatInput.addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = (this.scrollHeight > 100 ? 100 : this.scrollHeight) + 'px';
        });

        function sendMessage() {
            const message = chatInput.value.trim();
            if (!message) return;

            addMessage(message, 'user');
            chatInput.value = '';
            chatInput.style.height = '40px';

            // 改变按钮状态
            setSendButtonState('stop');
            
            const typingId = showTypingIndicator();
            simulateAIResponse(message, typingId);
        }

        function stopRequest() {
            if (abortController) {
                abortController.abort();
                abortController = null;
            }
            
            if (isTypingEffectActive) {
                clearTimeout(typingTimeoutId);
                isTypingEffectActive = false;
                
                // 添加停止提示
                const stoppedDiv = document.createElement('div');
                stoppedDiv.className = 'message ai-message stopped-message';
                stoppedDiv.textContent = '已停止生成回复';
                chatMessages.appendChild(stoppedDiv);
                chatMessages.scrollTop = chatMessages.scrollHeight;
            }
            
            setSendButtonState('send');
            isWaitingForResponse = false;
            
            // 移除正在输入指示器
            const typingElements = document.querySelectorAll('[id^="typing-"]');
            typingElements.forEach(el => el.remove());
        }

        function setSendButtonState(state) {
            if (state === 'stop') {
                sendBtn.textContent = '停止';
                sendBtn.classList.add('stop');
                sendBtn.removeEventListener('click', sendMessage);
                sendBtn.addEventListener('click', stopRequest);
                isWaitingForResponse = true;
            } else {
                sendBtn.textContent = '发送';
                sendBtn.classList.remove('stop');
                sendBtn.removeEventListener('click', stopRequest);
                sendBtn.addEventListener('click', sendMessage);
                isWaitingForResponse = false;
            }
        }

        chatInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                if (!isWaitingForResponse) {
                    sendMessage();
                }
            }
        });

        // 初始化按钮事件
        sendBtn.addEventListener('click', sendMessage);

        function addMessage(text, sender) {
            const messageDiv = document.createElement('div');
            messageDiv.className = `message ${sender}-message`;
            messageDiv.textContent = text;
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function showTypingIndicator() {
            const typingDiv = document.createElement('div');
            typingDiv.className = 'message ai-message';
            typingDiv.id = 'typing-' + Date.now();

            const typingText = document.createElement('span');
            typingText.textContent = 'YiLin:';

            const typingDots = document.createElement('span');
            typingDots.className = 'typing-indicator';
            for (let i = 0; i < 3; i++) {
                const dot = document.createElement('span');
                dot.className = 'typing-dot';
                typingDots.appendChild(dot);
            }

            typingDiv.appendChild(typingText);
            typingDiv.appendChild(typingDots);
            chatMessages.appendChild(typingDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;

            return typingDiv.id;
        }

        function removeTypingIndicator(id) {
            const typingElement = document.getElementById(id);
            if (typingElement) {
                typingElement.remove();
            }
        }
        
        async function simulateAIResponse(userMessage, typingId) {
            abortController = new AbortController();
            
            try {
                const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
                    method: "POST",
                    headers: {
                        "Authorization": "Bearer sk-or-v1-xxxxxxxxxxxx",
                        "HTTP-Referer": "https://nanwish.love",
                        "X-Title": "沂霖博客",
                        "Content-Type": "application/json"
                    },
                    body: JSON.stringify({
                        "model": "mistralai/mistral-small-3.2-24b-instruct:free",
                        "messages": [
                            {
                                "role": "user",
                                "content": [
                                    {
                                        "type": "text",
                                        "text": userMessage
                                    }
                                ]
                            }
                        ]
                    }),
                    signal: abortController.signal
                });
                
                const data = await response.json();
                removeTypingIndicator(typingId);
                
                if (data.choices && data.choices[0].message.content) {
                    typeWriterEffect(data.choices[0].message.content);
                } else {
                    addMessage("抱歉,未能获取有效回复", 'ai');
                    setSendButtonState('send');
                }
            } catch (error) {
                if (error.name !== 'AbortError') {
                    removeTypingIndicator(typingId);
                    addMessage("抱歉,发生错误: " + error.message, 'ai');
                    setSendButtonState('send');
                }
            } finally {
                abortController = null;
            }
        }

        function typeWriterEffect(text) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message ai-message';
            chatMessages.appendChild(messageDiv);

            let i = 0;
            const speed = 20;
            isTypingEffectActive = true;

            function type() {
                if (i < text.length) {
                    messageDiv.textContent += text.charAt(i);
                    i++;
                    chatMessages.scrollTop = chatMessages.scrollHeight;
                    typingTimeoutId = setTimeout(type, speed);
                } else {
                    isTypingEffectActive = false;
                    setSendButtonState('send');
                }
            }
            type();
        }

        // 初始化
        window.addEventListener('DOMContentLoaded', () => {
            minimizedChat.style.display = 'none';
            
            setTimeout(() => {
                typeWriterEffect("你好!我是沂霖,我可以辅助你使用MarkDown,聊天,查资料!你可以输入问题或指令,我会尽力回答。");
            }, 500);
        });

        // 窗口大小变化时重新限制位置
        window.addEventListener('resize', () => {
            if (!chatContainer.classList.contains('minimized')) {
                const rect = chatContainer.getBoundingClientRect();
                const constrained = constrainToWindow(chatContainer, rect.left, rect.top);
                chatContainer.style.left = `${constrained.x}px`;
                chatContainer.style.top = `${constrained.y}px`;
            }
            
            if (minimizedChat.style.display === 'block') {
                const rect = minimizedChat.getBoundingClientRect();
                const constrained = constrainToWindow(minimizedChat, rect.left, rect.top);
                minimizedChat.style.left = `${constrained.x}px`;
                minimizedChat.style.top = `${constrained.y}px`;
            }
        });
    </script>
</body>

</html>

把这个嵌入到你的网站页面 就可以实现站点助手了

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到