Ver Fonte

feat: 添加Excel文件解析和AI分析功能

1. 集成SheetJS库用于Excel文件读取
2. 优化AI回复的显示样式
3. 实现Excel数据的上下文记忆
4. 支持基于Excel内容的持续对话
alibct há 7 meses atrás
pai
commit
040adec56e
6 ficheiros alterados com 251 adições e 17 exclusões
  1. 75 0
      css/content.css
  2. 9 0
      js/ai-service.js
  3. 161 16
      js/chat-ui.js
  4. 1 0
      js/libs/xlsx.full.min.js
  5. 1 1
      manifest.json
  6. 4 0
      sidebar.html

+ 75 - 0
css/content.css

@@ -56,3 +56,78 @@ body.sidebar-open #paiwise-main-wrapper {
 }
 
 /* 移除所有动画关键帧,使用transform实现动画 */
+
+.message-content {
+  position: relative;
+  padding: 12px;
+  border-radius: 12px;
+  background: #fff;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s ease;
+  max-width: 100%;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+  word-break: break-word;
+  hyphens: auto;
+  white-space: pre-line; /* 保留换行但合并空格 */
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+    "Helvetica Neue", Arial, sans-serif;
+  line-height: 1.6;
+  text-align: left;
+}
+
+/* AI回复的特殊样式 */
+.message.assistant .message-content {
+  background: #f0f4f8;
+  border-top-left-radius: 4px;
+  padding: 12px; /* 统一内边距 */
+}
+
+/* 列表样式 */
+.message.assistant .message-content ol,
+.message.assistant .message-content ul {
+  margin: 8px 0;
+  padding-left: 24px;
+}
+
+/* 段落间距 */
+.message.assistant .message-content p {
+  margin: 8px 0;
+  text-align: left;
+  padding: 0;
+}
+
+/* 标题样式 */
+.message.assistant .message-content h1,
+.message.assistant .message-content h2,
+.message.assistant .message-content h3 {
+  margin: 16px 0 8px;
+  font-weight: 600;
+}
+
+/* 代码块样式 */
+.message.assistant .message-content pre,
+.message.assistant .message-content code {
+  background: rgba(0, 0, 0, 0.04);
+  padding: 2px 4px;
+  border-radius: 4px;
+  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+}
+
+/* 表格样式 */
+.message.assistant .message-content table {
+  border-collapse: collapse;
+  margin: 8px 0;
+  width: 100%;
+}
+
+.message.assistant .message-content th,
+.message.assistant .message-content td {
+  border: 1px solid #e0e4e8;
+  padding: 8px;
+  text-align: left;
+}
+
+.message.assistant .message-content th {
+  background: #f5f7f9;
+}

+ 9 - 0
js/ai-service.js

