GPT对话UI--通义千问API

GPT对话UI

项目介绍

一个基于 GPT 的智能对话界面,提供简洁优雅的用户体验。本项目使用纯前端技术栈实现,无需后端服务器即可运行。

功能特点

  • 💬 实时对话:支持与 AI 进行实时对话交互
  • 🌓 主题切换:支持浅色/深色主题,自动跟随系统设置
  • 📝 Markdown 渲染:AI 回复支持 Markdown 格式,包含代码高亮
  • 💾 本地存储:对话历史保存在本地,刷新页面不会丢失
  • 🔍 智能推荐:每次对话后自动推荐相关问题
  • ⌨️ 快捷操作:支持快捷键发送消息,Shift+Enter 换行
  • 📊 代码优化:支持代码块语法高亮和一键复制
  • 🔧 可配置:支持配置 API 密钥
  • 📱 响应式:适配不同屏幕尺寸
  • 🎨 字体调节:支持动态调整界面字体大小

快速开始

  1. 克隆项目到本地
bash 复制代码
git clone https://gitee.com/anxwefndu/gpt-chat-ui.git
  1. 打开项目目录
bash 复制代码
cd gpt-chat-ui
  1. 在浏览器中打开 index.html 文件即可使用

使用说明

  1. 首次使用需要配置 API 密钥

    • 点击左侧边栏的"配置密钥"按钮
    • 输入你的 API 密钥
    • 点击保存即可使用
  2. 基本操作

    • 在输入框输入问题后点击发送或按回车键发送
    • 使用 Shift + Enter 可以在输入框换行
    • 点击推荐问题可以快速发送相关提问
    • 可以通过左侧的滑块调整字体大小
    • 点击主题按钮切换深色/浅色模式
  3. 历史记录

    • 所有对话记录会自动保存在本地
    • 可以通过"清除历史记录"按钮清空所有对话
    • 刷新页面不会丢失历史记录

技术栈

  • HTML5
  • CSS3 (Tailwind CSS)
  • JavaScript
  • Marked.js (Markdown 渲染)
  • Highlight.js (代码高亮)
  • Font Awesome (图标)

注意事项

  • 本项目需要有效的 API 密钥才能正常使用
  • 建议使用现代浏览器访问以获得最佳体验
  • 所有数据均存储在本地,清除浏览器数据会导致历史记录丢失
  • 目前文件上传这一块还没来得及加,后续添加上该块内容

源码下载

GPT对话UI

演示截图

1.系统首页

2.使用说明

3.配色API-key

4.系统首页-暗色

核心代码

index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI 智能助手</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="./resource/marked.min.js"></script>
  <link rel="stylesheet" href="./resource/github.min.css">
  <script src="./resource/highlight.min.js"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#4A90E2',
            secondary: '#E3F2FD',
            'primary-dark': '#2B4C7E',
            'text-dark': '#E2E8F0'  // 暗色主题下的主要文字颜色
          },
          borderRadius: {
            'none': '0px',
            'sm': '2px',
            DEFAULT: '4px',
            'md': '8px',
            'lg': '12px',
            'xl': '16px',
            '2xl': '20px',
            '3xl': '24px',
            'full': '9999px',
            'button': '4px'
          }
        }
      },
      darkMode: 'class' // 启用暗色模式
    }
