samlax12 commited on
Commit
9e00cc6
·
verified ·
1 Parent(s): 11e1d12

Upload 29 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ uploads/496449c167344601ba87c24743701b99.pdf filter=lfs diff=lfs merge=lfs -text
37
+ uploads/eb55dde866f64be4a1fdbc81d3d1df0e.pdf filter=lfs diff=lfs merge=lfs -text
modules/agent_builder/__pycache__/routes.cpython-312.pyc ADDED
Binary file (18.8 kB). View file
 
modules/code_executor/__pycache__/routes.cpython-312.pyc ADDED
Binary file (11.5 kB). View file
 
modules/knowledge_base/__pycache__/generator.cpython-312.pyc ADDED
Binary file (17.4 kB). View file
 
modules/knowledge_base/__pycache__/processor.cpython-312.pyc ADDED
Binary file (11 kB). View file
 
modules/knowledge_base/__pycache__/reranker.cpython-312.pyc ADDED
Binary file (2.26 kB). View file
 
modules/knowledge_base/__pycache__/retriever.cpython-312.pyc ADDED
Binary file (4.36 kB). View file
 
modules/knowledge_base/__pycache__/routes.cpython-312.pyc ADDED
Binary file (11.6 kB). View file
 
modules/knowledge_base/__pycache__/vector_store.cpython-312.pyc ADDED
Binary file (7.18 kB). View file
 
modules/knowledge_base/processor.py CHANGED
@@ -14,9 +14,12 @@ import io
14
 
15
  class DocumentLoader:
16
  """通用文档加载器"""
17
- def __init__(self, file_path: str):
18
  self.file_path = file_path
19
- self.extension = os.path.splitext(file_path)[1].lower()
 
 
 
20
  self.api_key = os.getenv("API_KEY")
21
  self.api_base = os.getenv("BASE_URL")
22
 
@@ -74,15 +77,27 @@ class DocumentLoader:
74
 
75
  def load(self):
76
  try:
 
 
77
  if self.extension == '.md':
78
- loader = UnstructuredMarkdownLoader(self.file_path, encoding='utf-8')
79
- return loader.load()
 
 
 
 
 
80
  elif self.extension == '.pdf':
81
  loader = PyPDFLoader(self.file_path)
82
  return loader.load()
83
  elif self.extension == '.txt':
84
- loader = TextLoader(self.file_path, encoding='utf-8')
85
- return loader.load()
 
 
 
 
 
86
  elif self.extension in ['.png', '.jpg', '.jpeg', '.gif', '.bmp']:
87
  # 处理图片
88
  description = self.process_image(self.file_path)
@@ -92,18 +107,29 @@ class DocumentLoader:
92
  page_content=description,
93
  metadata={
94
  'source': self.file_path,
 
95
  'img_url': os.path.abspath(self.file_path) # 存储图片的绝对路径
96
  }
97
  )
98
  return [doc]
99
  else:
 
100
  raise ValueError(f"不支持的文件格式: {self.extension}")
101
 
102
  except UnicodeDecodeError:
103
- # 如果 utf-8 失败,尝试 gbk
 
104
  if self.extension in ['.md', '.txt']:
105
- loader = TextLoader(self.file_path, encoding='gbk')
106
- return loader.load()
 
 
 
 
 
 
 
 
107
  raise
108
 
109
  class DocumentProcessor:
@@ -123,12 +149,13 @@ class DocumentProcessor:
123
  # 如果是文件,使用文件名(不含扩展名)
124
  return f"rag_{os.path.splitext(os.path.basename(path))[0].lower()}"
125
 
126
- def process(self, path: str, progress_callback: Optional[Callable] = None) -> List[Dict]:
127
  """
128
  加载并处理文档,支持目录或单个文件
129
  参数:
130
  path: 文档路径
131
  progress_callback: 进度回调函数,用于报告处理进度
 
132
  返回:处理后的文档列表
133
  """
134
  if os.path.isdir(path):
@@ -148,11 +175,12 @@ class DocumentProcessor:
148
  processed_files += 1
149
  progress_callback(processed_size, f"处理文件 {processed_files}/{total_files}: {file}")
150
 
151
- loader = DocumentLoader(file_path)
 
152
  docs = loader.load()
153
  # 添加文件名到metadata
154
  for doc in docs:
155
- doc.metadata['file_name'] = os.path.basename(file_path)
156
  documents.extend(docs)
157
  except Exception as e:
158
  print(f"警告:加载文件 {file_path} 时出错: {str(e)}")
@@ -161,17 +189,18 @@ class DocumentProcessor:
161
  try:
162
  if progress_callback:
163
  file_size = os.path.getsize(path)
164
- progress_callback(file_size * 0.3, f"加载文件: {os.path.basename(path)}")
165
 
166
- loader = DocumentLoader(path)
 
167
  documents = loader.load()
168
 
169
  # 更新进度
170
  if progress_callback:
171
  progress_callback(file_size * 0.6, f"处理文件内容...")
172
 
173
- # 添加文件名到metadata
174
- file_name = os.path.basename(path)
175
  for doc in documents:
176
  doc.metadata['file_name'] = file_name
177
  except Exception as e:
 
14
 
15
  class DocumentLoader:
16
  """通用文档加载器"""
17
+ def __init__(self, file_path: str, original_filename: str = None):
18
  self.file_path = file_path
19
+ # 使用传入的原始文件名或者从路径提取的文件名
20
+ self.original_filename = original_filename or os.path.basename(file_path)
21
+ # 从原始文件名中获取扩展名,确保中文文件名也能正确识别文件类型
22
+ self.extension = os.path.splitext(self.original_filename)[1].lower()
23
  self.api_key = os.getenv("API_KEY")
24
  self.api_base = os.getenv("BASE_URL")
25
 
 
77
 
78
  def load(self):
79
  try:
80
+ print(f"正在加载文件: {self.file_path}, 原始文件名: {self.original_filename}, 扩展名: {self.extension}")
81
+
82
  if self.extension == '.md':
83
+ try:
84
+ loader = UnstructuredMarkdownLoader(self.file_path, encoding='utf-8')
85
+ return loader.load()
86
+ except UnicodeDecodeError:
87
+ # 如果UTF-8失败,尝试GBK
88
+ loader = UnstructuredMarkdownLoader(self.file_path, encoding='gbk')
89
+ return loader.load()
90
  elif self.extension == '.pdf':
91
  loader = PyPDFLoader(self.file_path)
92
  return loader.load()
93
  elif self.extension == '.txt':
94
+ try:
95
+ loader = TextLoader(self.file_path, encoding='utf-8')
96
+ return loader.load()
97
+ except UnicodeDecodeError:
98
+ # 如果UTF-8失败,尝试GBK
99
+ loader = TextLoader(self.file_path, encoding='gbk')
100
+ return loader.load()
101
  elif self.extension in ['.png', '.jpg', '.jpeg', '.gif', '.bmp']:
102
  # 处理图片
103
  description = self.process_image(self.file_path)
 
107
  page_content=description,