@@ -7,6 +7,7 @@ class AIService {
     this.apiEndpoint = CONFIG.AI_API.ENDPOINT;
     this.model = CONFIG.AI_API.MODEL;
     this.context = [];
+    this.currentExcelData = null; // 保存当前Excel数据
     // 使用默认API密钥
     this.apiKey = CONFIG.AI_API.DEFAULT_API_KEY;
     this.controller = null; // 用于中断请求的 AbortController
@@ -171,6 +172,14 @@ ${pageInfo.mainContent}
 
 请以"以下是对该页面内容的总结:"开头,然后用要点的形式列出主要内容。`;
   }
+
+  /**
+   * 设置当前Excel数据
+   * @param {Object} data Excel数据和元信息
+   */
+  setExcelData(data) {
+    this.currentExcelData = data;
+  }
 }
 
 // 确保在DOM加载完成后再创建实例

+ 161 - 16
js/chat-ui.js

@@ -6,6 +6,8 @@ class ChatUI {
     this.stopButton = document.getElementById("stop-button");
     this.promptButton = document.getElementById("prompt-button");
     this.promptPanel = document.getElementById("prompt-panel");
+    this.uploadButton = document.getElementById("upload-button");
+    this.fileInput = document.getElementById("file-input");
 
     // 确保AI服务已经初始化
     if (!window.aiService) {
@@ -25,6 +27,13 @@ class ChatUI {
     // 获取总结按钮
     this.summarizeButton = document.querySelector(".summarize-button");
 
+    // 支持的Excel文件类型
+    this.excelTypes = {
+      xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+      xls: "application/vnd.ms-excel",
+      csv: "text/csv",
+    };
+
     // 初始化其他组件
     this.setupEventListeners();
     this.init();
@@ -165,22 +174,19 @@ class ChatUI {
       }
     });
 
-    // 文件上传功能
-    const uploadButton = document.getElementById("upload-button");
-    const fileInput = document.getElementById("file-input");
-
-    uploadButton.addEventListener("click", () => {
-      fileInput.click();
+    // 上传按钮点击事件
+    this.uploadButton.addEventListener("click", () => {
+      this.fileInput.click();
     });
 
-    fileInput.addEventListener("change", (e) => {
-      const files = Array.from(e.target.files);
+    // 文件选择事件
+    this.fileInput.addEventListener("change", (event) => {
+      const files = event.target.files;
       if (files.length > 0) {
-        // 处理文件上传
         this.handleFileUpload(files);
       }
-      // 清空文件输入框,以便可以重复选择同一文件
-      fileInput.value = "";
+      // 清空文件输入框,确保同一文件可以重复上传
+      event.target.value = "";
     });
   }
 
@@ -219,7 +225,21 @@ class ChatUI {
       this.setInputState(true); // 禁用输入框
       this.loadingDiv = this.createLoadingMessage();
 
-      const response = await this.aiService.sendMessage(message);
+      // 如果存在Excel数据,添加上下文提示
+      let prompt = message;
+      if (this.aiService.currentExcelData) {
+        const { fileName, headers, totalRows } =
+          this.aiService.currentExcelData;
+        prompt = `请记住之前我们正在讨论的Excel文件:
+- 文件名:${fileName}
+- 列标题:${headers.join(", ")}
+- 总行数:${totalRows}
+
+基于这个Excel文件的内容,请回答以下问题:
+${message}`;
+      }
+
+      const response = await this.aiService.sendMessage(prompt);
 
       if (this.loadingDiv) {
         this.loadingDiv.remove();
@@ -699,12 +719,137 @@ class ChatUI {
 
   /**
    * 处理文件上传
-   * @param {File[]} files 上传的文件数组
+   * @param {FileList} files 上传的文件列表
    */
   async handleFileUpload(files) {
-    // 目前仅显示文件信息,后续可以添加实际的上传和处理逻辑
-    const fileNames = files.map((file) => file.name).join(", ");
-    this.addMessage(`已选择文件:${fileNames}`, "user", false);
+    try {
+      for (const file of files) {
+        const extension = file.name.split(".").pop().toLowerCase();
+
+        // 显示文件上传消息
+        this.addMessage(`已上传文件:${file.name}`, "user", false);
+
+        if (this.excelTypes[extension]) {
+          try {
+            this.setInputState(true);
+            this.loadingDiv = this.createLoadingMessage();
+
+            // 读取Excel文件
+            const data = await this.readExcelFile(file);
+
+            // 保存Excel数据到AI服务
+            this.aiService.setExcelData({
+              fileName: file.name,
+              headers: data[0],
+              rows: data.slice(1),
+              totalRows: data.length - 1,
+            });
+
+            // 构建Excel理解提示词
+            const prompt = this.buildExcelUnderstandingPrompt(data, file.name);
+
+            // 调用AI服务理解数据
+            const response = await this.aiService.sendMessage(prompt);
+
+            if (this.loadingDiv) {
+              this.loadingDiv.remove();
+              this.loadingDiv = null;
+            }
+
+            // 显示AI的理解结果
+            this.addMessage(response, "assistant", true);
+          } catch (error) {
+            console.error("Excel processing error:", error);
+            this.addMessage(
+              "Excel文件处理过程中出现错误,请重试。",
+              "assistant",
+              false
+            );
+          } finally {
+            this.setInputState(false);
+          }
+        }
+      }
+    } catch (error) {
+      console.error("File upload error:", error);
+      this.addMessage("文件上传过程中出现错误,请重试。", "assistant", false);
+    }
+  }
+
+  /**
+   * 构建Excel理解提示词
+   */
+  buildExcelUnderstandingPrompt(data, fileName) {
+    if (!data || data.length < 2) {
+      return "这是一个空的Excel文件,请检查文件内容。";
+    }
+
+    const headers = data[0];
+    const rows = data.slice(1);
+    const sampleRows = rows.slice(0, 2);
+
+    return `我将向你展示一个通过SheetJS库读取的Excel文件内容。请帮我理解这些数据:
+
+文件名:${fileName}
+列标题:${headers.join(", ")}
+数据行数:${rows.length}
+
+示例数据(前2行):
+${sampleRows
+  .map((row, index) => {
+    return `第${index + 1}行: ${row
+      .map((cell, i) => `${headers[i]}=${cell}`)
+      .join(", ")}`;
+  })
+  .join("\n")}
+
+请简要说明:
+1. 这个表格的主要用途
+2. 数据之间的关系
+3. 可能的使用场景
+
+请记住这些数据内容,因为后续我可能会询问更多相关问题。
+请用简洁的语言回复,避免过多修饰词。`;
+  }
+
+  /**
+   * 读取Excel文件
+   * @param {File} file
+   * @returns {Promise<Array>}
+   */
+  async readExcelFile(file) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+
+      reader.onload = (e) => {
+        try {
+          const data = new Uint8Array(e.target.result);
+          const workbook = XLSX.read(data, {
+            type: "array",
+            cellDates: true,
+            cellNF: false,
+            cellText: false,
+          });
+
+          // 获取第一个工作表
+          const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
+
+          // 转换为JSON数据
+          const jsonData = XLSX.utils.sheet_to_json(firstSheet, {
+            header: 1,
+            raw: true,
+            defval: "",
+          });
+
+          resolve(jsonData);
+        } catch (error) {
+          reject(error);
+        }
+      };
+
+      reader.onerror = () => reject(reader.error);
+      reader.readAsArrayBuffer(file);
+    });
   }
 }
 

Diff do ficheiro suprimidas por serem muito extensas
+ 1 - 0
js/libs/xlsx.full.min.js


+ 1 - 1
manifest.json

@@ -1,7 +1,7 @@
 {
   "manifest_version": 3,
   "name": "派维斯智能体助手",
-  "version": "0.1.1",
+  "version": "0.1.2",
   "description": "一个辅助用户处理业务流程的智能体助手",
   "author": "Paiwise Team",
   "permissions": [

+ 4 - 0
sidebar.html

@@ -211,6 +211,10 @@
       </div>
     </div>
 
+    <!-- 第三方库 -->
+    <script src="js/libs/xlsx.full.min.js"></script>
+
+    <!-- 应用脚本 -->
     <script src="js/config.js"></script>
     <script src="js/utils.js"></script>
     <script src="js/ai-service.js"></script>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff