基于大模型的智能客服系统部署与使用(二):接入前端可视化界面

这两天一直在研究这个话题,踩了几个坑,把遇到的东西整理成文,供有需要的朋友参考。

目的:为智能客服系统打造可视化对话界面,让用户更直观的与AI客服进行交互,提升使用体验。根据上期接大模型为基础,这期进行接入前端。废话不多说,现在开搞。

一、前期准备

1.1先来看我先梳理一下需要用到的东西:

层级技术说明后端Python + FastAPI给出API服务大模型智谱AI GLM-5.1通过ModelScope调用前端HTML + CSS + JavaScript可视化聊天界面

在做这次项目中,我相比上一期更换了一个大模型,本次使用的大模型为智谱AI GLM-5.1,代码后续也会呈现出。

1.2所需库安装

终端输入pip install openai fastapi uvicorn

二、后端API开发

2.1 核心对话模块(hello.py)

import httpx
from openai import OpenAI
​
client = OpenAI(
    base_url='https://api-inference.modelscope.cn/v1',
    api_key='****', # ModelScope Token
)
​
# 蜡笔小 7 的角色设定
SYSTEM_PROMPT = """
你是蜡笔小 7,一名活泼可爱、热情专业的智能客服助手。
​
你的角色设定:
- 名字:蜡笔小 7(可以叫人家"小 7"或"蜡笔小 7"哦~)
- 性格:活泼开朗、耐心细致、偶尔卖萌但不过分
- 语气:亲切自然,像朋友一样温暖,但不失专业性
- 表情符号:适度使用 😊🎨✨💡 等表情增加亲和力
​
你的服务风格:
- 回答简洁有条理,优先给出解决步骤
- 用"您"称呼用户,偶尔用"亲"、"小伙伴"拉近距离
- 如果用户情绪不好(抱怨、生气),先共情安抚再解决问题
- 当问题超出你的知识范围时,主动引导用户转人工或提供自助查询路径
- 不懂的问题不要瞎编,诚实告知并提供替代方案
​
你支持的服务范围:
- 账户与订单查询指导
- 产品功能使用帮助
- 退换货与售后政策解答
- 支付与发票问题
​
禁止事项:
- 不允许编造订单、退款等具体数字信息
- 不允许索要用户密码、验证码等敏感信息
- 不允许过度闲聊,始终围绕用户需求展开对话
"""
​
def chat_with_jay(user_message, chat_history):
    """
    与蜡笔小 7 客服对话
    chat_history: 之前的对话列表,用于保持上下文
    """
    # 构建完整消息(系统设定 + 历史对话 + 当前问题)
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        *chat_history,
        {"role": "user", "content": user_message}
    ]
​
    try:
        response = client.chat.completions.create(
            model='ZhipuAI/GLM-5.1',
            messages=messages,
            stream=True
        )
        return response
    except Exception as e:
        return f"抱歉,小 7 暂时遇到技术问题({str(e)})。请稍后再试或转人工客服。"
​
​
def main():
    """主函数:运行交互式客服对话"""
    print("\n" + "=" * 50)
    print("🎨 蜡笔小 7 智能客服已上线  🎨")
    print("=" * 50)
    print("💬 您可以咨询:订单问题、产品使用、售后服务等")
    print("💡 输入 'quit' 或 '退出' 结束对话")
    print("-" * 50 + "\n")
​
    chat_history = []  # 保存对话历史
​
    while True:
        # 获取用户输入
        user_input = input("👤 您:").strip()
​
        # 退出判断
        if user_input.lower() in ['quit', 'exit', '退出', 'q']:
            print("\n🎨 蜡笔小 7: 感谢您的咨询,祝您生活愉快!有问题随时来找小 7 哦~🎨\n")
            break
​
        # 跳过空输入
        if not user_input:
            continue
​
        # 调用客服
        print("🎨 蜡笔小 7: ", end="", flush=True)
        reply = chat_with_jay(user_input, chat_history)
        print(reply)
        print()  # 空行分隔
​
        # 保存对话历史(用于多轮上下文)
        chat_history.append({"role": "user", "content": user_input})
        chat_history.append({"role": "assistant", "content": reply})
​
        # 可选:限制历史长度,防止 token 超限
        if len(chat_history) > 20:
            chat_history = chat_history[-20:]
​
​
if __name__ == "__main__":
    main()

2.2 FastAPI服务(main.py)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from hello import chat_with_jay
import asyncio
​
​
app = FastAPI()
​
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
​
​
class ChatRequest(BaseModel):
    message: str
​
@app.post("/api/chat")
async def chat_endpoint(request: ChatRequest):
    response = chat_with_jay(request.message, [])
    
    def stream_response():
        if isinstance(response, str):
            yield response
            return
        for chunk in response:
            if chunk.choices and chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content
    
    return StreamingResponse(stream_response(), media_type="text/plain")
​
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

确保 hello.py 和 main.py 在同一目录下运行python main.py

成功启动显示INFO: Uvicorn running on http://127.0.0.1:8000

三、前端界面开发