108
  metadata={
109
  'source': self.file_path,
110
+ 'file_name': self.original_filename, # 使用原始文件名
111
  'img_url': os.path.abspath(self.file_path) # 存储图片的绝对路径
112
  }
113
  )
114
  return [doc]
115
  else:
116
+ print(f"不支持的文件扩展名: {self.extension}")
117
  raise ValueError(f"不支持的文件格式: {self.extension}")
118
 
119
  except UnicodeDecodeError:
120
+ # 如果默认编码处理失败,尝试其他编码
121
+ print(f"文件编码错误,尝试其他编码: {self.file_path}")
122
  if self.extension in ['.md', '.txt']:
123
+ try:
124
+ loader = TextLoader(self.file_path, encoding='gbk')
125
+ return loader.load()
126
+ except Exception as e:
127
+ print(f"尝试GBK编码也失败: {str(e)}")
128
+ raise
129
+ except Exception as e:
130
+ print(f"加载文件 {self.file_path} 时出错: {str(e)}")
131
+ import traceback
132
+ traceback.print_exc()
133
  raise
134
 
135
  class DocumentProcessor:
 
149
  # 如果是文件,使用文件名(不含扩展名)
150
  return f"rag_{os.path.splitext(os.path.basename(path))[0].lower()}"
151
 
152
+ def process(self, path: str, progress_callback: Optional[Callable] = None, original_filename: str = None) -> List[Dict]:
153
  """
154
  加载并处理文档,支持目录或单个文件
155
  参数:
156
  path: 文档路径
157
  progress_callback: 进度回调函数,用于报告处理进度
158
+ original_filename: 原始文件名(包括中文)
159
  返回:处理后的文档列表
160
  """
161
  if os.path.isdir(path):
 
175
  processed_files += 1
176
  progress_callback(processed_size, f"处理文件 {processed_files}/{total_files}: {file}")
177
 
178
+ # 为目录中的每个文件,传递原始文件名
179
+ loader = DocumentLoader(file_path, original_filename=file)
180
  docs = loader.load()
181
  # 添加文件名到metadata
182
  for doc in docs:
183
+ doc.metadata['file_name'] = file # 使用原始文件名
184
  documents.extend(docs)
185
  except Exception as e:
186
  print(f"警告:加载文件 {file_path} 时出错: {str(e)}")
 
189
  try:
190
  if progress_callback:
191
  file_size = os.path.getsize(path)
192
+ progress_callback(file_size * 0.3, f"加载文件: {original_filename or os.path.basename(path)}")
193
 
194
+ # 为单个文件,传递原始文件名
195
+ loader = DocumentLoader(path, original_filename=original_filename)
196
  documents = loader.load()
197
 
198
  # 更新进度
199
  if progress_callback:
200
  progress_callback(file_size * 0.6, f"处理文件内容...")
201
 
202
+ # 使用原始文件名而不是存储的文件名
203
+ file_name = original_filename or os.path.basename(path)
204
  for doc in documents:
205
  doc.metadata['file_name'] = file_name
206
  except Exception as e:
modules/knowledge_base/routes.py CHANGED
@@ -60,12 +60,12 @@ def create_knowledge():
60
  if not name:
61
  return jsonify({"success": False, "message": "Knowledge base name cannot be empty"}), 400
62
 
63
- # Check if knowledge base already exists
64
  indices = retriever.get_all_indices()
65
  if f"rag_{name}" in indices:
66
  return jsonify({"success": False, "message": f"Knowledge base '{name}' already exists"}), 400
67
 
68
- # Process uploaded file
69
  if 'file' not in request.files:
70
  return jsonify({"success": False, "message": "No file uploaded"}), 400
71
 
@@ -73,53 +73,62 @@ def create_knowledge():
73
  if file.filename == '':
74
  return jsonify({"success": False, "message": "No file selected"}), 400
75
 
76
- # Save file
77
- filename = secure_filename(file.filename)
78
- file_path = os.path.join(UPLOAD_FOLDER, filename)
 
 
 
 
79
  file.save(file_path)
80
 
81
- # Create task ID
82
  task_id = f"task_{int(time.time())}_{name}"
83
 
84
- # Initialize task status
85
  processing_tasks[task_id] = {
86
  "progress": 0,
87
  "status": "Starting document processing...",
88
  "index_name": name,
89
  "file_path": file_path,
 
90
  "error": False,
91
  "docCount": 0
92
  }
93
 
94
- # Process documents in a separate thread
95
  def process_in_thread():
96
  try:
97
- # Update task status
98
  processing_tasks[task_id]["progress"] = 10
99
  processing_tasks[task_id]["status"] = "Loading document..."
100
 
101
- # Process document with progress tracking
102
  def update_progress(progress, status):
103
  processing_tasks[task_id]["progress"] = min(95, progress)
104
  processing_tasks[task_id]["status"] = status
105
 
106
- # Process the document
107
- processed_docs = doc_processor.process(file_path, progress_callback=update_progress)
 
 
 
 
108
 
109
- # Update task status
110
  processing_tasks[task_id]["progress"] = 95
111
  processing_tasks[task_id]["status"] = "Creating vector store..."
112
  processing_tasks[task_id]["docCount"] = len(processed_docs)
113
 
114
- # Store vectors
115
  vector_store.store(processed_docs, f"rag_{name}")
116
 
117
- # Complete task
118
  processing_tasks[task_id]["progress"] = 100
119
  processing_tasks[task_id]["status"] = "Processing complete"
120
 
121
  except Exception as e:
122
- # Record error
123
  processing_tasks[task_id]["error"] = True
124
  processing_tasks[task_id]["status"] = f"Processing failed: {str(e)}"
125
  import traceback
@@ -158,12 +167,12 @@ def get_progress(task_id):
158
  def add_documents(index_id):
159
  """Add documents to a knowledge base"""
160
  try:
161
- # Check if knowledge base exists
162
  indices = retriever.get_all_indices()
163
  if index_id not in indices:
164
  return jsonify({"success": False, "message": "Knowledge base does not exist"}), 404
165
 
166
- # Process uploaded file
167
  if 'file' not in request.files:
168
  return jsonify({"success": False, "message": "No file uploaded"}), 400
169
 
@@ -171,56 +180,63 @@ def add_documents(index_id):
171
  if file.filename == '':
172
  return jsonify({"success": False, "message": "No file selected"}), 400
173
 
174
- # Save file
175
- filename = secure_filename(file.filename)
176
- file_path = os.path.join(UPLOAD_FOLDER, filename)
 
 
177
  file.save(file_path)
178
 
179
- # Extract knowledge base name from index ID
180
  kb_name = index_id[4:] if index_id.startswith('rag_') else index_id
181
 
182
- # Create task ID
183
- task_id = f"task_{int(time.time())}_{kb_name}_{filename}"
184
 
185
- # Initialize task status
186
  processing_tasks[task_id] = {
187
  "progress": 0,
188
  "status": "Starting document processing...",
189
  "index_name": kb_name,
190
  "file_path": file_path,
 
191
  "error": False,
192
  "docCount": 0
193
  }
194
 
195
- # Process documents in a separate thread
196
  def process_in_thread():
197
  try:
198
- # Update task status
199
  processing_tasks[task_id]["progress"] = 10