</script>
  <style>
    /* 保留原有样式 */
    .message-bubble {
      max-width: 80%;
      margin: 8px 0;
      padding: 12px 16px;
      border-radius: 12px;
      width: fit-content;
    }

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

    .ai-message {
      background-color: #F5F5F5;
      margin-right: auto;
      border-top-left-radius: 4px;
    }

    /* 自定义滚动条样式 */
    .custom-scrollbar::-webkit-scrollbar {
      width: 8px;
    }

    .custom-scrollbar::-webkit-scrollbar-track {
      background: #f1f1f1;
      border-radius: 4px;
    }

    .custom-scrollbar::-webkit-scrollbar-thumb {
      background: #c1c1c1;
      border-radius: 4px;
    }

    .custom-scrollbar::-webkit-scrollbar-thumb:hover {
      background: #a8a8a8;
    }

    /* 暗色主题样式 */
    .dark .message-bubble.ai-message {
      background-color: #2D3748;
    }

    .dark .message-bubble.user-message {
      background-color: #2B4C7E;
    }

    .dark .custom-scrollbar::-webkit-scrollbar-track {
      background: #1A202C;
    }

    .dark .custom-scrollbar::-webkit-scrollbar-thumb {
      background: #4A5568;
    }

    .dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
      background: #718096;
    }

    input[type="range"] {
      appearance: none;
      width: 100%;
      height: 4px;
      background: #E5E7EB;
      border-radius: 2px;
    }

    input[type="range"]::-webkit-slider-thumb {
      appearance: none;
      width: 16px;
      height: 16px;
      background: #4A90E2;
      border-radius: 50%;
      cursor: pointer;
    }

    /* 添加 Markdown 样式 */
    .markdown-body {
      font-size: 1em;
      line-height: 1.6;
    }

    .markdown-body h1,
    .markdown-body h2,
    .markdown-body h3,
    .markdown-body h4,
    .markdown-body h5,
    .markdown-body h6 {
      margin-top: 1.5em;
      margin-bottom: 0.5em;
      font-weight: 600;
    }

    .markdown-body code {
      color: #0f172a;
      padding: 0.2em 0.4em;
      font-size: 0.9em;
      background-color: rgba(175, 184, 193, 0.2);
      border-radius: 6px;
    }

    .markdown-body pre {
      padding: 16px;
      overflow: auto;
      border-radius: 6px;
      background-color: #f6f8fa;
      margin: 1em 0;
    }

    .markdown-body pre code {
      padding: 0;
      background-color: #f6f8fa;
      white-space: pre;
      font-size: 0.95em;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    }

    .dark .markdown-body pre {
      background-color: #f6f8fa;
    }

    /* 代码块内的复制按钮样式 */
    .copy-button {
      position: absolute;
      top: 8px;
      right: 8px;
      padding: 4px 8px;
      font-size: 12px;
      color: #64748b;
      background-color: #f1f5f9;
      border: 1px solid #e2e8f0;
      border-radius: 4px;
      opacity: 0;
      transition: all 0.2s ease;
    }

    .copy-button:hover {
      color: #0f172a;
      background-color: #e2e8f0;
      border-color: #cbd5e1;
    }

    /* 暗色主题下的复制按钮 */
    .dark .copy-button {
      color: #94a3b8;
      background-color: #1e293b;
      border-color: #334155;
    }

    .dark .copy-button:hover {
      color: #f1f5f9;
      background-color: #334155;
      border-color: #475569;
    }

    /* 鼠标悬停在代码块上时显示复制按钮 */
    .markdown-body pre:hover .copy-button {
      opacity: 1;
    }

    /* 代码高亮主题颜色优化 */
    .markdown-body code {
      padding: 0.2em 0.4em;
      font-size: 0.95em;
      background-color: #f1f5f9;
      border-radius: 4px;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    }

    .markdown-body ul,
    .markdown-body ol {
      padding-left: 2em;
      margin: 1em 0;
    }

    .markdown-body li {
      margin: 0.5em 0;
    }

    .markdown-body p {
      margin: 1em 0;
    }

    .markdown-body blockquote {
      padding: 0 1em;
      color: #57606a;
      border-left: 0.25em solid #d0d7de;
      margin: 1em 0;
    }

    .dark .markdown-body blockquote {
      color: #8b949e;
      border-left-color: #3b434b;
    }

    .markdown-body table {
      border-collapse: collapse;
      margin: 1em 0;
      width: 100%;
    }

    .markdown-body table th,
    .markdown-body table td {
      padding: 6px 13px;
      border: 1px solid #d0d7de;
    }

    .dark .markdown-body table th,
    .dark .markdown-body table td {
      border-color: #3b434b;
    }

    .markdown-body table tr:nth-child(2n) {
      background-color: #f6f8fa;
    }

    .dark .markdown-body table tr:nth-child(2n) {
      background-color: #161b22;
    }
  </style>