3.1 完整前端代码(index.html)


    
    
    蜡笔小7 - 智能客服
    
    
    
    
    
        * {
            scrollbar-width: thin;
            scrollbar-color: #e5e7eb transparent;
        }
        *::-webkit-scrollbar {
            width: 6px;
        }
        *::-webkit-scrollbar-track {
            background: transparent;
        }
        *::-webkit-scrollbar-thumb {
            background-color: #e5e7eb;
            border-radius: 3px;
        }
        .markdown-body p { margin-bottom: 0.5rem; }
        .markdown-body p:last-child { margin-bottom: 0; }
        .markdown-body ol, .markdown-body ul { padding-left: 1.5rem; margin-bottom: 0.5rem; list-style-type: disc;}
        .markdown-body ol { list-style-type: decimal; }
        .markdown-body pre { background-color: #f3f4f6; padding: 0.75rem; border-radius: 0.5rem; overflow-x: auto; margin: 0.5rem 0;}
        .markdown-body code { font-family: 'Fira Code', monospace; background-color: rgba(0,0,0,0.05); padding: 0.1rem 0.3rem; border-radius: 0.25rem; }
        .markdown-body h1, .markdown-body h2, .markdown-body h3 { margin-top: 0.75rem; margin-bottom: 0.5rem; font-weight: 600; }
        .markdown-body h1 { font-size: 1.5em; }
        .markdown-body h2 { font-size: 1.25em; }
        .markdown-body h3 { font-size: 1.1em; }
        .typing-indicator {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 2px 0;
        }
        .typing-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background-color: #9ca3af;
            animation: typing-pulse 1.4s ease-in-out infinite;
        }
        .typing-dot:nth-child(2) { animation-delay: 0.2s; }
        .typing-dot:nth-child(3) { animation-delay: 0.4s; }
        @keyframes typing-pulse {
            0%, 60%, 100% { transform: scale(0.8); opacity: 0.5; }
            30% { transform: scale(1); opacity: 1; }
        }
        @keyframes slideIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        .message-bubble {
            animation: slideIn 0.3s ease-out;
        }
    

​
    
        

            

                🖍️
            

            

                

#                    蜡笔小7
                

                

在线服务中

            

        

        

            
                
                    

                    
                
            
            
                
                    
                
            
        

    
​
    
        

            

                

                    🖍️
                

                

## 蜡笔小7 随时为您服务

                有任何问题都可以问我哦~

            

        

        
        

            

                7
            

            

                嗨~ 我是蜡笔小7!🎨 有什么可以帮助您的吗?
            

        

    
​
    
        

            

                
                    
                        

                    
                
                
                    
                        
                    
                
            

            

                
            

            
                
                    
                
            
        

        

            支持 Markdown 格式
            |
            Shift + Enter 换行
        

    
​
    
        marked.setOptions({
            highlight: function(code, lang) {
                const language = hljs.getLanguage(lang) ? lang : 'plaintext';
                return hljs.highlight(code, { language }).value;
            },
            breaks: true
        });
​
        const chatContainer = document.getElementById('chat-container');
        const userInput = document.getElementById('user-input');
        const sendBtn = document.getElementById('send-btn');
        const API_URL = "http://127.0.0.1:8000/api/chat";
​
        userInput.addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = Math.min(this.scrollHeight, 128) + 'px';
        });
​
        sendBtn.addEventListener('click', sendMessage);
        userInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });
​
        function scrollToBottom() {
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }
​
        function createMessageBubble(sender, initialContent = '') {
            const wrapper = document.createElement('div');
            wrapper.className = `flex items-start space-x-3 message-bubble ${sender === 'user' ? 'flex-row-reverse space-x-reverse' : ''}`;
            
            const avatar = document.createElement('div');
            avatar.className = `w-10 h-10 rounded-2xl flex items-center justify-center text-white font-bold shadow-md shrink-0 ${
                sender === 'user'
                    ? 'bg-gradient-to-br from-blue-500 to-indigo-600'
                    : 'bg-gradient-to-br from-orange-400 to-red-500 shadow-orange-100'
            }`;
            avatar.innerHTML = sender === 'user' ? '我' : '7';
​
            const contentDiv = document.createElement('div');
            contentDiv.className = `p-4 rounded-2xl shadow-sm max-w-[75%] markdown-body ${
                sender === 'user'
                    ? 'bg-gradient-to-br from-blue-500 to-indigo-600 text-white rounded-tr-none'
                    : 'bg-white text-gray-800 rounded-tl-none'
            }`;
            
            if (sender === 'user') {
                contentDiv.innerText = initialContent;
            } else {
                contentDiv.innerHTML = marked.parse(initialContent);
            }
​
            wrapper.appendChild(avatar);
            wrapper.appendChild(contentDiv);
            chatContainer.appendChild(wrapper);
            scrollToBottom();
​
            return contentDiv;
        }
​
        async function sendMessage() {
            const message = userInput.value.trim();
            if (!message) return;
​
            userInput.value = '';
            userInput.style.height = 'auto';
            userInput.disabled = true;
            sendBtn.disabled = true;
​
            createMessageBubble('user', message);
​
            const aiContentDiv = createMessageBubble('ai', '

');
            let aiResponseText = '';
​
            try {
                const response = await fetch(API_URL, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ message: message })
                });
​
                if (!response.ok) throw new Error('网络请求错误');
​
                aiContentDiv.innerHTML = '';
​
                const reader = response.body.getReader();
                const decoder = new TextDecoder('utf-8');
​
                while (true) {
                    const { done, value } = await reader.read();
                    if (done) break;
​
                    const chunk = decoder.decode(value, { stream: true });
                    aiResponseText += chunk;
                    aiContentDiv.innerHTML = marked.parse(aiResponseText);
                    scrollToBottom();
                }
​
            } catch (error) {
                console.error(error);
                aiContentDiv.innerHTML = `发生错误,请稍后再试。`;
            } finally {
                userInput.disabled = false;
                sendBtn.disabled = false;
                userInput.focus();
            }
        }
    

四.结语

这期的蜡笔小7的接入前端可视化界面源码都在这里了,大家能够复制代码去尝试一下,此外下次更新一些学习中的一些过程,边学变更。大家敬请期待。


本次分享就到这里。技术这东西越研究越有意思,后续有新的收获我也会继续更新。

评论 (0)

暂无评论