200
  processing_tasks[task_id]["status"] = "Loading document..."
201
 
202
- # Process document with progress tracking
203
  def update_progress(progress, status):
204
  processing_tasks[task_id]["progress"] = min(95, progress)
205
  processing_tasks[task_id]["status"] = status
206
 
207
- # Process the document
208
- processed_docs = doc_processor.process(file_path, progress_callback=update_progress)
 
 
 
 
209
 
210
- # Update task status
211
  processing_tasks[task_id]["progress"] = 95
212
  processing_tasks[task_id]["status"] = "Creating vector store..."
213
  processing_tasks[task_id]["docCount"] = len(processed_docs)
214
 
215
- # Store vectors
216
  vector_store.store(processed_docs, index_id)
217
 
218
- # Complete task
219
  processing_tasks[task_id]["progress"] = 100
220
  processing_tasks[task_id]["status"] = "Processing complete"
221
 
222
  except Exception as e:
223
- # Record error
224
  processing_tasks[task_id]["error"] = True
225
  processing_tasks[task_id]["status"] = f"Processing failed: {str(e)}"
226
  import traceback
 
60
  if not name:
61
  return jsonify({"success": False, "message": "Knowledge base name cannot be empty"}), 400
62
 
63
+ # 检查知识库是否已存在
64
  indices = retriever.get_all_indices()
65
  if f"rag_{name}" in indices:
66
  return jsonify({"success": False, "message": f"Knowledge base '{name}' already exists"}), 400
67
 
68
+ # 处理上传文件
69
  if 'file' not in request.files:
70
  return jsonify({"success": False, "message": "No file uploaded"}), 400
71
 
 
73
  if file.filename == '':
74
  return jsonify({"success": False, "message": "No file selected"}), 400
75
 
76
+ # 保存原始文件名
77
+ original_filename = file.filename
78
+ # 从原始文件名中提取扩展名,确保中文文件名也能正确识别文件类型
79
+ file_ext = os.path.splitext(original_filename)[1].lower()
80
+ # 使用UUID生成唯一文件名
81
+ unique_filename = f"{uuid.uuid4().hex}{file_ext}"
82
+ file_path = os.path.join(UPLOAD_FOLDER, unique_filename)
83
  file.save(file_path)
84
 
85
+ # 创建任务ID
86
  task_id = f"task_{int(time.time())}_{name}"
87
 
88
+ # 初始化任务状态
89
  processing_tasks[task_id] = {
90
  "progress": 0,
91
  "status": "Starting document processing...",
92
  "index_name": name,
93
  "file_path": file_path,
94
+ "original_filename": original_filename, # 保存原始文件名
95
  "error": False,
96
  "docCount": 0
97
  }
98
 
99
+ # 处理文档的线程函数
100
  def process_in_thread():
101
  try:
102
+ # 更新任务状态
103
  processing_tasks[task_id]["progress"] = 10
104
  processing_tasks[task_id]["status"] = "Loading document..."
105
 
106
+ # 处理文档进度回调
107
  def update_progress(progress, status):
108
  processing_tasks[task_id]["progress"] = min(95, progress)
109
  processing_tasks[task_id]["status"] = status
110
 
111
+ # 处理文档,传递原始文件名
112
+ processed_docs = doc_processor.process(
113
+ file_path,
114
+ progress_callback=update_progress,
115
+ original_filename=original_filename # 传递原始文件名
116
+ )
117
 
118
+ # 更新任务状态
119
  processing_tasks[task_id]["progress"] = 95
120
  processing_tasks[task_id]["status"] = "Creating vector store..."
121
  processing_tasks[task_id]["docCount"] = len(processed_docs)
122
 
123
+ # 存储向量
124
  vector_store.store(processed_docs, f"rag_{name}")
125
 
126
+ # 完成任务
127
  processing_tasks[task_id]["progress"] = 100
128
  processing_tasks[task_id]["status"] = "Processing complete"
129
 
130
  except Exception as e:
131
+ # 记录错误
132
  processing_tasks[task_id]["error"] = True
133
  processing_tasks[task_id]["status"] = f"Processing failed: {str(e)}"
134
  import traceback
 
167
  def add_documents(index_id):
168
  """Add documents to a knowledge base"""
169
  try:
170
+ # 检查知识库是否存在
171
  indices = retriever.get_all_indices()
172
  if index_id not in indices:
173
  return jsonify({"success": False, "message": "Knowledge base does not exist"}), 404
174
 
175
+ # 处理上传文件
176
  if 'file' not in request.files:
177
  return jsonify({"success": False, "message": "No file uploaded"}), 400
178
 
 
180
  if file.filename == '':
181
  return jsonify({"success": False, "message": "No file selected"}), 400
182
 
183
+ # 保存原始文件名并使用UUID生成唯一文件名
184
+ original_filename = file.filename
185
+ file_ext = os.path.splitext(original_filename)[1].lower()
186
+ unique_filename = f"{uuid.uuid4().hex}{file_ext}"
187
+ file_path = os.path.join(UPLOAD_FOLDER, unique_filename)
188
  file.save(file_path)
189
 
190
+ # 提取知识库名称
191
  kb_name = index_id[4:] if index_id.startswith('rag_') else index_id
192
 
193
+ # 创建任务ID
194
+ task_id = f"task_{int(time.time())}_{kb_name}_{uuid.uuid4().hex[:8]}"
195
 
196
+ # 初始化任务状态
197
  processing_tasks[task_id] = {
198
  "progress": 0,
199
  "status": "Starting document processing...",
200
  "index_name": kb_name,
201
  "file_path": file_path,
202
+ "original_filename": original_filename, # 保存原始文件名
203
  "error": False,
204
  "docCount": 0
205
  }
206
 
207
+ # 处理文档的线程函数
208
  def process_in_thread():
209
  try:
210
+ # 更新任务状态
211
  processing_tasks[task_id]["progress"] = 10
212
  processing_tasks[task_id]["status"] = "Loading document..."
213
 
214
+ # 处理文档进度回调
215
  def update_progress(progress, status):
216
  processing_tasks[task_id]["progress"] = min(95, progress)
217
  processing_tasks[task_id]["status"] = status
218
 
219
+ # 处理文档,传递原始文件名
220
+ processed_docs = doc_processor.process(
221
+ file_path,
222
+ progress_callback=update_progress,
223
+ original_filename=original_filename # 传递原始文件名
224
+ )
225
 
226
+ # 更新任务状态
227
  processing_tasks[task_id]["progress"] = 95
228
  processing_tasks[task_id]["status"] = "Creating vector store..."
229
  processing_tasks[task_id]["docCount"] = len(processed_docs)
230
 
231
+ # 存储向量
232
  vector_store.store(processed_docs, index_id)
233
 
234
+ # 完成任务
235
  processing_tasks[task_id]["progress"] = 100
236
  processing_tasks[task_id]["status"] = "Processing complete"
237
 
238
  except Exception as e:
239
+ # 记录错误
240
  processing_tasks[task_id]["error"] = True
241
  processing_tasks[task_id]["status"] = f"Processing failed: {str(e)}"
