云计算百科
云计算领域专业知识百科平台

【AI大模型接入SDK】前端页面提示词

头像

🎬 个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:

在这里插入图片描述


文章目录

  • 1 ~> AI 聊天助手前端工程需求与实现
    • 1.1 项目背景
    • 1.2 后端接口完整定义
      • 1.2.1 获取会话列表 `GET /api/sessions`
      • 1.2.2 获取可用模型 `GET /api/models`
      • 1.2.3 创建新会话 `POST /api/session`
      • 1.2.4 获取会话历史消息 `GET /api/session/${session_id}/history`
      • 1.2.5 发送消息 – 流式 SSE 响应 `POST /api/message/async`
      • 1.2.6 删除会话 `DELETE /api/session/${session_id}`
    • 1.3 页面功能需求规约
      • 1.3.1 页面布局
      • 1.3.2 页面生命周期行为
      • 1.3.3 快捷键
    • 2 ~> 完整前端实现代码
      • 2.1 index.html
      • 2.2 style.css
      • 2.3 app.js
  • 3 ~> Trae AI 生成前端页面提示词工程
    • 3.1 提示词编写原则
    • 3.2 后端 C++ httplib 静态资源挂载关键代码片段
  • 4 ~> 部署调试注意事项
    • 4.1 跨域问题
    • 4.2 SSE 流式解析
    • 4.3 XSS 安全防护
    • 4.4 第三方 CDN 依赖
    • 4.5 输入校验
  • 结尾

在这里插入图片描述


1 ~> AI 聊天助手前端工程需求与实现

1.1 项目背景

  • 后端服务地址:192.168.100.238:8080,C++ httplib 实现聊天服务
  • 静态资源挂载:服务将./www目录挂载为/,访问ip:port / ip:port/index.html加载前端页面
  • 技术选型:HTML + CSS + JavaScript;引入 marked 解析 Markdown,highlight.js 实现代码语法高亮
  • 代码分离原则:HTML、CSS、JS 拆分独立文件,降低局部修改带来的牵一发而动全身的副作用,便于接口调试、功能迭代维护
  • 页面原型参考:DeepSeek 网页版聊天界面

1.2 后端接口完整定义

1.2.1 获取会话列表 GET /api/sessions

  • HTTP 状态码:200 OK
  • 响应 JSON 结构

{
"success": true,
"message": "string",
"data": [
{
"id": "string",
"model": "string",
"created_at": "int64_t",
"updated_at": "int64_t",
"message_count": "int",
"first_user_message": "string"
}
]
}

1.2.2 获取可用模型 GET /api/models

  • HTTP 状态码:200 OK
  • 响应 JSON 结构

{
"success": true,
"message": "string",
"data": [
{
"name": "string",
"desc": "string"
}
]
}

1.2.3 创建新会话 POST /api/session

  • 请求体

{
"model": "string"
}

  • HTTP 状态码:200 OK
  • 响应 JSON 结构

{
"success": true,
"message": "string",
"data": {
"sessionid": "string",
"model": "string"
}
}

1.2.4 获取会话历史消息 GET /api/session/${session_id}/history

  • HTTP 状态码:200 OK
  • 响应 JSON 结构

{
"success": true,
"message": "string",
"data": [
{
"id": "string",
"role": "string",
"content": "string",
"timestamp": 0
}
]
}

1.2.5 发送消息 – 流式 SSE 响应 POST /api/message/async

  • 请求体

{
"session_id": "string",
"message": "string"
}

  • HTTP 状态码:200 OK
  • SSE 流式返回格式:

data: 文本数据\\n\\n
data: 文本数据\\n\\n
data: [DONE]\\n\\n

  • [DONE]标记流式输出结束

1.2.6 删除会话 DELETE /api/session/${session_id}

  • HTTP 状态码:200 OK
  • 响应 JSON 结构

{
"success": true,
"message": "string"
}

1.3 页面功能需求规约

