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

助手效果图

看完这篇文章,你将免费拥有你自己的Ai助手,全程干货,先到先得

获取免费的AI大模型接口

访问这个地址 生成key https://openrouter.ai/mistralai/mistral-small-3.2-24b-instruct:free/api

或者调用其他的免费大模型,这个根据自己的需求更改,要先注册这个网站

修改默认的参数

最主要的就是你申请生成的key

助手源码

纯HTML的源码,嘎嘎够劲

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>

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

相关推荐
OpenTiny社区几秒前
操作ArkTS页面跳转及路由相关心得
前端·typescript·web·opentiny
xiaohua0708day1 分钟前
Lodash库
前端·javascript·vue.js
huakoh1 分钟前
Claude Code 从零到上手指南:国产工具链复现80% Agent能力,DeepSeek+LangChain实战
前端
七牛开发者1 分钟前
如何从零开发一个工业级的 SKILL
人工智能·程序员·agent
瘦瘦瘦大人3 分钟前
豆包与抖音联动创作新手实战指南
人工智能
Ankkaya4 分钟前
浏览器插件接入 Google 登录
前端
Asmewill5 分钟前
DeepAgents学习笔记一(构建深度多智能体)
前端
万物皆对象6666 分钟前
切换路由时页面空白问题(vue3)
前端·vue.js·typescript
突然好热6 分钟前
TS 调试技巧
前端·javascript·typescript
三无推导7 分钟前
ComfyUI 安装部署教程:Windows 下快速搭建可视化 AI 绘图工作流,零基础也能跑通
人工智能·pytorch·windows·stable diffusion·aigc·ai绘画·持续部署