242
  import traceback
modules/visualization/__pycache__/routes.cpython-312.pyc ADDED
Binary file (8.64 kB). View file
 
templates/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
8
  <title>教育AI助手开发平台</title>
9
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
@@ -1653,249 +1653,327 @@
1653
  }
1654
 
1655
  // 加载知识库列表
1656
- async function loadKnowledgeBases() {
1657
- try {
1658
- const response = await fetch('/api/knowledge');
1659
- const result = await response.json();
 
 
 
1660
 
1661
- if (result.success) {
1662
- allKnowledgeBases = result.data || [];
1663
-
1664
- // 更新仪表盘计数
1665
- document.getElementById('knowledge-count').textContent = allKnowledgeBases.length;
1666
-
1667
- // 更新Agent创建页面的知识库列表
1668
- const knowledgeListElement = document.getElementById('knowledge-list');
1669
- if (knowledgeListElement) {
1670
- if (allKnowledgeBases.length === 0) {
1671
- knowledgeListElement.innerHTML = `
1672
- <div class="alert alert-info">
1673
- <i class="bi bi-info-circle me-2"></i>
1674
- 暂无知识库,请先创建知识库
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1675
  </div>
1676
- <div class="text-center">
1677
- <button class="btn btn-primary" onclick="switchSection('knowledge-base')">
1678
- 创建知识库
1679
- </button>
 
 
 
 
1680
  </div>
1681
  `;
1682
- } else {
1683
- knowledgeListElement.innerHTML = '';
1684
 
1685
- allKnowledgeBases.forEach(knowledge => {
1686
- const item = document.createElement('div');
1687
- item.className = 'plugin-option';
1688
- item.setAttribute('data-id', knowledge.id);
1689
- item.setAttribute('onclick', 'toggleKnowledgeSelection(this)');
1690
-
1691
- item.innerHTML = `
1692
- <div class="plugin-icon">
1693
- <i class="bi bi-journal-text"></i>
1694
- </div>
1695
- <div class="plugin-content">
1696
- <div class="plugin-title">${knowledge.name}</div>
1697
- <div class="plugin-description">
1698
- <small>${knowledge.fileCount || 0}个文件</small>
1699
- </div>
1700
- </div>
1701
- <div class="checkbox">
1702
- <input type="checkbox" class="form-check-input">
1703
- </div>
1704
- `;
1705
-
1706
- knowledgeListElement.appendChild(item);
1707
- });
1708
- }
1709
  }
1710
-
1711
- // 更新知识库管理页面的列表
1712
- const existingKnowledgeList = document.getElementById('existing-knowledge-list');
1713
- if (existingKnowledgeList) {
1714
- if (allKnowledgeBases.length === 0) {
1715
- existingKnowledgeList.innerHTML = `
1716
- <div class="alert alert-info">
1717
- <i class="bi bi-info-circle me-2"></i>
1718
- 暂无知识库,请创建第一个知识库
1719
- </div>
1720
- `;
1721
- } else {
1722
- existingKnowledgeList.innerHTML = '';
 
 
 
 
 
1723
 
1724
- allKnowledgeBases.forEach(knowledge => {
1725
- const item = document.createElement('div');
1726
- item.className = 'knowledge-item';
1727
-
1728
- let fileListHtml = '';
1729
- if (knowledge.files && knowledge.files.length > 0) {
1730
- fileListHtml = '<div class="knowledge-files">';
1731
- knowledge.files.forEach(file => {
1732
- const isImage = file.toLowerCase().endsWith('.jpg') || file.toLowerCase().endsWith('.png') || file.toLowerCase().endsWith('.jpeg');
1733
- const isPdf = file.toLowerCase().endsWith('.pdf');
1734
- const isDoc = file.toLowerCase().endsWith('.doc') || file.toLowerCase().endsWith('.docx');
1735
-
1736
- let iconClass = 'bi-file-text';
1737
- if (isImage) iconClass = 'bi-file-image';
1738
- if (isPdf) iconClass = 'bi-file-pdf';
1739
- if (isDoc) iconClass = 'bi-file-earmark-word';
1740
-
1741
- fileListHtml += `
1742
- <div class="knowledge-file">
1743
- <div class="knowledge-file-icon">
1744
- <i class="bi ${iconClass}"></i>
1745
- </div>
1746
- <div class="knowledge-file-info">
1747
- <div class="knowledge-file-name">${file}</div>
1748
- </div>
1749
- <button class="btn btn-sm btn-icon btn-light" onclick="downloadFile('${knowledge.id}', '${file}')">
1750
- <i class="bi bi-download"></i>
1751
- </button>
1752
  </div>
1753
- `;
1754
- });
1755
- fileListHtml += '</div>';
1756
- }
1757
-
1758
- item.innerHTML = `
1759
- <div class="knowledge-header">
1760
- <h5 class="knowledge-title">
1761
- <i class="bi bi-journal-text"></i> ${knowledge.name}
1762
- </h5>
1763
- <div>
1764
- <button class="btn btn-sm btn-light me-1" onclick="editKnowledgeBase('${knowledge.id}')">
1765
- <i class="bi bi-pencil"></i>
1766
- </button>
1767
- <button class="btn btn-sm btn-light text-danger" onclick="deleteKnowledgeBase('${knowledge.id}')">
1768
  <i class="bi bi-trash"></i>
1769
  </button>
1770
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1771
  </div>
1772
- ${fileListHtml}
1773
- <div class="knowledge-meta">
1774
- <div class="knowledge-meta-item">
1775
- <i class="bi bi-file-text"></i>
1776
- <span>${knowledge.fileCount || 0}个文件</span>
1777
- </div>
1778
- <div class="knowledge-meta-item">
1779
- <i class="bi bi-calendar3"></i>
1780
- <span>创建于 ${new Date(knowledge.created_at * 1000).toLocaleDateString()}</span>
1781
- </div>
1782
  </div>
1783
- <div class="mt-3">
1784
- <form class="upload-form" data-id="${knowledge.id}">
1785
- <div class="input-group">
1786
- <input type="file" class="form-control form-control-sm" required>
1787
- <button type="submit" class="btn btn-sm btn-outline-primary">添加文件</button>
1788
- </div>
1789
- </form>
1790
  </div>
1791
- `;
1792
-
1793
- existingKnowledgeList.appendChild(item);
1794
- });
 
 
 
 
 
 
1795
 
1796
- // 添加上传表单事件监听
1797
- document.querySelectorAll('.upload-form').forEach(form => {
1798
- form.addEventListener('submit', function(event) {
1799
- event.preventDefault();
1800
- addFileToKnowledgeBase(form);
1801
- });
 
 
1802
  });
1803
- }
1804
  }
1805
- } else {
1806
- console.error('加载知识库失败:', result.message);
1807
  }
1808
- } catch (error) {
1809
- console.error('加载知识库出错:', error);
1810
  }
 
 
1811
  }
 
1812
 
1813
  // 创建知识库