1.3.1 页面布局

  • 页面左上角:AI Chat 图标 + AI聊天助手文字 +【新建对话】按钮
  • 左侧侧边栏:历史会话列表
    • 每条会话展示:会话更新时间、会话第一条消息、模型名称、删除按钮(按钮文本…)
    • 点击删除按钮调用后端接口删除服务器对应 session 会话
  • 右侧主区域默认欢迎页面
    • 展示图标 + 提示文本:选择一个历史对话继续,或创建新的对话,开启您的AI之旅
    • 附带【新建对话】按钮
  • 会话创建成功后右侧主区域
    • 85% 高度为聊天消息显示区域;模型消息居左侧展示,用户消息居右侧展示;每条消息附带收发时间
    • 底部输入编辑框:占位提示请输入您的消息… (Enter发送,Shift+Enter换行),最大输入 2000 字符;右侧发送按钮;下方展示当前字数/2000字数统计
  • 模型选择弹窗
    • 点击新建对话触发弹窗,网格布局渲染全部可用模型,单选选择模型,展示模型名称 + 描述
    • 【确认创建对话】:关闭弹窗,向后端发起创建会话请求
    • 【取消】:直接关闭弹窗,不创建会话
    • 同一时间仅允许选中一个模型
  • 1.3.2 页面生命周期行为

  • 浏览器加载index.html完成后,自动请求后端获取会话列表,渲染左侧会话栏
  • 两处新建对话按钮(左上角、欢迎页):先请求后端获取模型列表,用户选定模型确认后创建会话,会话加入左侧会话列表,进入聊天会话界面
  • SSE 流式消息处理
    • 流式返回内容做 Markdown 解析渲染
    • 代码块启用语法高亮,代码块支持一键复制
    • 处理大量流式分片,防止页面 DOM 变形乱码
  • 1.3.3 快捷键

    • Enter:发送消息
    • Shift + Enter:输入框内换行

    2 ~> 完整前端实现代码

    文件目录:www/目录下存放三个文件:index.html、style.css、app.js;后端 httplib 将./www挂载为 web 根路径

    2.1 index.html

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI聊天助手</title>
    <link rel="stylesheet" href="style.css">
    <!– Markdown解析库 marked –>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <!– highlight.js 代码高亮 –>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github.min.css">
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/core.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/javascript.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/python.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/cpp.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/css.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/bash.min.js"></script>
    </head>
    <body>
    <div id="app">
    <!– 左侧会话侧边栏 –>
    <aside class="sidebar">
    <div class="sidebar-header">
    <div class="logo">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
    </svg>
    </div>
    <span class="title">AI聊天助手</span>
    <button id="newChatBtn" class="new-chat-btn" title="新建对话">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <line x1="12" y1="5" x2="12" y2="19"></line>
    <line x1="5" y1="12" x2="19" y2="12"></line>
    </svg>
    新建对话
    </button>
    </div>
    <div id="sessionList" class="session-list">
    <div class="loading">加载中…</div>
    </div>
    </aside>

    <!– 主内容区域 –>
    <main class="main-content">
    <!– 欢迎页面 –>
    <div id="welcomeSection" class="welcome-section">
    <div class="welcome-icon">
    <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
    <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
    </svg>
    </div>
    <p class="welcome-text">选择一个历史对话继续,或创建新的对话,开启您的AI之旅</p>
    <button id="welcomeNewChatBtn" class="primary-btn">新建对话</button>
    </div>

    <!– 聊天会话区域 –>
    <div id="chatSection" class="chat-section" style="display: none;">
    <div id="messagesContainer" class="messages-container"></div>
    <div class="input-area">
    <div class="input-wrapper">
    <textarea id="messageInput" class="message-input"
    placeholder="请输入您的消息…(Enter发送,Shift+Enter换行)"
    maxlength="2000" rows="1">
    </textarea>
    <button id="sendBtn" class="send-btn" disabled>
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <line x1="22" y1="2" x2="11" y2="13"></line>
    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
    </svg>
    </button>
    </div>
    <div class="char-count">
    <span id="charCount">0</span>/2000
    </div>
    </div>
    </div>
    </main>
    </div>

    <!– 模型选择弹窗 –>
    <div id="modelModal" class="modal-overlay" style="display: none;">
    <div class="modal-content">
    <h3>选择模型</h3>
    <div id="modelGrid" class="model-grid">
    <div class="loading">加载中…</div>
    </div>
    <div class="modal-actions">
    <button id="cancelBtn" class="secondary-btn">取消</button>
    <button id="confirmBtn" class="primary-btn" disabled>确认创建对话</button>
    </div>
    </div>
    </div>

    <script src="app.js"></script>
    </body>
    </html>

    2.2 style.css

    * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }

    body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: 14px;
    color: #333;
    background-color: #f7f7f8;
    height: 100vh;
    overflow: hidden;
    }

    #app {
    display: flex;
    height: 100vh;
    }

    /* ========== 侧边栏样式 ========== */
    .sidebar {
    width: 260px;
    background-color: #202123;
    color: #fff;
    display: flex;
    flex-direction: column;
    border-right: 1px solid #2d2d2d;
    }

    .sidebar-header {
    padding: 16px;
    display: flex;
    align-items: center;
    gap: 10px;
    border-bottom: 1px solid #2d2d2d;
    }

    .logo {
    color: #10a37f;
    display: flex;
    align-items: center;
    }

    .title {
    font-size: 16px;
    font-weight: 600;
    flex: 1;
    }

    .new-chat-btn {
    background: transparent;
    border: 1px solid #4d4d4f;
    color: #fff;
    padding: 6px 10px;
    border-radius: 6px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 13px;
    transition: background-color 0.2s;
    }

    .new-chat-btn:hover {
    background-color: #2d2d2d;
    }

    .session-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px;
    }

    .session-item {
    padding: 12px;
    border-radius: 8px;
    cursor: pointer;
    margin-bottom: 4px;
    position: relative;
    transition: background-color 0.2s;
    }

    .session-item:hover {
    background-color: #2d2d2d;
    }

    .session-item.active {
    background-color: #343541;
    }

    .session-first-msg {
    font-size: 14px;
    color: #ececf1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 6px;
    }

    .session-meta {
    font-size: 12px;
    color: #8e8ea0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    }

    .session-model {
    background-color: #343541;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 11px;
    }

    .delete-btn {
    background: none;
    border: none;
    color: #8e8ea0;
    cursor: pointer;
    font-size: 16px;
    padding: 0 4px;
    opacity: 0;
    transition: opacity 0.2s;
    }

    .session-item:hover .delete-btn {
    opacity: 1;
    }

    .delete-btn:hover {
    color: #fff;
    }

    .loading {
    text-align: center;
    color: #8e8ea0;
    padding: 20px;
    }

    /* ========== 主区域 ========== */
    .main-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    position: relative;
    }

    .welcome-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    color: #6e6e80;
    }

    .welcome-icon {
    color: #10a37f;
    }

    .welcome-text {
    font-size: 16px;
    text-align: center;
    max-width: 400px;
    line-height: 1.6;
    }

    .primary-btn {
    background-color: #10a37f;
    color: #fff;
    border: none;
    padding: 10px 24px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: background-color 0.2s;
    }

    .primary-btn:hover:not(:disabled) {
    background-color: #0d8a6c;
    }

    .primary-btn:disabled {
    background-color: #a8d8cd;
    cursor: not-allowed;
    }

    .secondary-btn {
    background-color: #f0f0f0;
    color: #333;
    border: none;
    padding: 10px 24px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 14px;
    transition: background-color 0.2s;
    }

    .secondary-btn:hover {
    background-color: #e5e5e5;
    }

    .chat-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    height: 100%;
    }

    .messages-container {
    flex: 1;
    overflow-y: auto;
    padding: 20px 0;
    height: calc(85vh – 60px);
    }

    .message {
    max-width: 800px;
    margin: 0 auto 24px;
    padding: 0 20px;
    display: flex;
    gap: 16px;
    }

    .message.user {
    flex-direction: row-reverse;
    }

    .message-avatar {
    width: 36px;
    height: 36px;
    border-radius: 6px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 14px;
    }

    .message.assistant .message-avatar {
    background-color: #10a37f;
    color: #fff;
    }

    .message.user .message-avatar {
    background-color: #e5e5e5;
    color: #333;
    }

    .message-content {
    flex: 1;
    min-width: 0;
    }

    .message-text {
    line-height: 1.6;
    word-wrap: break-word;
    }

    .message-text p {
    margin-bottom: 12px;
    }

    .message-text p:last-child {
    margin-bottom: 0;
    }

    .message-text pre {
    background-color: #f6f8fa;
    border-radius: 6px;
    padding: 12px;
    overflow-x: auto;
    margin: 12px 0;
    position: relative;
    }

    .message-text code {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 13px;
    }

    .message-text pre code {
    background: none;
    padding: 0;
    }

    .message-text :not(pre) > code {
    background-color: #f0f2f5;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 13px;
    }

    .copy-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    background: #fff;
    border: 1px solid #d0d7de;
    border-radius: 4px;
    padding: 4px 8px;
    cursor: pointer;
    font-size: 12px;
    opacity: 0;
    transition: opacity 0.2s;
    }

    .message-text pre:hover .copy-btn {
    opacity: 1;
    }

    .message-time {
    font-size: 12px;
    color: #999;
    margin-top: 6px;
    }

    .message.user .message-time {
    text-align: right;
    }

    /* ========== 输入区域 ========== */
    .input-area {
    border-top: 1px solid #e5e5e5;
    padding: 16px 20px;
    background-color: #fff;
    }

    .input-wrapper {
    max-width: 800px;
    margin: 0 auto;
    position: relative;
    border: 1px solid #d0d0d5;
    border-radius: 12px;
    background-color: #fff;
    transition: border-color 0.2s;
    }

    .input-wrapper:focus-within {
    border-color: #10a37f;
    box-shadow: 0 0 0 2px rgba(16, 163, 127, 0.1);
    }

    .message-input {
    width: 100%;
    border: none;
    outline: none;
    resize: none;
    padding: 12px 50px 12px 16px;
    font-size: 14px;
    font-family: inherit;
    line-height: 1.5;
    max-height: 200px;
    border-radius: 12px;
    background: transparent;
    }

    .send-btn {
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    border: none;
    background-color: #10a37f;
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.2s;
    }

    .send-btn:hover:not(:disabled) {
    background-color: #0d8a6c;
    }

    .send-btn:disabled {
    background-color: #d0d0d5;
    cursor: not-allowed;
    }

    .char-count {
    max-width: 800px;
    margin: 6px auto 0;
    text-align: right;
    font-size: 12px;
    color: #999;
    }

    /* ========== 模型弹窗 ========== */
    .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    }

    .modal-content {
    background-color: #fff;
    border-radius: 12px;
    width: 600px;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    }

    .modal-content h3 {
    padding: 20px 24px;
    border-bottom: 1px solid #e5e5e5;
    font-size: 18px;
    font-weight: 600;
    }

    .model-grid {
    flex: 1;
    overflow-y: auto;
    padding: 20px 24px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
    }

    .model-card {
    border: 1px solid #d0d0d5;
    border-radius: 8px;
    padding: 16px;
    cursor: pointer;
    transition: all 0.2s;
    position: relative;
    }

    .model-card:hover {
    border-color: #10a37f;
    }

    .model-card.selected {
    border-color: #10a37f;
    background-color: rgba(16, 163, 127, 0.05);
    }

    .model-card input[type="radio"] {
    position: absolute;
    top: 16px;
    right: 16px;
    accent-color: #10a37f;
    }

    .model-name {
    font-weight: 600;
    margin-bottom: 8px;
    padding-right: 24px;
    }

    .model-desc {
    font-size: 13px;
    color: #6e6e80;
    line-height: 1.5;
    }

    .modal-actions {
    padding: 16px 24px;
    border-top: 1px solid #e5e5e5;
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    }

    /* 滚动条美化 */
    ::-webkit-scrollbar {
    width: 6px;
    }
    ::-webkit-scrollbar-track {
    background: transparent;
    }
    ::-webkit-scrollbar-thumb {
    background-color: #c1c1c1;
    border-radius: 3px;
    }
    .sidebar ::-webkit-scrollbar-thumb {
    background-color: #565869;
    }

    2.3 app.js

    // ========== 后端服务基础配置 ==========
    const BASE_URL = 'http://192.168.100.238:8080';
    const MAX_CHARS = 2000;

    // ========== 全局状态变量 ==========
    let currentSessionId = null;
    let selectedModel = null;
    let isSending = false;

    // ========== DOM节点缓存 ==========
    const elements = {
    sessionList: document.getElementById('sessionList'),
    newChatBtn: document.getElementById('newChatBtn'),
    welcomeNewChatBtn: document.getElementById('welcomeNewChatBtn'),
    welcomeSection: document.getElementById('welcomeSection'),
    chatSection: document.getElementById('chatSection'),
    messagesContainer: document.getElementById('messagesContainer'),
    messageInput: document.getElementById('messageInput'),
    sendBtn: document.getElementById('sendBtn'),
    charCount: document.getElementById('charCount'),
    modelModal: document.getElementById('modelModal'),
    modelGrid: document.getElementById('modelGrid'),
    confirmBtn: document.getElementById('confirmBtn'),
    cancelBtn: document.getElementById('cancelBtn')
    };

    // ========== 页面初始化入口 ==========
    document.addEventListener('DOMContentLoaded', init);

    function init() {
    // marked Markdown解析配置,对接highlight.js做代码高亮
    marked.setOptions({
    highlight: function (code, lang) {
    if (lang && hljs.getLanguage(lang)) {
    try {
    return hljs.highlight(code, {language: lang}).value;
    } catch (e) {
    }
    }
    return code;
    },
    breaks: true
    });
    loadSessions();
    bindEvents();
    }

    // ========== 事件绑定 ==========
    function bindEvents() {
    elements.newChatBtn.addEventListener('click', openModelModal);
    elements.welcomeNewChatBtn.addEventListener('click', openModelModal);

    elements.cancelBtn.addEventListener('click', closeModelModal);
    elements.confirmBtn.addEventListener('click', handleCreateSession);

    elements.sendBtn.addEventListener('click', handleSendMessage);
    elements.messageInput.addEventListener('keydown', handleInputKeydown);
    elements.messageInput.addEventListener('input', handleInputChange);

    // 点击遮罩层关闭弹窗
    elements.modelModal.addEventListener('click', (e) => {
    if (e.target === elements.modelModal) {
    closeModelModal();
    }
    });
    }

    // ========== API请求封装 ==========
    /**
    * 获取全部会话列表
    */

    async function fetchSessions() {
    const res = await fetch(`${BASE_URL}/api/sessions`);
    const data = await res.json();
    if (data.success) {
    return data.data;
    }
    throw new Error(data.message || '获取会话列表失败');
    }

    /**
    * 获取后端支持模型列表
    */

    async function fetchModels() {
    const res = await fetch(`${BASE_URL}/api/models`);
    const data = await res.json();
    if (data.success) {
    return data.data;
    }
    throw new Error(data.message || '获取模型列表失败');
    }

    /**
    * 创建会话
    * @param {string} model 模型名称
    */

    async function createSession(model) {
    const res = await fetch(`${BASE_URL}/api/session`, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({model})
    });
    const data = await res.json();
    if (data.success) {
    return data.data;
    }
    throw new Error(data.message || '创建会话失败');
    }

    /**
    * 删除指定会话
    * @param {string} sessionId
    */

    async function deleteSession(sessionId) {
    const res = await fetch(`${BASE_URL}/api/session/${sessionId}`, {
    method: 'DELETE'
    });
    const data = await res.json();
    if (!data.success) {
    throw new Error(data.message || '删除会话失败');
    }
    }

    /**
    * 获取会话历史消息
    * @param {string} sessionId
    */

    async function fetchSessionHistory(sessionId) {
    const res = await fetch(`${BASE_URL}/api/session/${sessionId}/history`);
    const data = await res.json();
    if (data.success) {
    return data.data;
    }
    throw new Error(data.message || '获取会话历史失败');
    }

    /**
    * SSE流式发送消息,XMLHttpRequest实现SSE分片解析
    * @param {string} sessionId
    * @param {string} message 用户输入消息
    * @param {Function} onMessage 分片回调
    * @param {Function} onDone 结束/异常回调
    */

    function sendMessageStream(sessionId, message, onMessage, onDone) {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', `${BASE_URL}/api/message/async`);
    xhr.setRequestHeader('Content-Type', 'application/json');

    let received = '';
    let lastPos = 0;

    xhr.onprogress = function () {
    received = xhr.responseText;
    const newData = received.slice(lastPos);
    lastPos = received.length;
    const lines = newData.split('\\n\\n');
    for (const line of lines) {
    if (line.startsWith('data: ')) {
    const content = line.slice(6);
    if (content === '[DONE]') {
    onDone && onDone(null);
    return;
    }
    onMessage && onMessage(content);
    }
    }
    };

    xhr.onload = function () {
    if (xhr.status !== 200) {
    onDone && onDone(new Error(`HTTP ${xhr.status} 请求失败`));
    }
    };

    xhr.onerror = function () {
    onDone && onDone(new Error('网络请求异常'));
    };

    xhr.send(JSON.stringify({session_id: sessionId, message}));
    }

    // ========== 业务逻辑函数 ==========
    /**
    * 加载会话列表并渲染侧边栏
    */

    async function loadSessions() {
    try {
    const sessions = await fetchSessions();
    renderSessions(sessions);
    } catch (e) {
    elements.sessionList.innerHTML = '<div class="loading">加载失败</div>';
    console.error(e);
    }
    }

    /**
    * 渲染会话侧边栏
    * @param {Array} sessions
    */

    function renderSessions(sessions) {
    if (sessions.length === 0) {
    elements.sessionList.innerHTML = '<div class="loading">暂无会话</div>';
    return;
    }
    // 更新时间倒序排序
    sessions.sort((a, b) => b.updated_at – a.updated_at);

    elements.sessionList.innerHTML = sessions.map(session => `
    <div class="session-item
    ${session.id === currentSessionId ? 'active' : ''}" data-id="${escapeHtml(session.id)}">
    <div class="session-first-msg">
    ${escapeHtml(session.first_user_message || '新对话')}</div>
    <div class="session-meta">
    <span class="session-model">
    ${escapeHtml(session.model)}</span>
    <span>
    ${formatTime(session.updated_at)}</span>
    <button class="delete-btn" data-id="
    ${escapeHtml(session.id)}" title="删除">…</button>
    </div>
    </div>
    `
    ).join('');

    // 会话点击切换事件
    document.querySelectorAll('.session-item').forEach(item => {
    item.addEventListener('click', (e) => {
    if (e.target.classList.contains('delete-btn')) return;
    const id = item.dataset.id;
    switchToSession(id);
    });
    });

    // 删除会话按钮事件
    document.querySelectorAll('.delete-btn').forEach(btn => {
    btn.addEventListener('click', async (e) => {
    e.stopPropagation();
    const id = btn.dataset.id;
    if (confirm('确定要删除这个会话吗?')) {
    try {
    await deleteSession(id);
    if (currentSessionId === id) {
    switchToWelcome();
    }
    loadSessions();
    } catch (err) {
    alert('删除失败:' + err.message);
    }
    }
    });
    });
    }

    /**
    * 打开模型选择弹窗,加载模型列表
    */

    async function openModelModal() {
    elements.modelModal.style.display = 'flex';
    elements.modelGrid.innerHTML = '<div class="loading">加载中…</div>';
    elements.confirmBtn.disabled = true;
    selectedModel = null;
    try {
    const models = await fetchModels();
    renderModels(models);
    } catch (e) {
    elements.modelGrid.innerHTML = '<div class="loading">加载失败</div>';
    console.error(e);
    }
    }

    /**
    * 渲染模型卡片网格
    * @param {Array} models
    */

    function renderModels(models) {
    elements.modelGrid.innerHTML = models.map(model => `
    <div class="model-card" data-name="
    ${escapeHtml(model.name)}">
    <input type="radio" name="model" value="
    ${escapeHtml(model.name)}">
    <div class="model-name">
    ${escapeHtml(model.name)}</div>
    <div class="model-desc">
    ${escapeHtml(model.desc)}</div>
    </div>
    `
    ).join('');

    document.querySelectorAll('.model-card').forEach(card => {
    card.addEventListener('click', () => {
    document.querySelectorAll('.model-card').forEach(c => c.classList.remove('selected'));
    card.classList.add('selected');
    card.querySelector('input').checked = true;
    selectedModel = card.dataset.name;
    elements.confirmBtn.disabled = false;
    });
    });
    }

    /**
    * 关闭模型弹窗
    */

    function closeModelModal() {
    elements.modelModal.style.display = 'none';
    }

    /**
    * 确认创建会话
    */

    async function handleCreateSession() {
    if (!selectedModel) return;
    elements.confirmBtn.disabled = true;
    elements.confirmBtn.textContent = '创建中…';
    try {
    const result = await createSession(selectedModel);
    currentSessionId = result.sessionid;
    closeModelModal();
    switchToChat();
    loadSessions();
    } catch (e) {
    alert('创建会话失败:' + e.message);
    } finally {
    elements.confirmBtn.disabled = false;
    elements.confirmBtn.textContent = '确认创建对话';
    }
    }

    /**
    * 切换到指定会话,加载历史消息
    * @param {string} sessionId
    */

    async function switchToSession(sessionId) {
    currentSessionId = sessionId;
    switchToChat();
    loadSessions();
    elements.messagesContainer.innerHTML = '<div class="loading">加载中…</div>';
    try {
    const history = await fetchSessionHistory(sessionId);
    renderHistory(history);
    } catch (e) {
    elements.messagesContainer.innerHTML = '<div class="loading">加载历史消息失败</div>';
    console.error(e);
    }
    }

    /**
    * 渲染会话全部历史消息
    * @param {Array} messages
    */

    function renderHistory(messages) {
    elements.messagesContainer.innerHTML = '';
    for (const msg of messages) {
    addMessage(msg.role, msg.content, msg.timestamp);
    }
    scrollToBottom();
    }

    /**
    * 切换聊天界面
    */

    function switchToChat() {
    elements.welcomeSection.style.display = 'none';
    elements.chatSection.style.display = 'flex';
    elements.messageInput.focus();
    }

    /**
    * 切回欢迎首页
    */

    function switchToWelcome() {
    currentSessionId = null;
    elements.welcomeSection.style.display = 'flex';
    elements.chatSection.style.display = 'none';
    loadSessions();
    }

    /**
    * 发送消息主逻辑
    */

    async function handleSendMessage() {
    if (isSending) return;
    const message = elements.messageInput.value.trim();
    if (!message || !currentSessionId) return;

    isSending = true;
    elements.sendBtn.disabled = true;
    elements.messageInput.disabled = true;

    // 渲染用户消息
    addMessage('user', message, Date.now());
    elements.messageInput.value = '';
    updateCharCount();

    // AI消息占位容器
    const aiMsgElement = addMessage('assistant', '', Date.now());
    const msgContentEl = aiMsgElement.querySelector('.message-text');
    let fullContent = '';

    sendMessageStream(
    currentSessionId,
    message,
    (chunk) => {
    fullContent += chunk;
    msgContentEl.innerHTML = renderMarkdown(fullContent);
    addCopyButtons(msgContentEl);
    scrollToBottom();
    },
    (err) => {
    isSending = false;
    elements.sendBtn.disabled = false;
    elements.messageInput.disabled = false;
    elements.messageInput.focus();
    if (err) {
    msgContentEl.innerHTML += `<p style="color: red;">出错了:${escapeHtml(err.message)}</p>`;
    }
    const timeEl = aiMsgElement.querySelector('.message-time');
    if (timeEl) {
    timeEl.textContent = formatTime(Date.now());
    }
    loadSessions();
    }
    );
    scrollToBottom();
    }

    /**
    * 添加单条消息DOM
    * @param {string} role user / assistant
    * @param {string} content
    * @param {number} timestamp
    * @returns {HTMLElement}
    */

    function addMessage(role, content, timestamp) {
    const msgDiv = document.createElement('div');
    msgDiv.className = `message ${role}`;
    const avatar = role === 'assistant' ? 'AI' : '我';
    msgDiv.innerHTML = `
    <div class="message-avatar">
    ${avatar}</div>
    <div class="message-content">
    <div class="message-text">
    ${renderMarkdown(content)}</div>
    <div class="message-time">
    ${formatTime(timestamp)}</div>
    </div>
    `
    ;
    elements.messagesContainer.appendChild(msgDiv);
    addCopyButtons(msgDiv);
    scrollToBottom();
    return msgDiv;
    }

    /**
    * Markdown渲染,异常降级转义HTML
    * @param {string} text
    * @returns {string}
    */

    function renderMarkdown(text) {
    if (!text) return '';
    try {
    return marked.parse(text);
    } catch (e) {
    return escapeHtml(text);
    }
    }

    /**
    * 给pre代码块附加复制按钮
    * @param {HTMLElement} container
    */

    function addCopyButtons(container) {
    const pres = container.querySelectorAll('pre');
    pres.forEach(pre => {
    if (pre.querySelector('.copy-btn')) return;
    const btn = document.createElement('button');
    btn.className = 'copy-btn';
    btn.textContent = '复制';
    btn.addEventListener('click', () => {
    const code = pre.querySelector('code');
    if (code) {
    navigator.clipboard.writeText(code.innerText).then(() => {
    btn.textContent = '已复制';
    setTimeout(() => btn.textContent = '复制', 2000);
    });
    }
    });
    pre.appendChild(btn);
    });
    }

    /**
    * 输入框键盘事件 Enter发送 Shift+Enter换行
    * @param {KeyboardEvent} e
    */

    function handleInputKeydown(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    handleSendMessage();
    }
    }

    /**
    * 输入框内容变化,更新字数、自动高度
    */

    function handleInputChange() {
    updateCharCount();
    elements.sendBtn.disabled = !elements.messageInput.value.trim() || isSending;
    elements.messageInput.style.height = 'auto';
    elements.messageInput.style.height = Math.min(elements.messageInput.scrollHeight, 200) + 'px';
    }

    /**
    * 更新输入框字数统计
    */

    function updateCharCount() {
    const len = elements.messageInput.value.length;
    elements.charCount.textContent = len;
    }

    /**
    * 消息容器滚动到底部
    */

    function scrollToBottom() {
    elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight;
    }

    // ========== 通用工具函数 ==========
    /**
    * 时间格式化,毫秒时间戳转友好中文时间
    * @param {number} timestamp
    * @returns {string}
    */

    function formatTime(timestamp) {
    const date = new Date(timestamp);
    const now = new Date();
    // 今天
    if (date.toDateString() === now.toDateString()) {
    return date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
    }
    // 昨天
    const yesterday = new Date(now);
    yesterday.setDate(yesterday.getDate() – 1);
    if (date.toDateString() === yesterday.toDateString()) {
    return '昨天 ' + date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
    }
    // 本年度
    if (date.getFullYear() === now.getFullYear()) {
    return date.toLocaleDateString('zh-CN', {month: '2-digit', day: '2-digit'}) + ' ' +
    date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
    }
    return date.toLocaleDateString('zh-CN');
    }

    /**
    * HTML特殊字符转义,防止XSS
    * @param {string} str
    * @returns {string}
    */

    function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
    }


    3 ~> Trae AI 生成前端页面提示词工程

    3.1 提示词编写原则

    • 将完整接口定义、页面交互需求、样式约束全部写入文本文件Chat前端页面需求说明.txt
    • 在 Trae Builder 对话窗口,使用#引用 File 类型的需求说明 txt 文件,选择 DeepSeek 模型,Auto 模式生成代码
    • 优势:避免对话框输入字数限制,完整传递全部需求,便于修改维护需求文档,无需重复粘贴大段提示词

    3.2 后端 C++ httplib 静态资源挂载关键代码片段

    // 设置http路由
    sethttpRoutes();
    // 挂载www目录为web静态资源根目录
    // 访问 http://ip:port 自动映射 index.html
    _chaterver->set_mount_point("/", "./www");

    // 后台子线程运行http服务,不阻塞主线程
    std::thread serverThread([this](){
    _chaterver->listen(_config.host, _config.port);
    INFO("ChatServer start on {}:{}",_config.host,_config.port);
    });
    serverThread.detach();
    _isRunning.store(true);
    INFO("ChatServer start success!!!");

    • 前端三个文件全部放置服务器./www目录
    • 访问路径:http://192.168.100.238:8080/index.html,简写http://192.168.100.238:8080等价访问 index.html

    4 ~> 部署调试注意事项

    4.1 跨域问题

    • 前端直接浏览器本地打开 html 文件会触发 CORS 跨域;优先通过后端 httplib 服务访问页面
    • 后端服务必须配置 CORS 响应头,允许前端页面发起 fetch / XMLHttpRequest 请求

    4.2 SSE 流式解析

    • 使用XMLHttpRequest处理 SSE 分片,按\\n\\n分割事件块,识别data: 前缀,遇到[DONE]结束流式会话
    • 不要一次性等待 response 全部返回,依靠onprogress事件逐块处理输出

    4.3 XSS 安全防护

    • 所有后端返回的原始文本,渲染 DOM 前使用escapeHtml()做 HTML 转义;Markdown 渲染由 marked 库处理。

    4.4 第三方 CDN 依赖

    • marked、highlight.js 使用公共 CDN;离线部署场景需要将 js/css 下载放到 www 目录,修改 html 中引用路径。

    4.5 输入校验

    • 前端限制最大输入字符 2000;后端接口同样要做字符长度校验,不能只依赖前端校验。

    结尾

    uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

    艾莉丝努力练剑

    C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主


    👀
    【关注】 跟随我一起深耕技术领域,见证每一次成长。

    ❤️
    【点赞】 让优质内容被更多人看见,让知识传递更有力量。

    ⭐
    【收藏】 把核心知识点存好,在需要时随时查、随时用。

    💬
    【评论】 分享你的经验或疑问,评论区一起交流避坑!

    不要忘记给博主“一键四连”哦!

    “今日练剑达成!”

    “技术之路难免有困惑,但同行的人会让前进更有方向。”

    结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

    往期回顾:

    【AI大模型接入SDK】ChatServer接口测试

    🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

    ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【AI大模型接入SDK】前端页面提示词
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!