aiedit icon

作者:
藤原
Fork(复制)
下载
嵌入
设置
BUG反馈
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="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0"
    />
    <link
      href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css"
      rel="stylesheet"
    />
    <script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <style>
      /* 基础样式和变量 */
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: "Poppins", sans-serif;
        outline: none;
      }

      :root {
        /* 深色模式颜色 (默认) */
        --text-color: #e3e3e3;
        --subheading-color: #828282;
        --placeholder-color: #a6a6a6;
        --primary-color: #242424;
        --secondary-color: #383838;
        --secondary-hover-color: #444;
        --user-message-color: #4285f4;
        --ai-message-color: #383838;

        /* 终端工具颜色 */
        --editor-bg: #1e293b;
        --editor-secondary-bg: #334155;
        --line-color: #64748b;
        --keyword-color: #7dd3fc;
        --string-color: #fca5a5;
        --comment-color: #86efac;
        --number-color: #b5cea8;
        --function-color: #dcdcaa;
        --ai-output-color: #67e8f9;
        --user-input-color: #fde047;
        --accent-color: #818cf8;
        --accent-hover: #a5b4fc;
        --border-radius: 8px;
        --calendar-today-bg: rgba(129, 140, 248, 0.3);
        --calendar-selected-bg: var(--accent-color);
        --birthday-color: #f472b6;
      }

      .light_mode {
        /* 浅色模式颜色 */
        --text-color: #222;
        --subheading-color: #a0a0a0;
        --placeholder-color: #6c6c6c;
        --primary-color: #fff;
        --secondary-color: #e9eef6;
        --secondary-hover-color: #dbe1ea;
        --user-message-color: #4285f4;
        --ai-message-color: #e9eef6;

        /* 终端工具颜色 (浅色模式适应) */
        --editor-bg: #f8fafc;
        --editor-secondary-bg: #e2e8f0;
        --line-color: #94a3b8;
        --keyword-color: #0ea5e9;
        --string-color: #ef4444;
        --comment-color: #22c55e;
        --number-color: #a855f7;
        --function-color: #f97316;
        --ai-output-color: #06b6d4;
        --user-input-color: #eab308;
        --accent-color: #6366f1;
        --accent-hover: #818cf8;
        --calendar-today-bg: rgba(99, 102, 241, 0.3);
        --calendar-selected-bg: var(--accent-color);
        --birthday-color: #ec4899;
      }

      body {
        background: var(--primary-color);
        color: var(--text-color);
        display: flex;
        flex-direction: column;
        min-height: 100vh;
        overflow-x: hidden; /* 防止横向滚动条 */
      }

      /* 头部样式 */
      .header {
        margin: 6vh auto 0;
        padding: 1rem;
        max-width: 980px;
        overflow-x: hidden;
        width: 100%;
        transition: margin 0.3s ease, opacity 0.3s ease;
      }

      body.hide-header .header {
        margin: 0;
        height: 0;
        opacity: 0;
        overflow: hidden;
        padding: 0;
      }

      .header .title {
        width: fit-content;
        font-size: 3rem;
        font-weight: 500;
        line-height: 4rem;
        background-clip: text;
        background: linear-gradient(to right, #4285f4, #d96570);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
      }

      .header .subtitle {
        font-size: 2.6rem;
        font-weight: 500;
        line-height: 4rem;
        color: var(--subheading-color);
      }

      .suggestion-list {
        width: 100%;
        list-style: none;
        display: flex;
        gap: 1.25rem;
        margin-top: 9.5vh;
        overflow: hidden;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        scrollbar-width: none; /* 适用于 Firefox */
      }

      .suggestion-list::-webkit-scrollbar {
        display: none; /* 适用于 Chrome, Safari, Opera */
      }

      .suggestion-list .suggestion {
        cursor: pointer;
        padding: 1.25rem;
        width: 222px;
        flex-shrink: 0;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        border-radius: 0.75rem;
        justify-content: space-between;
        background: var(--secondary-color);
        transition: 0.2s ease;
      }

      .suggestion-list .suggestion:hover {
        background: var(--secondary-hover-color);
      }

      .suggestion-list .suggestion .text {
        font-weight: 400;
        color: var(--text-color);
      }

      .suggestion-list .suggestion .icon {
        width: 42px;
        height: 42px;
        display: flex;
        font-size: 1.3rem;
        margin-top: 2.5rem;
        align-self: flex-end;
        align-items: center;
        border-radius: 50%;
        justify-content: center;
        color: var(--text-color);
        background: var(--primary-color);
      }

      /* 聊天列表样式 */
      .chat-list {
        flex: 1;
        padding: 2rem 1rem 12rem;
        max-height: 100vh; /* 确保它不会溢出视口 */
        overflow-y: auto;
        scrollbar-color: #999 transparent; /* 适用于 Firefox */
        transition: padding 0.3s ease;
      }

      .chat-list::-webkit-scrollbar {
        width: 8px;
      }

      .chat-list::-webkit-scrollbar-track {
        background: transparent;
      }

      .chat-list::-webkit-scrollbar-thumb {
        background-color: #999;
        border-radius: 4px;
      }

      .chat-list .message {
        max-width: 980px;
        margin: 0 auto 1.5rem;
        display: flex;
        gap: 1.5rem;
        width: 100%;
        align-items: flex-start; /* 内容顶部对齐 */
      }

      .chat-list .message.user {
        justify-content: flex-end; /* 用户消息靠右 */
      }

      .chat-list .message.ai {
        justify-content: flex-start; /* AI消息靠左 */
      }

      .chat-list .message .message-content {
        display: flex;
        gap: 1.5rem;
        max-width: 80%;
        align-items: flex-start;
      }

      .chat-list .message.user .message-content {
        flex-direction: row-reverse; /* 用户消息头像在右边 */
      }

      .chat-list .message.ai .message-content {
        flex-direction: row; /* AI消息头像在左边 */
      }

      .chat-list .message .avatar {
        width: 40px;
        height: 40px;
        object-fit: cover;
        border-radius: 50%;
        flex-shrink: 0; /* 防止头像缩小 */
      }

      .chat-list .message.loading .avatar {
        animation: rotate 3s linear infinite;
      }

      @keyframes rotate {
        100% {
          transform: rotate(360deg);
        }
      }

      .chat-list .message .icon {
        color: var(--text-color);
        cursor: pointer;
        height: 35px;
        width: 35px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        background: none;
        font-size: 1.25rem;
        margin-left: 3.5rem;
        visibility: hidden;
        transition: background 0.2s ease;
        flex-shrink: 0; /* 防止图标缩小 */
      }

      .chat-list .message .icon.hide {
        visibility: hidden;
      }

      .chat-list .message:not(.loading, .error):hover .icon:not(.hide) {
        visibility: visible;
      }

      .chat-list .message .icon:hover {
        background: var(--secondary-hover-color);
      }

      /* 消息气泡样式 */
      .message-bubble {
        padding: 12px 18px;
        border-radius: 18px;
        max-width: 100%; /* 限制气泡宽度 */
        word-wrap: break-word;
        display: flex;
        flex-direction: column;
        gap: 8px; /* 气泡内元素间距 */
        position: relative; /* 用于气泡尾巴 */
      }

      .chat-list .message.user .message-bubble {
        background-color: var(--user-message-color); /* 用户消息气泡颜色 */
        color: white;
        border-bottom-right-radius: 4px; /* 气泡尾巴效果 */
      }

      .chat-list .message.ai .message-bubble {
        background-color: var(--ai-message-color); /* AI消息气泡颜色 */
        color: var(--text-color);
        border-bottom-left-radius: 4px; /* 气泡尾巴效果 */
      }

      /* 气泡内的文本 */
      .message-bubble .text {
        white-space: pre-wrap; /* 保留空白和换行符 */
        font-size: 1rem;
        line-height: 1.5;
      }

      .message-bubble .text.error {
        color: #e55865;
      }

      /* 加载指示器 */
      .loading-indicator {
        display: flex;
        flex-direction: column;
        gap: 0.8rem;
        width: 100%;
      }

      .message.loading .loading-indicator {
        display: flex;
      }

      .loading-indicator .loading-bar {
        height: 11px;
        width: 100%;
        border-radius: 0.135rem;
        background-position: -800px 0;
        background: linear-gradient(to right, #4285f4, var(--primary-color), #4285f4);
        animation: loading 3s linear infinite;
      }

      .loading-indicator .loading-bar:last-child {
        width: 70%;
      }

      @keyframes loading {
        0% {
          background-position: -800px 0;
        }
        100% {
          background-position: 800px 0;
        }
      }

      /* 工具/组件气泡的特殊处理 */
      .message-tool-bubble {
        padding: 0; /* 工具容器自身有内边距 */
        background: transparent; /* 让工具容器自身定义背景 */
        border-radius: 0; /* 让工具容器自身定义圆角 */
        max-width: 100%; /* 允许工具容器占据气泡的全部宽度 */
        box-shadow: none; /* 移除冗余阴影 */
        border: none; /* 移除冗余边框 */
      }

      /* 输入区域样式 */
      .typing-area {
        position: fixed;
        width: 100%;
        left: 0;
        bottom: 0;
        padding: 1rem;
        background: var(--primary-color);
        z-index: 10;
      }

      .typing-area .typing-form,
      .typing-area .action-buttons {
        display: flex;
        gap: 0.75rem;
      }

      .typing-area .typing-form {
        max-width: 980px;
        margin: 0 auto;
      }

      .typing-form .input-wrapper {
        width: 100%;
        height: 56px;
        display: flex;
        position: relative;
      }

      .typing-form .typing-input {
        height: 100%;
        width: 100%;
        border: none;
        outline: none;
        resize: none;
        font-size: 1rem;
        color: var(--text-color);
        padding: 1.1rem 4rem 1.1rem 1.5rem;
        border-radius: 100px;
        background: var(--secondary-color);
        overflow: hidden; /* 隐藏滚动条 */
      }

      .typing-form .typing-input:focus {
        background: var(--secondary-hover-color);
      }

      .typing-form .typing-input::placeholder {
        color: var(--placeholder-color);
      }

      .typing-area .icon {
        width: 56px;
        height: 56px;
        flex-shrink: 0;
        cursor: pointer;
        border-radius: 50%;
        display: flex;
        font-size: 1.4rem;
        color: var(--text-color);
        align-items: center;
        justify-content: center;
        background: var(--secondary-color);
        transition: 0.2s ease;
        border: none; /* 确保按钮没有默认边框 */
        outline: none; /* 确保按钮没有默认轮廓 */
      }

      .typing-area .icon:hover {
        background: var(--secondary-hover-color);
      }

      .typing-form #send-message-button {
        position: absolute;
        right: 0;
        transform: scale(0);
        background: transparent;
        transition: transform 0.2s ease;
      }

      .typing-form .typing-input:valid ~ #send-message-button {
        transform: scale(1);
      }

      .typing-area .disclaimer-text {
        text-align: center;
        font-size: 0.85rem;
        margin-top: 1rem;
        color: var(--placeholder-color);
      }

      /* 响应式媒体查询 */
      @media (max-width: 768px) {
        .header :is(.title, .subtitle) {
          font-size: 2rem;
          line-height: 2.6rem;
        }
        .header .subtitle {
          font-size: 1.7rem;
        }
        .typing-area :where(.typing-form, .action-buttons) {
          gap: 0.4rem;
        }
        .typing-form .input-wrapper {
          height: 50px;
        }
        .typing-form .typing-input {
          padding: 1.1rem 3.5rem 1.1rem 1.2rem;
        }
        .typing-area .icon {
          height: 50px;
          width: 50px;
        }
        .typing-area .disclaimer-text {
          font-size: 0.75rem;
          margin-top: 0.5rem;
        }
        .message-bubble {
          max-width: 85%; /* 小屏幕上气泡宽度更大 */
        }
      }

      /* 图片预览样式 */
      .image-preview {
        max-width: 100%;
        max-height: 300px;
        margin-top: 10px;
        border-radius: 8px;
        object-fit: contain;
        cursor: pointer;
        transition: transform 0.2s ease;
      }

      .image-preview:hover {
        transform: scale(1.02);
      }

      /* ================ 统一工具样式 ================ */
      .tool-container {
        margin-top: 15px;
        padding: 15px;
        background: var(--editor-secondary-bg);
        border-radius: var(--border-radius);
        border: 1px solid rgba(255, 255, 255, 0.1);
        width: 100%;
      }

      .tool-title {
        font-size: 14px;
        font-weight: 600;
        margin-bottom: 12px;
        color: var(--text-color);
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .tool-options {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin-bottom: 12px;
      }

      .tool-btn {
        padding: 8px 16px;
        background-color: var(--secondary-color);
        color: var(--text-color);
        border-radius: 6px;
        border: none;
        cursor: pointer;
        font-size: 14px;
        transition: all 0.2s;
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .tool-btn:hover {
        background-color: var(--secondary-hover-color);
      }

      .tool-btn.active {
        background-color: var(--accent-color);
        color: white;
      }

      .tool-btn i {
        font-size: 16px;
      }

      .tool-control {
        margin-top: 12px;
      }

      .tool-label {
        display: flex;
        justify-content: space-between;
        font-size: 12px;
        color: var(--subheading-color);
        margin-bottom: 5px;
      }

      .tool-slider {
        width: 100%;
        height: 6px;
        -webkit-appearance: none;
        appearance: none;
        background: var(--secondary-color);
        border-radius: 3px;
        outline: none;
      }

      .tool-slider::-webkit-slider-thumb {
        -webkit-appearance: none;
        appearance: none;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        background: var(--accent-color);
        cursor: pointer;
        transition: all 0.2s;
      }

      .tool-slider::-webkit-slider-thumb:hover {
        transform: scale(1.1);
      }

      .tool-action {
        margin-top: 15px;
        display: flex;
        gap: 10px;
      }

      .tool-action-btn {
        padding: 8px 16px;
        border-radius: 6px;
        border: none;
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
        transition: all 0.2s;
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .tool-primary-btn {
        background-color: var(--accent-color);
        color: white;
      }

      .tool-primary-btn:hover {
        background-color: var(--accent-hover);
      }

      .tool-secondary-btn {
        background-color: var(--secondary-color);
        color: var(--text-color);
      }

      .tool-secondary-btn:hover {
        background-color: var(--secondary-hover-color);
      }

      .tool-progress {
        width: 100%;
        height: 6px;
        background-color: var(--editor-secondary-bg);
        border-radius: 3px;
        margin-top: 15px;
        overflow: hidden;
        display: none;
      }
      
      .tool-progress-bar {
        height: 100%;
        width: 0%;
        background-color: var(--accent-color);
        transition: width 0.3s ease;
      }

      .tool-result {
        margin-top: 15px;
        padding: 15px;
        background: var(--editor-secondary-bg);
        border-radius: var(--border-radius);
        border: 1px solid rgba(255, 255, 255, 0.1);
        display: none;
      }

      .tool-result-title {
        font-size: 14px;
        font-weight: 600;
        margin-bottom: 12px;
        color: var(--text-color);
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .tool-meta {
        display: flex;
        gap: 15px;
        margin-top: 10px;
        font-size: 12px;
        color: var(--subheading-color);
      }

      .tool-meta-item {
        display: flex;
        align-items: center;
        gap: 5px;
      }

      .download-btn {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        margin-top: 12px;
        padding: 8px 16px;
        background-color: var(--accent-color);
        color: white;
        border-radius: 6px;
        text-decoration: none;
        font-size: 14px;
        font-weight: 500;
        transition: all 0.2s;
      }

      .download-btn:hover {
        background-color: var(--accent-hover);
        transform: translateY(-1px);
      }

      /* 视频播放器样式 */
      .video-player-container {
        width: 100%;
        margin-bottom: 15px;
      }

      .video-player-container iframe {
        width: 100%;
        height: 400px;
        border-radius: var(--border-radius);
        border: none;
      }

      /* 天气信息样式 */
      .weather-info {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 12px;
        margin-top: 15px;
      }

      .weather-item {
        display: flex;
        flex-direction: column;
        padding: 12px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: var(--border-radius);
      }

      .weather-item-label {
        font-size: 12px;
        color: var(--subheading-color);
        margin-bottom: 5px;
      }

      .weather-item-value {
        font-size: 14px;
        font-weight: 500;
        color: var(--text-color);
      }

      /* 日历样式 */
      .calendar-grid {
        display: grid;
        grid-template-columns: repeat(7, 1fr);
        gap: 8px;
        margin-top: 15px;
      }

      .calendar-weekday {
        font-size: 12px;
        color: var(--subheading-color);
        text-align: center;
        padding: 5px;
      }

      .calendar-day {
        padding: 8px;
        text-align: center;
        border-radius: var(--border-radius);
        cursor: pointer;
        transition: all 0.2s;
      }

      .calendar-day:hover {
        background-color: var(--secondary-hover-color);
      }

      .calendar-day.today {
        background-color: var(--calendar-today-bg);
      }

      .calendar-day.selected {
        background-color: var(--calendar-selected-bg);
        color: white;
      }

      .calendar-day.birthday {
        position: relative;
      }

      .calendar-day.birthday::after {
        content: "🎂";
        position: absolute;
        top: -5px;
        right: -5px;
        font-size: 12px;
      }

      .calendar-day.other-month {
        opacity: 0.5;
      }

      /* 全屏图片预览 */
      .fullscreen-image-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.9);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 9999;
        cursor: zoom-out;
      }
      .fullscreen-image-overlay img {
        max-width: 90%;
        max-height: 90%;
        border-radius: 10px;
        object-fit: contain;
      }
    </style>
  </head>
  <body>
    <!-- 顶部区域:标题和快捷建议 -->
    <header class="header">
      <h1 class="title">你好</h1>
      <p class="subtitle">今天有什么可以帮您的吗?</p>
      <ul class="suggestion-list">
        <li class="suggestion" data-command="帮我用100元预算为5个好朋友策划一个游戏之夜。">
          <h4 class="text">帮我用100元预算为5个好朋友策划一个游戏之夜。</h4>
          <span class="icon material-symbols-rounded">stadia_controller</span>
        </li>
        <li class="suggestion" data-command="提高公开演讲技巧的最佳建议是什么?">
          <h4 class="text">提高公开演讲技巧的最佳建议是什么?</h4>
          <span class="icon material-symbols-rounded">lightbulb</span>
        </li>
        <li class="suggestion" data-command="你能帮我找到关于网页开发的最新新闻吗?">
          <h4 class="text">你能帮我找到关于网页开发的最新新闻吗?</h4>
          <span class="icon material-symbols-rounded">travel_explore</span>
        </li>
        <li class="suggestion" data-command="编写JavaScript代码计算数组中所有元素的和。">
          <h4 class="text">编写JavaScript代码计算数组中所有元素的和。</h4>
          <span class="icon material-symbols-rounded">code</span>
        </li>
      </ul>
    </header>

    <!-- 中部区域:聊天记录 -->
    <div class="chat-list"></div>

    <!-- 底部区域:输入框和功能按钮 -->
    <div class="typing-area">
      <form class="typing-form">
        <div class="input-wrapper">
          <textarea
            placeholder="在此输入您的提示或命令..."
            class="typing-input"
            required
            rows="1"
          ></textarea>
          <button id="send-message-button" class="icon material-symbols-rounded">
            send
          </button>
        </div>
        <div class="action-buttons">
          <button
            type="button"
            id="theme-toggle-button"
            class="icon material-symbols-rounded"
          >
            light_mode
          </button>
          <button
            type="button"
            id="delete-chat-button"
            class="icon material-symbols-rounded"
          >
            delete
          </button>
          <input type="file" id="image-upload" accept="image/*" style="display: none;" />
          <button
            type="button"
            id="upload-image-button"
            class="icon material-symbols-rounded"
          >
            image
          </button>
        </div>
      </form>
      <p class="disclaimer-text">
        超级AI助手可能会显示不准确的信息,包括关于人物的信息,请仔细检查其回复。
        <br />
        **注意:此版本使用预定义回复和模拟工具。如需真正的AI功能,需要后端服务器支持。**
      </p>
    </div>

    <script>
      // 获取DOM元素
      const typingForm = document.querySelector(".typing-form");
      const typingInput = typingForm.querySelector(".typing-input");
      const chatContainer = document.querySelector(".chat-list");
      const suggestions = document.querySelectorAll(".suggestion");
      const toggleThemeButton = document.querySelector("#theme-toggle-button");
      const deleteChatButton = document.querySelector("#delete-chat-button");
      const uploadImageButton = document.querySelector("#upload-image-button");
      const imageUploadInput = document.querySelector("#image-upload");
      const header = document.querySelector(".header");
      const sendMessageButton = document.querySelector("#send-message-button");

      // 状态变量
      let messages = []; // 存储聊天记录的数组
      let isResponseGenerating = false;
      let commandHistoryArray = JSON.parse(localStorage.getItem("commandHistory") || "[]");
      let historyIndex = -1;
      let totalParseCount = parseInt(localStorage.getItem("totalParseCount") || "0");
      let successParseCount = parseInt(localStorage.getItem("successParseCount") || "0");
      let currentConversionSettings = {
        format: "png",
        quality: 0.8,
        resize: null
      };

      // API配置 (模拟,实际需要后端)
      const videoApis = [
        { name: "默认线路【推荐】", url: "https://jx.xmflv.com/?url=" },
        { name: "线路一【推荐】", url: "https://jx.we-vip.com/?url=" },
        { name: "线路二【推荐】", url: "https://z1.m1907.top/?jx=" },
        { name: "线路三", url: "https://www.ckplayer.vip/jiexi/?url=" },
        { name: "线路五", url: "https://jx.jsonplayer.com/player/?url=" },
        { name: "线路六", url: "https://jx.4kdv.com/?url=" },
        { name: "线路七", url: "https://api.jiexi.la/?url=" },
        { name: "线路八", url: "https://jx.qqwtt.com/?url=" },
        { name: "线路九", url: "https://www.playm3u8.cn/jiexi.php?url=" },
        { name: "线路十", url: "https://www.8090g.cn/jiexi/?url=" },
      ];

      // 预定义回复 (已扩展,用于AI命令)
      const predefinedResponses = {
        "帮我用100元预算为5个好朋友策划一个游戏之夜。":
          "为5个好朋友策划一个100元预算的游戏之夜,可以考虑以下几点:\n\n1.  **食物和饮料 (约50元):** 自制披萨、零食 (薯片、爆米花),以及各种汽水或经济实惠的潘趣酒。\n2.  **游戏 (约30元):** 纸牌游戏 (例如:Uno、真心话大冒险 - 如果合适)、棋盘游戏 (例如:只言片语、爆炸猫),或者免费的在线派对游戏。\n3.  **装饰/氛围 (约10元):** 串灯、有趣的播放列表,以及一些DIY拍照道具。\n4.  **奖品 (约10元):** 为游戏赢家准备一些小而有趣的奖品,如糖果、迷你礼品卡或搞怪奖杯。\n\n记得去当地的折扣店或一元店寻找经济实惠的选择!",
        "提高公开演讲技巧的最佳建议是什么?":
          "要提高公开演讲技巧,请关注以下几个关键领域:\n\n1.  **定期练习:** 练习得越多,你就会越自在。对着镜子、朋友练习,或者录下自己。\n2.  **了解你的听众:** 根据他们的兴趣和知识水平调整你的信息。\n3.  **组织你的演讲:** 拥有清晰的引言、主体和结论。使用过渡语来引导听众。\n4.  **与听众互动:** 进行眼神交流,使用手势,并改变你的语调和节奏。\n5.  **管理紧张情绪:** 深呼吸练习、积极的想象和提前到达可以帮助缓解演讲前的紧张。\n6.  **寻求反馈:** 向信任的朋友或导师寻求建设性的批评。",
        "你能帮我找到关于网页开发的最新新闻吗?":
          "虽然我无法实时访问最新的新闻,但我可以告诉你,当前网页开发的热门话题通常包括:\n\n*   **框架更新:** React、Next.js、Vue 和 Angular 的新版本。\n*   **服务器组件:** React 服务器组件的演变和采用,以提高性能。\n*   **AI在Web开发中的应用:** 集成AI用于代码生成、测试和内容创建的工具和库。\n*   **WebAssembly (Wasm):** 其在高性能Web应用中的日益增长的使用。\n*   **CSS创新:** 新的CSS特性,如容器查询、`:has()` 和改进的布局技术。\n*   **可访问性 (A11y):** 持续强调构建包容性的Web体验。\n\n如需实时更新,我建议查看流行的科技新闻网站、开发者博客和社区,如 Dev.to、Hashnode、Smashing Magazine 和 Twitter/X。",
        "编写JavaScript代码计算数组中所有元素的和。": `function sumArray(arr) {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
}

// 示例用法:
const numbers = [1, 2, 3, 4, 5];
const total = sumArray(numbers);
console.log(total); // 输出: 15`,
        "image": "请上传图片,我将帮您转换格式。支持以下格式:\n- PNG (无损)\n- JPEG (有损压缩)\n- WEBP (现代格式)\n\n上传后可以选择转换选项。",
        default:
          "我是一个简单的AI助手,目前使用预定义回复。请尝试其他提示或输入 'help' 查看可用命令!",
      };

      // ================ 辅助函数 ================

      // 从本地存储加载聊天记录和主题
      const loadFromLocalStorage = () => {
        const savedMessages = localStorage.getItem("chatHistory");
        if (savedMessages) {
          messages = JSON.parse(savedMessages);
          messages.forEach((msg) => {
            const messageElement = createMessageElement(msg);
            chatContainer.appendChild(messageElement);
          });
          if (messages.length > 0) {
            header.classList.add("hide-header");
          }
          chatContainer.scrollTo(0, chatContainer.scrollHeight);
        }

        const isLightMode = localStorage.getItem("themeColor") === "light_mode";
        document.body.classList.toggle("light_mode", isLightMode);
        toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
      };

      // 将聊天记录保存到本地存储
      const saveToLocalStorage = () => {
        localStorage.setItem("chatHistory", JSON.stringify(messages));
      };

      // 创建新消息元素并返回
      const createMessageElement = (msg) => {
        const div = document.createElement("div");
        div.classList.add("message", msg.sender === "user" ? "user" : "ai");
        div.setAttribute("data-message-id", msg.id); // Add data-message-id for easier selection

        const avatarSrc = msg.sender === "user" ? "images/user.jpg" : "images/gemini.svg";
        const avatarAlt = msg.sender === "user" ? "用户头像" : "AI头像";

        let bubbleContentHtml = "";
        let bubbleClasses = "message-bubble"; // Default bubble class

        if (msg.type === "loading") {
          bubbleContentHtml = `
            <p class="text"></p>
            <div class="loading-indicator">
              <div class="loading-bar"></div>
              <div class="loading-bar"></div>
              <div class="loading-bar"></div>
            </div>
          `;
        } else if (msg.type === "text" || msg.type === "error") {
          bubbleContentHtml = `<p class="text ${msg.type === "error" ? "error" : ""}">${msg.text}</p>`;
          if (msg.image && msg.sender === "user") {
            bubbleContentHtml += `
              <img src="${msg.image}" class="image-preview" alt="上传的图片" onclick="showFullImage('${msg.image}')" />
              <div class="tool-container">
                <div class="tool-title">
                  <i class="fas fa-cog"></i> 图片转换选项
                </div>
                <div class="tool-options">
                  <button class="tool-btn ${currentConversionSettings.format === "png" ? "active" : ""}" data-format="png" onclick="setImageConversionFormat('png')">
                    <i class="fas fa-file-image"></i> PNG
                  </button>
                  <button class="tool-btn ${currentConversionSettings.format === "jpeg" ? "active" : ""}" data-format="jpeg" onclick="setImageConversionFormat('jpeg')">
                    <i class="fas fa-file-image"></i> JPEG
                  </button>
                  <button class="tool-btn ${currentConversionSettings.format === "webp" ? "active" : ""}" data-format="webp" onclick="setImageConversionFormat('webp')">
                    <i class="fas fa-file-image"></i> WEBP
                  </button>
                </div>
                <div class="tool-control">
                  <div class="tool-label">
                    <span>质量: ${Math.round(currentConversionSettings.quality * 100)}%</span>
                  </div>
                  <input type="range" min="0.1" max="1" step="0.1" value="${currentConversionSettings.quality}" 
                    class="tool-slider" oninput="setImageQuality(this.value)">
                </div>
                <div class="tool-progress" id="progress-${msg.id}">
                  <div class="tool-progress-bar"></div>
                </div>
                <div class="tool-result" id="result-${msg.id}">
                  <div class="tool-result-title">
                    <i class="fas fa-check-circle"></i> 转换完成
                  </div>
                  <img src="" class="image-preview" alt="转换后的图片" onclick="showFullImage(this.src)" />
                  <div class="tool-meta">
                    <div class="tool-meta-item">
                      <i class="fas fa-file-format"></i> 格式: <span class="format-value"></span>
                    </div>
                    <div class="tool-meta-item">
                      <i class="fas fa-tachometer-alt"></i> 质量: <span class="quality-value"></span>
                    </div>
                    <div class="tool-meta-item">
                      <i class="fas fa-weight-hanging"></i> 大小: <span class="size-value"></span>
                    </div>
                  </div>
                  <a href="#" class="download-btn" download>
                    <i class="fas fa-download"></i> 下载图片
                  </a>
                </div>
                <div class="tool-action">
                  <button class="tool-action-btn tool-primary-btn" onclick="convertImage('${msg.id}')">
                    <i class="fas fa-exchange-alt"></i> 开始转换
                  </button>
                  <button class="tool-action-btn tool-secondary-btn" onclick="cancelImageConversion('${msg.id}')">
                    <i class="fas fa-times"></i> 取消
                  </button>
                </div>
              </div>
            `;
          }
          if (msg.convertedImage && msg.sender === "ai") {
            bubbleContentHtml += `
              <div class="tool-result" style="display: block;">
                <div class="tool-result-title">
                  <i class="fas fa-check-circle"></i> 转换完成
                </div>
                <img src="${msg.convertedImage}" class="image-preview" alt="转换后的图片" onclick="showFullImage('${msg.convertedImage}')" />
                <div class="tool-meta">
                  <div class="tool-meta-item">
                    <i class="fas fa-file-format"></i> 格式: ${msg.convertedFormat.toUpperCase()}
                  </div>
                  <div class="tool-meta-item">
                    <i class="fas fa-tachometer-alt"></i> 质量: ${Math.round(msg.convertedQuality * 100)}%
                  </div>
                  <div class="tool-meta-item">
                    <i class="fas fa-weight-hanging"></i> 大小: ${formatFileSize(msg.convertedSize)}
                  </div>
                </div>
                <a href="${msg.convertedImage}" download="${msg.originalFileName ? msg.originalFileName.replace(/\.[^/.]+$/, "") + "." + msg.convertedFormat : "converted_image." + msg.convertedFormat}" class="download-btn">
                  <i class="fas fa-download"></i> 下载图片
                </a>
              </div>
            `;
          }
        } else if (msg.type === "html" && msg.htmlContent) {
          bubbleContentHtml = msg.htmlContent;
          bubbleClasses += " message-tool-bubble"; // Add tool bubble specific class
        } else if (msg.type === "component" && msg.componentType) {
          if (msg.componentType === "weather") {
            bubbleContentHtml = renderWeatherComponent(msg.componentData);
          } else if (msg.componentType === "calendar") {
            bubbleContentHtml = renderCalendarComponent(msg.componentData);
          }
          bubbleClasses += " message-tool-bubble"; // Add tool bubble specific class
        }

        let contentHtml = `
          <div class="message-content">
            <img class="avatar" src="${avatarSrc}" alt="${avatarAlt}">
            <div class="${bubbleClasses}">
              ${bubbleContentHtml}
            </div>
          </div>
        `;

        if (msg.sender === "ai" && msg.type !== "loading") {
          contentHtml += `
            <button onclick="copyMessage(this)" class="icon material-symbols-rounded">content_copy</button>
          `;
        }

        div.innerHTML = contentHtml;
        return div;
      };

      // 添加消息到聊天列表并滚动到底部
      const addMessageToChat = (msg) => {
        messages.push(msg);
        saveToLocalStorage();
        const messageElement = createMessageElement(msg);
        chatContainer.appendChild(messageElement);
        chatContainer.scrollTo(0, chatContainer.scrollHeight);
      };

      // 显示打字效果,逐字显示
      const showTypingEffect = (text, textElement, incomingMessageDiv) => {
        let i = 0;
        const typingInterval = setInterval(() => {
          if (i < text.length) {
            textElement.innerText += text.charAt(i);
            i++;
            chatContainer.scrollTo(0, chatContainer.scrollHeight);
          } else {
            clearInterval(typingInterval);
            isResponseGenerating = false;
            incomingMessageDiv.classList.remove("loading");
            const copyButton = incomingMessageDiv.querySelector(".icon");
            if (copyButton) copyButton.style.visibility = "visible";
          }
        }, 20); // 调整速度
      };

      // 复制消息文本到剪贴板
      window.copyMessage = (copyButton) => {
        const messageText = copyButton.parentElement.querySelector(".text").innerText;
        navigator.clipboard.writeText(messageText);
        copyButton.innerText = "done"; // 显示确认图标
        setTimeout(() => (copyButton.innerText = "content_copy"), 1000); // 1秒后恢复图标
      };

      // 显示提示信息
      function showAlert(message) {
        const alert = document.createElement("div");
        alert.style.cssText = `
          position: fixed;
          bottom: 30px;
          left: 50%;
          transform: translateX(-50%);
          background: rgba(30, 41, 59, 0.95);
          color: white;
          padding: 12px 24px;
          border-radius: 25px;
          box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
          z-index: 9999;
          font-size: 14px;
          backdrop-filter: blur(10px);
          border: 1px solid rgba(255, 255, 255, 0.1);
        `;
        alert.textContent = message;

        document.body.appendChild(alert);
        setTimeout(() => alert.remove(), 3000);
      }

      // URL提取函数
      function extractURL(text) {
        try {
          const urlRegex =
            /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))/g;
          const matches = text.match(urlRegex);
          return matches ? matches[0] : null;
        } catch (e) {
          console.error("URL提取错误:", e);
          return null;
        }
      }

      // 格式化文件大小
      function formatFileSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
      }

      // ================ 核心聊天和命令处理 ================

      // 处理发送聊天消息或命令
      const handleOutgoingChat = async (input) => {
        const messageText = input.trim();
        if (!messageText || isResponseGenerating) return;

        // 添加用户消息到聊天列表
        addMessageToChat({
          id: `msg-${Date.now()}`,
          text: messageText,
          sender: "user",
          type: "text",
        });

        typingInput.value = ""; // 清空输入
        typingInput.style.height = "auto"; // 重置文本域高度
        header.classList.add("hide-header");

        // 检查是否是命令
        const commandParts = messageText.toLowerCase().split(" ");
        const command = commandParts[0];
        const args = commandParts.slice(1).join(" ");

        // 保存到历史记录
        saveToHistory(messageText);

        isResponseGenerating = true; // 标记为正在生成响应

        // 尝试识别URL进行视频解析
        const detectedUrl = extractURL(messageText);
        if (detectedUrl) {
          if (
            detectedUrl.includes("douyin.com") ||
            detectedUrl.includes("kuaishou.com") ||
            detectedUrl.includes("xiaohongshu.com")
          ) {
            await processShortVideoCommand(detectedUrl);
          } else {
            await processVideoCommand(detectedUrl);
          }
        } else {
          // 如果不是URL,则检查是否是命令
          switch (command) {
            case "help":
              await processHelpCommand();
              break;
            case "clear":
              processClearCommand();
              break;
            case "history":
              processHistoryCommand();
              break;
            case "video":
              await processVideoCommand(args);
              break;
            case "shortvideo":
              await processShortVideoCommand(args);
              break;
            case "ai":
              await processAiCommand(args);
              break;
            case "image":
              await processImageCommand(args);
              break;
            case "weather":
              await processWeatherCommand(args);
              break;
            case "calendar":
              await processCalendarCommand();
              break;
            case "stats":
              await processStatsCommand();
              break;
            default:
              // 如果不是命令也不是URL,则作为普通AI对话处理
              await processAiCommand(messageText);
              break;
          }
        }
        isResponseGenerating = false; // 命令执行完毕,重置状态
      };

      // ================ 命令处理函数 ================

      async function processHelpCommand() {
        const helpText = `可用命令:
基础命令:
- help: 显示帮助信息
- clear: 清空终端
- history: 显示命令历史记录

影视工具:
- video [url]: 解析并播放长视频(支持爱奇艺、腾讯、优酷等)
  例如: video https://v.qq.com/x/cover/mzc00200mp8er9d.html
- shortvideo [url]: 解析短视频(支持抖音、快手、小红书)
  例如: shortvideo https://v.douyin.com/xxxxxx

AI工具:
- ai [message]: 与AI助手对话
  例如: ai 你好

图片工具:
- image: 转换图片格式 (支持PNG/JPEG/WEBP)
  例如: image (会弹出文件选择框)

天气工具:
- weather [ip]: 查询指定IP或当前位置的天气信息
  例如: weather 或 weather 49.234.56.78

其他工具:
- calendar: 显示日历信息
- stats: 显示系统统计信息`;

        addMessageToChat({
          text: helpText,
          sender: "ai",
          type: "text",
        });
      }

      function processClearCommand() {
        messages = [];
        localStorage.removeItem("chatHistory");
        localStorage.removeItem("commandHistory"); // 清除命令历史
        localStorage.removeItem("totalParseCount"); // 清除统计
        localStorage.removeItem("successParseCount"); // 清除统计
        totalParseCount = 0;
        successParseCount = 0;
        updateParseStats(); // 更新显示
        chatContainer.innerHTML = ""; // 从DOM中清除消息
        header.classList.remove("hide-header"); // 再次显示头部
        addMessageToChat({
          text: "聊天记录已清空。",
          sender: "ai",
          type: "text",
        });
      }

      function processHistoryCommand() {
        if (commandHistoryArray.length === 0) {
          addMessageToChat({
            text: "暂无命令历史记录。",
            sender: "ai",
            type: "text",
          });
          return;
        }

        let historyText = "命令历史记录:\n";
        commandHistoryArray
          .slice()
          .reverse()
          .forEach((cmd, index) => {
            historyText += `${commandHistoryArray.length - index}. ${cmd}\n`;
          });
        addMessageToChat({
          text: historyText,
          sender: "ai",
          type: "text",
        });
      }

      async function processVideoCommand(url) {
        if (!url || !url.startsWith("http")) {
          addMessageToChat({
            text: "请输入有效的视频URL,例如: video https://v.qq.com/x/cover/xxx.html",
            sender: "ai",
            type: "error",
          });
          return;
        }

        totalParseCount++;
        updateParseStats();

        const loadingMsgId = `loading-${Date.now()}`;
        addMessageToChat({
          id: loadingMsgId,
          text: "正在解析视频,请稍候...",
          sender: "ai",
          type: "loading",
        });

        // 模拟解析延迟
        await new Promise((resolve) => setTimeout(resolve, 1500));

        const defaultApi = videoApis[0];
        const videoSrc = defaultApi.url + encodeURIComponent(url);

        const htmlContent = `
          <div class="tool-container">
            <div class="tool-title">
              <i class="fas fa-video"></i> 视频解析结果
            </div>
            <div class="video-player-container">
              <iframe id="terminal-video" src="${videoSrc}" frameborder="0" allowfullscreen></iframe>
            </div>
            <div class="tool-meta">
              <div class="tool-meta-item">
                <i class="fas fa-network-wired"></i> 当前线路: ${defaultApi.name}
              </div>
            </div>
            <div class="tool-action">
              <button class="tool-action-btn tool-primary-btn" onclick="document.getElementById('terminal-video').src = document.getElementById('terminal-video').src;">
                <i class="fas fa-sync-alt"></i> 刷新
              </button>
              <button class="tool-action-btn tool-primary-btn" onclick="showVideoApiSelector('${url}')">
                <i class="fas fa-exchange-alt"></i> 切换线路
              </button>
              <button class="tool-action-btn tool-primary-btn" onclick="document.getElementById('terminal-video').requestFullscreen()">
                <i class="fas fa-expand"></i> 全屏
              </button>
              <button class="tool-action-btn tool-primary-btn" onclick="downloadVideo('${videoSrc}', '解析视频')">
                <i class="fas fa-download"></i> 下载视频
              </button>
            </div>
          </div>
        `;

        // 替换加载消息为实际内容
        messages = messages.map((msg) =>
          msg.id === loadingMsgId
            ? {
                ...msg,
                text: "视频解析完成!",
                type: "html",
                htmlContent: htmlContent,
                sender: "ai",
              }
            : msg,
        );
        saveToLocalStorage();
        chatContainer.innerHTML = ""; // 清空DOM重新渲染
        messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
        chatContainer.scrollTo(0, chatContainer.scrollHeight);

        successParseCount++;
        updateParseStats();
      }

      async function processShortVideoCommand(url) {
        if (!url || !url.startsWith("http")) {
          addMessageToChat({
            text: "请输入有效的短视频URL,例如: shortvideo https://v.douyin.com/xxx",
            sender: "ai",
            type: "error",
          });
          return;
        }

        totalParseCount++;
        updateParseStats();

        const loadingMsgId = `loading-${Date.now()}`;
        addMessageToChat({
          id: loadingMsgId,
          text: "正在解析短视频,请稍候...",
          sender: "ai",
          type: "loading",
        });

        // 模拟解析延迟
        await new Promise((resolve) => setTimeout(resolve, 1500));

        const isVideo = Math.random() > 0.5; // 随机决定是视频还是图集

        let mediaContent = "";
        if (isVideo) {
          mediaContent = `
            <div class="video-player-container">
              <video controls style="width: 100%; height: auto; border-radius: var(--border-radius);" crossorigin="anonymous">
                <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
                您的浏览器不支持视频播放。
              </video>
            </div>
          `;
        } else {
          const images = [
            "https://picsum.photos/800/1600?random=1",
            "https://picsum.photos/800/1600?random=2",
            "https://picsum.photos/800/1600?random=3",
          ];
          const galleryHTML = images
            .map(
              (img, index) => `
            <div style="position: relative; margin-bottom: 10px;">
              <img src="${img}" alt="图片 ${index + 1}" class="image-preview" onclick="showFullImage('${img}')">
              <div style="position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.7); color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;">
                ${index + 1}/${images.length}
              </div>
            </div>
          `,
            )
            .join("");

          mediaContent = `
            <div style="max-height: 400px; overflow-y: auto;">
              ${galleryHTML}
            </div>
          `;
        }

        const htmlContent = `
          <div class="tool-container">
            <div class="tool-title">
              <i class="fas fa-video"></i> 短视频解析结果
            </div>
            <div style="display: flex; align-items: center; margin-bottom: 15px; padding: 12px; background: rgba(255, 255, 255, 0.05); border-radius: var(--border-radius);">
              <img src="https://picsum.photos/100/100?random=${Math.floor(
                Math.random() * 100,
              )}" style="width: 40px; height: 40px; border-radius: 50%; margin-right: 12px;">
              <div>
                <div style="font-weight: 600; color: var(--text-color); margin-bottom: 2px;">示例作者</div>
                <div style="font-size: 12px; color: var(--line-color);">来自 ${
                  url.includes("douyin") ? "抖音" : url.includes("kuaishou") ? "快手" : "小红书"
                }</div>
              </div>
            </div>
            ${mediaContent}
            <div class="tool-action">
              <button class="tool-action-btn tool-primary-btn" onclick="${
                isVideo 
                  ? "downloadVideo('https://www.w3schools.com/html/mov_bbb.mp4', '示例短视频')" 
                  : "downloadAllImages(['https://picsum.photos/800/1600?random=1','https://picsum.photos/800/1600?random=2','https://picsum.photos/800/1600?random=3'], '示例图集')"
              }">
                <i class="fas fa-download"></i> ${isVideo ? "下载视频" : "打包下载 (3张)"}
              </button>
            </div>
            <div style="margin-top: 15px; padding: 12px; background: rgba(255, 255, 255, 0.05); border-radius: var(--border-radius);">
              <div class="tool-title">
                <i class="fas fa-music"></i> 背景音乐
              </div>
              <div style="font-size: 14px; color: var(--line-color);">
                示例音乐 - 示例作者
              </div>
            </div>
          </div>
        `;

        messages = messages.map((msg) =>
          msg.id === loadingMsgId
            ? {
                ...msg,
                text: "短视频解析完成!",
                type: "html",
                htmlContent: htmlContent,
                sender: "ai",
              }
            : msg,
        );
        saveToLocalStorage();
        chatContainer.innerHTML = ""; // 清空DOM重新渲染
        messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
        chatContainer.scrollTo(0, chatContainer.scrollHeight);

        successParseCount++;
        updateParseStats();
      }

      async function processAiCommand(message) {
        const loadingMsgId = `loading-${Date.now()}`;
        addMessageToChat({
          id: loadingMsgId,
          text: "",
          sender: "ai",
          type: "loading",
        });

        // 模拟AI回复
        await new Promise((resolve) => setTimeout(resolve, 1000));
        const normalizedUserMessage = message.toLowerCase().trim();
        let response = predefinedResponses[normalizedUserMessage] || predefinedResponses.default;

        // 找到并更新加载消息
        const loadingMessageElement = chatContainer.querySelector(
          `[data-message-id="${loadingMsgId}"] .text`,
        );
        if (loadingMessageElement) {
          const incomingMessageDiv = loadingMessageElement.closest(".message");
          showTypingEffect(response, loadingMessageElement, incomingMessageDiv);
          messages = messages.map((msg) =>
            msg.id === loadingMsgId ? { ...msg, text: response, type: "text" } : msg,
          );
          saveToLocalStorage();
        }
      }

      async function processImageCommand(format) {
        // 触发文件上传,后续由 imageUploadInput 的 change 事件处理
        imageUploadInput.click();
      }

      async function processWeatherCommand(ip = null) {
        const loadingMsgId = `loading-${Date.now()}`;
        addMessageToChat({
          id: loadingMsgId,
          text: ip ? `正在查询IP ${ip} 的天气信息...` : "正在查询当前位置的天气信息...",
          sender: "ai",
          type: "loading",
        });

        try {
          const weatherData = await fetchWeatherByIP(ip);
          if (weatherData && weatherData.code === 200) {
            // 替换加载消息为天气组件
            messages = messages.map((msg) =>
              msg.id === loadingMsgId
                ? {
                    ...msg,
                    text: "天气信息已获取:",
                    type: "component",
                    componentType: "weather",
                    componentData: weatherData,
                    sender: "ai",
                  }
                : msg,
            );
            saveToLocalStorage();
            chatContainer.innerHTML = ""; // 清空DOM重新渲染
            messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
            chatContainer.scrollTo(0, chatContainer.scrollHeight);
          } else {
            messages = messages.map((msg) =>
              msg.id === loadingMsgId
                ? {
                    ...msg,
                    text: `获取天气数据失败: ${weatherData.msg || "未知错误"}`,
                    type: "error",
                    sender: "ai",
                  }
                : msg,
            );
            saveToLocalStorage();
            chatContainer.innerHTML = ""; // 清空DOM重新渲染
            messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
            chatContainer.scrollTo(0, chatContainer.scrollHeight);
          }
        } catch (error) {
          messages = messages.map((msg) =>
            msg.id === loadingMsgId
              ? {
                  ...msg,
                  text: `获取天气数据失败: ${error.message || "请检查网络连接或稍后重试"}`,
                  type: "error",
                  sender: "ai",
                }
              : msg,
          );
          saveToLocalStorage();
          chatContainer.innerHTML = ""; // 清空DOM重新渲染
          messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
          chatContainer.scrollTo(0, chatContainer.scrollHeight);
        }
      }

      async function processCalendarCommand() {
        addMessageToChat({
          text: "日历信息:",
          sender: "ai",
          type: "component",
          componentType: "calendar",
          componentData: {
            currentMonth: new Date().getMonth(),
            currentYear: new Date().getFullYear(),
            selectedDate: new Date(),
          },
        });
      }

      async function processStatsCommand() {
        addMessageToChat({
          text: `系统统计信息:
总解析次数: ${totalParseCount}
成功解析次数: ${successParseCount}
成功率: ${totalParseCount > 0 ? Math.round((successParseCount / totalParseCount) * 100) : 0}%
命令历史记录: ${commandHistoryArray.length} 条
当前时间: ${new Date().toLocaleString()}`,
          sender: "ai",
          type: "text",
        });
      }

      // ================ 事件监听器 ================

      // 切换浅色/深色主题
      toggleThemeButton.addEventListener("click", () => {
        const isLightMode = document.body.classList.toggle("light_mode");
        localStorage.setItem("themeColor", isLightMode ? "dark_mode" : "light_mode");
        toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
      });

      // 删除所有聊天记录
      deleteChatButton.addEventListener("click", () => {
        if (confirm("您确定要清除所有聊天记录吗?")) {
          messages = [];
          localStorage.removeItem("chatHistory");
          localStorage.removeItem("commandHistory"); // 清除命令历史
          localStorage.removeItem("totalParseCount"); // 清除统计
          localStorage.removeItem("successParseCount"); // 清除统计
          totalParseCount = 0;
          successParseCount = 0;
          updateParseStats(); // 更新显示
          chatContainer.innerHTML = ""; // 从DOM中清除消息
          header.classList.remove("hide-header"); // 再次显示头部
          addMessageToChat({
            text: "所有聊天和数据已清空。",
            sender: "ai",
            type: "text",
          });
        }
      });

      // 处理图片上传按钮点击
      uploadImageButton.addEventListener("click", () => {
        imageUploadInput.click();
      });

      // 处理图片文件选择 (用于 image 命令)
      imageUploadInput.addEventListener("change", async (e) => {
        const file = e.target.files[0];
        if (!file) {
          isResponseGenerating = false; // 如果没有选择文件,重置状态
          return;
        }

        if (!file.type.startsWith("image/")) {
          showAlert("请选择图片文件。");
          isResponseGenerating = false; // 如果不是图片,重置状态
          return;
        }

        const reader = new FileReader();
        reader.onloadend = async () => {
          const base64Image = reader.result;
          // 立即显示用户上传的图片消息
          addMessageToChat({
            id: `msg-${Date.now()}`,
            text: `已上传图片: ${file.name}`,
            sender: "user",
            type: "text",
            image: base64Image,
            originalFileName: file.name
          });

          // 重置转换设置
          currentConversionSettings = {
            format: "png",
            quality: 0.8,
            resize: null
          };
        };
        reader.readAsDataURL(file);
      });

      // 处理建议点击
      suggestions.forEach((suggestion) => {
        suggestion.addEventListener("click", () => {
          const text = suggestion.dataset.command || suggestion.querySelector(".text").innerText;
          handleOutgoingChat(text);
        });
      });

      // 处理表单提交 (点击发送按钮)
      typingForm.addEventListener("submit", (e) => {
        e.preventDefault();
        handleOutgoingChat(typingInput.value.trim());
      });

      // 添加回车键发送信息功能
      typingInput.addEventListener("keydown", (e) => {
        if (e.key === "Enter" && !e.shiftKey) {
          e.preventDefault(); // 阻止默认的换行行为
          handleOutgoingChat(typingInput.value.trim());
        } else if (e.key === "ArrowUp") {
          e.preventDefault();
          if (historyIndex < commandHistoryArray.length - 1) {
            historyIndex++;
            typingInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
          }
        } else if (e.key === "ArrowDown") {
          e.preventDefault();
          if (historyIndex > 0) {
            historyIndex--;
            typingInput.value = commandHistoryArray[commandHistoryArray.length - 1 - historyIndex];
          } else if (historyIndex === 0) {
            historyIndex = -1;
            typingInput.value = "";
          }
        }
      });

      // 自动调整文本域大小
      typingInput.addEventListener("input", () => {
        typingInput.style.height = "auto";
        typingInput.style.height = `${typingInput.scrollHeight}px`;
      });

      // ================ 图片转换功能 ================

      // 设置图片转换格式
      window.setImageConversionFormat = (format) => {
        currentConversionSettings.format = format;
        
        // 更新按钮样式
        document.querySelectorAll('.tool-btn[data-format]').forEach(btn => {
          btn.classList.remove('active');
          if (btn.dataset.format === format) {
            btn.classList.add('active');
          }
        });
      };

      // 设置图片质量
      window.setImageQuality = (quality) => {
        currentConversionSettings.quality = parseFloat(quality);
        document.querySelectorAll('.tool-slider').forEach(slider => {
          slider.value = quality;
        });
        document.querySelectorAll('.tool-label span').forEach(label => {
          label.textContent = `质量: ${Math.round(quality * 100)}%`;
        });
      };

      // 转换图片
      window.convertImage = async (messageId) => {
        const message = messages.find(msg => msg.id === messageId);
        if (!message || !message.image) return;

        // 显示进度条
        const progressBar = document.getElementById(`progress-${messageId}`);
        const resultContainer = document.getElementById(`result-${messageId}`);
        progressBar.style.display = 'block';
        
        try {
          // 获取原始图片数据
          const response = await fetch(message.image);
          if (!response.ok) throw new Error('图片加载失败');
          const blob = await response.blob();
          
          // 模拟进度更新
          let progress = 0;
          const progressInterval = setInterval(() => {
            progress += 0.1;
            const progressBarElement = progressBar.querySelector('.tool-progress-bar');
            if (progressBarElement) {
              progressBarElement.style.width = `${Math.min(progress * 100, 100)}%`;
            }
            if (progress >= 1) clearInterval(progressInterval);
          }, 200);

          // 实际转换过程
          const convertedImage = await convertImageFormat(blob, currentConversionSettings.format, currentConversionSettings.quality);
          
          // 计算转换后大小
          const convertedSize = convertedImage.size;
          
          // 转换为DataURL用于显示
          const reader = new FileReader();
          reader.onloadend = () => {
            const convertedDataUrl = reader.result;
            
            // 更新结果容器
            resultContainer.style.display = 'block';
            resultContainer.querySelector('img').src = convertedDataUrl;
            resultContainer.querySelector('.format-value').textContent = currentConversionSettings.format.toUpperCase();
            resultContainer.querySelector('.quality-value').textContent = `${Math.round(currentConversionSettings.quality * 100)}%`;
            resultContainer.querySelector('.size-value').textContent = formatFileSize(convertedSize);
            
            // 更新下载链接
            const downloadLink = resultContainer.querySelector('.download-btn');
            downloadLink.href = convertedDataUrl;
            downloadLink.download = message.originalFileName ? 
              message.originalFileName.replace(/\.[^/.]+$/, "") + "." + currentConversionSettings.format : 
              "converted_image." + currentConversionSettings.format;
            
            // 添加到消息历史
            messages = messages.map(msg => 
              msg.id === messageId ? {
                ...msg,
                convertedImage: convertedDataUrl,
                convertedFormat: currentConversionSettings.format,
                convertedQuality: currentConversionSettings.quality,
                convertedSize: convertedSize
              } : msg
            );
            saveToLocalStorage();
            
            showAlert("图片转换成功!");
          };
          reader.readAsDataURL(convertedImage);
        } catch (error) {
          console.error("图片转换失败:", error);
          showAlert("图片转换失败: " + error.message);
        } finally {
          progressBar.style.display = 'none';
        }
      };

      // 取消图片转换
      window.cancelImageConversion = (messageId) => {
        const progressBar = document.getElementById(`progress-${messageId}`);
        progressBar.style.display = 'none';
        showAlert("已取消图片转换");
      };

      // 图片格式转换函数
      const convertImageFormat = (blob, format, quality = 0.8) => {
        return new Promise((resolve, reject) => {
          const img = new Image();
          img.onload = () => {
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0);
            
            // 根据格式和质量进行转换
            let mimeType;
            switch (format.toLowerCase()) {
              case 'jpeg':
              case 'jpg':
                mimeType = 'image/jpeg';
                break;
              case 'webp':
                mimeType = 'image/webp';
                break;
              case 'png':
              default:
                mimeType = 'image/png';
                break;
            }
            
            canvas.toBlob(
              (blob) => {
                if (blob) {
                  resolve(blob);
                } else {
                  reject(new Error('图片转换失败'));
                }
              },
              mimeType,
              quality
            );
          };
          
          img.onerror = () => {
            reject(new Error('图片加载失败'));
          };
          
          img.src = URL.createObjectURL(blob);
        });
      };

      // ================ 终端工具辅助函数 (从TENGYUAN项目移植并适配) ================

      // 天气API请求 (模拟)
      async function fetchWeatherByIP(ip = null) {
        // 这是一个模拟函数,实际需要调用后端API
        await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟网络延迟
        const mockData = {
          code: 200,
          msg: "成功",
          place: ip ? `IP: ${ip}` : "当前位置",
          weather1: "多云",
          weather2: "晴",
          temperature: "25",
          precipitation: "0.1",
          pressure: "1012",
          humidity: "60",
          windDirection: "东南风",
          windScale: "3-4级",
          windSpeed: "5.2",
          windDirectionDegree: "135",
        };
        if (ip === "error") {
          throw new Error("模拟错误:无法获取指定IP天气。");
        }
        return mockData;
      }

      function getWeatherIcon(weather1, weather2) {
        const weather = weather1 + (weather2 ? weather2 : "");
        const iconMap = {
          晴: "fas fa-sun",
          多云: "fas fa-cloud-sun",
          阴: "fas fa-cloud",
          小雨: "fas fa-cloud-rain",
          中雨: "fas fa-cloud-rain",
          大雨: "fas fa-cloud-showers-heavy",
          雷阵雨: "fas fa-bolt",
          雪: "fas fa-snowflake",
          雾: "fas fa-smog",
          霾: "fas fa-smog",
          云: "fas fa-cloud",
        };

        for (const key in iconMap) {
          if (weather.includes(key)) {
            return iconMap[key];
          }
        }
        return "fas fa-cloud";
      }

      function getWindDescription(windDirection, windScale) {
        return `${windDirection} ${windScale}`;
      }

      // 渲染天气组件的HTML
      function renderWeatherComponent(weatherData) {
        const weatherIcon = getWeatherIcon(weatherData.weather1, weatherData.weather2);
        const weatherDesc =
          weatherData.weather1 +
          (weatherData.weather2 && weatherData.weather2 !== weatherData.weather1
            ? "转" + weatherData.weather2
            : "");
        const windDesc = getWindDescription(weatherData.windDirection, weatherData.windScale);

        return `
          <div class="tool-container">
            <div class="tool-title">
              <i class="fas fa-cloud-sun"></i> 天气信息
            </div>
            <div class="weather-info">
              <div class="weather-item">
                <div class="weather-item-label">位置</div>
                <div class="weather-item-value">${weatherData.place}</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">天气</div>
                <div class="weather-item-value">${weatherDesc}</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">温度</div>
                <div class="weather-item-value">${weatherData.temperature}°C</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">降水量</div>
                <div class="weather-item-value">${weatherData.precipitation}mm</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">气压</div>
                <div class="weather-item-value">${weatherData.pressure}hPa</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">湿度</div>
                <div class="weather-item-value">${weatherData.humidity}%</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">风力</div>
                <div class="weather-item-value">${windDesc}</div>
              </div>
              <div class="weather-item">
                <div class="weather-item-label">风速</div>
                <div class="weather-item-value">${weatherData.windSpeed}m/s</div>
              </div>
            </div>
            <div style="font-size: 11px; color: var(--line-color); margin-top: 10px;">
              💡 基于位置自动获取天气信息 (模拟数据)
            </div>
          </div>
        `;
      }

      // 渲染日历组件的HTML
      function renderCalendarComponent(calendarData) {
        const monthNames = [
          "一月",
          "二月",
          "三月",
          "四月",
          "五月",
          "六月",
          "七月",
          "八月",
          "九月",
          "十月",
          "十一月",
          "十二月",
        ];
        let currentMonth = calendarData.currentMonth;
        let currentYear = calendarData.currentYear;
        let selectedDate = calendarData.selectedDate;

        const firstDay = new Date(currentYear, currentMonth, 1);
        const lastDay = new Date(currentYear, currentMonth + 1, 0);
        const prevMonthLastDay = new Date(currentYear, currentMonth, 0).getDate();
        const firstDayIndex = firstDay.getDay();
        const lastDate = lastDay.getDate();
        const today = new Date();

        let daysHtml = `
          <div class="calendar-weekday">日</div>
          <div class="calendar-weekday">一</div>
          <div class="calendar-weekday">二</div>
          <div class="calendar-weekday">三</div>
          <div class="calendar-weekday">四</div>
          <div class="calendar-weekday">五</div>
          <div class="calendar-weekday">六</div>
        `;

        // 上个月的日期
        for (let i = firstDayIndex; i > 0; i--) {
          daysHtml += `<div class="calendar-day other-month">${prevMonthLastDay - i + 1}</div>`;
        }

        // 当前月的日期
        for (let i = 1; i <= lastDate; i++) {
          let classList = "calendar-day";
          if (
            i === today.getDate() &&
            currentMonth === today.getMonth() &&
            currentYear === today.getFullYear()
          ) {
            classList += " today";
          }
          if (
            i === selectedDate.getDate() &&
            currentMonth === selectedDate.getMonth() &&
            currentYear === selectedDate.getFullYear()
          ) {
            classList += " selected";
          }
          // 藤原生日标记
          if (i === 1 && currentMonth === 10) {
            // 11月1日
            classList += " birthday";
          }
          daysHtml += `<div class="${classList}" data-day="${i}">${i}</div>`;
        }

        // 下个月的日期
        const daysNeeded = 42 - (firstDayIndex + lastDate);
        for (let i = 1; i <= daysNeeded; i++) {
          daysHtml += `<div class="calendar-day other-month">${i}</div>`;
        }

        const calendarHtml = `
          <div class="tool-container">
            <div class="tool-title">
              <i class="fas fa-calendar-alt"></i> 日历
            </div>
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
              <div style="font-weight: 600; color: var(--text-color);">${currentYear}年${monthNames[currentMonth]}</div>
              <div style="display: flex; gap: 8px;">
                <button class="tool-btn" data-action="prevMonth" aria-label="上个月">
                  <i class="fas fa-chevron-left"></i>
                </button>
                <button class="tool-btn" data-action="nextMonth" aria-label="下个月">
                  <i class="fas fa-chevron-right"></i>
                </button>
              </div>
            </div>
            <div class="calendar-grid">
              ${daysHtml}
            </div>
            <div style="font-size: 11px; color: var(--line-color); margin-top: 10px;">
              💡 点击日期可选择,点击箭头切换月份。
            </div>
          </div>
        `;

        // 返回HTML字符串,并添加事件监听器
        setTimeout(() => {
          const calendarElement = chatContainer.querySelector(".tool-container");
          if (calendarElement) {
            calendarElement
              .querySelector('[data-action="prevMonth"]')
              .addEventListener("click", () => {
                currentMonth--;
                if (currentMonth < 0) {
                  currentMonth = 11;
                  currentYear--;
                }
                updateCalendarInChat(currentMonth, currentYear, selectedDate);
              });
            calendarElement
              .querySelector('[data-action="nextMonth"]')
              .addEventListener("click", () => {
                currentMonth++;
                if (currentMonth > 11) {
                  currentMonth = 0;
                  currentYear++;
                }
                updateCalendarInChat(currentMonth, currentYear, selectedDate);
              });
            calendarElement.querySelectorAll(".calendar-day").forEach((dayElem) => {
              if (!dayElem.classList.contains("other-month")) {
                dayElem.addEventListener("click", () => {
                  const day = parseInt(dayElem.dataset.day);
                  selectedDate = new Date(currentYear, currentMonth, day);
                  updateCalendarInChat(currentMonth, currentYear, selectedDate);
                  showAlert(`已选择日期: ${currentYear}年${currentMonth + 1}月${day}日`);
                });
              }
            });
          }
        }, 100); // 延迟绑定事件,确保DOM已渲染

        return calendarHtml;
      }

      // 更新聊天中日历的函数
      function updateCalendarInChat(month, year, date) {
        // 找到日历消息并更新其数据
        messages = messages.map((msg) => {
          if (msg.type === "component" && msg.componentType === "calendar") {
            return {
              ...msg,
              componentData: {
                currentMonth: month,
                currentYear: year,
                selectedDate: date,
              },
            };
          }
          return msg;
        });
        saveToLocalStorage();
        chatContainer.innerHTML = ""; // 清空DOM重新渲染
        messages.forEach((msg) => chatContainer.appendChild(createMessageElement(msg)));
        chatContainer.scrollTo(0, chatContainer.scrollHeight);
      }

      // 显示全屏图片
      window.showFullImage = (url) => {
        const overlay = document.createElement("div");
        overlay.className = "fullscreen-image-overlay";
        const img = document.createElement("img");
        img.src = url;
        overlay.onclick = () => overlay.remove();
        overlay.appendChild(img);
        document.body.appendChild(overlay);
      };

      // 下载所有图片 (JSZip)
      window.downloadAllImages = async (images, title) => {
        if (!window.JSZip) {
          showAlert("打包下载功能需要JSZip库支持。");
          return;
        }

        try {
          const zip = new JSZip();
          const imgFolder = zip.folder("images");

          showAlert("正在打包下载,请稍候...");

          const downloadPromises = images.map(async (imgUrl, index) => {
            try {
              const response = await fetch(imgUrl);
              if (!response.ok) throw new Error(`图片${index + 1}下载失败`);
              const blob = await response.blob();
              imgFolder.file(`image_${index + 1}.jpg`, blob);
            } catch (error) {
              console.warn(`图片${index + 1}下载失败:`, error);
            }
          });

          await Promise.all(downloadPromises);

          const zipBlob = await zip.generateAsync({
            type: "blob",
            compression: "DEFLATE",
            compressionOptions: { level: 6 },
          });

          const cleanTitle = title.replace(/[<>:"/\\|?*]/g, "");
          saveAs(zipBlob, `${cleanTitle}.zip`);

          showAlert("打包下载完成!");
        } catch (error) {
          console.error("打包下载失败:", error);
          showAlert("打包下载失败,请重试。");
        }
      };

      // 下载视频 (FileSaver)
      window.downloadVideo = async (url, title) => {
        try {
          showAlert("正在下载视频,请稍候...");

          const response = await fetch(url);
          if (!response.ok) throw new Error(`下载失败: ${response.status}`);

          const blob = await response.blob();
          const cleanTitle = title.replace(/[<>:"/\\|?*]/g, "");
          saveAs(blob, `${cleanTitle}.mp4`);

          showAlert("视频下载完成!");
        } catch (error) {
          console.error("视频下载失败:", error);
          showAlert("视频下载失败,请重试。");
        }
      };

      // 保存命令历史
      function saveToHistory(command) {
        if (commandHistoryArray.length === 0 || commandHistoryArray[commandHistoryArray.length - 1] !== command) {
          commandHistoryArray.push(command);
          if (commandHistoryArray.length > 20) {
            commandHistoryArray.shift();
          }
          localStorage.setItem("commandHistory", JSON.stringify(commandHistoryArray));
        }
        historyIndex = -1;
      }

      // 更新解析统计
      function updateParseStats() {
        localStorage.setItem("totalParseCount", totalParseCount.toString());
        localStorage.setItem("successParseCount", successParseCount.toString());
      }

      // ================ 初始化 ================
      document.addEventListener("DOMContentLoaded", function () {
        loadFromLocalStorage(); // 加载保存的历史记录和主题

        // 聚焦到输入框
        typingInput.focus();

        // 显示欢迎信息
        setTimeout(() => {
          addMessageToChat({
            text: "欢迎使用超级AI助手!\n\n输入 'help' 查看可用命令,或直接输入问题与我对话。",
            sender: "ai",
            type: "text",
          });
        }, 500);
      });

      // 页面可见性变化时重新聚焦
      document.addEventListener("visibilitychange", () => {
        if (!document.hidden) {
          setTimeout(() => typingInput.focus(), 100);
        }
      });

      // 错误处理
      window.addEventListener("error", (e) => {
        console.error("全局错误:", e.error);
      });
      window.addEventListener("unhandledrejection", (e) => {
        console.error("未处理的Promise拒绝:", e.reason);
      });
    </script>
  </body>
</html>
        
预览
控制台