1814
- async function createKnowledgeBase(event) {
1815
- event.preventDefault();
1816
-
1817
- const nameInput = document.getElementById('knowledge-name');
1818
- const fileInput = document.getElementById('knowledge-file');
1819
- const progressBar = document.getElementById('upload-progress');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1820
 
1821
- if (!nameInput.value || !fileInput.files[0]) {
1822
- alert('请填写知识库名称并选择文件');
1823
- return;
1824
- }
 
1825
 
1826
- // 显示进度条
1827
- progressBar.style.display = 'block';
1828
- progressBar.querySelector('.progress-bar').style.width = '0%';
1829
 
1830
- const formData = new FormData();
1831
- formData.append('name', nameInput.value);
1832
- formData.append('file', fileInput.files[0]);
1833
 
1834
- try {
1835
- const response = await fetch('/api/knowledge', {
1836
- method: 'POST',
1837
- body: formData
1838
- });
1839
-
1840
- const result = await response.json();
1841
-
1842
- if (result.success) {
1843
- // 成功启动处理,轮询进度
1844
- pollProgress(result.task_id, progressBar);
1845
- } else {
1846
- alert('创建知识库失败: ' + result.message);
1847
- progressBar.style.display = 'none';
1848
- }
1849
- } catch (error) {
1850
- console.error('创建知识库出错:', error);
1851
- alert('创建知识库出错,请查看控制台日志');
1852
  progressBar.style.display = 'none';
 
 
1853
  }
1854
- }
1855
-
1856
- // 添加文件到知识库
1857
- async function addFileToKnowledgeBase(form) {
1858
- const knowledgeId = form.getAttribute('data-id');
1859
- const fileInput = form.querySelector('input[type="file"]');
1860
- const submitBtn = form.querySelector('button[type="submit"]');
1861
-
1862
- if (!fileInput.files[0]) {
1863
- alert('请选择文件');
1864
- return;
1865
  }
1866
 
1867
- // 禁用提交按钮
1868
- submitBtn.disabled = true;
1869
- submitBtn.textContent = '上传中...';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1870
 
1871
- const formData = new FormData();
1872
- formData.append('file', fileInput.files[0]);
1873
 
1874
- try {
1875
- const response = await fetch(`/api/knowledge/${knowledgeId}/documents`, {
1876
- method: 'POST',
1877
- body: formData
1878
- });
1879
-
1880
- const result = await response.json();
1881
-
1882
- if (result.success) {
1883
- alert('文件上传成功,开始处理');
1884
- // 轮询处理进度
1885
- pollProgressSimple(result.task_id, submitBtn);
1886
- } else {
1887
- alert('添加文件失败: ' + result.message);
1888
- submitBtn.disabled = false;
1889
- submitBtn.textContent = '添加文件';
1890
- }
1891
- } catch (error) {
1892
- console.error('添加文件出错:', error);
1893
- alert('添加文件出错,请查看控制台日志');
1894
  submitBtn.disabled = false;
1895
  submitBtn.textContent = '添加文件';
1896
  }
 
 
 
 
 
 
 
 
 
 
 
 
1897
  }
1898
-
1899
  // 删除知识库
1900
  async function deleteKnowledgeBase(knowledgeId) {
1901
  if (!confirm('确定要删除这个知识库吗?此操作不可恢复。')) {
@@ -1920,85 +1998,196 @@
1920
  alert('删除知识库出错,请查看控制台日志');
1921
  }
1922
  }