</head>

<body class="bg-white dark:bg-gray-900 transition-colors">
  <div class="flex h-screen">
    <!-- 修改侧边栏背景色,使其与主内容区一致 -->
    <aside class="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 flex flex-col">

      <div class="p-6 border-b border-gray-200">
        <h1 class="text-2xl font-['Pacifico'] text-primary">logo</h1>
      </div>

      <nav class="flex-1 p-6 space-y-6">
        <div class="space-y-2">
          <label class="text-sm font-medium text-gray-700 dark:text-gray-300">主题模式</label>
          <button
            onclick="toggleTheme()"
            class="flex items-center justify-between w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 rounded-button hover:bg-gray-100 dark:hover:bg-gray-600">
            <span id="themeText">浅色模式</span>
            <i class="fas fa-sun dark:fa-moon text-primary"></i>
          </button>
        </div>

        <div class="space-y-2">
          <label class="text-sm font-medium text-gray-700 dark:text-gray-300">字体大小</label>
          <div class="flex items-center space-x-2">
            <span class="text-xs text-gray-500 dark:text-gray-400">小</span>
            <input type="range" min="1" max="3" value="2" class="w-full">
            <span class="text-xs text-gray-500 dark:text-gray-400">大</span>
          </div>
        </div>

        <button
          class="flex items-center w-full px-4 py-2 text-sm text-white bg-primary dark:bg-primary-dark rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80 whitespace-nowrap">
          <i class="fas fa-trash-alt mr-2"></i>
          清除历史记录
        </button>

        <button onclick="showHelp()"
          class="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-button hover:bg-gray-100 dark:hover:bg-gray-700 whitespace-nowrap">
          <i class="fas fa-question-circle mr-2"></i>
          使用帮助
        </button>

        <button onclick="showConfig()"
                class="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-button hover:bg-gray-100 dark:hover:bg-gray-700 whitespace-nowrap">
          <i class="fas fa-cog mr-2"></i>
          配置密钥
        </button>
      </nav>
    </aside>

    <main class="flex-1 flex flex-col">
      <div class="flex-1 overflow-y-auto p-6 space-y-4 custom-scrollbar"></div>

      <div class="border-t border-gray-200 dark:border-gray-700 p-6">
        <div class="relative">
          <textarea
            class="w-full h-24 px-4 py-3 text-gray-700 dark:text-text-dark border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 rounded-lg focus:outline-none focus:border-primary resize-none"
            placeholder="输入你的问题..."></textarea>
          <div class="absolute bottom-3 right-3 flex space-x-2">
            <button class="p-2 text-gray-400 hover:text-primary dark:hover:text-primary">
              <i class="fas fa-paperclip"></i>
            </button>
            <button class="px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80 whitespace-nowrap">
              发送 <i class="fas fa-paper-plane ml-2"></i>
            </button>
          </div>
        </div>
        <div class="mt-2 flex flex-wrap gap-2">
          <span class="px-3 py-1 text-sm text-primary dark:text-gray-300 bg-secondary dark:bg-gray-800 rounded-full cursor-pointer hover:bg-primary hover:text-white dark:hover:bg-primary-dark">
            如何优化代码?
          </span>
          <span class="px-3 py-1 text-sm text-primary bg-secondary dark:bg-gray-700 rounded-full cursor-pointer hover:bg-primary hover:text-white">
            写一篇营销文案
          </span>
          <span class="px-3 py-1 text-sm text-primary bg-secondary dark:bg-gray-700 rounded-full cursor-pointer hover:bg-primary hover:text-white">
            数据分析方法
          </span>
        </div>
      </div>
    </main>
  </div>

  <script>
    function toggleTheme() {
      const html = document.documentElement;
      const themeText = document.getElementById('themeText');
      const isDark = html.classList.toggle('dark');
      themeText.textContent = isDark ? '深色模式' : '浅色模式';
      localStorage.setItem('theme', isDark ? 'dark' : 'light');
    }

    // 初始化主题
    if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
      document.documentElement.classList.add('dark');
      document.getElementById('themeText').textContent = '深色模式';
    }

    function callGpt(questionText, onProgress, onDone) {
      const sk = loadSK();

      // 获取历史消息记录
      const messages = loadMessages();
      // 构建消息历史
      const messageHistory = messages.map(msg => ({
        role: msg.role === 'user' ? 'user' : 'assistant',
        content: msg.content
      }));

      // 添加当前问题
      messageHistory.push({
        role: 'user',
        content: questionText
      });

      fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
        method: "post",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${sk}`
        },
        body: JSON.stringify({
          model: "qwen-plus",
          messages: messageHistory,  // 使用完整的消息历史
          stream: true
        }),
      }).then(response => {
        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let fullText = "";

        function read() {
          reader.read().then(({done, value}) => {
            if (done) {
              onDone(fullText);
              return;
            }
            const text = decoder.decode(value, {stream: true});
            const chunks = text.replace("data: [DONE]", "").replace("data: ", "").split("data: ");
            for (let i = 0; i < chunks.length; i++) {
              try {
                const obj = JSON.parse(chunks[i]);
                const choices = obj.choices;
                const delta = choices[0].delta;
                fullText += delta.content;

                // 调用回调函数,实时更新内容
                onProgress(fullText);
              } catch (e) {
                console.log(chunks[i])
                read();
              }
            }

            read();
          }).catch(error => {
            console.log(error);
            alert("工具处理出错");
          });
        }

        read();
      }).catch(error => {
        console.log(error);
        alert("工具处理出错:" + error);
      });
    }

    // 消息记录管理
    const MESSAGE_STORAGE_KEY = 'chat_messages';

    function loadMessages() {
      const stored = localStorage.getItem(MESSAGE_STORAGE_KEY);
      return stored ? JSON.parse(stored) : [];
    }

    function saveMessages(messages) {
      localStorage.setItem(MESSAGE_STORAGE_KEY, JSON.stringify(messages));
    }

    function appendMessage(role, content) {
      const messages = loadMessages();
      messages.push({
        role,
        content,
        timestamp: new Date().toLocaleTimeString('zh', { hour: '2-digit', minute: '2-digit' })
      });
      saveMessages(messages);
      renderMessages();
    }

    function clearMessages() {
      localStorage.removeItem(MESSAGE_STORAGE_KEY);
      renderMessages();
    }

    // 配置 marked 选项
    marked.setOptions({
      highlight: function(code, lang) {
        if (lang && hljs.getLanguage(lang)) {
          return hljs.highlight(code, { language: lang }).value;
        }
        return code;
      },
      breaks: true,
      gfm: true
    });

    function renderMessages() {
      const messagesContainer = document.querySelector('.custom-scrollbar');
      const messages = loadMessages();

      messagesContainer.innerHTML = messages.map(msg => `
      <div class="message-bubble ${msg.role === 'user' ? 'user-message' : 'ai-message'}">
        <div class="text-gray-800 dark:text-text-dark markdown-body">
          ${msg.role === 'user' ? msg.content : marked.parse(msg.content)}
        </div>
        <span class="text-xs text-gray-400 dark:text-gray-500 mt-1 block">${msg.timestamp}</span>
      </div>
    `).join('');

      // 处理代码块的高亮和复制功能
      const codeBlocks = messagesContainer.querySelectorAll('pre code');
      codeBlocks.forEach(block => {
        // 应用代码高亮
        hljs.highlightElement(block);

        // 添加复制按钮
        const copyButton = document.createElement('button');
        copyButton.className = 'copy-button';
        copyButton.innerHTML = '<i class="fas fa-copy mr-1"></i>复制';
        copyButton.onclick = async () => {
          try {
            await navigator.clipboard.writeText(block.textContent);
            copyButton.innerHTML = '<i class="fas fa-check mr-1"></i>已复制';
            setTimeout(() => {
              copyButton.innerHTML = '<i class="fas fa-copy mr-1"></i>复制';
            }, 2000);
          } catch (err) {
            console.error('复制失败:', err);
          }
        };

        // 为代码块容器添加相对定位
        const preBlock = block.parentElement;
        preBlock.style.position = 'relative';
        preBlock.appendChild(copyButton);
      });

      // 滚动到底部
      messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    // 发送消息
    function sendMessage() {
      const textarea = document.querySelector('textarea');

      const content = textarea.value.trim();
      if (!content) return;

      const sk = loadSK();
      if (!sk) {
        alert('请先配置 API 密钥');
        showConfig();
        return;
      }

      // 添加用户消息
      appendMessage('user', content);
      textarea.value = '';

      // 调用 GPT
      callGpt(content, (text) => {
        const messages = loadMessages();
        if (messages[messages.length - 1].role === 'assistant') {
          messages[messages.length - 1].content = text;
        } else {
          messages.push({
            role: 'assistant',
            content: text,
            timestamp: new Date().toLocaleTimeString('zh', {hour: '2-digit', minute: '2-digit'})
          });
        }
        saveMessages(messages);
        renderMessages();
      }, (finalText) => {
        console.log('回复完成:', finalText);

        renderSuggestion();
      });
    }

    // 事件处理
    document.addEventListener('DOMContentLoaded', () => {
      const textarea = document.querySelector('textarea');
      const sendButton = document.querySelector('button:has(.fa-paper-plane)');
      const clearButton = document.querySelector('button:has(.fa-trash-alt)');
      const suggestionSpans = document.querySelectorAll('.mt-2.flex.flex-wrap.gap-2 span');

      // 绑定事件
      sendButton.addEventListener('click', sendMessage);
      textarea.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
          e.preventDefault();
          sendMessage();
        }
      });

      // 快捷提问
      suggestionSpans.forEach(span => {
        span.addEventListener('click', () => {
          textarea.value = span.textContent.trim();
          sendMessage();
        });
      });

      // 初始化消息记录
      renderMessages();

      // 修改清除按钮事件
      clearButton.addEventListener('click', confirmClear);

      renderSuggestion();
    });

    function showHelp() {
      const helpContent = `
        <div class="bg-white dark:bg-gray-800 p-6 rounded-lg max-w-2xl mx-auto" style="z-index: 999">
          <h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">使用说明</h2>
          <div class="space-y-3 text-gray-700 dark:text-text-dark">
            <p>1. 在输入框中输入您的问题,点击发送按钮或按回车键发送</p>
            <p>2. 支持快捷提问,点击下方预设的问题直接发送</p>
            <p>3. 可以使用 Shift + Enter 在输入框中换行</p>
            <p>4. 支持深色/浅色主题切换,可根据个人喜好选择</p>
            <p>5. 支持调整字体大小,通过滑块可以设置小、中、大三种字号</p>
            <p>6. 所有对话记录会保存在本地,刷新页面不会丢失</p>
            <p>7. 清除历史记录会删除所有本地保存的对话</p>
            <p>8. 需要配置 API 密钥才能使用对话功能</p>
            <p>9. 每次对话完成后会自动推荐相关的后续问题</p>
            <p>10. 支持实时显示 AI 回复内容</p>
          </div>
          <button onclick="closeHelp()" class="mt-6 px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80" style=" margin-left: auto; display: flex; justify-content: right">
            知道了
          </button>
        </div>
        <div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeHelp()"></div>
      `;

      const helpDiv = document.createElement('div');
      helpDiv.id = 'helpModal';
      helpDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
      helpDiv.innerHTML = helpContent;
      document.body.appendChild(helpDiv);
    }

    function closeHelp() {
      const helpModal = document.getElementById('helpModal');
      if (helpModal) {
        helpModal.remove();
      }
    }

    function confirmClear() {
      const confirmContent = `
        <div class="bg-white s dark:bg-gray-800 p-6 rounded-lg max-w-md mx-auto" style="z-index: 999">
          <h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">确认清除</h2>
          <p class="text-gray-700 dark:text-text-dark">确定要清除所有对话记录吗?此操作不可恢复。</p>
          <div class="mt-6 flex space-x-4" style="display: flex; justify-content: right">
            <button onclick="closeConfirm()" class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-button hover:bg-gray-200 dark:hover:bg-gray-600">
              取消
            </button>
            <button onclick="clearAndClose()" class="px-4 py-2 bg-red-500 text-white rounded-button hover:bg-red-600">
              确认清除
            </button>
          </div>
        </div>
        <div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeConfirm()"></div>
      `;

      const confirmDiv = document.createElement('div');
      confirmDiv.id = 'confirmModal';
      confirmDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
      confirmDiv.innerHTML = confirmContent;
      document.body.appendChild(confirmDiv);
    }

    function closeConfirm() {
      const confirmModal = document.getElementById('confirmModal');
      if (confirmModal) {
        confirmModal.remove();
      }
    }

    function clearAndClose() {
      clearMessages();
      closeConfirm();
    }

    function renderSuggestion() {
      const messages = loadMessages();

      // 获取最近的对话内容
      if (messages.length > 0) {
        const recentMessages = messages.slice(-3).map(msg => msg.content).join('\n');
        getSuggestions(recentMessages, (suggestions) => {
          const suggestionContainer = document.querySelector('.mt-2.flex.flex-wrap.gap-2');
          suggestionContainer.innerHTML = suggestions.map(suggestion => `
                <span class="px-3 py-1 text-sm text-primary dark:text-gray-300 bg-secondary dark:bg-gray-800 rounded-full cursor-pointer hover:bg-primary hover:text-white dark:hover:bg-primary-dark">
                  ${suggestion}
                </span>
              `).join('');

          // 重新绑定事件
          const suggestionSpans = document.querySelectorAll('.mt-2.flex.flex-wrap.gap-2 span');
          suggestionSpans.forEach(span => {
            span.addEventListener('click', () => {
              document.querySelector('textarea').value = span.textContent.trim();
              sendMessage();
            });
          });
        });
      }
    }

    function getSuggestions(context, callback) {
      const sk = loadSK();
      if (!sk) {
        callback(['如何继续优化这个方案?', '有什么相关的最佳实践?', '还有其他方面需要考虑吗?']);
        return;
      }

      fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
        method: "post",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${sk}`
        },
        body: JSON.stringify({
          model: "qwen-plus",
          messages: [
            {
              role: "user",
              content: `基于以下对话内容,推荐3个相关的后续提问,直接返回3个问题,用|||分隔:\n${context}`
            }
          ],
          stream: false
        }),
      }).then(response => response.json()).then(data => {
        const suggestions = data.choices[0].message.content.split('|||').map(q => q.trim());
        callback(suggestions);
      }).catch(error => {
        console.error('获取建议问题失败:', error);
        // 发生错误时使用默认建议
        callback(['如何继续优化这个方案?', '有什么相关的最佳实践?', '还有其他方面需要考虑吗?']);
      });
    }

    const SK_STORAGE_KEY = 'chat_sk';

    function loadSK() {
      return localStorage.getItem(SK_STORAGE_KEY) || '';
    }

    function saveSK(sk) {
      localStorage.setItem(SK_STORAGE_KEY, sk);
    }

    function showConfig() {
      const currentSK = loadSK();
      const configContent = `
        <div class="bg-white dark:bg-gray-800 p-6 rounded-lg max-w-md mx-auto" style="z-index: 999; min-width: 500px">
          <h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">配置 API 密钥</h2>
          <div class="space-y-4">
            <div>
              <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">API 密钥 (SK)</label>
              <input type="password" id="skInput" value="${currentSK}"
                class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary dark:bg-gray-700 dark:text-white"
                placeholder="请输入你的 API 密钥">
            </div>
            <div class="text-sm text-gray-500 dark:text-gray-400">
              请妥善保管你的 API 密钥,不要泄露给他人。
            </div>
          </div>
          <div class="mt-6 flex justify-end space-x-4">
            <button onclick="closeConfig()" class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-button hover:bg-gray-200 dark:hover:bg-gray-600">
              取消
            </button>
            <button onclick="saveConfig()" class="px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80">
              保存
            </button>
          </div>
        </div>
        <div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeConfig()"></div>
      `;

      const configDiv = document.createElement('div');
      configDiv.id = 'configModal';
      configDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
      configDiv.innerHTML = configContent;
      document.body.appendChild(configDiv);
    }

    function closeConfig() {
      const configModal = document.getElementById('configModal');
      if (configModal) {
        configModal.remove();
      }
    }

    function saveConfig() {
      const skInput = document.getElementById('skInput');
      const sk = skInput.value.trim();
      if (!sk) {
        alert('请输入有效的 API 密钥');
        return;
      }
      saveSK(sk);
      closeConfig();
    }

    const FONT_SIZE_KEY = 'chat_font_size';

    function loadFontSize() {
      return localStorage.getItem(FONT_SIZE_KEY) || '2';
    }

    function saveFontSize(size) {
      localStorage.setItem(FONT_SIZE_KEY, size);
    }

    function updateFontSize(size) {
      const html = document.documentElement;
      switch(size) {
        case '1':
          html.style.fontSize = '14px';
          break;
        case '2':
          html.style.fontSize = '16px';
          break;
        case '3':
          html.style.fontSize = '18px';
          break;
      }
    }

    document.addEventListener('DOMContentLoaded', () => {
      // 初始化字体大小
      const fontSizeSlider = document.querySelector('input[type="range"]');
      fontSizeSlider.value = loadFontSize();
      updateFontSize(fontSizeSlider.value);

      // 监听字体大小变化
      fontSizeSlider.addEventListener('change', (e) => {
        const size = e.target.value;
        saveFontSize(size);
        updateFontSize(size);
      });
    });
  </script>
</body>

</html>
相关推荐
移动安全星球1 小时前
从零开始:CherryStudio 打造专属本地 AI 知识库全攻略
人工智能·gpt·claude·本地知识库·deepseek
benben0445 小时前
Unity3D仿星露谷物语开发36之锄地动画2
游戏·ui·unity·游戏引擎
活跃家族18 小时前
绕过UI的cooke和token的验证
ui
陈奕昆19 小时前
论文降重GPT指令-实侧有效从98%降低到8%
人工智能·gpt·论文·降重
Listennnn3 天前
GPT,Bert类模型对比
人工智能·gpt·自然语言处理·bert
—Qeyser3 天前
用 Deepseek 写的uniapp油耗计算器
前端·vue.js·gpt·chatgpt·uni-app·gpt-3·deepseek
大海里的番茄3 天前
告别昂贵语音合成服务!用GPT-SoVITS生成你的个性化AI语音
人工智能·gpt
web守墓人3 天前
【gpt生成-总览】怎样才算开发了一门编程语言,需要通过什么测试
运维·服务器·gpt
骑着牛的奇兵4 天前
DB-GPT 最新0.7.0版本Windows 部署
gpt·ai