1923
-
1924
- // 轮询进度(带进度条显示)
1925
- function pollProgress(taskId, progressElement) {
1926
- const progressBar = progressElement.querySelector('.progress-bar');
1927
-
1928
- const interval = setInterval(async () => {
1929
- try {
1930
- const response = await fetch(`/api/progress/${taskId}`);
1931
- const data = await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1932
 
1933
- if (data.success) {
1934
- const progressData = data.data;
1935
-
1936
- // 更新进度条
1937
- progressBar.style.width = progressData.progress + '%';
 
 
 
 
 
 
 
1938
 
1939
- // 处理完成或出错时
1940
- if (progressData.progress >= 100 || progressData.error) {
1941
- clearInterval(interval);
1942
-
1943
- if (progressData.error) {
1944
- alert('处理失败: ' + progressData.status);
1945
- } else {
1946
- alert(`知识库处理成功! 已处理${progressData.docCount}个文档片段`);
1947
- }
1948
-
1949
- // 隐藏进度条
1950
- progressElement.style.display = 'none';
1951
-
1952
- // 重置表单
1953
- document.getElementById('knowledge-name').value = '';
1954
- document.getElementById('knowledge-file').value = '';
1955
-
1956
- // 重新加载知识库列表
1957
- loadKnowledgeBases();
1958
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1959
  }
1960
- } catch (error) {
1961
- console.error('获取进度信息出错:', error);
1962
  }
1963
- }, 1000); // 每秒轮询一次
1964
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1965
 
1966
- // 简化版轮询进度(不显示进度条)
1967
- function pollProgressSimple(taskId, buttonElement) {
1968
- const originalText = buttonElement.textContent;
1969
-
1970
- const interval = setInterval(async () => {
1971
- try {
1972
- const response = await fetch(`/api/progress/${taskId}`);
1973
- const data = await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
1974
 
1975
- if (data.success) {
1976
- const progressData = data.data;
 
 
 
 
1977
 
1978
- // 处理完成或出错时
1979
- if (progressData.progress >= 100 || progressData.error) {
1980
- clearInterval(interval);
1981
-
1982
- if (progressData.error) {
1983
- alert('处理失败: ' + progressData.status);
1984
- } else {
1985
- alert(`处理成功! 已处理${progressData.docCount}个文档片段`);
1986
- }
1987
-
1988
- // 恢复按钮
1989
- buttonElement.disabled = false;
1990
- buttonElement.textContent = originalText;
1991
-
1992
- // 重新加载知识库列表
1993
- loadKnowledgeBases();
1994
  }
 
 
 
 
 
 
 
1995
  }
1996
- } catch (error) {
1997
- console.error('获取进度信息出错:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1998
  }
1999
- }, 1000); // 每秒轮询一次
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2000
  }
2001
-
2002
  // 使用AI生成工作流
2003
  async function generateAIWorkflow() {
2004
  const description = document.getElementById('agent-description').value;
 
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+
8
  <title>教育AI助手开发平台</title>
9
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
 
1653
  }
1654
 
1655
  // 加载知识库列表
1656
+ async function loadKnowledgeBases() {
1657
+ try {
1658
+ const response = await fetch('/api/knowledge');
1659
+ const result = await response.json();
1660
+
1661
+ if (result.success) {
1662
+ allKnowledgeBases = result.data || [];
1663
 
1664
+ // 更新仪表盘计数
1665
+ document.getElementById('knowledge-count').textContent = allKnowledgeBases.length;
1666
+
1667
+ // 更新Agent创建页面的知识库列表
1668
+ const knowledgeListElement = document.getElementById('knowledge-list');
1669
+ if (knowledgeListElement) {
1670
+ if (allKnowledgeBases.length === 0) {
1671
+ knowledgeListElement.innerHTML = `
1672
+ <div class="alert alert-info">
1673
+ <i class="bi bi-info-circle me-2"></i>
1674
+ 暂无知识库,请先创建知识库
1675
+ </div>
1676
+ <div class="text-center">
1677
+ <button class="btn btn-primary" onclick="switchSection('knowledge-base')">
1678
+ 创建知识库
1679
+ </button>
1680
+ </div>
1681
+ `;
1682
+ } else {
1683
+ knowledgeListElement.innerHTML = '';
1684
+
1685
+ allKnowledgeBases.forEach(knowledge => {
1686
+ const item = document.createElement('div');
1687
+ item.className = 'plugin-option';
1688
+ item.setAttribute('data-id', knowledge.id);
1689
+ item.setAttribute('onclick', 'toggleKnowledgeSelection(this)');
1690
+
1691
+ item.innerHTML = `
1692
+ <div class="plugin-icon">
1693
+ <i class="bi bi-journal-text"></i>
1694
  </div>
1695
+ <div class="plugin-content">
1696
+ <div class="plugin-title">${knowledge.name}</div>
1697
+ <div class="plugin-description">
1698
+ <small>${knowledge.fileCount || 0}个文件</small>
1699
+ </div>
1700
+ </div>
1701
+ <div class="checkbox">
1702
+ <input type="checkbox" class="form-check-input">
1703
  </div>
1704
  `;
 
 
1705
 
1706
+ knowledgeListElement.appendChild(item);
1707
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1708
  }
1709
+ }
1710
+
1711
+ // 更新知识库管理页面的列表
1712
+ const existingKnowledgeList = document.getElementById('existing-knowledge-list');
1713
+ if (existingKnowledgeList) {
1714
+ if (allKnowledgeBases.length === 0) {
1715
+ existingKnowledgeList.innerHTML = `
1716
+ <div class="alert alert-info">
1717
+ <i class="bi bi-info-circle me-2"></i>
1718
+ 暂无知识库,请创建第一个知识库
1719
+ </div>
1720
+ `;
1721
+ } else {
1722
+ existingKnowledgeList.innerHTML = '';
1723
+
1724
+ allKnowledgeBases.forEach(knowledge => {
1725
+ const item = document.createElement('div');
1726
+ item.className = 'knowledge-item';
1727
 
1728
+ let fileListHtml = '';
1729
+ if (knowledge.files && knowledge.files.length > 0) {
1730
+ fileListHtml = '<div class="knowledge-files mt-3">';
1731
+ knowledge.files.forEach(file => {
1732
+ // 使用文件名来判断文件类型
1733
+ const isImage = /\.(jpg|jpeg|png|gif|bmp)$/i.test(file);
1734
+ const isPdf = /\.pdf$/i.test(file);
1735
+ const isDoc = /\.(doc|docx)$/i.test(file);
1736
+ const isTxt = /\.txt$/i.test(file);
1737
+ const isMd = /\.md$/i.test(file);
1738
+
1739
+ let iconClass = 'bi-file-text';
1740
+ if (isImage) iconClass = 'bi-file-image';
1741
+ if (isPdf) iconClass = 'bi-file-pdf';
1742
+ if (isDoc) iconClass = 'bi-file-earmark-word';
1743
+ if (isTxt) iconClass = 'bi-file-text';
1744
+ if (isMd) iconClass = 'bi-file-text';
1745
+
1746
+ fileListHtml += `
1747
+ <div class="d-flex align-items-center p-2 border-bottom">
1748
+ <i class="bi ${iconClass} me-2" style="font-size: 1.2rem; color: var(--secondary-color);"></i>
1749
+ <div class="flex-grow-1 text-truncate" title="${file}">
1750
+ ${file}
 
 
 
 
 
1751
  </div>
1752
+ <button class="btn btn-sm btn-icon btn-light" onclick="deleteFile('${knowledge.id}', '${encodeURIComponent(file)}')">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1753
  <i class="bi bi-trash"></i>
1754
  </button>
1755
  </div>
1756
+ `;
1757
+ });
1758
+ fileListHtml += '</div>';
1759
+ }
1760
+
1761
+ item.innerHTML = `
1762
+ <div class="knowledge-header">
1763
+ <h5 class="knowledge-title">
1764
+ <i class="bi bi-journal-text"></i> ${knowledge.name}
1765
+ </h5>
1766
+ <div>
1767
+ <button class="btn btn-sm btn-light me-1" onclick="editKnowledgeBase('${knowledge.id}')">
1768
+ <i class="bi bi-pencil"></i>
1769
+ </button>
1770
+ <button class="btn btn-sm btn-light text-danger" onclick="deleteKnowledgeBase('${knowledge.id}')">
1771
+ <i class="bi bi-trash"></i>
1772
+ </button>
1773
  </div>
1774
+ </div>
1775
+ ${fileListHtml}
1776
+ <div class="knowledge-meta">
1777
+ <div class="knowledge-meta-item">
1778
+ <i class="bi bi-file-text"></i>
1779
+ <span>${knowledge.fileCount || 0}个文件</span>
 
 
 
 
1780
  </div>
1781
+ <div class="knowledge-meta-item">
1782
+ <i class="bi bi-calendar3"></i>
1783
+ <span>创建于 ${new Date(knowledge.created_at * 1000).toLocaleDateString()}</span>
 
 
 
 
1784
  </div>
1785
+ </div>
1786
+ <div class="mt-3">
1787
+ <form class="upload-form" data-id="${knowledge.id}">
1788
+ <div class="input-group">
1789
+ <input type="file" class="form-control form-control-sm" required>
1790
+ <button type="submit" class="btn btn-sm btn-outline-primary">添加文件</button>
1791
+ </div>
1792
+ </form>
1793
+ </div>
1794
+ `;
1795
 
1796
+ existingKnowledgeList.appendChild(item);
1797
+ });
1798
+
1799
+ // 添加上传表单事件监听
1800
+ document.querySelectorAll('.upload-form').forEach(form => {
1801
+ form.addEventListener('submit', function(event) {
1802
+ event.preventDefault();
1803
+ addFileToKnowledgeBase(form);
1804
  });
1805
+ });
1806
  }
 
 
1807
  }
1808
+ } else {
1809
+ console.error('加载知识库失败:', result.message);
1810
  }
1811
+ } catch (error) {
1812
+ console.error('加载知识库出错:', error);
1813
  }
1814
+ }
1815
 
1816
  // 创建知识库
1817
+ async function createKnowledgeBase(event) {
1818
+ event.preventDefault();
1819
+
1820
+ const nameInput = document.getElementById('knowledge-name');
1821
+ const fileInput = document.getElementById('knowledge-file');
1822
+ const progressBar = document.getElementById('upload-progress');
1823
+ const submitBtn = event.submitter || document.querySelector('#knowledge-form button[type="submit"]');
1824
+
1825
+ if (!nameInput.value || !fileInput.files[0]) {
1826
+ alert('请填写知识库名称并选择文件');
1827
+ return;
1828
+ }
1829
+
1830
+ // 获取文件扩展名
1831
+ const fileName = fileInput.files[0].name;
1832
+ const fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
1833
+
1834
+ // 检查文件类型
1835
+ const supportedExts = ['.txt', '.pdf', '.md', '.jpg', '.jpeg', '.png', '.gif', '.bmp'];
1836
+ if (!supportedExts.includes(fileExt)) {
1837
+ alert(`不支持的文件类型: ${fileExt}。\n支持的文件类型: ${supportedExts.join(', ')}`);
1838
+ return;
1839
+ }
1840
+
1841
+ // 检查文件大小
1842
+ const fileSize = fileInput.files[0].size;
1843
+ const fileSizeMB = fileSize / (1024 * 1024);
1844
+ const maxFileSizeMB = 16; // 最大16MB
1845
+
1846
+ if (fileSizeMB > maxFileSizeMB) {
1847
+ alert(`文件大小(${fileSizeMB.toFixed(2)}MB)超过最大限制(${maxFileSizeMB}MB)`);
1848
+ return;
1849
+ }
1850
+
1851
+ // 显示进度条并禁用提交按钮
1852
+ progressBar.style.display = 'block';
1853
+ progressBar.querySelector('.progress-bar').style.width = '0%';
1854
+ submitBtn.disabled = true;
1855
+ submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 上传中...';
1856
+
1857
+ const formData = new FormData();
1858
+ formData.append('name', nameInput.value);
1859
+ formData.append('file', fileInput.files[0]);
1860
+
1861
+ try {
1862
+ // 使用 fetch 的 timeout 设置
1863
+ const controller = new AbortController();
1864
+ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
1865
 
1866
+ const response = await fetch('/api/knowledge', {
1867
+ method: 'POST',
1868
+ body: formData,
1869
+ signal: controller.signal
1870
+ });
1871
 
1872
+ clearTimeout(timeoutId);
 
 
1873
 
1874
+ const result = await response.json();
 
 
1875
 
1876
+ if (result.success) {
1877
+ // 成功启动处理,轮询进度
1878
+ pollProgress(result.task_id, progressBar, submitBtn);
1879
+ } else {
1880
+ alert('创建知识库失败: ' + result.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
1881
  progressBar.style.display = 'none';
1882
+ submitBtn.disabled = false;
1883
+ submitBtn.textContent = '创建知识库';
1884
  }
1885
+ } catch (error) {
1886
+ console.error('创建知识库出错:', error);
1887
+
1888
+ // 判断是否是超时错误
1889
+ let errorMessage = '创建知识库出错,请查看控制台日志';
1890
+ if (error.name === 'AbortError') {
1891
+ errorMessage = '上传请求超时,请检查网络连接或尝试上传较小的文件';
 
 
 
 
1892
  }
1893
 
1894
+ alert(errorMessage);
1895
+ progressBar.style.display = 'none';
1896
+ submitBtn.disabled = false;
1897
+ submitBtn.textContent = '创建知识库';
1898
+ }
1899
+ }
1900
+ // 添加文件到知识库
1901
+ async function addFileToKnowledgeBase(form) {
1902
+ const knowledgeId = form.getAttribute('data-id');
1903
+ const fileInput = form.querySelector('input[type="file"]');
1904
+ const submitBtn = form.querySelector('button[type="submit"]');
1905
+
1906
+ if (!fileInput.files[0]) {
1907
+ alert('请选择文件');
1908
+ return;
1909
+ }
1910
+
1911
+ // 获取文件扩展名
1912
+ const fileName = fileInput.files[0].name;
1913
+ const fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
1914
+
1915
+ // 检查文件类型
1916
+ const supportedExts = ['.txt', '.pdf', '.md', '.jpg', '.jpeg', '.png', '.gif', '.bmp'];
1917
+ if (!supportedExts.includes(fileExt)) {
1918
+ alert(`不支持的文件类型: ${fileExt}。\n支持的文件类型: ${supportedExts.join(', ')}`);
1919
+ return;
1920
+ }
1921
+
1922
+ // 检查文件大小
1923
+ const fileSize = fileInput.files[0].size;
1924
+ const fileSizeMB = fileSize / (1024 * 1024);
1925
+ const maxFileSizeMB = 16; // 最大16MB
1926
+
1927
+ if (fileSizeMB > maxFileSizeMB) {
1928
+ alert(`文件大小(${fileSizeMB.toFixed(2)}MB)超过最大限制(${maxFileSizeMB}MB)`);
1929
+ return;
1930
+ }
1931
+
1932
+ // 禁用提交按钮
1933
+ submitBtn.disabled = true;
1934
+ submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 上传中...';
1935
+
1936
+ const formData = new FormData();
1937
+ formData.append('file', fileInput.files[0]);
1938
+
1939
+ try {
1940
+ // 使用 fetch 的 timeout 设置
1941
+ const controller = new AbortController();
1942
+ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
1943
+
1944
+ const response = await fetch(`/api/knowledge/${knowledgeId}/documents`, {
1945
+ method: 'POST',
1946
+ body: formData,
1947
+ signal: controller.signal
1948
+ });
1949
 
1950
+ clearTimeout(timeoutId);
 
1951
 
1952
+ const result = await response.json();
1953
+
1954
+ if (result.success) {
1955
+ alert('文件上传成功,开始处理');
1956
+ // 轮询处理进度
1957
+ pollProgressSimple(result.task_id, submitBtn);
1958
+ } else {
1959
+ alert('添加文件失败: ' + result.message);
 
 
 
 
 
 
 
 
 
 
 
 
1960
  submitBtn.disabled = false;
1961
  submitBtn.textContent = '添加文件';
1962
  }
1963
+ } catch (error) {
1964
+ console.error('添加文件出错:', error);
1965
+
1966
+ // 判断是否是超时错误
1967
+ let errorMessage = '添加文件出错,请查看控制台日志';
1968
+ if (error.name === 'AbortError') {
1969
+ errorMessage = '上传请求超时,请检查网络连接或尝试上传较小的文件';
1970
+ }
1971
+
1972
+ alert(errorMessage);
1973
+ submitBtn.disabled = false;
1974
+ submitBtn.textContent = '添加文件';
1975
  }
1976
+ }
1977
  // 删除知识库
1978
  async function deleteKnowledgeBase(knowledgeId) {
1979
  if (!confirm('确定要删除这个知识库吗?此操作不可恢复。')) {
 
1998
  alert('删除知识库出错,请查看控制台日志');
1999
  }
2000
  }
2001
+ // 删除知识库中的单个文件
2002
+ async function deleteFile(indexId, encodedFileName) {
2003
+ try {
2004
+ // 解码文件名以在确认对话框中显示
2005
+ const fileName = decodeURIComponent(encodedFileName);
2006
+
2007
+ if (!confirm(`确定要删除文件 "${fileName}" 吗?此操作不可恢复。`)) {
2008
+ return;
2009
+ }
2010
+
2011
+ const response = await fetch(`/api/knowledge/${indexId}/documents/${encodedFileName}`, {
2012
+ method: 'DELETE'
2013
+ });
2014
+
2015
+ const result = await response.json();
2016
+
2017
+ if (result.success) {
2018
+ alert('文件删除成功!');
2019
+ loadKnowledgeBases(); // 重新加载知识库列表
2020
+ } else {
2021
+ alert('删除文件失败: ' + result.message);
2022
+ }
2023
+ } catch (error) {
2024
+ console.error('删除文件出错:', error);
2025
+ alert('删除文件出错,请查看控制台日志');
2026
+ }
2027
+ }
2028
+ // 轮询进度(带进度条显示)
2029
+ function pollProgress(taskId, progressElement, submitBtn) {
2030
+ const progressBar = progressElement.querySelector('.progress-bar');
2031
+ let retryCount = 0;
2032
+ const maxRetries = 3;
2033
+
2034
+ const interval = setInterval(async () => {
2035
+ try {
2036
+ const response = await fetch(`/api/progress/${taskId}`);
2037
+
2038
+ if (!response.ok) {
2039
+ throw new Error(`HTTP错误: ${response.status}`);
2040
+ }
2041
+
2042
+ const data = await response.json();
2043
+
2044
+ if (data.success) {
2045
+ const progressData = data.data;
2046
 
2047
+ // 更新进度条
2048
+ progressBar.style.width = progressData.progress + '%';
2049
+ progressBar.setAttribute('aria-valuenow', progressData.progress);
2050
+
2051
+ // 更新进度状态
2052
+ if (submitBtn.tagName === 'BUTTON') {
2053
+ submitBtn.innerHTML = `<i class="bi bi-arrow-repeat spin me-1"></i> ${progressData.status || '处理中...'}`;
2054
+ }
2055
+
2056
+ // 处理完成或出错时
2057
+ if (progressData.progress >= 100 || progressData.error) {
2058
+ clearInterval(interval);
2059
 
2060
+ if (progressData.error) {
2061
+ alert('处理失败: ' + progressData.status);
2062
+ } else {
2063
+ alert(`知识库处理成功! 已处理${progressData.docCount}个文档片段`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2064
  }
2065
+
2066
+ // 隐藏进度条
2067
+ progressElement.style.display = 'none';
2068
+
2069
+ // 恢复按钮状态
2070
+ submitBtn.disabled = false;
2071
+ submitBtn.innerHTML = '创建知识库';
2072
+
2073
+ // 重置表单
2074
+ document.getElementById('knowledge-name').value = '';
2075
+ document.getElementById('knowledge-file').value = '';
2076
+
2077
+ // 重新加载知识库列表
2078
+ loadKnowledgeBases();
2079
  }
2080
+ } else {
2081
+ throw new Error('轮询返回错误状态');
2082
  }
2083
+
2084
+ // 重置重试计数
2085
+ retryCount = 0;
2086
+
2087
+ } catch (error) {
2088
+ console.error('获取进度信息出错:', error);
2089
+ retryCount++;
2090
+
2091
+ if (retryCount >= maxRetries) {
2092
+ clearInterval(interval);
2093
+ alert('获取进度信息失败,请检查控制台日志');
2094
+
2095
+ // 恢复按钮状态
2096
+ submitBtn.disabled = false;
2097
+ submitBtn.innerHTML = '创建知识库';
2098
+ progressElement.style.display = 'none';
2099
+ }
2100
+ }
2101
+ }, 1000); // 每秒轮询一次
2102
+ }
2103
 
2104
+ // 简化版轮询进度(不显示进度条)
2105
+ function pollProgressSimple(taskId, buttonElement) {
2106
+ const originalText = buttonElement.textContent;
2107
+ let retryCount = 0;
2108
+ const maxRetries = 3;
2109
+
2110
+ buttonElement.innerHTML = '<i class="bi bi-arrow-repeat spin me-1"></i> 处理中...';
2111
+
2112
+ const interval = setInterval(async () => {
2113
+ try {
2114
+ const response = await fetch(`/api/progress/${taskId}`);
2115
+
2116
+ if (!response.ok) {
2117
+ throw new Error(`HTTP错误: ${response.status}`);
2118
+ }
2119
+
2120
+ const data = await response.json();
2121
+
2122
+ if (data.success) {
2123
+ const progressData = data.data;
2124
 
2125
+ // 更新按钮文本显示进度
2126
+ buttonElement.innerHTML = `<i class="bi bi-arrow-repeat spin me-1"></i> ${progressData.status || '处理中...'} (${progressData.progress}%)`;
2127
+
2128
+ // 处理完成或出错时
2129
+ if (progressData.progress >= 100 || progressData.error) {
2130
+ clearInterval(interval);
2131
 
2132
+ if (progressData.error) {
2133
+ alert('处理失败: ' + progressData.status);
2134
+ } else {
2135
+ alert(`处理成功! 已处理${progressData.docCount}个文档片段`);
 
 
 
 
 
 
 
 
 
 
 
 
2136
  }
2137
+
2138
+ // 恢复按钮
2139
+ buttonElement.disabled = false;
2140
+ buttonElement.textContent = originalText;
2141
+
2142
+ // 重新加载知识库列表
2143
+ loadKnowledgeBases();
2144
  }
2145
+ } else {
2146
+ throw new Error('轮询返回错误状态');
2147
+ }
2148
+
2149
+ // 重置重试计数
2150
+ retryCount = 0;
2151
+
2152
+ } catch (error) {
2153
+ console.error('获取进度信息出错:', error);
2154
+ retryCount++;
2155
+
2156
+ if (retryCount >= maxRetries) {
2157
+ clearInterval(interval);
2158
+ alert('获取进度信息失败,请检查控制台日志');
2159
+
2160
+ // 恢复按钮
2161
+ buttonElement.disabled = false;
2162
+ buttonElement.textContent = originalText;
2163
  }
2164
+ }
2165
+ }, 1000); // 每秒轮询一次
2166
+ }
2167
+ // 根据文件扩展名获取图标类
2168
+ function getFileIconClass(fileName) {
2169
+ if (!fileName) return 'bi-file';
2170
+
2171
+ const ext = fileName.split('.').pop().toLowerCase();
2172
+
2173
+ switch (ext) {
2174
+ case 'pdf': return 'bi-file-pdf';
2175
+ case 'doc':
2176
+ case 'docx': return 'bi-file-earmark-word';
2177
+ case 'xls':
2178
+ case 'xlsx': return 'bi-file-earmark-excel';
2179
+ case 'ppt':
2180
+ case 'pptx': return 'bi-file-earmark-ppt';
2181
+ case 'jpg':
2182
+ case 'jpeg':
2183
+ case 'png':
2184
+ case 'gif':
2185
+ case 'bmp': return 'bi-file-image';
2186
+ case 'md': return 'bi-markdown';
2187
+ case 'txt': return 'bi-file-text';
2188
+ default: return 'bi-file';
2189
  }
2190
+ }
2191
  // 使用AI生成工作流
2192
  async function generateAIWorkflow() {
2193
  const description = document.getElementById('agent-description').value;
uploads/496449c167344601ba87c24743701b99.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a046fe2339ff94de24bb636ebdffbb108629131e633e3e6e9090b9550f0afeda
3
+ size 499224
uploads/eb55dde866f64be4a1fdbc81d3d1df0e.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a046fe2339ff94de24bb636ebdffbb108629131e633e3e6e9090b9550f0afeda
3
+ size 499224