From d68a46cdd2ee9836bb7737c4d94fbefc3a903be9 Mon Sep 17 00:00:00 2001 From: wxd <1363812980@qq.com> Date: Sat, 20 Jun 2026 23:00:59 +0800 Subject: [PATCH 1/4] feat: add table knowledge base with column-level preprocessing --- astrbot/core/db/vec_db/faiss_impl/vec_db.py | 20 +- astrbot/core/knowledge_base/kb_db_sqlite.py | 40 +++ astrbot/core/knowledge_base/kb_helper.py | 188 +++++++++- astrbot/core/knowledge_base/kb_mgr.py | 3 + astrbot/core/knowledge_base/models.py | 6 + .../knowledge_base/parsers/table_parser.py | 133 +++++++ astrbot/dashboard/api/knowledge_bases.py | 36 ++ astrbot/dashboard/schemas.py | 1 + .../services/knowledge_base_service.py | 236 +++++++++++++ .../src/api/generated/openapi-v1/sdk.gen.ts | 32 +- .../src/api/generated/openapi-v1/types.gen.ts | 46 +++ dashboard/src/api/v1.ts | 16 + .../en-US/features/knowledge-base/detail.json | 31 ++ .../en-US/features/knowledge-base/index.json | 3 + .../ru-RU/features/knowledge-base/detail.json | 31 ++ .../ru-RU/features/knowledge-base/index.json | 3 + .../zh-CN/features/knowledge-base/detail.json | 31 ++ .../zh-CN/features/knowledge-base/index.json | 3 + .../src/views/knowledge-base/KBDetail.vue | 10 + dashboard/src/views/knowledge-base/KBList.vue | 32 +- .../components/DocumentsTab.vue | 42 ++- .../components/TableImportDialog.vue | 333 ++++++++++++++++++ openspec/openapi-v1.yaml | 77 ++++ 23 files changed, 1339 insertions(+), 14 deletions(-) create mode 100644 astrbot/core/knowledge_base/parsers/table_parser.py create mode 100644 dashboard/src/views/knowledge-base/components/TableImportDialog.vue diff --git a/astrbot/core/db/vec_db/faiss_impl/vec_db.py b/astrbot/core/db/vec_db/faiss_impl/vec_db.py index 0474683754..801eadc694 100644 --- a/astrbot/core/db/vec_db/faiss_impl/vec_db.py +++ b/astrbot/core/db/vec_db/faiss_impl/vec_db.py @@ -65,11 +65,15 @@ async def insert_batch( tasks_limit: int = 3, max_retries: int = 3, progress_callback=None, + embedding_texts: list[str] | None = None, ) -> list[int]: """批量插入文本和其对应向量,自动生成 ID 并保持一致性。 Args: progress_callback: 进度回调函数,接收参数 (current, total) + embedding_texts: 可选的向量化文本,用于将"用于语义匹配的文本"与 + "用于存储/检索返回的文本(contents)"解耦。表格知识库使用索引列 + 文本进行向量化,但存储并返回整行文本。缺省时回退为 contents。 """ metadatas = metadatas or [{} for _ in contents] @@ -81,6 +85,20 @@ async def insert_batch( ) return [] + texts_to_embed = embedding_texts if embedding_texts is not None else contents + if len(texts_to_embed) != len(contents): + raise KnowledgeBaseUploadError( + stage="embedding", + user_message=( + f"向量化失败:用于向量化的文本数量与文本分块数量不一致" + f"(期望 {len(contents)},实际 {len(texts_to_embed)})。" + ), + details={ + "expected_contents": len(contents), + "actual_embedding_texts": len(texts_to_embed), + }, + ) + content_count = len(contents) if len(metadatas) != content_count: raise KnowledgeBaseUploadError( @@ -110,7 +128,7 @@ async def insert_batch( start = time.time() logger.debug(f"Generating embeddings for {len(contents)} contents...") vectors = await self.embedding_provider.get_embeddings_batch( - contents, + texts_to_embed, batch_size=batch_size, tasks_limit=tasks_limit, max_retries=max_retries, diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py index 6a2cb5e0a8..b77781d48e 100644 --- a/astrbot/core/knowledge_base/kb_db_sqlite.py +++ b/astrbot/core/knowledge_base/kb_db_sqlite.py @@ -167,6 +167,46 @@ async def migrate_to_v1(self) -> None: await session.commit() + async def migrate_to_v2(self) -> None: + """Run knowledge base database v2 migration. + + Adds the table knowledge base columns to existing databases that were + created before the table feature. SQLite does not support + ``ADD COLUMN IF NOT EXISTS``, so existing columns are checked via + ``PRAGMA table_info`` before issuing ``ALTER TABLE`` statements. + """ + async with self.get_db() as session: + session: AsyncSession + async with session.begin(): + kb_columns = { + row[1] + for row in ( + await session.execute( + text("PRAGMA table_info(knowledge_bases)") + ) + ).fetchall() + } + if "kb_type" not in kb_columns: + await session.execute( + text( + "ALTER TABLE knowledge_bases " + "ADD COLUMN kb_type VARCHAR(20) NOT NULL DEFAULT 'text'", + ), + ) + + doc_columns = { + row[1] + for row in ( + await session.execute(text("PRAGMA table_info(kb_documents)")) + ).fetchall() + } + if "table_schema" not in doc_columns: + await session.execute( + text("ALTER TABLE kb_documents ADD COLUMN table_schema TEXT"), + ) + + await session.commit() + async def close(self) -> None: """关闭数据库连接""" await self.engine.dispose() diff --git a/astrbot/core/knowledge_base/kb_helper.py b/astrbot/core/knowledge_base/kb_helper.py index c29e45876d..d23abcdae0 100644 --- a/astrbot/core/knowledge_base/kb_helper.py +++ b/astrbot/core/knowledge_base/kb_helper.py @@ -465,6 +465,171 @@ async def embedding_progress_callback(current, total) -> None: raise + @staticmethod + def _format_row_text(columns: list[dict], row: dict[str, str]) -> str: + """Format selected columns of a row as ``name: value`` lines. + + Args: + columns: Column descriptors to render, each with a ``name`` key. + row: Mapping of header name to cell value for one row. + + Returns: + A newline-joined ``name: value`` representation, skipping blank cells. + """ + lines = [] + for col in columns: + name = col.get("name") + if not name: + continue + value = str(row.get(name, "")).strip() + if value: + lines.append(f"{name}: {value}") + return "\n".join(lines) + + async def upload_table_document( + self, + file_name: str, + file_type: str, + headers: list[str], + rows: list[list[str]], + columns_config: list[dict], + file_size: int = 0, + batch_size: int = 32, + tasks_limit: int = 3, + max_retries: int = 3, + progress_callback=None, + ) -> KBDocument: + """Upload a structured table where each row is an independent chunk. + + Index columns are concatenated to build the embedding text (used for + semantic matching), while the full row is stored/returned and the raw + row values are kept in chunk metadata under ``row_data``. + + Args: + file_name: Original table file name. + file_type: File extension without the dot (e.g. ``csv``). + headers: Column header names aligned to ``rows``. + rows: Row values, each aligned to ``headers``. + columns_config: Per-column config items with keys ``name``, + ``is_index`` and ``is_returned``. + file_size: Original file size in bytes. + batch_size: Embedding batch size. + tasks_limit: Embedding concurrency limit. + max_retries: Embedding retry count. + progress_callback: Async callback ``(stage, current, total)``. + + Returns: + KBDocument: The created document metadata record. + + Raises: + KnowledgeBaseUploadError: If no indexable row can be produced. + """ + await self._ensure_vec_db() + doc_id = str(uuid.uuid4()) + + index_cols = [c for c in columns_config if c.get("is_index")] + if not index_cols: + raise KnowledgeBaseUploadError( + stage="validation", + user_message="表格导入失败:至少需要选择一个索引列用于语义检索。", + details={"file_name": file_name}, + ) + returned_cols = [c for c in columns_config if c.get("is_returned")] or [ + {"name": h} for h in headers + ] + + if progress_callback: + await progress_callback("parsing", 100, 100) + + contents: list[str] = [] + embedding_texts: list[str] = [] + metadatas: list[dict] = [] + for idx, row_values in enumerate(rows): + row = { + h: (row_values[i] if i < len(row_values) else "") + for i, h in enumerate(headers) + } + embedding_text = self._format_row_text(index_cols, row) + if not embedding_text: + # Skip rows whose index columns are all empty. + continue + content_text = self._format_row_text(returned_cols, row) or embedding_text + contents.append(content_text) + embedding_texts.append(embedding_text) + metadatas.append( + { + "kb_id": self.kb.kb_id, + "kb_doc_id": doc_id, + "chunk_index": len(contents) - 1, + "row_index": idx, + "row_data": row, + "is_table_row": True, + }, + ) + + if not contents: + raise KnowledgeBaseUploadError( + stage="validation", + user_message="表格导入失败:所选索引列在所有行中均为空,没有可索引的数据。", + details={"file_name": file_name}, + ) + + if progress_callback: + await progress_callback("chunking", 100, 100) + + async def embedding_progress_callback(current, total) -> None: + if progress_callback: + await progress_callback("embedding", current, total) + + try: + await self.vec_db.insert_batch( + contents=contents, + metadatas=metadatas, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + progress_callback=embedding_progress_callback, + embedding_texts=embedding_texts, + ) + except KnowledgeBaseUploadError: + raise + except Exception as exc: + raise KnowledgeBaseUploadError( + stage="storage", + user_message="存储失败:表格行已生成,但写入知识库索引时出错。", + details={"file_name": file_name}, + ) from exc + + doc = KBDocument( + doc_id=doc_id, + kb_id=self.kb.kb_id, + doc_name=file_name, + file_type=file_type, + file_size=file_size, + file_path="", + table_schema=json.dumps(columns_config, ensure_ascii=False), + chunk_count=len(contents), + media_count=0, + ) + try: + async with self.kb_db.get_db() as session: + async with session.begin(): + session.add(doc) + await session.commit() + await session.refresh(doc) + except Exception as exc: + raise KnowledgeBaseUploadError( + stage="metadata", + user_message="元数据保存失败:表格行已写入知识库,但文档记录保存失败。", + details={"file_name": file_name, "doc_id": doc_id}, + ) from exc + + vec_db: FaissVecDB = self.vec_db # type: ignore + await self.kb_db.update_kb_stats(kb_id=self.kb.kb_id, vec_db=vec_db) + await self.refresh_kb() + await self.refresh_document(doc_id) + return doc + async def list_documents( self, offset: int = 0, @@ -537,16 +702,19 @@ async def get_chunks_by_doc_id( result = [] for chunk in chunks: chunk_md = json.loads(chunk["metadata"]) - result.append( - { - "chunk_id": chunk["doc_id"], - "doc_id": chunk_md["kb_doc_id"], - "kb_id": chunk_md["kb_id"], - "chunk_index": chunk_md["chunk_index"], - "content": chunk["text"], - "char_count": len(chunk["text"]), - }, - ) + item = { + "chunk_id": chunk["doc_id"], + "doc_id": chunk_md["kb_doc_id"], + "kb_id": chunk_md["kb_id"], + "chunk_index": chunk_md["chunk_index"], + "content": chunk["text"], + "char_count": len(chunk["text"]), + } + if chunk_md.get("is_table_row"): + item["is_table_row"] = True + item["row_index"] = chunk_md.get("row_index") + item["row_data"] = chunk_md.get("row_data") + result.append(item) return result async def get_chunk_count_by_doc_id(self, doc_id: str) -> int: diff --git a/astrbot/core/knowledge_base/kb_mgr.py b/astrbot/core/knowledge_base/kb_mgr.py index 3285d42c79..30623a3496 100644 --- a/astrbot/core/knowledge_base/kb_mgr.py +++ b/astrbot/core/knowledge_base/kb_mgr.py @@ -59,6 +59,7 @@ async def _init_kb_database(self) -> None: self.kb_db = KBSQLiteDatabase(DB_PATH.as_posix()) await self.kb_db.initialize() await self.kb_db.migrate_to_v1() + await self.kb_db.migrate_to_v2() logger.info(f"KnowledgeBase database initialized: {DB_PATH}") async def load_kbs(self) -> None: @@ -94,6 +95,7 @@ async def create_kb( top_k_dense: int | None = None, top_k_sparse: int | None = None, top_m_final: int | None = None, + kb_type: str | None = None, ) -> KBHelper: """创建新的知识库实例""" if embedding_provider_id is None: @@ -102,6 +104,7 @@ async def create_kb( kb_name=kb_name, description=description, emoji=emoji or "📚", + kb_type=kb_type or "text", embedding_provider_id=embedding_provider_id, rerank_provider_id=rerank_provider_id, chunk_size=chunk_size if chunk_size is not None else 512, diff --git a/astrbot/core/knowledge_base/models.py b/astrbot/core/knowledge_base/models.py index da919a384a..18a791ed66 100644 --- a/astrbot/core/knowledge_base/models.py +++ b/astrbot/core/knowledge_base/models.py @@ -31,6 +31,9 @@ class KnowledgeBase(BaseKBModel, table=True): kb_name: str = Field(max_length=100, nullable=False) description: str | None = Field(default=None, sa_type=Text) emoji: str | None = Field(default="📚", max_length=10) + # Knowledge base type: "text" (unstructured documents) or "table" + # (structured row-level data, Coze-like table knowledge base). + kb_type: str = Field(default="text", max_length=20, nullable=False) embedding_provider_id: str | None = Field(default=None, max_length=100) rerank_provider_id: str | None = Field(default=None, max_length=100) # 分块配置参数 @@ -81,6 +84,9 @@ class KBDocument(BaseKBModel, table=True): file_type: str = Field(max_length=20, nullable=False) file_size: int = Field(nullable=False) file_path: str = Field(max_length=512, nullable=False) + # JSON column configuration for table documents (None for text documents). + # Stores the per-document column schema chosen during table preprocessing. + table_schema: str | None = Field(default=None, sa_type=Text) chunk_count: int = Field(default=0, nullable=False) media_count: int = Field(default=0, nullable=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/astrbot/core/knowledge_base/parsers/table_parser.py b/astrbot/core/knowledge_base/parsers/table_parser.py new file mode 100644 index 0000000000..cade6d616a --- /dev/null +++ b/astrbot/core/knowledge_base/parsers/table_parser.py @@ -0,0 +1,133 @@ +"""Structured table parser for the table knowledge base. + +Unlike the document parsers that flatten a spreadsheet into a single Markdown +text blob, this parser keeps the row/column structure so each row can be turned +into an independent knowledge unit (Coze-like table knowledge base). +""" + +import io +from dataclasses import dataclass +from pathlib import Path + +import pandas as pd + +SUPPORTED_TABLE_EXTS = {".csv", ".xlsx", ".xls"} + + +@dataclass +class TableParseResult: + """Structured parse result for a table file. + + Attributes: + headers: Column header names (de-duplicated, never empty strings). + rows: Row data, each row is a list of cell values aligned to ``headers``. + """ + + headers: list[str] + rows: list[list[str]] + + +def is_table_file(file_name: str) -> bool: + """Return whether the given file name is a supported table format.""" + return Path(file_name).suffix.lower() in SUPPORTED_TABLE_EXTS + + +def _normalize_headers(raw_headers: list[object]) -> list[str]: + """Build clean, unique header names from raw header cells. + + Args: + raw_headers: Raw header values parsed from the file. + + Returns: + A list of non-empty, de-duplicated header strings. + """ + headers: list[str] = [] + seen: dict[str, int] = {} + for idx, raw in enumerate(raw_headers): + name = str(raw).strip() if raw is not None else "" + if not name or name.lower().startswith("unnamed:"): + name = f"column_{idx + 1}" + if name in seen: + seen[name] += 1 + name = f"{name}_{seen[name]}" + else: + seen[name] = 0 + headers.append(name) + return headers + + +def _read_dataframe( + file_content: bytes, + file_name: str, + header_row: int, +) -> "pd.DataFrame": + """Read a table file into a DataFrame with all cells as strings. + + Args: + file_content: Raw file bytes. + file_name: Original file name used to infer the format. + header_row: 0-based index of the row that holds the column headers. + + Returns: + A pandas DataFrame where every cell is a string and blanks are "". + + Raises: + ValueError: If the file extension is not a supported table format. + """ + ext = Path(file_name).suffix.lower() + if ext not in SUPPORTED_TABLE_EXTS: + raise ValueError(f"暂时不支持的表格格式: {ext}") + + if ext == ".csv": + # Try common encodings so Chinese spreadsheets exported from Excel work. + last_error: Exception | None = None + for encoding in ("utf-8-sig", "utf-8", "gbk", "latin-1"): + try: + return pd.read_csv( + io.BytesIO(file_content), + header=header_row, + dtype=str, + keep_default_na=False, + encoding=encoding, + ) + except (UnicodeDecodeError, ValueError) as exc: + last_error = exc + continue + raise ValueError(f"无法解析 CSV 文件,可能是编码问题: {last_error}") + + return pd.read_excel( + io.BytesIO(file_content), + header=header_row, + dtype=str, + keep_default_na=False, + ) + + +def parse_table( + file_content: bytes, + file_name: str, + header_row: int = 0, +) -> TableParseResult: + """Parse a csv/xls/xlsx file into structured headers and rows. + + Args: + file_content: Raw file bytes. + file_name: Original file name used to infer the format. + header_row: 0-based index of the row that holds the column headers. + + Returns: + TableParseResult: Parsed headers and row values. + + Raises: + ValueError: If the file format is unsupported or no columns are found. + """ + df = _read_dataframe(file_content, file_name, header_row) + if df.shape[1] == 0: + raise ValueError("未能从表格中解析出任何列。") + + headers = _normalize_headers(list(df.columns)) + rows: list[list[str]] = [] + for record in df.itertuples(index=False, name=None): + rows.append(["" if cell is None else str(cell).strip() for cell in record]) + + return TableParseResult(headers=headers, rows=rows) diff --git a/astrbot/dashboard/api/knowledge_bases.py b/astrbot/dashboard/api/knowledge_bases.py index c6f62235dd..03576ffe58 100644 --- a/astrbot/dashboard/api/knowledge_bases.py +++ b/astrbot/dashboard/api/knowledge_bases.py @@ -205,6 +205,42 @@ async def _operation(): return await _run(_operation, prefix="上传文档失败") +@router.post("/knowledge-bases/{kb_id}/documents/preview-table") +async def preview_knowledge_base_table( + kb_id: str, + request: Request, + _auth: AuthContext = Depends(require_kb_scope), + service: KnowledgeBaseService = Depends(get_service), +): + async def _operation(): + form_data, files = await multipart_parts(request, extra_form={"kb_id": kb_id}) + return await service.preview_table( + content_type=request.headers.get("content-type"), + form_data=form_data, + files=files, + ) + + return await _run(_operation, prefix="表格预览失败") + + +@router.post("/knowledge-bases/{kb_id}/documents/import-table") +async def import_knowledge_base_table( + kb_id: str, + request: Request, + _auth: AuthContext = Depends(require_kb_scope), + service: KnowledgeBaseService = Depends(get_service), +): + async def _operation(): + form_data, files = await multipart_parts(request, extra_form={"kb_id": kb_id}) + return await service.import_table( + content_type=request.headers.get("content-type"), + form_data=form_data, + files=files, + ) + + return await _run(_operation, prefix="表格导入失败") + + @router.post("/knowledge-bases/{kb_id}/documents/import") async def import_knowledge_base_documents( kb_id: str, diff --git a/astrbot/dashboard/schemas.py b/astrbot/dashboard/schemas.py index e6d939fc81..58650811e3 100644 --- a/astrbot/dashboard/schemas.py +++ b/astrbot/dashboard/schemas.py @@ -208,6 +208,7 @@ class KnowledgeBaseRequest(OpenModel): kb_id: str | None = None name: str | None = None description: str | None = None + kb_type: str | None = None embedding_provider_id: str | None = None rerank_provider_id: str | None = None chunk_size: int | None = None diff --git a/astrbot/dashboard/services/knowledge_base_service.py b/astrbot/dashboard/services/knowledge_base_service.py index ad2121c9aa..c9a2eaa24b 100644 --- a/astrbot/dashboard/services/knowledge_base_service.py +++ b/astrbot/dashboard/services/knowledge_base_service.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import json import traceback import uuid from pathlib import Path @@ -329,6 +330,10 @@ async def create_kb(self, data: object) -> tuple[dict[str, Any], str]: f"测试重排序模型失败: {exc!s},请检查平台日志输出。" ) from exc + kb_type = payload.get("kb_type") or "text" + if kb_type not in ("text", "table"): + raise KnowledgeBaseServiceError(f"不支持的知识库类型: {kb_type}") + kb_helper = await kb_manager.create_kb( kb_name=kb_name, description=payload.get("description"), @@ -340,6 +345,7 @@ async def create_kb(self, data: object) -> tuple[dict[str, Any], str]: top_k_dense=payload.get("top_k_dense"), top_k_sparse=payload.get("top_k_sparse"), top_m_final=payload.get("top_m_final"), + kb_type=kb_type, ) return kb_helper.kb.model_dump(), "创建知识库成功" @@ -602,6 +608,236 @@ async def import_documents(self, data: object) -> dict[str, Any]: "message": "import task created, processing in background", } + @staticmethod + def _read_single_upload(form_data, files) -> tuple[str, bytes]: + """Read the first uploaded file from a multipart request into memory. + + Args: + form_data: Parsed multipart form fields. + files: Parsed multipart file fields. + + Returns: + A tuple of ``(file_name, file_content)``. + + Raises: + KnowledgeBaseServiceError: If no file is present. + """ + file_list = [] + for key in files.keys(): + if key == "file" or key.startswith("file") or key == "files[]": + file_list.extend(files.getlist(key)) + if not file_list: + raise KnowledgeBaseServiceError("缺少文件") + return file_list[0] + + async def preview_table( + self, + *, + content_type: str | None, + form_data, + files, + ) -> dict[str, Any]: + """Parse an uploaded table file and return headers plus sample rows. + + Args: + content_type: Request content type header. + form_data: Parsed multipart form fields (kb_id, header_row, ...). + files: Parsed multipart file fields. + + Returns: + A dict with ``headers``, sample ``rows`` and ``total_rows``. + + Raises: + KnowledgeBaseServiceError: On validation or parse failure. + """ + if content_type and "multipart/form-data" not in content_type: + raise KnowledgeBaseServiceError("Content-Type 须为 multipart/form-data") + if not form_data.get("kb_id"): + raise KnowledgeBaseServiceError("缺少参数 kb_id") + header_row = int(form_data.get("header_row", 0)) + preview_rows = int(form_data.get("preview_rows", 20)) + + file = self._read_single_upload(form_data, files) + file_name = file.filename + temp_file_path = ( + Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" + ) + await file.save(temp_file_path) + try: + async with aiofiles.open(temp_file_path, "rb") as file_obj: + file_content = await file_obj.read() + finally: + temp_file_path.unlink(missing_ok=True) + + from astrbot.core.knowledge_base.parsers.table_parser import ( + is_table_file, + parse_table, + ) + + if not is_table_file(file_name): + raise KnowledgeBaseServiceError( + "不支持的表格格式,请上传 csv/xls/xlsx 文件", + ) + try: + result = parse_table(file_content, file_name, header_row=header_row) + except Exception as exc: + raise KnowledgeBaseServiceError(f"解析表格失败: {exc!s}") from exc + + return { + "file_name": file_name, + "headers": result.headers, + "rows": result.rows[:preview_rows], + "total_rows": len(result.rows), + } + + async def import_table( + self, + *, + content_type: str | None, + form_data, + files, + ) -> dict[str, Any]: + """Start a background task to import a table with column configuration. + + Args: + content_type: Request content type header. + form_data: Parsed multipart form fields (kb_id, header_row, + columns_config JSON, batch params). + files: Parsed multipart file fields. + + Returns: + A dict with the background ``task_id``. + + Raises: + KnowledgeBaseServiceError: On validation failure. + """ + if content_type and "multipart/form-data" not in content_type: + raise KnowledgeBaseServiceError("Content-Type 须为 multipart/form-data") + kb_id = form_data.get("kb_id") + if not kb_id: + raise KnowledgeBaseServiceError("缺少参数 kb_id") + header_row = int(form_data.get("header_row", 0)) + batch_size = int(form_data.get("batch_size", 32)) + tasks_limit = int(form_data.get("tasks_limit", 3)) + max_retries = int(form_data.get("max_retries", 3)) + + columns_config_raw = form_data.get("columns_config") + if not columns_config_raw: + raise KnowledgeBaseServiceError("缺少参数 columns_config") + try: + columns_config = json.loads(columns_config_raw) + except (TypeError, ValueError) as exc: + raise KnowledgeBaseServiceError("columns_config 格式错误") from exc + if not isinstance(columns_config, list) or not columns_config: + raise KnowledgeBaseServiceError("columns_config 必须是非空列表") + if not any(c.get("is_index") for c in columns_config): + raise KnowledgeBaseServiceError("至少需要选择一个索引列") + + file = self._read_single_upload(form_data, files) + file_name = file.filename + temp_file_path = ( + Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" + ) + await file.save(temp_file_path) + try: + async with aiofiles.open(temp_file_path, "rb") as file_obj: + file_content = await file_obj.read() + finally: + temp_file_path.unlink(missing_ok=True) + file_type = file_name.rsplit(".", 1)[-1].lower() if "." in file_name else "" + file_size = len(file_content) + + kb_helper = await self.get_kb_manager().get_kb(kb_id) + if not kb_helper: + raise KnowledgeBaseServiceError("知识库不存在") + + task_id = str(uuid.uuid4()) + self.init_task(task_id, status="pending") + asyncio.create_task( + self.background_table_import_task( + task_id=task_id, + kb_helper=kb_helper, + file_name=file_name, + file_type=file_type, + file_content=file_content, + file_size=file_size, + columns_config=columns_config, + header_row=header_row, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + ), + ) + return { + "task_id": task_id, + "file_count": 1, + "message": "table import task created, processing in background", + } + + async def background_table_import_task( + self, + task_id: str, + kb_helper, + file_name: str, + file_type: str, + file_content: bytes, + file_size: int, + columns_config: list[dict[str, Any]], + header_row: int, + batch_size: int, + tasks_limit: int, + max_retries: int, + ) -> None: + """Parse a table file and import every row as a chunk in the background.""" + try: + self.init_task(task_id, status="processing") + self.upload_progress[task_id] = { + "status": "processing", + "file_index": 0, + "file_total": 1, + "file_name": file_name, + "stage": "parsing", + "current": 0, + "total": 100, + } + progress_callback = self.make_progress_callback(task_id, 0, file_name) + + from astrbot.core.knowledge_base.parsers.table_parser import parse_table + + result = parse_table(file_content, file_name, header_row=header_row) + doc = await kb_helper.upload_table_document( + file_name=file_name, + file_type=file_type, + headers=result.headers, + rows=result.rows, + columns_config=columns_config, + file_size=file_size, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + progress_callback=progress_callback, + ) + self.set_task_result( + task_id, + "completed", + result={ + "task_id": task_id, + "uploaded": [doc.model_dump()], + "failed": [], + "total": 1, + "success_count": 1, + "failed_count": 0, + }, + ) + except Exception as exc: + logger.error(f"后台表格导入任务 {task_id} 失败: {exc}") + logger.error(traceback.format_exc()) + self.set_task_result( + task_id, + "failed", + error=self.format_failed_doc_error(file_name, exc), + ) + def get_upload_progress(self, task_id: str | None) -> dict[str, Any]: if not task_id: raise KnowledgeBaseServiceError("缺少参数 task_id") diff --git a/dashboard/src/api/generated/openapi-v1/sdk.gen.ts b/dashboard/src/api/generated/openapi-v1/sdk.gen.ts index 39eca2f6fd..e26e60c848 100644 --- a/dashboard/src/api/generated/openapi-v1/sdk.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type OptionsLegacyParser, formDataBodySerializer } from '@hey-api/client-axios'; -import type { LoginData, LoginError, LoginResponse, LogoutError, LogoutResponse, GetAuthSetupStatusError, GetAuthSetupStatusResponse, SetupAuthData, SetupAuthError, SetupAuthResponse, SetupTotpData, SetupTotpError, SetupTotpResponse, RecoverTotpError, RecoverTotpResponse, UpdateAuthAccountData, UpdateAuthAccountError, UpdateAuthAccountResponse, ListApiKeysError, ListApiKeysResponse, CreateApiKeyData, CreateApiKeyError, CreateApiKeyResponse, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyResponse, DeleteApiKeyData, DeleteApiKeyError, DeleteApiKeyResponse, GetSystemConfigSchemaError, GetSystemConfigSchemaResponse, GetSystemConfigError, GetSystemConfigResponse, UpdateSystemConfigData, UpdateSystemConfigError, UpdateSystemConfigResponse, GetSystemConfigRuntimeError, GetSystemConfigRuntimeResponse, GetConfigProfileSchemaError, GetConfigProfileSchemaResponse, ListConfigProfilesError, ListConfigProfilesResponse, CreateConfigProfileData, CreateConfigProfileError, CreateConfigProfileResponse, GetConfigProfileData, GetConfigProfileError, GetConfigProfileResponse, UpdateConfigProfileContentData, UpdateConfigProfileContentError, UpdateConfigProfileContentResponse, RenameConfigProfileData, RenameConfigProfileError, RenameConfigProfileResponse, DeleteConfigProfileData, DeleteConfigProfileError, DeleteConfigProfileResponse, ListConfigRoutesError, ListConfigRoutesResponse, ReplaceConfigRoutesData, ReplaceConfigRoutesError, ReplaceConfigRoutesResponse, UpsertConfigRouteData, UpsertConfigRouteError, UpsertConfigRouteResponse, DeleteConfigRouteData, DeleteConfigRouteError, DeleteConfigRouteResponse, ListBotTypesError, ListBotTypesResponse, RegisterBotTypeData, RegisterBotTypeError, RegisterBotTypeResponse, ListBotsData, ListBotsError, ListBotsResponse, CreateBotData, CreateBotError, CreateBotResponse, ListBotStatsError, ListBotStatsResponse, GetBotByIdData, GetBotByIdError, GetBotByIdResponse, UpdateBotByIdData, UpdateBotByIdError, UpdateBotByIdResponse, DeleteBotByIdData, DeleteBotByIdError, DeleteBotByIdResponse, SetBotEnabledByIdData, SetBotEnabledByIdError, SetBotEnabledByIdResponse, TestBotByIdData, TestBotByIdError, TestBotByIdResponse, GetBotData, GetBotError, GetBotResponse, UpdateBotData, UpdateBotError, UpdateBotResponse, DeleteBotData, DeleteBotError, DeleteBotResponse, SetBotEnabledData, SetBotEnabledError, SetBotEnabledResponse, TestBotData, TestBotError, TestBotResponse, GetProviderSchemaError, GetProviderSchemaResponse, ListProviderSourcesError, ListProviderSourcesResponse, CreateProviderSourceData, CreateProviderSourceError, CreateProviderSourceResponse, GetProviderSourceByIdData, GetProviderSourceByIdError, GetProviderSourceByIdResponse, UpsertProviderSourceByIdData, UpsertProviderSourceByIdError, UpsertProviderSourceByIdResponse, DeleteProviderSourceByIdData, DeleteProviderSourceByIdError, DeleteProviderSourceByIdResponse, ListProviderSourceModelsByIdData, ListProviderSourceModelsByIdError, ListProviderSourceModelsByIdResponse, ListProvidersBySourceIdData, ListProvidersBySourceIdError, ListProvidersBySourceIdResponse, CreateProviderInSourceByIdData, CreateProviderInSourceByIdError, CreateProviderInSourceByIdResponse, GetProviderSourceData, GetProviderSourceError, GetProviderSourceResponse, UpsertProviderSourceData, UpsertProviderSourceError, UpsertProviderSourceResponse, DeleteProviderSourceData, DeleteProviderSourceError, DeleteProviderSourceResponse, ListProviderSourceModelsData, ListProviderSourceModelsError, ListProviderSourceModelsResponse, ListProvidersBySourceData, ListProvidersBySourceError, ListProvidersBySourceResponse, CreateProviderInSourceData, CreateProviderInSourceError, CreateProviderInSourceResponse, ListProvidersData, ListProvidersError, ListProvidersResponse, CreateProviderData, CreateProviderError, CreateProviderResponse, GetProviderByIdData, GetProviderByIdError, GetProviderByIdResponse, UpdateProviderByIdData, UpdateProviderByIdError, UpdateProviderByIdResponse, DeleteProviderByIdData, DeleteProviderByIdError, DeleteProviderByIdResponse, SetProviderEnabledByIdData, SetProviderEnabledByIdError, SetProviderEnabledByIdResponse, TestProviderByIdData, TestProviderByIdError, TestProviderByIdResponse, GetProviderEmbeddingDimensionByIdData, GetProviderEmbeddingDimensionByIdError, GetProviderEmbeddingDimensionByIdResponse, GetProviderData, GetProviderError, GetProviderResponse, UpdateProviderData, UpdateProviderError, UpdateProviderResponse, DeleteProviderData, DeleteProviderError, DeleteProviderResponse, SetProviderEnabledData, SetProviderEnabledError, SetProviderEnabledResponse, TestProviderData, TestProviderError, TestProviderResponse, GetProviderEmbeddingDimensionData, GetProviderEmbeddingDimensionError, GetProviderEmbeddingDimensionResponse, SendChatMessageData, SendChatMessageError, SendChatMessageResponse, OpenChatWebSocketData, OpenLiveChatWebSocketData, OpenUnifiedChatWebSocketData, ListChatSessionsData, ListChatSessionsError, ListChatSessionsResponse, CreateChatSessionData, CreateChatSessionError, CreateChatSessionResponse, BatchDeleteChatSessionsData, BatchDeleteChatSessionsError, BatchDeleteChatSessionsResponse, GetChatSessionData, GetChatSessionError, GetChatSessionResponse, UpdateChatSessionData, UpdateChatSessionError, UpdateChatSessionResponse, DeleteChatSessionData, DeleteChatSessionError, DeleteChatSessionResponse, StopChatSessionData, StopChatSessionError, StopChatSessionResponse, UpdateChatMessageData, UpdateChatMessageError, UpdateChatMessageResponse, RegenerateChatMessageData, RegenerateChatMessageError, RegenerateChatMessageResponse, ListChatConfigsError, ListChatConfigsResponse, CreateChatThreadData, CreateChatThreadError, CreateChatThreadResponse, GetChatThreadData, GetChatThreadError, GetChatThreadResponse, DeleteChatThreadData, DeleteChatThreadError, DeleteChatThreadResponse, SendChatThreadMessageData, SendChatThreadMessageError, SendChatThreadMessageResponse, ListChatProjectsError, ListChatProjectsResponse, CreateChatProjectData, CreateChatProjectError, CreateChatProjectResponse, GetChatProjectData, GetChatProjectError, GetChatProjectResponse, UpdateChatProjectData, UpdateChatProjectError, UpdateChatProjectResponse, DeleteChatProjectData, DeleteChatProjectError, DeleteChatProjectResponse, ListChatProjectSessionsData, ListChatProjectSessionsError, ListChatProjectSessionsResponse, AddChatProjectSessionData, AddChatProjectSessionError, AddChatProjectSessionResponse, RemoveChatProjectSessionData, RemoveChatProjectSessionError, RemoveChatProjectSessionResponse, SendImMessageData, SendImMessageError, SendImMessageResponse, ListImBotsError, ListImBotsResponse, UploadFileData, UploadFileError, UploadFileResponse, UploadOpenApiFileData, UploadOpenApiFileError, UploadOpenApiFileResponse, DownloadOpenApiFileData, DownloadOpenApiFileError, DownloadOpenApiFileResponse, GetFileByNameData, GetFileByNameError, GetFileByNameResponse, GetTokenFileData, GetTokenFileError, GetTokenFileResponse, GetAttachmentData, GetAttachmentError, GetAttachmentResponse, DeleteAttachmentData, DeleteAttachmentError, DeleteAttachmentResponse, DownloadAttachmentData, DownloadAttachmentError, DownloadAttachmentResponse, ListPluginsData, ListPluginsError, ListPluginsResponse, GetPluginByIdData, GetPluginByIdError, GetPluginByIdResponse, UninstallPluginByIdData, UninstallPluginByIdError, UninstallPluginByIdResponse, GetPluginConfigByIdData, GetPluginConfigByIdError, GetPluginConfigByIdResponse, UpdatePluginConfigByIdData, UpdatePluginConfigByIdError, UpdatePluginConfigByIdResponse, GetPluginConfigSchemaByIdData, GetPluginConfigSchemaByIdError, GetPluginConfigSchemaByIdResponse, ListPluginConfigFilesByIdData, ListPluginConfigFilesByIdError, ListPluginConfigFilesByIdResponse, UploadPluginConfigFilesByIdData, UploadPluginConfigFilesByIdError, UploadPluginConfigFilesByIdResponse, DeletePluginConfigFileByIdData, DeletePluginConfigFileByIdError, DeletePluginConfigFileByIdResponse, GetPluginReadmeByIdData, GetPluginReadmeByIdError, GetPluginReadmeByIdResponse, GetPluginChangelogByIdData, GetPluginChangelogByIdError, GetPluginChangelogByIdResponse, ReloadPluginByIdData, ReloadPluginByIdError, ReloadPluginByIdResponse, SetPluginEnabledByIdData, SetPluginEnabledByIdError, SetPluginEnabledByIdResponse, ListPluginPagesByIdData, ListPluginPagesByIdError, ListPluginPagesByIdResponse, GetPluginPageByIdData, GetPluginPageByIdError, GetPluginPageByIdResponse, GetPluginPageAssetByIdData, GetPluginPageAssetByIdError, GetPluginPageAssetByIdResponse, GetPluginData, GetPluginError, GetPluginResponse, UninstallPluginData, UninstallPluginError, UninstallPluginResponse, GetPluginConfigData, GetPluginConfigError, GetPluginConfigResponse, UpdatePluginConfigData, UpdatePluginConfigError, UpdatePluginConfigResponse, GetPluginConfigSchemaData, GetPluginConfigSchemaError, GetPluginConfigSchemaResponse, ListPluginConfigFilesData, ListPluginConfigFilesError, ListPluginConfigFilesResponse, UploadPluginConfigFilesData, UploadPluginConfigFilesError, UploadPluginConfigFilesResponse, DeletePluginConfigFileData, DeletePluginConfigFileError, DeletePluginConfigFileResponse, GetPluginReadmeData, GetPluginReadmeError, GetPluginReadmeResponse, GetPluginChangelogData, GetPluginChangelogError, GetPluginChangelogResponse, ReloadPluginData, ReloadPluginError, ReloadPluginResponse, SetPluginEnabledData, SetPluginEnabledError, SetPluginEnabledResponse, UpdatePluginData, UpdatePluginError, UpdatePluginResponse, UpdatePluginsData, UpdatePluginsError, UpdatePluginsResponse, CheckPluginVersionSupportData, CheckPluginVersionSupportError, CheckPluginVersionSupportResponse, ListFailedPluginsError, ListFailedPluginsResponse, UninstallFailedPluginData, UninstallFailedPluginError, UninstallFailedPluginResponse, ReloadFailedPluginData, ReloadFailedPluginError, ReloadFailedPluginResponse, InstallPluginFromGithubData, InstallPluginFromGithubError, InstallPluginFromGithubResponse, InstallPluginFromUrlData, InstallPluginFromUrlError, InstallPluginFromUrlResponse, InstallPluginFromUploadData, InstallPluginFromUploadError, InstallPluginFromUploadResponse, ListPluginMarketData, ListPluginMarketError, ListPluginMarketResponse, ListPluginMarketCategoriesError, ListPluginMarketCategoriesResponse, ListPluginSourcesError, ListPluginSourcesResponse, CreatePluginSourceData, CreatePluginSourceError, CreatePluginSourceResponse, ReplacePluginSourcesData, ReplacePluginSourcesError, ReplacePluginSourcesResponse, DeletePluginSourceData, DeletePluginSourceError, DeletePluginSourceResponse, DeletePluginSourceByIdData, DeletePluginSourceByIdError, DeletePluginSourceByIdResponse, ListPluginPagesData, ListPluginPagesError, ListPluginPagesResponse, GetPluginPageData, GetPluginPageError, GetPluginPageResponse, GetPluginPageAssetData, GetPluginPageAssetError, GetPluginPageAssetResponse, GetPluginPageBridgeSdkError, GetPluginPageBridgeSdkResponse, GetPluginExtensionRouteData, GetPluginExtensionRouteError, GetPluginExtensionRouteResponse, PostPluginExtensionRouteData, PostPluginExtensionRouteError, PostPluginExtensionRouteResponse, PutPluginExtensionRouteData, PutPluginExtensionRouteError, PutPluginExtensionRouteResponse, PatchPluginExtensionRouteData, PatchPluginExtensionRouteError, PatchPluginExtensionRouteResponse, DeletePluginExtensionRouteData, DeletePluginExtensionRouteError, DeletePluginExtensionRouteResponse, ListCommandsData, ListCommandsError, ListCommandsResponse, UpdateCommandData, UpdateCommandError, UpdateCommandResponse, ListCommandConflictsError, ListCommandConflictsResponse, ListToolsData, ListToolsError, ListToolsResponse, SetToolEnabledData, SetToolEnabledError, SetToolEnabledResponse, SetToolPermissionData, SetToolPermissionError, SetToolPermissionResponse, ListMcpServersError, ListMcpServersResponse, CreateMcpServerData, CreateMcpServerError, CreateMcpServerResponse, UpdateMcpServerByNameData, UpdateMcpServerByNameError, UpdateMcpServerByNameResponse, DeleteMcpServerByNameData, DeleteMcpServerByNameError, DeleteMcpServerByNameResponse, SetMcpServerEnabledByNameData, SetMcpServerEnabledByNameError, SetMcpServerEnabledByNameResponse, TestMcpServerByNameData, TestMcpServerByNameError, TestMcpServerByNameResponse, UpdateMcpServerData, UpdateMcpServerError, UpdateMcpServerResponse, DeleteMcpServerData, DeleteMcpServerError, DeleteMcpServerResponse, SetMcpServerEnabledData, SetMcpServerEnabledError, SetMcpServerEnabledResponse, TestMcpServerData, TestMcpServerError, TestMcpServerResponse, SyncModelScopeMcpServersData, SyncModelScopeMcpServersError, SyncModelScopeMcpServersResponse, ListSkillsData, ListSkillsError, ListSkillsResponse, UploadSkillData, UploadSkillError, UploadSkillResponse, UploadSkillsBatchData, UploadSkillsBatchError, UploadSkillsBatchResponse, UpdateSkillByNameData, UpdateSkillByNameError, UpdateSkillByNameResponse, DeleteSkillByNameData, DeleteSkillByNameError, DeleteSkillByNameResponse, DownloadSkillByNameData, DownloadSkillByNameError, DownloadSkillByNameResponse, ListSkillFilesByNameData, ListSkillFilesByNameError, ListSkillFilesByNameResponse, GetSkillFileByNameData, GetSkillFileByNameError, GetSkillFileByNameResponse, UpdateSkillFileByNameData, UpdateSkillFileByNameError, UpdateSkillFileByNameResponse, UpdateSkillData, UpdateSkillError, UpdateSkillResponse, DeleteSkillData, DeleteSkillError, DeleteSkillResponse, DownloadSkillData, DownloadSkillError, DownloadSkillResponse, ListSkillFilesData, ListSkillFilesError, ListSkillFilesResponse, GetSkillFileData, GetSkillFileError, GetSkillFileResponse, UpdateSkillFileData, UpdateSkillFileError, UpdateSkillFileResponse, ListNeoSkillCandidatesData, ListNeoSkillCandidatesError, ListNeoSkillCandidatesResponse, ListNeoSkillReleasesData, ListNeoSkillReleasesError, ListNeoSkillReleasesResponse, GetNeoSkillPayloadData, GetNeoSkillPayloadError, GetNeoSkillPayloadResponse, EvaluateNeoSkillCandidateData, EvaluateNeoSkillCandidateError, EvaluateNeoSkillCandidateResponse, PromoteNeoSkillCandidateData, PromoteNeoSkillCandidateError, PromoteNeoSkillCandidateResponse, RollbackNeoSkillReleaseData, RollbackNeoSkillReleaseError, RollbackNeoSkillReleaseResponse, SyncNeoSkillReleaseData, SyncNeoSkillReleaseError, SyncNeoSkillReleaseResponse, DeleteNeoSkillCandidateData, DeleteNeoSkillCandidateError, DeleteNeoSkillCandidateResponse, DeleteNeoSkillReleaseData, DeleteNeoSkillReleaseError, DeleteNeoSkillReleaseResponse, ListKnowledgeBasesData, ListKnowledgeBasesError, ListKnowledgeBasesResponse, CreateKnowledgeBaseData, CreateKnowledgeBaseError, CreateKnowledgeBaseResponse, GetKnowledgeBaseData, GetKnowledgeBaseError, GetKnowledgeBaseResponse, UpdateKnowledgeBaseData, UpdateKnowledgeBaseError, UpdateKnowledgeBaseResponse, DeleteKnowledgeBaseData, DeleteKnowledgeBaseError, DeleteKnowledgeBaseResponse, GetKnowledgeBaseStatsData, GetKnowledgeBaseStatsError, GetKnowledgeBaseStatsResponse, ListKnowledgeDocumentsData, ListKnowledgeDocumentsError, ListKnowledgeDocumentsResponse, UploadKnowledgeDocumentData, UploadKnowledgeDocumentError, UploadKnowledgeDocumentResponse, ImportKnowledgeDocumentsData, ImportKnowledgeDocumentsError, ImportKnowledgeDocumentsResponse, ImportKnowledgeDocumentFromUrlData, ImportKnowledgeDocumentFromUrlError, ImportKnowledgeDocumentFromUrlResponse, GetKnowledgeDocumentData, GetKnowledgeDocumentError, GetKnowledgeDocumentResponse, DeleteKnowledgeDocumentData, DeleteKnowledgeDocumentError, DeleteKnowledgeDocumentResponse, ListKnowledgeChunksData, ListKnowledgeChunksError, ListKnowledgeChunksResponse, DeleteKnowledgeChunkData, DeleteKnowledgeChunkError, DeleteKnowledgeChunkResponse, RetrieveKnowledgeBaseData, RetrieveKnowledgeBaseError, RetrieveKnowledgeBaseResponse, GetKnowledgeTaskData, GetKnowledgeTaskError, GetKnowledgeTaskResponse, GetPersonaTreeError, GetPersonaTreeResponse, ListPersonasData, ListPersonasError, ListPersonasResponse, CreatePersonaData, CreatePersonaError, CreatePersonaResponse, GetPersonaByIdData, GetPersonaByIdError, GetPersonaByIdResponse, UpdatePersonaByIdData, UpdatePersonaByIdError, UpdatePersonaByIdResponse, DeletePersonaByIdData, DeletePersonaByIdError, DeletePersonaByIdResponse, GetPersonaData, GetPersonaError, GetPersonaResponse, UpdatePersonaData, UpdatePersonaError, UpdatePersonaResponse, DeletePersonaData, DeletePersonaError, DeletePersonaResponse, ListPersonaFoldersData, ListPersonaFoldersError, ListPersonaFoldersResponse, CreatePersonaFolderData, CreatePersonaFolderError, CreatePersonaFolderResponse, UpdatePersonaFolderData, UpdatePersonaFolderError, UpdatePersonaFolderResponse, DeletePersonaFolderData, DeletePersonaFolderError, DeletePersonaFolderResponse, MovePersonaItemData, MovePersonaItemError, MovePersonaItemResponse, ReorderPersonaItemsData, ReorderPersonaItemsError, ReorderPersonaItemsResponse, ListSessionsData, ListSessionsError, ListSessionsResponse, ListActiveUmosError, ListActiveUmosResponse, ListSessionRulesData, ListSessionRulesError, ListSessionRulesResponse, UpsertSessionRuleData, UpsertSessionRuleError, UpsertSessionRuleResponse, DeleteSessionRulesData, DeleteSessionRulesError, DeleteSessionRulesResponse, BatchUpdateSessionProviderData, BatchUpdateSessionProviderError, BatchUpdateSessionProviderResponse, BatchUpdateSessionServiceData, BatchUpdateSessionServiceError, BatchUpdateSessionServiceResponse, ListSessionGroupsError, ListSessionGroupsResponse, CreateSessionGroupData, CreateSessionGroupError, CreateSessionGroupResponse, UpdateSessionGroupData, UpdateSessionGroupError, UpdateSessionGroupResponse, DeleteSessionGroupData, DeleteSessionGroupError, DeleteSessionGroupResponse, ListConversationsData, ListConversationsError, ListConversationsResponse, BatchDeleteConversationsData, BatchDeleteConversationsError, BatchDeleteConversationsResponse, GetConversationData, GetConversationError, GetConversationResponse, UpdateConversationData, UpdateConversationError, UpdateConversationResponse, DeleteConversationData, DeleteConversationError, DeleteConversationResponse, ReplaceConversationMessagesData, ReplaceConversationMessagesError, ReplaceConversationMessagesResponse, ExportConversationsData, ExportConversationsError, ExportConversationsResponse, GetStatsData, GetStatsError, GetStatsResponse, GetProviderTokenStatsData, GetProviderTokenStatsError, GetProviderTokenStatsResponse, GetVersionError, GetVersionResponse, GetFirstNoticeData, GetFirstNoticeError, GetFirstNoticeResponse, TestGhproxyConnectionData, TestGhproxyConnectionError, TestGhproxyConnectionResponse, ListChangelogVersionsError, ListChangelogVersionsResponse, GetChangelogData, GetChangelogError, GetChangelogResponse, GetStartTimeError, GetStartTimeResponse, GetStorageStatusError, GetStorageStatusResponse, CleanupStorageData, CleanupStorageError, CleanupStorageResponse, RestartCoreError, RestartCoreResponse, ListBackupsData, ListBackupsError, ListBackupsResponse, CreateBackupData, CreateBackupError, CreateBackupResponse, UploadBackupData, UploadBackupError, UploadBackupResponse, InitBackupUploadData, InitBackupUploadError, InitBackupUploadResponse, UploadBackupChunkData, UploadBackupChunkError, UploadBackupChunkResponse, CompleteBackupUploadData, CompleteBackupUploadError, CompleteBackupUploadResponse, AbortBackupUploadData, AbortBackupUploadError, AbortBackupUploadResponse, GetBackupProgressData, GetBackupProgressError, GetBackupProgressResponse, DownloadBackupData, DownloadBackupError, DownloadBackupResponse, RenameBackupData, RenameBackupError, RenameBackupResponse, DeleteBackupData, DeleteBackupError, DeleteBackupResponse, CheckBackupData, CheckBackupError, CheckBackupResponse, ImportBackupData, ImportBackupError, ImportBackupResponse, CheckUpdateError, CheckUpdateResponse, ListReleasesData, ListReleasesError, ListReleasesResponse, UpdateCoreData, UpdateCoreError, UpdateCoreResponse, UpdateDashboardData, UpdateDashboardError, UpdateDashboardResponse, GetUpdateProgressData, GetUpdateProgressError, GetUpdateProgressResponse, InstallPipPackageData, InstallPipPackageError, InstallPipPackageResponse, ListCronJobsData, ListCronJobsError, ListCronJobsResponse, CreateCronJobData, CreateCronJobError, CreateCronJobResponse, UpdateCronJobData, UpdateCronJobError, UpdateCronJobResponse, DeleteCronJobData, DeleteCronJobError, DeleteCronJobResponse, RunCronJobData, RunCronJobError, RunCronJobResponse, StreamLiveLogsError, StreamLiveLogsResponse, GetLogHistoryError, GetLogHistoryResponse, GetTraceSettingsError, GetTraceSettingsResponse, UpdateTraceSettingsData, UpdateTraceSettingsError, UpdateTraceSettingsResponse, ListT2iTemplatesError, ListT2iTemplatesResponse, CreateT2iTemplateData, CreateT2iTemplateError, CreateT2iTemplateResponse, GetActiveT2iTemplateError, GetActiveT2iTemplateResponse, SetActiveT2iTemplateData, SetActiveT2iTemplateError, SetActiveT2iTemplateResponse, ResetDefaultT2iTemplateError, ResetDefaultT2iTemplateResponse, GetT2iTemplateData, GetT2iTemplateError, GetT2iTemplateResponse, UpdateT2iTemplateData, UpdateT2iTemplateError, UpdateT2iTemplateResponse, DeleteT2iTemplateData, DeleteT2iTemplateError, DeleteT2iTemplateResponse, GetSubagentConfigError, GetSubagentConfigResponse, UpdateSubagentConfigData, UpdateSubagentConfigError, UpdateSubagentConfigResponse, ListSubagentAvailableToolsError, ListSubagentAvailableToolsResponse, VerifyPlatformWebhookData, VerifyPlatformWebhookError, VerifyPlatformWebhookResponse, ReceivePlatformWebhookData, ReceivePlatformWebhookError, ReceivePlatformWebhookResponse } from './types.gen'; +import type { LoginData, LoginError, LoginResponse, LogoutError, LogoutResponse, GetAuthSetupStatusError, GetAuthSetupStatusResponse, SetupAuthData, SetupAuthError, SetupAuthResponse, SetupTotpData, SetupTotpError, SetupTotpResponse, RecoverTotpError, RecoverTotpResponse, UpdateAuthAccountData, UpdateAuthAccountError, UpdateAuthAccountResponse, ListApiKeysError, ListApiKeysResponse, CreateApiKeyData, CreateApiKeyError, CreateApiKeyResponse, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyResponse, DeleteApiKeyData, DeleteApiKeyError, DeleteApiKeyResponse, GetSystemConfigSchemaError, GetSystemConfigSchemaResponse, GetSystemConfigError, GetSystemConfigResponse, UpdateSystemConfigData, UpdateSystemConfigError, UpdateSystemConfigResponse, GetSystemConfigRuntimeError, GetSystemConfigRuntimeResponse, GetConfigProfileSchemaError, GetConfigProfileSchemaResponse, ListConfigProfilesError, ListConfigProfilesResponse, CreateConfigProfileData, CreateConfigProfileError, CreateConfigProfileResponse, GetConfigProfileData, GetConfigProfileError, GetConfigProfileResponse, UpdateConfigProfileContentData, UpdateConfigProfileContentError, UpdateConfigProfileContentResponse, RenameConfigProfileData, RenameConfigProfileError, RenameConfigProfileResponse, DeleteConfigProfileData, DeleteConfigProfileError, DeleteConfigProfileResponse, ListConfigRoutesError, ListConfigRoutesResponse, ReplaceConfigRoutesData, ReplaceConfigRoutesError, ReplaceConfigRoutesResponse, UpsertConfigRouteData, UpsertConfigRouteError, UpsertConfigRouteResponse, DeleteConfigRouteData, DeleteConfigRouteError, DeleteConfigRouteResponse, ListBotTypesError, ListBotTypesResponse, RegisterBotTypeData, RegisterBotTypeError, RegisterBotTypeResponse, ListBotsData, ListBotsError, ListBotsResponse, CreateBotData, CreateBotError, CreateBotResponse, ListBotStatsError, ListBotStatsResponse, GetBotByIdData, GetBotByIdError, GetBotByIdResponse, UpdateBotByIdData, UpdateBotByIdError, UpdateBotByIdResponse, DeleteBotByIdData, DeleteBotByIdError, DeleteBotByIdResponse, SetBotEnabledByIdData, SetBotEnabledByIdError, SetBotEnabledByIdResponse, TestBotByIdData, TestBotByIdError, TestBotByIdResponse, GetBotData, GetBotError, GetBotResponse, UpdateBotData, UpdateBotError, UpdateBotResponse, DeleteBotData, DeleteBotError, DeleteBotResponse, SetBotEnabledData, SetBotEnabledError, SetBotEnabledResponse, TestBotData, TestBotError, TestBotResponse, GetProviderSchemaError, GetProviderSchemaResponse, ListProviderSourcesError, ListProviderSourcesResponse, CreateProviderSourceData, CreateProviderSourceError, CreateProviderSourceResponse, GetProviderSourceByIdData, GetProviderSourceByIdError, GetProviderSourceByIdResponse, UpsertProviderSourceByIdData, UpsertProviderSourceByIdError, UpsertProviderSourceByIdResponse, DeleteProviderSourceByIdData, DeleteProviderSourceByIdError, DeleteProviderSourceByIdResponse, ListProviderSourceModelsByIdData, ListProviderSourceModelsByIdError, ListProviderSourceModelsByIdResponse, ListProvidersBySourceIdData, ListProvidersBySourceIdError, ListProvidersBySourceIdResponse, CreateProviderInSourceByIdData, CreateProviderInSourceByIdError, CreateProviderInSourceByIdResponse, GetProviderSourceData, GetProviderSourceError, GetProviderSourceResponse, UpsertProviderSourceData, UpsertProviderSourceError, UpsertProviderSourceResponse, DeleteProviderSourceData, DeleteProviderSourceError, DeleteProviderSourceResponse, ListProviderSourceModelsData, ListProviderSourceModelsError, ListProviderSourceModelsResponse, ListProvidersBySourceData, ListProvidersBySourceError, ListProvidersBySourceResponse, CreateProviderInSourceData, CreateProviderInSourceError, CreateProviderInSourceResponse, ListProvidersData, ListProvidersError, ListProvidersResponse, CreateProviderData, CreateProviderError, CreateProviderResponse, GetProviderByIdData, GetProviderByIdError, GetProviderByIdResponse, UpdateProviderByIdData, UpdateProviderByIdError, UpdateProviderByIdResponse, DeleteProviderByIdData, DeleteProviderByIdError, DeleteProviderByIdResponse, SetProviderEnabledByIdData, SetProviderEnabledByIdError, SetProviderEnabledByIdResponse, TestProviderByIdData, TestProviderByIdError, TestProviderByIdResponse, GetProviderEmbeddingDimensionByIdData, GetProviderEmbeddingDimensionByIdError, GetProviderEmbeddingDimensionByIdResponse, GetProviderData, GetProviderError, GetProviderResponse, UpdateProviderData, UpdateProviderError, UpdateProviderResponse, DeleteProviderData, DeleteProviderError, DeleteProviderResponse, SetProviderEnabledData, SetProviderEnabledError, SetProviderEnabledResponse, TestProviderData, TestProviderError, TestProviderResponse, GetProviderEmbeddingDimensionData, GetProviderEmbeddingDimensionError, GetProviderEmbeddingDimensionResponse, SendChatMessageData, SendChatMessageError, SendChatMessageResponse, OpenChatWebSocketData, OpenLiveChatWebSocketData, OpenUnifiedChatWebSocketData, ListChatSessionsData, ListChatSessionsError, ListChatSessionsResponse, CreateChatSessionData, CreateChatSessionError, CreateChatSessionResponse, BatchDeleteChatSessionsData, BatchDeleteChatSessionsError, BatchDeleteChatSessionsResponse, GetChatSessionData, GetChatSessionError, GetChatSessionResponse, UpdateChatSessionData, UpdateChatSessionError, UpdateChatSessionResponse, DeleteChatSessionData, DeleteChatSessionError, DeleteChatSessionResponse, StopChatSessionData, StopChatSessionError, StopChatSessionResponse, UpdateChatMessageData, UpdateChatMessageError, UpdateChatMessageResponse, RegenerateChatMessageData, RegenerateChatMessageError, RegenerateChatMessageResponse, ListChatConfigsError, ListChatConfigsResponse, CreateChatThreadData, CreateChatThreadError, CreateChatThreadResponse, GetChatThreadData, GetChatThreadError, GetChatThreadResponse, DeleteChatThreadData, DeleteChatThreadError, DeleteChatThreadResponse, SendChatThreadMessageData, SendChatThreadMessageError, SendChatThreadMessageResponse, ListChatProjectsError, ListChatProjectsResponse, CreateChatProjectData, CreateChatProjectError, CreateChatProjectResponse, GetChatProjectData, GetChatProjectError, GetChatProjectResponse, UpdateChatProjectData, UpdateChatProjectError, UpdateChatProjectResponse, DeleteChatProjectData, DeleteChatProjectError, DeleteChatProjectResponse, ListChatProjectSessionsData, ListChatProjectSessionsError, ListChatProjectSessionsResponse, AddChatProjectSessionData, AddChatProjectSessionError, AddChatProjectSessionResponse, RemoveChatProjectSessionData, RemoveChatProjectSessionError, RemoveChatProjectSessionResponse, SendImMessageData, SendImMessageError, SendImMessageResponse, ListImBotsError, ListImBotsResponse, UploadFileData, UploadFileError, UploadFileResponse, UploadOpenApiFileData, UploadOpenApiFileError, UploadOpenApiFileResponse, DownloadOpenApiFileData, DownloadOpenApiFileError, DownloadOpenApiFileResponse, GetFileByNameData, GetFileByNameError, GetFileByNameResponse, GetTokenFileData, GetTokenFileError, GetTokenFileResponse, GetAttachmentData, GetAttachmentError, GetAttachmentResponse, DeleteAttachmentData, DeleteAttachmentError, DeleteAttachmentResponse, DownloadAttachmentData, DownloadAttachmentError, DownloadAttachmentResponse, ListPluginsData, ListPluginsError, ListPluginsResponse, GetPluginByIdData, GetPluginByIdError, GetPluginByIdResponse, UninstallPluginByIdData, UninstallPluginByIdError, UninstallPluginByIdResponse, GetPluginConfigByIdData, GetPluginConfigByIdError, GetPluginConfigByIdResponse, UpdatePluginConfigByIdData, UpdatePluginConfigByIdError, UpdatePluginConfigByIdResponse, GetPluginConfigSchemaByIdData, GetPluginConfigSchemaByIdError, GetPluginConfigSchemaByIdResponse, ListPluginConfigFilesByIdData, ListPluginConfigFilesByIdError, ListPluginConfigFilesByIdResponse, UploadPluginConfigFilesByIdData, UploadPluginConfigFilesByIdError, UploadPluginConfigFilesByIdResponse, DeletePluginConfigFileByIdData, DeletePluginConfigFileByIdError, DeletePluginConfigFileByIdResponse, GetPluginReadmeByIdData, GetPluginReadmeByIdError, GetPluginReadmeByIdResponse, GetPluginChangelogByIdData, GetPluginChangelogByIdError, GetPluginChangelogByIdResponse, ReloadPluginByIdData, ReloadPluginByIdError, ReloadPluginByIdResponse, SetPluginEnabledByIdData, SetPluginEnabledByIdError, SetPluginEnabledByIdResponse, ListPluginPagesByIdData, ListPluginPagesByIdError, ListPluginPagesByIdResponse, GetPluginPageByIdData, GetPluginPageByIdError, GetPluginPageByIdResponse, GetPluginPageAssetByIdData, GetPluginPageAssetByIdError, GetPluginPageAssetByIdResponse, GetPluginData, GetPluginError, GetPluginResponse, UninstallPluginData, UninstallPluginError, UninstallPluginResponse, GetPluginConfigData, GetPluginConfigError, GetPluginConfigResponse, UpdatePluginConfigData, UpdatePluginConfigError, UpdatePluginConfigResponse, GetPluginConfigSchemaData, GetPluginConfigSchemaError, GetPluginConfigSchemaResponse, ListPluginConfigFilesData, ListPluginConfigFilesError, ListPluginConfigFilesResponse, UploadPluginConfigFilesData, UploadPluginConfigFilesError, UploadPluginConfigFilesResponse, DeletePluginConfigFileData, DeletePluginConfigFileError, DeletePluginConfigFileResponse, GetPluginReadmeData, GetPluginReadmeError, GetPluginReadmeResponse, GetPluginChangelogData, GetPluginChangelogError, GetPluginChangelogResponse, ReloadPluginData, ReloadPluginError, ReloadPluginResponse, SetPluginEnabledData, SetPluginEnabledError, SetPluginEnabledResponse, UpdatePluginData, UpdatePluginError, UpdatePluginResponse, UpdatePluginsData, UpdatePluginsError, UpdatePluginsResponse, CheckPluginVersionSupportData, CheckPluginVersionSupportError, CheckPluginVersionSupportResponse, ListFailedPluginsError, ListFailedPluginsResponse, UninstallFailedPluginData, UninstallFailedPluginError, UninstallFailedPluginResponse, ReloadFailedPluginData, ReloadFailedPluginError, ReloadFailedPluginResponse, InstallPluginFromGithubData, InstallPluginFromGithubError, InstallPluginFromGithubResponse, InstallPluginFromUrlData, InstallPluginFromUrlError, InstallPluginFromUrlResponse, InstallPluginFromUploadData, InstallPluginFromUploadError, InstallPluginFromUploadResponse, ListPluginMarketData, ListPluginMarketError, ListPluginMarketResponse, ListPluginMarketCategoriesError, ListPluginMarketCategoriesResponse, ListPluginSourcesError, ListPluginSourcesResponse, CreatePluginSourceData, CreatePluginSourceError, CreatePluginSourceResponse, ReplacePluginSourcesData, ReplacePluginSourcesError, ReplacePluginSourcesResponse, DeletePluginSourceData, DeletePluginSourceError, DeletePluginSourceResponse, DeletePluginSourceByIdData, DeletePluginSourceByIdError, DeletePluginSourceByIdResponse, ListPluginPagesData, ListPluginPagesError, ListPluginPagesResponse, GetPluginPageData, GetPluginPageError, GetPluginPageResponse, GetPluginPageAssetData, GetPluginPageAssetError, GetPluginPageAssetResponse, GetPluginPageBridgeSdkError, GetPluginPageBridgeSdkResponse, GetPluginExtensionRouteData, GetPluginExtensionRouteError, GetPluginExtensionRouteResponse, PostPluginExtensionRouteData, PostPluginExtensionRouteError, PostPluginExtensionRouteResponse, PutPluginExtensionRouteData, PutPluginExtensionRouteError, PutPluginExtensionRouteResponse, PatchPluginExtensionRouteData, PatchPluginExtensionRouteError, PatchPluginExtensionRouteResponse, DeletePluginExtensionRouteData, DeletePluginExtensionRouteError, DeletePluginExtensionRouteResponse, ListCommandsData, ListCommandsError, ListCommandsResponse, UpdateCommandData, UpdateCommandError, UpdateCommandResponse, ListCommandConflictsError, ListCommandConflictsResponse, ListToolsData, ListToolsError, ListToolsResponse, SetToolEnabledData, SetToolEnabledError, SetToolEnabledResponse, SetToolPermissionData, SetToolPermissionError, SetToolPermissionResponse, ListMcpServersError, ListMcpServersResponse, CreateMcpServerData, CreateMcpServerError, CreateMcpServerResponse, UpdateMcpServerByNameData, UpdateMcpServerByNameError, UpdateMcpServerByNameResponse, DeleteMcpServerByNameData, DeleteMcpServerByNameError, DeleteMcpServerByNameResponse, SetMcpServerEnabledByNameData, SetMcpServerEnabledByNameError, SetMcpServerEnabledByNameResponse, TestMcpServerByNameData, TestMcpServerByNameError, TestMcpServerByNameResponse, UpdateMcpServerData, UpdateMcpServerError, UpdateMcpServerResponse, DeleteMcpServerData, DeleteMcpServerError, DeleteMcpServerResponse, SetMcpServerEnabledData, SetMcpServerEnabledError, SetMcpServerEnabledResponse, TestMcpServerData, TestMcpServerError, TestMcpServerResponse, SyncModelScopeMcpServersData, SyncModelScopeMcpServersError, SyncModelScopeMcpServersResponse, ListSkillsData, ListSkillsError, ListSkillsResponse, UploadSkillData, UploadSkillError, UploadSkillResponse, UploadSkillsBatchData, UploadSkillsBatchError, UploadSkillsBatchResponse, UpdateSkillByNameData, UpdateSkillByNameError, UpdateSkillByNameResponse, DeleteSkillByNameData, DeleteSkillByNameError, DeleteSkillByNameResponse, DownloadSkillByNameData, DownloadSkillByNameError, DownloadSkillByNameResponse, ListSkillFilesByNameData, ListSkillFilesByNameError, ListSkillFilesByNameResponse, GetSkillFileByNameData, GetSkillFileByNameError, GetSkillFileByNameResponse, UpdateSkillFileByNameData, UpdateSkillFileByNameError, UpdateSkillFileByNameResponse, UpdateSkillData, UpdateSkillError, UpdateSkillResponse, DeleteSkillData, DeleteSkillError, DeleteSkillResponse, DownloadSkillData, DownloadSkillError, DownloadSkillResponse, ListSkillFilesData, ListSkillFilesError, ListSkillFilesResponse, GetSkillFileData, GetSkillFileError, GetSkillFileResponse, UpdateSkillFileData, UpdateSkillFileError, UpdateSkillFileResponse, ListNeoSkillCandidatesData, ListNeoSkillCandidatesError, ListNeoSkillCandidatesResponse, ListNeoSkillReleasesData, ListNeoSkillReleasesError, ListNeoSkillReleasesResponse, GetNeoSkillPayloadData, GetNeoSkillPayloadError, GetNeoSkillPayloadResponse, EvaluateNeoSkillCandidateData, EvaluateNeoSkillCandidateError, EvaluateNeoSkillCandidateResponse, PromoteNeoSkillCandidateData, PromoteNeoSkillCandidateError, PromoteNeoSkillCandidateResponse, RollbackNeoSkillReleaseData, RollbackNeoSkillReleaseError, RollbackNeoSkillReleaseResponse, SyncNeoSkillReleaseData, SyncNeoSkillReleaseError, SyncNeoSkillReleaseResponse, DeleteNeoSkillCandidateData, DeleteNeoSkillCandidateError, DeleteNeoSkillCandidateResponse, DeleteNeoSkillReleaseData, DeleteNeoSkillReleaseError, DeleteNeoSkillReleaseResponse, ListKnowledgeBasesData, ListKnowledgeBasesError, ListKnowledgeBasesResponse, CreateKnowledgeBaseData, CreateKnowledgeBaseError, CreateKnowledgeBaseResponse, GetKnowledgeBaseData, GetKnowledgeBaseError, GetKnowledgeBaseResponse, UpdateKnowledgeBaseData, UpdateKnowledgeBaseError, UpdateKnowledgeBaseResponse, DeleteKnowledgeBaseData, DeleteKnowledgeBaseError, DeleteKnowledgeBaseResponse, GetKnowledgeBaseStatsData, GetKnowledgeBaseStatsError, GetKnowledgeBaseStatsResponse, ListKnowledgeDocumentsData, ListKnowledgeDocumentsError, ListKnowledgeDocumentsResponse, UploadKnowledgeDocumentData, UploadKnowledgeDocumentError, UploadKnowledgeDocumentResponse, ImportKnowledgeDocumentsData, ImportKnowledgeDocumentsError, ImportKnowledgeDocumentsResponse, ImportKnowledgeDocumentFromUrlData, ImportKnowledgeDocumentFromUrlError, ImportKnowledgeDocumentFromUrlResponse, PreviewKnowledgeTableData, PreviewKnowledgeTableError, PreviewKnowledgeTableResponse, ImportKnowledgeTableData, ImportKnowledgeTableError, ImportKnowledgeTableResponse, GetKnowledgeDocumentData, GetKnowledgeDocumentError, GetKnowledgeDocumentResponse, DeleteKnowledgeDocumentData, DeleteKnowledgeDocumentError, DeleteKnowledgeDocumentResponse, ListKnowledgeChunksData, ListKnowledgeChunksError, ListKnowledgeChunksResponse, DeleteKnowledgeChunkData, DeleteKnowledgeChunkError, DeleteKnowledgeChunkResponse, RetrieveKnowledgeBaseData, RetrieveKnowledgeBaseError, RetrieveKnowledgeBaseResponse, GetKnowledgeTaskData, GetKnowledgeTaskError, GetKnowledgeTaskResponse, GetPersonaTreeError, GetPersonaTreeResponse, ListPersonasData, ListPersonasError, ListPersonasResponse, CreatePersonaData, CreatePersonaError, CreatePersonaResponse, GetPersonaByIdData, GetPersonaByIdError, GetPersonaByIdResponse, UpdatePersonaByIdData, UpdatePersonaByIdError, UpdatePersonaByIdResponse, DeletePersonaByIdData, DeletePersonaByIdError, DeletePersonaByIdResponse, GetPersonaData, GetPersonaError, GetPersonaResponse, UpdatePersonaData, UpdatePersonaError, UpdatePersonaResponse, DeletePersonaData, DeletePersonaError, DeletePersonaResponse, ListPersonaFoldersData, ListPersonaFoldersError, ListPersonaFoldersResponse, CreatePersonaFolderData, CreatePersonaFolderError, CreatePersonaFolderResponse, UpdatePersonaFolderData, UpdatePersonaFolderError, UpdatePersonaFolderResponse, DeletePersonaFolderData, DeletePersonaFolderError, DeletePersonaFolderResponse, MovePersonaItemData, MovePersonaItemError, MovePersonaItemResponse, ReorderPersonaItemsData, ReorderPersonaItemsError, ReorderPersonaItemsResponse, ListSessionsData, ListSessionsError, ListSessionsResponse, ListActiveUmosError, ListActiveUmosResponse, ListSessionRulesData, ListSessionRulesError, ListSessionRulesResponse, UpsertSessionRuleData, UpsertSessionRuleError, UpsertSessionRuleResponse, DeleteSessionRulesData, DeleteSessionRulesError, DeleteSessionRulesResponse, BatchUpdateSessionProviderData, BatchUpdateSessionProviderError, BatchUpdateSessionProviderResponse, BatchUpdateSessionServiceData, BatchUpdateSessionServiceError, BatchUpdateSessionServiceResponse, ListSessionGroupsError, ListSessionGroupsResponse, CreateSessionGroupData, CreateSessionGroupError, CreateSessionGroupResponse, UpdateSessionGroupData, UpdateSessionGroupError, UpdateSessionGroupResponse, DeleteSessionGroupData, DeleteSessionGroupError, DeleteSessionGroupResponse, ListConversationsData, ListConversationsError, ListConversationsResponse, BatchDeleteConversationsData, BatchDeleteConversationsError, BatchDeleteConversationsResponse, GetConversationData, GetConversationError, GetConversationResponse, UpdateConversationData, UpdateConversationError, UpdateConversationResponse, DeleteConversationData, DeleteConversationError, DeleteConversationResponse, ReplaceConversationMessagesData, ReplaceConversationMessagesError, ReplaceConversationMessagesResponse, ExportConversationsData, ExportConversationsError, ExportConversationsResponse, GetStatsData, GetStatsError, GetStatsResponse, GetProviderTokenStatsData, GetProviderTokenStatsError, GetProviderTokenStatsResponse, GetVersionError, GetVersionResponse, GetFirstNoticeData, GetFirstNoticeError, GetFirstNoticeResponse, TestGhproxyConnectionData, TestGhproxyConnectionError, TestGhproxyConnectionResponse, ListChangelogVersionsError, ListChangelogVersionsResponse, GetChangelogData, GetChangelogError, GetChangelogResponse, GetStartTimeError, GetStartTimeResponse, GetStorageStatusError, GetStorageStatusResponse, CleanupStorageData, CleanupStorageError, CleanupStorageResponse, RestartCoreError, RestartCoreResponse, ListBackupsData, ListBackupsError, ListBackupsResponse, CreateBackupData, CreateBackupError, CreateBackupResponse, UploadBackupData, UploadBackupError, UploadBackupResponse, InitBackupUploadData, InitBackupUploadError, InitBackupUploadResponse, UploadBackupChunkData, UploadBackupChunkError, UploadBackupChunkResponse, CompleteBackupUploadData, CompleteBackupUploadError, CompleteBackupUploadResponse, AbortBackupUploadData, AbortBackupUploadError, AbortBackupUploadResponse, GetBackupProgressData, GetBackupProgressError, GetBackupProgressResponse, DownloadBackupData, DownloadBackupError, DownloadBackupResponse, RenameBackupData, RenameBackupError, RenameBackupResponse, DeleteBackupData, DeleteBackupError, DeleteBackupResponse, CheckBackupData, CheckBackupError, CheckBackupResponse, ImportBackupData, ImportBackupError, ImportBackupResponse, CheckUpdateError, CheckUpdateResponse, ListReleasesData, ListReleasesError, ListReleasesResponse, UpdateCoreData, UpdateCoreError, UpdateCoreResponse, UpdateDashboardData, UpdateDashboardError, UpdateDashboardResponse, GetUpdateProgressData, GetUpdateProgressError, GetUpdateProgressResponse, InstallPipPackageData, InstallPipPackageError, InstallPipPackageResponse, ListCronJobsData, ListCronJobsError, ListCronJobsResponse, CreateCronJobData, CreateCronJobError, CreateCronJobResponse, UpdateCronJobData, UpdateCronJobError, UpdateCronJobResponse, DeleteCronJobData, DeleteCronJobError, DeleteCronJobResponse, RunCronJobData, RunCronJobError, RunCronJobResponse, StreamLiveLogsError, StreamLiveLogsResponse, GetLogHistoryError, GetLogHistoryResponse, GetTraceSettingsError, GetTraceSettingsResponse, UpdateTraceSettingsData, UpdateTraceSettingsError, UpdateTraceSettingsResponse, ListT2iTemplatesError, ListT2iTemplatesResponse, CreateT2iTemplateData, CreateT2iTemplateError, CreateT2iTemplateResponse, GetActiveT2iTemplateError, GetActiveT2iTemplateResponse, SetActiveT2iTemplateData, SetActiveT2iTemplateError, SetActiveT2iTemplateResponse, ResetDefaultT2iTemplateError, ResetDefaultT2iTemplateResponse, GetT2iTemplateData, GetT2iTemplateError, GetT2iTemplateResponse, UpdateT2iTemplateData, UpdateT2iTemplateError, UpdateT2iTemplateResponse, DeleteT2iTemplateData, DeleteT2iTemplateError, DeleteT2iTemplateResponse, GetSubagentConfigError, GetSubagentConfigResponse, UpdateSubagentConfigData, UpdateSubagentConfigError, UpdateSubagentConfigResponse, ListSubagentAvailableToolsError, ListSubagentAvailableToolsResponse, VerifyPlatformWebhookData, VerifyPlatformWebhookError, VerifyPlatformWebhookResponse, ReceivePlatformWebhookData, ReceivePlatformWebhookError, ReceivePlatformWebhookResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -2145,6 +2145,36 @@ export const importKnowledgeDocumentFromUrl = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/api/v1/knowledge-bases/{kb_id}/documents/preview-table' + }); +}; + +/** + * Import a table file with column configuration + */ +export const importKnowledgeTable = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/api/v1/knowledge-bases/{kb_id}/documents/import-table' + }); +}; + /** * Get a knowledge base document */ diff --git a/dashboard/src/api/generated/openapi-v1/types.gen.ts b/dashboard/src/api/generated/openapi-v1/types.gen.ts index a75cf49314..f595f3a3f2 100644 --- a/dashboard/src/api/generated/openapi-v1/types.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/types.gen.ts @@ -86,6 +86,9 @@ export type ChatProjectRequest = { }; export type ChatRequest = { + /** + * Caller-declared WebChat sender/session owner. This value is used as the message sender identity and may participate in sender-ID-based command permission checks. Treat chat-scoped API keys as trusted backend credentials and map or validate usernames before accepting end-user input. + */ username?: string; session_id?: string; /** @@ -255,12 +258,15 @@ export type JsonSchema = { export type KnowledgeBaseRequest = { name: string; description?: string; + kb_type?: 'text' | 'table'; embedding_provider_id?: string; rerank_provider_id?: string; chunking?: DynamicConfig; metadata?: DynamicConfig; }; +export type kb_type = 'text' | 'table'; + export type KnowledgeDocumentImportRequest = { paths: Array<(string)>; parser?: string; @@ -282,6 +288,24 @@ export type KnowledgeRetrieveRequest = { score_threshold?: number; }; +export type KnowledgeTableImportRequest = { + file: (Blob | File); + /** + * JSON string array of column configuration objects. + */ + columns_config: string; + header_row?: number; + batch_size?: number; + tasks_limit?: number; + max_retries?: number; +}; + +export type KnowledgeTablePreviewRequest = { + file: (Blob | File); + header_row?: number; + preview_rows?: number; +}; + export type LoginRequest = { username: string; password: string; @@ -2661,6 +2685,28 @@ export type ImportKnowledgeDocumentFromUrlResponse = (SuccessEnvelope); export type ImportKnowledgeDocumentFromUrlError = unknown; +export type PreviewKnowledgeTableData = { + body: KnowledgeTablePreviewRequest; + path: { + kb_id: string; + }; +}; + +export type PreviewKnowledgeTableResponse = (SuccessEnvelope); + +export type PreviewKnowledgeTableError = unknown; + +export type ImportKnowledgeTableData = { + body: KnowledgeTableImportRequest; + path: { + kb_id: string; + }; +}; + +export type ImportKnowledgeTableResponse = (SuccessEnvelope); + +export type ImportKnowledgeTableError = unknown; + export type GetKnowledgeDocumentData = { path: { document_id: string; diff --git a/dashboard/src/api/v1.ts b/dashboard/src/api/v1.ts index b0b248de50..a30a619048 100644 --- a/dashboard/src/api/v1.ts +++ b/dashboard/src/api/v1.ts @@ -1387,6 +1387,22 @@ export const knowledgeApi = { }), ); }, + previewTable(kbId: string, formData: FormData) { + return typed( + openApiV1.previewKnowledgeTable({ + path: { kb_id: kbId }, + body: generatedFormData(formData), + }), + ); + }, + importTable(kbId: string, formData: FormData) { + return typed( + openApiV1.importKnowledgeTable({ + path: { kb_id: kbId }, + body: generatedFormData(formData), + }), + ); + }, task(taskId: string) { return typed( openApiV1.getKnowledgeTask({ path: { task_id: taskId } }), diff --git a/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json index 78a00669e3..e8ffbe4ceb 100644 --- a/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "Name", "description": "Description", "emoji": "Icon", + "type": "Type", + "typeText": "Text Knowledge Base", + "typeTable": "Table Knowledge Base", "createdAt": "Created At", "updatedAt": "Updated At", "stats": "Statistics", @@ -28,6 +31,7 @@ "documents": { "title": "Documents", "upload": "Upload Document", + "importTable": "Import Table", "empty": "No documents", "name": "Name", "type": "Type", @@ -77,6 +81,33 @@ "urlHint": "The main content will be automatically extracted from the target URL as a document. Currently supports {supported} pages. Before use, please ensure that the target web page allows crawler access.", "beta": "Beta" }, + "table": { + "title": "Import Table", + "dropzone": "Drop a csv/xls/xlsx file here or click to select", + "supportedFormats": "Supported formats: .csv, .xls, .xlsx", + "headerRow": "Header Row", + "headerRowHint": "0 means the first row is the header", + "preview": "Parse & Preview", + "previewing": "Parsing...", + "reselect": "Reselect", + "totalRows": "{count} rows in total", + "columnConfig": "Column Configuration", + "columnConfigHint": "Choose index columns for semantic retrieval and the columns returned on a hit.", + "columnName": "Column", + "indexColumn": "Index", + "returnColumn": "Return", + "selectAll": "All", + "dataPreview": "Data Preview", + "back": "Back", + "import": "Start Import", + "importing": "Importing...", + "cancel": "Cancel", + "fileRequired": "Please select a table file", + "indexRequired": "Please select at least one index column", + "previewFailed": "Failed to preview table", + "importStarted": "Importing table in the background...", + "importFailed": "Failed to import table" + }, "retrieval": { "title": "Retrieval", "subtitle": "Test the knowledge base using dense and sparse retrieval methods", diff --git a/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json b/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json index 67bb4d5717..0b3170560e 100644 --- a/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "Description", "descriptionPlaceholder": "Describe the purpose of this knowledge base...", "emojiLabel": "Icon", + "typeText": "Text Knowledge Base", + "typeTable": "Table Knowledge Base", + "typeHint": "Text knowledge bases handle unstructured content like documents and web pages; table knowledge bases treat each row as an independent unit with per-column preprocessing. The type cannot be changed after creation.", "embeddingModelLabel": "Embedding Model", "rerankModelLabel": "Rerank Model (Optional)", "providerInfo": "Provider: {id} | Dimensions: {dimensions}", diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json index 5145d5c285..32e6b3d6d2 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "Название", "description": "Описание", "emoji": "Иконка", + "type": "Тип", + "typeText": "Текстовая база знаний", + "typeTable": "Табличная база знаний", "createdAt": "Создана", "updatedAt": "Обновлена", "stats": "Статистика", @@ -28,6 +31,7 @@ "documents": { "title": "Список документов", "upload": "Загрузить", + "importTable": "Импорт таблицы", "empty": "Документов нет", "name": "Имя файла", "type": "Тип", @@ -77,6 +81,33 @@ "urlHint": "Контент будет автоматически извлечен со страницы. Убедитесь, что сайт разрешает доступ роботам.", "beta": "Бета-версия" }, + "table": { + "title": "Импорт таблицы", + "dropzone": "Перетащите файл csv/xls/xlsx сюда или нажмите для выбора", + "supportedFormats": "Форматы: .csv, .xls, .xlsx", + "headerRow": "Строка заголовка", + "headerRowHint": "0 означает, что первая строка является заголовком", + "preview": "Разобрать и просмотреть", + "previewing": "Разбор...", + "reselect": "Выбрать заново", + "totalRows": "Всего строк: {count}", + "columnConfig": "Настройка столбцов", + "columnConfigHint": "Выберите индексные столбцы для семантического поиска и столбцы для возврата.", + "columnName": "Столбец", + "indexColumn": "Индекс", + "returnColumn": "Возврат", + "selectAll": "Все", + "dataPreview": "Предпросмотр данных", + "back": "Назад", + "import": "Начать импорт", + "importing": "Импорт...", + "cancel": "Отмена", + "fileRequired": "Пожалуйста, выберите файл таблицы", + "indexRequired": "Выберите хотя бы один индексный столбец", + "previewFailed": "Не удалось разобрать таблицу", + "importStarted": "Импорт таблицы в фоновом режиме...", + "importFailed": "Ошибка импорта таблицы" + }, "retrieval": { "title": "Поиск и проверка", "subtitle": "Проверьте качество поиска (Dense & Sparse) по вашей базе знаний", diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json index 4eb99d5f06..9406c35a4d 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "Описание", "descriptionPlaceholder": "Для чего нужна эта база?", "emojiLabel": "Иконка", + "typeText": "Текстовая база знаний", + "typeTable": "Табличная база знаний", + "typeHint": "Текстовая база знаний подходит для неструктурированного контента (документы, веб-страницы); табличная база знаний рассматривает каждую строку как отдельную единицу с настройкой по столбцам. Тип нельзя изменить после создания.", "embeddingModelLabel": "Embedding модель", "rerankModelLabel": "Rerank модель (опционально)", "providerInfo": "Провайдер: {id} | Размерность: {dimensions}", diff --git a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json index 54bc60b7a7..e7d85dabab 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "名称", "description": "描述", "emoji": "图标", + "type": "类型", + "typeText": "文本知识库", + "typeTable": "表格知识库", "createdAt": "创建时间", "updatedAt": "更新时间", "stats": "统计信息", @@ -28,6 +31,7 @@ "documents": { "title": "文档列表", "upload": "上传文档", + "importTable": "导入表格", "empty": "暂无文档", "name": "文档名称", "type": "类型", @@ -77,6 +81,33 @@ "urlHint": "将自动从目标 URL 提取主要内容作为文档。目前支持 {supported} 页面,请确保目标网页允许爬虫访问。", "beta": "测试版" }, + "table": { + "title": "导入表格", + "dropzone": "拖放 csv/xls/xlsx 文件到这里或点击选择", + "supportedFormats": "支持的格式: .csv, .xls, .xlsx", + "headerRow": "表头所在行", + "headerRowHint": "0 表示第一行为表头", + "preview": "解析预览", + "previewing": "解析中...", + "reselect": "重新选择", + "totalRows": "共 {count} 行", + "columnConfig": "列配置", + "columnConfigHint": "选择用于语义检索的索引列,以及命中后返回的列。", + "columnName": "列名", + "indexColumn": "索引列", + "returnColumn": "返回列", + "selectAll": "全选", + "dataPreview": "数据预览", + "back": "上一步", + "import": "开始导入", + "importing": "导入中...", + "cancel": "取消", + "fileRequired": "请选择表格文件", + "indexRequired": "请至少选择一个索引列", + "previewFailed": "表格预览失败", + "importStarted": "正在后台导入表格...", + "importFailed": "表格导入失败" + }, "retrieval": { "title": "知识库检索", "subtitle": "使用稠密检索和稀疏检索测试知识库内容", diff --git a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json index cac88bacd1..82f2a491aa 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "描述", "descriptionPlaceholder": "简单描述这个知识库的用途...", "emojiLabel": "图标", + "typeText": "文本知识库", + "typeTable": "表格知识库", + "typeHint": "文本知识库适用于文档/网页等非结构化内容;表格知识库将每一行作为独立知识单元,支持按列预处理。类型创建后不可修改。", "embeddingModelLabel": "嵌入模型 (Embedding Model)", "rerankModelLabel": "重排序模型 (Rerank Model, 可选)", "providerInfo": "提供商: {id} | 维度: {dimensions}", diff --git a/dashboard/src/views/knowledge-base/KBDetail.vue b/dashboard/src/views/knowledge-base/KBDetail.vue index 13797939ed..fad3606f52 100644 --- a/dashboard/src/views/knowledge-base/KBDetail.vue +++ b/dashboard/src/views/knowledge-base/KBDetail.vue @@ -62,6 +62,16 @@ {{ kb.emoji || '📚' }} + + + {{ t('overview.type') }} + + {{ kb.kb_type === 'table' ? t('overview.typeTable') : t('overview.typeText') }} + + + + + diff --git a/openspec/openapi-v1.yaml b/openspec/openapi-v1.yaml index 80cbeef3b1..d968742b81 100644 --- a/openspec/openapi-v1.yaml +++ b/openspec/openapi-v1.yaml @@ -3468,6 +3468,42 @@ paths: "200": $ref: "#/components/responses/Ok" + /api/v1/knowledge-bases/{kb_id}/documents/preview-table: + post: + tags: [Knowledge Base] + summary: Preview a table file headers and sample rows + operationId: previewKnowledgeTable + x-astrbot-scope: kb + parameters: + - $ref: "#/components/parameters/KbId" + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/KnowledgeTablePreviewRequest" + responses: + "200": + $ref: "#/components/responses/Ok" + + /api/v1/knowledge-bases/{kb_id}/documents/import-table: + post: + tags: [Knowledge Base] + summary: Import a table file with column configuration + operationId: importKnowledgeTable + x-astrbot-scope: kb + parameters: + - $ref: "#/components/parameters/KbId" + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/KnowledgeTableImportRequest" + responses: + "200": + $ref: "#/components/responses/Ok" + /api/v1/knowledge-bases/{kb_id}/documents/{document_id}: get: tags: [Knowledge Base] @@ -5619,6 +5655,10 @@ components: type: string description: type: string + kb_type: + type: string + enum: [text, table] + default: text embedding_provider_id: type: string rerank_provider_id: @@ -5675,6 +5715,43 @@ components: type: number additionalProperties: false + KnowledgeTablePreviewRequest: + type: object + required: [file] + properties: + file: + type: string + format: binary + header_row: + type: integer + default: 0 + preview_rows: + type: integer + default: 20 + + KnowledgeTableImportRequest: + type: object + required: [file, columns_config] + properties: + file: + type: string + format: binary + columns_config: + type: string + description: JSON string array of column configuration objects. + header_row: + type: integer + default: 0 + batch_size: + type: integer + default: 32 + tasks_limit: + type: integer + default: 3 + max_retries: + type: integer + default: 3 + PersonaRequest: type: object required: [persona_id, system_prompt] From 753c4668fb904a4d91199b9858fae3ea42eb6a33 Mon Sep 17 00:00:00 2001 From: wxd <1363812980@qq.com> Date: Sat, 20 Jun 2026 23:00:59 +0800 Subject: [PATCH 2/4] feat: add table knowledge base with column-level preprocessing --- astrbot/core/db/vec_db/faiss_impl/vec_db.py | 20 +- astrbot/core/knowledge_base/kb_db_sqlite.py | 40 +++ astrbot/core/knowledge_base/kb_helper.py | 188 +++++++++- astrbot/core/knowledge_base/kb_mgr.py | 3 + astrbot/core/knowledge_base/models.py | 6 + .../knowledge_base/parsers/table_parser.py | 133 +++++++ astrbot/dashboard/api/knowledge_bases.py | 36 ++ astrbot/dashboard/schemas.py | 1 + .../services/knowledge_base_service.py | 236 +++++++++++++ .../src/api/generated/openapi-v1/sdk.gen.ts | 32 +- .../src/api/generated/openapi-v1/types.gen.ts | 46 +++ dashboard/src/api/v1.ts | 16 + .../en-US/features/knowledge-base/detail.json | 31 ++ .../en-US/features/knowledge-base/index.json | 3 + .../ru-RU/features/knowledge-base/detail.json | 31 ++ .../ru-RU/features/knowledge-base/index.json | 3 + .../zh-CN/features/knowledge-base/detail.json | 31 ++ .../zh-CN/features/knowledge-base/index.json | 3 + .../src/views/knowledge-base/KBDetail.vue | 10 + dashboard/src/views/knowledge-base/KBList.vue | 32 +- .../components/DocumentsTab.vue | 42 ++- .../components/TableImportDialog.vue | 333 ++++++++++++++++++ openspec/openapi-v1.yaml | 77 ++++ 23 files changed, 1339 insertions(+), 14 deletions(-) create mode 100644 astrbot/core/knowledge_base/parsers/table_parser.py create mode 100644 dashboard/src/views/knowledge-base/components/TableImportDialog.vue diff --git a/astrbot/core/db/vec_db/faiss_impl/vec_db.py b/astrbot/core/db/vec_db/faiss_impl/vec_db.py index 0474683754..801eadc694 100644 --- a/astrbot/core/db/vec_db/faiss_impl/vec_db.py +++ b/astrbot/core/db/vec_db/faiss_impl/vec_db.py @@ -65,11 +65,15 @@ async def insert_batch( tasks_limit: int = 3, max_retries: int = 3, progress_callback=None, + embedding_texts: list[str] | None = None, ) -> list[int]: """批量插入文本和其对应向量,自动生成 ID 并保持一致性。 Args: progress_callback: 进度回调函数,接收参数 (current, total) + embedding_texts: 可选的向量化文本,用于将"用于语义匹配的文本"与 + "用于存储/检索返回的文本(contents)"解耦。表格知识库使用索引列 + 文本进行向量化,但存储并返回整行文本。缺省时回退为 contents。 """ metadatas = metadatas or [{} for _ in contents] @@ -81,6 +85,20 @@ async def insert_batch( ) return [] + texts_to_embed = embedding_texts if embedding_texts is not None else contents + if len(texts_to_embed) != len(contents): + raise KnowledgeBaseUploadError( + stage="embedding", + user_message=( + f"向量化失败:用于向量化的文本数量与文本分块数量不一致" + f"(期望 {len(contents)},实际 {len(texts_to_embed)})。" + ), + details={ + "expected_contents": len(contents), + "actual_embedding_texts": len(texts_to_embed), + }, + ) + content_count = len(contents) if len(metadatas) != content_count: raise KnowledgeBaseUploadError( @@ -110,7 +128,7 @@ async def insert_batch( start = time.time() logger.debug(f"Generating embeddings for {len(contents)} contents...") vectors = await self.embedding_provider.get_embeddings_batch( - contents, + texts_to_embed, batch_size=batch_size, tasks_limit=tasks_limit, max_retries=max_retries, diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py index 6a2cb5e0a8..b77781d48e 100644 --- a/astrbot/core/knowledge_base/kb_db_sqlite.py +++ b/astrbot/core/knowledge_base/kb_db_sqlite.py @@ -167,6 +167,46 @@ async def migrate_to_v1(self) -> None: await session.commit() + async def migrate_to_v2(self) -> None: + """Run knowledge base database v2 migration. + + Adds the table knowledge base columns to existing databases that were + created before the table feature. SQLite does not support + ``ADD COLUMN IF NOT EXISTS``, so existing columns are checked via + ``PRAGMA table_info`` before issuing ``ALTER TABLE`` statements. + """ + async with self.get_db() as session: + session: AsyncSession + async with session.begin(): + kb_columns = { + row[1] + for row in ( + await session.execute( + text("PRAGMA table_info(knowledge_bases)") + ) + ).fetchall() + } + if "kb_type" not in kb_columns: + await session.execute( + text( + "ALTER TABLE knowledge_bases " + "ADD COLUMN kb_type VARCHAR(20) NOT NULL DEFAULT 'text'", + ), + ) + + doc_columns = { + row[1] + for row in ( + await session.execute(text("PRAGMA table_info(kb_documents)")) + ).fetchall() + } + if "table_schema" not in doc_columns: + await session.execute( + text("ALTER TABLE kb_documents ADD COLUMN table_schema TEXT"), + ) + + await session.commit() + async def close(self) -> None: """关闭数据库连接""" await self.engine.dispose() diff --git a/astrbot/core/knowledge_base/kb_helper.py b/astrbot/core/knowledge_base/kb_helper.py index c29e45876d..d23abcdae0 100644 --- a/astrbot/core/knowledge_base/kb_helper.py +++ b/astrbot/core/knowledge_base/kb_helper.py @@ -465,6 +465,171 @@ async def embedding_progress_callback(current, total) -> None: raise + @staticmethod + def _format_row_text(columns: list[dict], row: dict[str, str]) -> str: + """Format selected columns of a row as ``name: value`` lines. + + Args: + columns: Column descriptors to render, each with a ``name`` key. + row: Mapping of header name to cell value for one row. + + Returns: + A newline-joined ``name: value`` representation, skipping blank cells. + """ + lines = [] + for col in columns: + name = col.get("name") + if not name: + continue + value = str(row.get(name, "")).strip() + if value: + lines.append(f"{name}: {value}") + return "\n".join(lines) + + async def upload_table_document( + self, + file_name: str, + file_type: str, + headers: list[str], + rows: list[list[str]], + columns_config: list[dict], + file_size: int = 0, + batch_size: int = 32, + tasks_limit: int = 3, + max_retries: int = 3, + progress_callback=None, + ) -> KBDocument: + """Upload a structured table where each row is an independent chunk. + + Index columns are concatenated to build the embedding text (used for + semantic matching), while the full row is stored/returned and the raw + row values are kept in chunk metadata under ``row_data``. + + Args: + file_name: Original table file name. + file_type: File extension without the dot (e.g. ``csv``). + headers: Column header names aligned to ``rows``. + rows: Row values, each aligned to ``headers``. + columns_config: Per-column config items with keys ``name``, + ``is_index`` and ``is_returned``. + file_size: Original file size in bytes. + batch_size: Embedding batch size. + tasks_limit: Embedding concurrency limit. + max_retries: Embedding retry count. + progress_callback: Async callback ``(stage, current, total)``. + + Returns: + KBDocument: The created document metadata record. + + Raises: + KnowledgeBaseUploadError: If no indexable row can be produced. + """ + await self._ensure_vec_db() + doc_id = str(uuid.uuid4()) + + index_cols = [c for c in columns_config if c.get("is_index")] + if not index_cols: + raise KnowledgeBaseUploadError( + stage="validation", + user_message="表格导入失败:至少需要选择一个索引列用于语义检索。", + details={"file_name": file_name}, + ) + returned_cols = [c for c in columns_config if c.get("is_returned")] or [ + {"name": h} for h in headers + ] + + if progress_callback: + await progress_callback("parsing", 100, 100) + + contents: list[str] = [] + embedding_texts: list[str] = [] + metadatas: list[dict] = [] + for idx, row_values in enumerate(rows): + row = { + h: (row_values[i] if i < len(row_values) else "") + for i, h in enumerate(headers) + } + embedding_text = self._format_row_text(index_cols, row) + if not embedding_text: + # Skip rows whose index columns are all empty. + continue + content_text = self._format_row_text(returned_cols, row) or embedding_text + contents.append(content_text) + embedding_texts.append(embedding_text) + metadatas.append( + { + "kb_id": self.kb.kb_id, + "kb_doc_id": doc_id, + "chunk_index": len(contents) - 1, + "row_index": idx, + "row_data": row, + "is_table_row": True, + }, + ) + + if not contents: + raise KnowledgeBaseUploadError( + stage="validation", + user_message="表格导入失败:所选索引列在所有行中均为空,没有可索引的数据。", + details={"file_name": file_name}, + ) + + if progress_callback: + await progress_callback("chunking", 100, 100) + + async def embedding_progress_callback(current, total) -> None: + if progress_callback: + await progress_callback("embedding", current, total) + + try: + await self.vec_db.insert_batch( + contents=contents, + metadatas=metadatas, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + progress_callback=embedding_progress_callback, + embedding_texts=embedding_texts, + ) + except KnowledgeBaseUploadError: + raise + except Exception as exc: + raise KnowledgeBaseUploadError( + stage="storage", + user_message="存储失败:表格行已生成,但写入知识库索引时出错。", + details={"file_name": file_name}, + ) from exc + + doc = KBDocument( + doc_id=doc_id, + kb_id=self.kb.kb_id, + doc_name=file_name, + file_type=file_type, + file_size=file_size, + file_path="", + table_schema=json.dumps(columns_config, ensure_ascii=False), + chunk_count=len(contents), + media_count=0, + ) + try: + async with self.kb_db.get_db() as session: + async with session.begin(): + session.add(doc) + await session.commit() + await session.refresh(doc) + except Exception as exc: + raise KnowledgeBaseUploadError( + stage="metadata", + user_message="元数据保存失败:表格行已写入知识库,但文档记录保存失败。", + details={"file_name": file_name, "doc_id": doc_id}, + ) from exc + + vec_db: FaissVecDB = self.vec_db # type: ignore + await self.kb_db.update_kb_stats(kb_id=self.kb.kb_id, vec_db=vec_db) + await self.refresh_kb() + await self.refresh_document(doc_id) + return doc + async def list_documents( self, offset: int = 0, @@ -537,16 +702,19 @@ async def get_chunks_by_doc_id( result = [] for chunk in chunks: chunk_md = json.loads(chunk["metadata"]) - result.append( - { - "chunk_id": chunk["doc_id"], - "doc_id": chunk_md["kb_doc_id"], - "kb_id": chunk_md["kb_id"], - "chunk_index": chunk_md["chunk_index"], - "content": chunk["text"], - "char_count": len(chunk["text"]), - }, - ) + item = { + "chunk_id": chunk["doc_id"], + "doc_id": chunk_md["kb_doc_id"], + "kb_id": chunk_md["kb_id"], + "chunk_index": chunk_md["chunk_index"], + "content": chunk["text"], + "char_count": len(chunk["text"]), + } + if chunk_md.get("is_table_row"): + item["is_table_row"] = True + item["row_index"] = chunk_md.get("row_index") + item["row_data"] = chunk_md.get("row_data") + result.append(item) return result async def get_chunk_count_by_doc_id(self, doc_id: str) -> int: diff --git a/astrbot/core/knowledge_base/kb_mgr.py b/astrbot/core/knowledge_base/kb_mgr.py index 3285d42c79..30623a3496 100644 --- a/astrbot/core/knowledge_base/kb_mgr.py +++ b/astrbot/core/knowledge_base/kb_mgr.py @@ -59,6 +59,7 @@ async def _init_kb_database(self) -> None: self.kb_db = KBSQLiteDatabase(DB_PATH.as_posix()) await self.kb_db.initialize() await self.kb_db.migrate_to_v1() + await self.kb_db.migrate_to_v2() logger.info(f"KnowledgeBase database initialized: {DB_PATH}") async def load_kbs(self) -> None: @@ -94,6 +95,7 @@ async def create_kb( top_k_dense: int | None = None, top_k_sparse: int | None = None, top_m_final: int | None = None, + kb_type: str | None = None, ) -> KBHelper: """创建新的知识库实例""" if embedding_provider_id is None: @@ -102,6 +104,7 @@ async def create_kb( kb_name=kb_name, description=description, emoji=emoji or "📚", + kb_type=kb_type or "text", embedding_provider_id=embedding_provider_id, rerank_provider_id=rerank_provider_id, chunk_size=chunk_size if chunk_size is not None else 512, diff --git a/astrbot/core/knowledge_base/models.py b/astrbot/core/knowledge_base/models.py index da919a384a..18a791ed66 100644 --- a/astrbot/core/knowledge_base/models.py +++ b/astrbot/core/knowledge_base/models.py @@ -31,6 +31,9 @@ class KnowledgeBase(BaseKBModel, table=True): kb_name: str = Field(max_length=100, nullable=False) description: str | None = Field(default=None, sa_type=Text) emoji: str | None = Field(default="📚", max_length=10) + # Knowledge base type: "text" (unstructured documents) or "table" + # (structured row-level data, Coze-like table knowledge base). + kb_type: str = Field(default="text", max_length=20, nullable=False) embedding_provider_id: str | None = Field(default=None, max_length=100) rerank_provider_id: str | None = Field(default=None, max_length=100) # 分块配置参数 @@ -81,6 +84,9 @@ class KBDocument(BaseKBModel, table=True): file_type: str = Field(max_length=20, nullable=False) file_size: int = Field(nullable=False) file_path: str = Field(max_length=512, nullable=False) + # JSON column configuration for table documents (None for text documents). + # Stores the per-document column schema chosen during table preprocessing. + table_schema: str | None = Field(default=None, sa_type=Text) chunk_count: int = Field(default=0, nullable=False) media_count: int = Field(default=0, nullable=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/astrbot/core/knowledge_base/parsers/table_parser.py b/astrbot/core/knowledge_base/parsers/table_parser.py new file mode 100644 index 0000000000..cade6d616a --- /dev/null +++ b/astrbot/core/knowledge_base/parsers/table_parser.py @@ -0,0 +1,133 @@ +"""Structured table parser for the table knowledge base. + +Unlike the document parsers that flatten a spreadsheet into a single Markdown +text blob, this parser keeps the row/column structure so each row can be turned +into an independent knowledge unit (Coze-like table knowledge base). +""" + +import io +from dataclasses import dataclass +from pathlib import Path + +import pandas as pd + +SUPPORTED_TABLE_EXTS = {".csv", ".xlsx", ".xls"} + + +@dataclass +class TableParseResult: + """Structured parse result for a table file. + + Attributes: + headers: Column header names (de-duplicated, never empty strings). + rows: Row data, each row is a list of cell values aligned to ``headers``. + """ + + headers: list[str] + rows: list[list[str]] + + +def is_table_file(file_name: str) -> bool: + """Return whether the given file name is a supported table format.""" + return Path(file_name).suffix.lower() in SUPPORTED_TABLE_EXTS + + +def _normalize_headers(raw_headers: list[object]) -> list[str]: + """Build clean, unique header names from raw header cells. + + Args: + raw_headers: Raw header values parsed from the file. + + Returns: + A list of non-empty, de-duplicated header strings. + """ + headers: list[str] = [] + seen: dict[str, int] = {} + for idx, raw in enumerate(raw_headers): + name = str(raw).strip() if raw is not None else "" + if not name or name.lower().startswith("unnamed:"): + name = f"column_{idx + 1}" + if name in seen: + seen[name] += 1 + name = f"{name}_{seen[name]}" + else: + seen[name] = 0 + headers.append(name) + return headers + + +def _read_dataframe( + file_content: bytes, + file_name: str, + header_row: int, +) -> "pd.DataFrame": + """Read a table file into a DataFrame with all cells as strings. + + Args: + file_content: Raw file bytes. + file_name: Original file name used to infer the format. + header_row: 0-based index of the row that holds the column headers. + + Returns: + A pandas DataFrame where every cell is a string and blanks are "". + + Raises: + ValueError: If the file extension is not a supported table format. + """ + ext = Path(file_name).suffix.lower() + if ext not in SUPPORTED_TABLE_EXTS: + raise ValueError(f"暂时不支持的表格格式: {ext}") + + if ext == ".csv": + # Try common encodings so Chinese spreadsheets exported from Excel work. + last_error: Exception | None = None + for encoding in ("utf-8-sig", "utf-8", "gbk", "latin-1"): + try: + return pd.read_csv( + io.BytesIO(file_content), + header=header_row, + dtype=str, + keep_default_na=False, + encoding=encoding, + ) + except (UnicodeDecodeError, ValueError) as exc: + last_error = exc + continue + raise ValueError(f"无法解析 CSV 文件,可能是编码问题: {last_error}") + + return pd.read_excel( + io.BytesIO(file_content), + header=header_row, + dtype=str, + keep_default_na=False, + ) + + +def parse_table( + file_content: bytes, + file_name: str, + header_row: int = 0, +) -> TableParseResult: + """Parse a csv/xls/xlsx file into structured headers and rows. + + Args: + file_content: Raw file bytes. + file_name: Original file name used to infer the format. + header_row: 0-based index of the row that holds the column headers. + + Returns: + TableParseResult: Parsed headers and row values. + + Raises: + ValueError: If the file format is unsupported or no columns are found. + """ + df = _read_dataframe(file_content, file_name, header_row) + if df.shape[1] == 0: + raise ValueError("未能从表格中解析出任何列。") + + headers = _normalize_headers(list(df.columns)) + rows: list[list[str]] = [] + for record in df.itertuples(index=False, name=None): + rows.append(["" if cell is None else str(cell).strip() for cell in record]) + + return TableParseResult(headers=headers, rows=rows) diff --git a/astrbot/dashboard/api/knowledge_bases.py b/astrbot/dashboard/api/knowledge_bases.py index c6f62235dd..03576ffe58 100644 --- a/astrbot/dashboard/api/knowledge_bases.py +++ b/astrbot/dashboard/api/knowledge_bases.py @@ -205,6 +205,42 @@ async def _operation(): return await _run(_operation, prefix="上传文档失败") +@router.post("/knowledge-bases/{kb_id}/documents/preview-table") +async def preview_knowledge_base_table( + kb_id: str, + request: Request, + _auth: AuthContext = Depends(require_kb_scope), + service: KnowledgeBaseService = Depends(get_service), +): + async def _operation(): + form_data, files = await multipart_parts(request, extra_form={"kb_id": kb_id}) + return await service.preview_table( + content_type=request.headers.get("content-type"), + form_data=form_data, + files=files, + ) + + return await _run(_operation, prefix="表格预览失败") + + +@router.post("/knowledge-bases/{kb_id}/documents/import-table") +async def import_knowledge_base_table( + kb_id: str, + request: Request, + _auth: AuthContext = Depends(require_kb_scope), + service: KnowledgeBaseService = Depends(get_service), +): + async def _operation(): + form_data, files = await multipart_parts(request, extra_form={"kb_id": kb_id}) + return await service.import_table( + content_type=request.headers.get("content-type"), + form_data=form_data, + files=files, + ) + + return await _run(_operation, prefix="表格导入失败") + + @router.post("/knowledge-bases/{kb_id}/documents/import") async def import_knowledge_base_documents( kb_id: str, diff --git a/astrbot/dashboard/schemas.py b/astrbot/dashboard/schemas.py index 773ed6cd87..607dad8f7a 100644 --- a/astrbot/dashboard/schemas.py +++ b/astrbot/dashboard/schemas.py @@ -208,6 +208,7 @@ class KnowledgeBaseRequest(OpenModel): kb_id: str | None = None name: str | None = None description: str | None = None + kb_type: str | None = None embedding_provider_id: str | None = None rerank_provider_id: str | None = None chunk_size: int | None = None diff --git a/astrbot/dashboard/services/knowledge_base_service.py b/astrbot/dashboard/services/knowledge_base_service.py index ad2121c9aa..c9a2eaa24b 100644 --- a/astrbot/dashboard/services/knowledge_base_service.py +++ b/astrbot/dashboard/services/knowledge_base_service.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import json import traceback import uuid from pathlib import Path @@ -329,6 +330,10 @@ async def create_kb(self, data: object) -> tuple[dict[str, Any], str]: f"测试重排序模型失败: {exc!s},请检查平台日志输出。" ) from exc + kb_type = payload.get("kb_type") or "text" + if kb_type not in ("text", "table"): + raise KnowledgeBaseServiceError(f"不支持的知识库类型: {kb_type}") + kb_helper = await kb_manager.create_kb( kb_name=kb_name, description=payload.get("description"), @@ -340,6 +345,7 @@ async def create_kb(self, data: object) -> tuple[dict[str, Any], str]: top_k_dense=payload.get("top_k_dense"), top_k_sparse=payload.get("top_k_sparse"), top_m_final=payload.get("top_m_final"), + kb_type=kb_type, ) return kb_helper.kb.model_dump(), "创建知识库成功" @@ -602,6 +608,236 @@ async def import_documents(self, data: object) -> dict[str, Any]: "message": "import task created, processing in background", } + @staticmethod + def _read_single_upload(form_data, files) -> tuple[str, bytes]: + """Read the first uploaded file from a multipart request into memory. + + Args: + form_data: Parsed multipart form fields. + files: Parsed multipart file fields. + + Returns: + A tuple of ``(file_name, file_content)``. + + Raises: + KnowledgeBaseServiceError: If no file is present. + """ + file_list = [] + for key in files.keys(): + if key == "file" or key.startswith("file") or key == "files[]": + file_list.extend(files.getlist(key)) + if not file_list: + raise KnowledgeBaseServiceError("缺少文件") + return file_list[0] + + async def preview_table( + self, + *, + content_type: str | None, + form_data, + files, + ) -> dict[str, Any]: + """Parse an uploaded table file and return headers plus sample rows. + + Args: + content_type: Request content type header. + form_data: Parsed multipart form fields (kb_id, header_row, ...). + files: Parsed multipart file fields. + + Returns: + A dict with ``headers``, sample ``rows`` and ``total_rows``. + + Raises: + KnowledgeBaseServiceError: On validation or parse failure. + """ + if content_type and "multipart/form-data" not in content_type: + raise KnowledgeBaseServiceError("Content-Type 须为 multipart/form-data") + if not form_data.get("kb_id"): + raise KnowledgeBaseServiceError("缺少参数 kb_id") + header_row = int(form_data.get("header_row", 0)) + preview_rows = int(form_data.get("preview_rows", 20)) + + file = self._read_single_upload(form_data, files) + file_name = file.filename + temp_file_path = ( + Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" + ) + await file.save(temp_file_path) + try: + async with aiofiles.open(temp_file_path, "rb") as file_obj: + file_content = await file_obj.read() + finally: + temp_file_path.unlink(missing_ok=True) + + from astrbot.core.knowledge_base.parsers.table_parser import ( + is_table_file, + parse_table, + ) + + if not is_table_file(file_name): + raise KnowledgeBaseServiceError( + "不支持的表格格式,请上传 csv/xls/xlsx 文件", + ) + try: + result = parse_table(file_content, file_name, header_row=header_row) + except Exception as exc: + raise KnowledgeBaseServiceError(f"解析表格失败: {exc!s}") from exc + + return { + "file_name": file_name, + "headers": result.headers, + "rows": result.rows[:preview_rows], + "total_rows": len(result.rows), + } + + async def import_table( + self, + *, + content_type: str | None, + form_data, + files, + ) -> dict[str, Any]: + """Start a background task to import a table with column configuration. + + Args: + content_type: Request content type header. + form_data: Parsed multipart form fields (kb_id, header_row, + columns_config JSON, batch params). + files: Parsed multipart file fields. + + Returns: + A dict with the background ``task_id``. + + Raises: + KnowledgeBaseServiceError: On validation failure. + """ + if content_type and "multipart/form-data" not in content_type: + raise KnowledgeBaseServiceError("Content-Type 须为 multipart/form-data") + kb_id = form_data.get("kb_id") + if not kb_id: + raise KnowledgeBaseServiceError("缺少参数 kb_id") + header_row = int(form_data.get("header_row", 0)) + batch_size = int(form_data.get("batch_size", 32)) + tasks_limit = int(form_data.get("tasks_limit", 3)) + max_retries = int(form_data.get("max_retries", 3)) + + columns_config_raw = form_data.get("columns_config") + if not columns_config_raw: + raise KnowledgeBaseServiceError("缺少参数 columns_config") + try: + columns_config = json.loads(columns_config_raw) + except (TypeError, ValueError) as exc: + raise KnowledgeBaseServiceError("columns_config 格式错误") from exc + if not isinstance(columns_config, list) or not columns_config: + raise KnowledgeBaseServiceError("columns_config 必须是非空列表") + if not any(c.get("is_index") for c in columns_config): + raise KnowledgeBaseServiceError("至少需要选择一个索引列") + + file = self._read_single_upload(form_data, files) + file_name = file.filename + temp_file_path = ( + Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" + ) + await file.save(temp_file_path) + try: + async with aiofiles.open(temp_file_path, "rb") as file_obj: + file_content = await file_obj.read() + finally: + temp_file_path.unlink(missing_ok=True) + file_type = file_name.rsplit(".", 1)[-1].lower() if "." in file_name else "" + file_size = len(file_content) + + kb_helper = await self.get_kb_manager().get_kb(kb_id) + if not kb_helper: + raise KnowledgeBaseServiceError("知识库不存在") + + task_id = str(uuid.uuid4()) + self.init_task(task_id, status="pending") + asyncio.create_task( + self.background_table_import_task( + task_id=task_id, + kb_helper=kb_helper, + file_name=file_name, + file_type=file_type, + file_content=file_content, + file_size=file_size, + columns_config=columns_config, + header_row=header_row, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + ), + ) + return { + "task_id": task_id, + "file_count": 1, + "message": "table import task created, processing in background", + } + + async def background_table_import_task( + self, + task_id: str, + kb_helper, + file_name: str, + file_type: str, + file_content: bytes, + file_size: int, + columns_config: list[dict[str, Any]], + header_row: int, + batch_size: int, + tasks_limit: int, + max_retries: int, + ) -> None: + """Parse a table file and import every row as a chunk in the background.""" + try: + self.init_task(task_id, status="processing") + self.upload_progress[task_id] = { + "status": "processing", + "file_index": 0, + "file_total": 1, + "file_name": file_name, + "stage": "parsing", + "current": 0, + "total": 100, + } + progress_callback = self.make_progress_callback(task_id, 0, file_name) + + from astrbot.core.knowledge_base.parsers.table_parser import parse_table + + result = parse_table(file_content, file_name, header_row=header_row) + doc = await kb_helper.upload_table_document( + file_name=file_name, + file_type=file_type, + headers=result.headers, + rows=result.rows, + columns_config=columns_config, + file_size=file_size, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + progress_callback=progress_callback, + ) + self.set_task_result( + task_id, + "completed", + result={ + "task_id": task_id, + "uploaded": [doc.model_dump()], + "failed": [], + "total": 1, + "success_count": 1, + "failed_count": 0, + }, + ) + except Exception as exc: + logger.error(f"后台表格导入任务 {task_id} 失败: {exc}") + logger.error(traceback.format_exc()) + self.set_task_result( + task_id, + "failed", + error=self.format_failed_doc_error(file_name, exc), + ) + def get_upload_progress(self, task_id: str | None) -> dict[str, Any]: if not task_id: raise KnowledgeBaseServiceError("缺少参数 task_id") diff --git a/dashboard/src/api/generated/openapi-v1/sdk.gen.ts b/dashboard/src/api/generated/openapi-v1/sdk.gen.ts index 39eca2f6fd..e26e60c848 100644 --- a/dashboard/src/api/generated/openapi-v1/sdk.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type OptionsLegacyParser, formDataBodySerializer } from '@hey-api/client-axios'; -import type { LoginData, LoginError, LoginResponse, LogoutError, LogoutResponse, GetAuthSetupStatusError, GetAuthSetupStatusResponse, SetupAuthData, SetupAuthError, SetupAuthResponse, SetupTotpData, SetupTotpError, SetupTotpResponse, RecoverTotpError, RecoverTotpResponse, UpdateAuthAccountData, UpdateAuthAccountError, UpdateAuthAccountResponse, ListApiKeysError, ListApiKeysResponse, CreateApiKeyData, CreateApiKeyError, CreateApiKeyResponse, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyResponse, DeleteApiKeyData, DeleteApiKeyError, DeleteApiKeyResponse, GetSystemConfigSchemaError, GetSystemConfigSchemaResponse, GetSystemConfigError, GetSystemConfigResponse, UpdateSystemConfigData, UpdateSystemConfigError, UpdateSystemConfigResponse, GetSystemConfigRuntimeError, GetSystemConfigRuntimeResponse, GetConfigProfileSchemaError, GetConfigProfileSchemaResponse, ListConfigProfilesError, ListConfigProfilesResponse, CreateConfigProfileData, CreateConfigProfileError, CreateConfigProfileResponse, GetConfigProfileData, GetConfigProfileError, GetConfigProfileResponse, UpdateConfigProfileContentData, UpdateConfigProfileContentError, UpdateConfigProfileContentResponse, RenameConfigProfileData, RenameConfigProfileError, RenameConfigProfileResponse, DeleteConfigProfileData, DeleteConfigProfileError, DeleteConfigProfileResponse, ListConfigRoutesError, ListConfigRoutesResponse, ReplaceConfigRoutesData, ReplaceConfigRoutesError, ReplaceConfigRoutesResponse, UpsertConfigRouteData, UpsertConfigRouteError, UpsertConfigRouteResponse, DeleteConfigRouteData, DeleteConfigRouteError, DeleteConfigRouteResponse, ListBotTypesError, ListBotTypesResponse, RegisterBotTypeData, RegisterBotTypeError, RegisterBotTypeResponse, ListBotsData, ListBotsError, ListBotsResponse, CreateBotData, CreateBotError, CreateBotResponse, ListBotStatsError, ListBotStatsResponse, GetBotByIdData, GetBotByIdError, GetBotByIdResponse, UpdateBotByIdData, UpdateBotByIdError, UpdateBotByIdResponse, DeleteBotByIdData, DeleteBotByIdError, DeleteBotByIdResponse, SetBotEnabledByIdData, SetBotEnabledByIdError, SetBotEnabledByIdResponse, TestBotByIdData, TestBotByIdError, TestBotByIdResponse, GetBotData, GetBotError, GetBotResponse, UpdateBotData, UpdateBotError, UpdateBotResponse, DeleteBotData, DeleteBotError, DeleteBotResponse, SetBotEnabledData, SetBotEnabledError, SetBotEnabledResponse, TestBotData, TestBotError, TestBotResponse, GetProviderSchemaError, GetProviderSchemaResponse, ListProviderSourcesError, ListProviderSourcesResponse, CreateProviderSourceData, CreateProviderSourceError, CreateProviderSourceResponse, GetProviderSourceByIdData, GetProviderSourceByIdError, GetProviderSourceByIdResponse, UpsertProviderSourceByIdData, UpsertProviderSourceByIdError, UpsertProviderSourceByIdResponse, DeleteProviderSourceByIdData, DeleteProviderSourceByIdError, DeleteProviderSourceByIdResponse, ListProviderSourceModelsByIdData, ListProviderSourceModelsByIdError, ListProviderSourceModelsByIdResponse, ListProvidersBySourceIdData, ListProvidersBySourceIdError, ListProvidersBySourceIdResponse, CreateProviderInSourceByIdData, CreateProviderInSourceByIdError, CreateProviderInSourceByIdResponse, GetProviderSourceData, GetProviderSourceError, GetProviderSourceResponse, UpsertProviderSourceData, UpsertProviderSourceError, UpsertProviderSourceResponse, DeleteProviderSourceData, DeleteProviderSourceError, DeleteProviderSourceResponse, ListProviderSourceModelsData, ListProviderSourceModelsError, ListProviderSourceModelsResponse, ListProvidersBySourceData, ListProvidersBySourceError, ListProvidersBySourceResponse, CreateProviderInSourceData, CreateProviderInSourceError, CreateProviderInSourceResponse, ListProvidersData, ListProvidersError, ListProvidersResponse, CreateProviderData, CreateProviderError, CreateProviderResponse, GetProviderByIdData, GetProviderByIdError, GetProviderByIdResponse, UpdateProviderByIdData, UpdateProviderByIdError, UpdateProviderByIdResponse, DeleteProviderByIdData, DeleteProviderByIdError, DeleteProviderByIdResponse, SetProviderEnabledByIdData, SetProviderEnabledByIdError, SetProviderEnabledByIdResponse, TestProviderByIdData, TestProviderByIdError, TestProviderByIdResponse, GetProviderEmbeddingDimensionByIdData, GetProviderEmbeddingDimensionByIdError, GetProviderEmbeddingDimensionByIdResponse, GetProviderData, GetProviderError, GetProviderResponse, UpdateProviderData, UpdateProviderError, UpdateProviderResponse, DeleteProviderData, DeleteProviderError, DeleteProviderResponse, SetProviderEnabledData, SetProviderEnabledError, SetProviderEnabledResponse, TestProviderData, TestProviderError, TestProviderResponse, GetProviderEmbeddingDimensionData, GetProviderEmbeddingDimensionError, GetProviderEmbeddingDimensionResponse, SendChatMessageData, SendChatMessageError, SendChatMessageResponse, OpenChatWebSocketData, OpenLiveChatWebSocketData, OpenUnifiedChatWebSocketData, ListChatSessionsData, ListChatSessionsError, ListChatSessionsResponse, CreateChatSessionData, CreateChatSessionError, CreateChatSessionResponse, BatchDeleteChatSessionsData, BatchDeleteChatSessionsError, BatchDeleteChatSessionsResponse, GetChatSessionData, GetChatSessionError, GetChatSessionResponse, UpdateChatSessionData, UpdateChatSessionError, UpdateChatSessionResponse, DeleteChatSessionData, DeleteChatSessionError, DeleteChatSessionResponse, StopChatSessionData, StopChatSessionError, StopChatSessionResponse, UpdateChatMessageData, UpdateChatMessageError, UpdateChatMessageResponse, RegenerateChatMessageData, RegenerateChatMessageError, RegenerateChatMessageResponse, ListChatConfigsError, ListChatConfigsResponse, CreateChatThreadData, CreateChatThreadError, CreateChatThreadResponse, GetChatThreadData, GetChatThreadError, GetChatThreadResponse, DeleteChatThreadData, DeleteChatThreadError, DeleteChatThreadResponse, SendChatThreadMessageData, SendChatThreadMessageError, SendChatThreadMessageResponse, ListChatProjectsError, ListChatProjectsResponse, CreateChatProjectData, CreateChatProjectError, CreateChatProjectResponse, GetChatProjectData, GetChatProjectError, GetChatProjectResponse, UpdateChatProjectData, UpdateChatProjectError, UpdateChatProjectResponse, DeleteChatProjectData, DeleteChatProjectError, DeleteChatProjectResponse, ListChatProjectSessionsData, ListChatProjectSessionsError, ListChatProjectSessionsResponse, AddChatProjectSessionData, AddChatProjectSessionError, AddChatProjectSessionResponse, RemoveChatProjectSessionData, RemoveChatProjectSessionError, RemoveChatProjectSessionResponse, SendImMessageData, SendImMessageError, SendImMessageResponse, ListImBotsError, ListImBotsResponse, UploadFileData, UploadFileError, UploadFileResponse, UploadOpenApiFileData, UploadOpenApiFileError, UploadOpenApiFileResponse, DownloadOpenApiFileData, DownloadOpenApiFileError, DownloadOpenApiFileResponse, GetFileByNameData, GetFileByNameError, GetFileByNameResponse, GetTokenFileData, GetTokenFileError, GetTokenFileResponse, GetAttachmentData, GetAttachmentError, GetAttachmentResponse, DeleteAttachmentData, DeleteAttachmentError, DeleteAttachmentResponse, DownloadAttachmentData, DownloadAttachmentError, DownloadAttachmentResponse, ListPluginsData, ListPluginsError, ListPluginsResponse, GetPluginByIdData, GetPluginByIdError, GetPluginByIdResponse, UninstallPluginByIdData, UninstallPluginByIdError, UninstallPluginByIdResponse, GetPluginConfigByIdData, GetPluginConfigByIdError, GetPluginConfigByIdResponse, UpdatePluginConfigByIdData, UpdatePluginConfigByIdError, UpdatePluginConfigByIdResponse, GetPluginConfigSchemaByIdData, GetPluginConfigSchemaByIdError, GetPluginConfigSchemaByIdResponse, ListPluginConfigFilesByIdData, ListPluginConfigFilesByIdError, ListPluginConfigFilesByIdResponse, UploadPluginConfigFilesByIdData, UploadPluginConfigFilesByIdError, UploadPluginConfigFilesByIdResponse, DeletePluginConfigFileByIdData, DeletePluginConfigFileByIdError, DeletePluginConfigFileByIdResponse, GetPluginReadmeByIdData, GetPluginReadmeByIdError, GetPluginReadmeByIdResponse, GetPluginChangelogByIdData, GetPluginChangelogByIdError, GetPluginChangelogByIdResponse, ReloadPluginByIdData, ReloadPluginByIdError, ReloadPluginByIdResponse, SetPluginEnabledByIdData, SetPluginEnabledByIdError, SetPluginEnabledByIdResponse, ListPluginPagesByIdData, ListPluginPagesByIdError, ListPluginPagesByIdResponse, GetPluginPageByIdData, GetPluginPageByIdError, GetPluginPageByIdResponse, GetPluginPageAssetByIdData, GetPluginPageAssetByIdError, GetPluginPageAssetByIdResponse, GetPluginData, GetPluginError, GetPluginResponse, UninstallPluginData, UninstallPluginError, UninstallPluginResponse, GetPluginConfigData, GetPluginConfigError, GetPluginConfigResponse, UpdatePluginConfigData, UpdatePluginConfigError, UpdatePluginConfigResponse, GetPluginConfigSchemaData, GetPluginConfigSchemaError, GetPluginConfigSchemaResponse, ListPluginConfigFilesData, ListPluginConfigFilesError, ListPluginConfigFilesResponse, UploadPluginConfigFilesData, UploadPluginConfigFilesError, UploadPluginConfigFilesResponse, DeletePluginConfigFileData, DeletePluginConfigFileError, DeletePluginConfigFileResponse, GetPluginReadmeData, GetPluginReadmeError, GetPluginReadmeResponse, GetPluginChangelogData, GetPluginChangelogError, GetPluginChangelogResponse, ReloadPluginData, ReloadPluginError, ReloadPluginResponse, SetPluginEnabledData, SetPluginEnabledError, SetPluginEnabledResponse, UpdatePluginData, UpdatePluginError, UpdatePluginResponse, UpdatePluginsData, UpdatePluginsError, UpdatePluginsResponse, CheckPluginVersionSupportData, CheckPluginVersionSupportError, CheckPluginVersionSupportResponse, ListFailedPluginsError, ListFailedPluginsResponse, UninstallFailedPluginData, UninstallFailedPluginError, UninstallFailedPluginResponse, ReloadFailedPluginData, ReloadFailedPluginError, ReloadFailedPluginResponse, InstallPluginFromGithubData, InstallPluginFromGithubError, InstallPluginFromGithubResponse, InstallPluginFromUrlData, InstallPluginFromUrlError, InstallPluginFromUrlResponse, InstallPluginFromUploadData, InstallPluginFromUploadError, InstallPluginFromUploadResponse, ListPluginMarketData, ListPluginMarketError, ListPluginMarketResponse, ListPluginMarketCategoriesError, ListPluginMarketCategoriesResponse, ListPluginSourcesError, ListPluginSourcesResponse, CreatePluginSourceData, CreatePluginSourceError, CreatePluginSourceResponse, ReplacePluginSourcesData, ReplacePluginSourcesError, ReplacePluginSourcesResponse, DeletePluginSourceData, DeletePluginSourceError, DeletePluginSourceResponse, DeletePluginSourceByIdData, DeletePluginSourceByIdError, DeletePluginSourceByIdResponse, ListPluginPagesData, ListPluginPagesError, ListPluginPagesResponse, GetPluginPageData, GetPluginPageError, GetPluginPageResponse, GetPluginPageAssetData, GetPluginPageAssetError, GetPluginPageAssetResponse, GetPluginPageBridgeSdkError, GetPluginPageBridgeSdkResponse, GetPluginExtensionRouteData, GetPluginExtensionRouteError, GetPluginExtensionRouteResponse, PostPluginExtensionRouteData, PostPluginExtensionRouteError, PostPluginExtensionRouteResponse, PutPluginExtensionRouteData, PutPluginExtensionRouteError, PutPluginExtensionRouteResponse, PatchPluginExtensionRouteData, PatchPluginExtensionRouteError, PatchPluginExtensionRouteResponse, DeletePluginExtensionRouteData, DeletePluginExtensionRouteError, DeletePluginExtensionRouteResponse, ListCommandsData, ListCommandsError, ListCommandsResponse, UpdateCommandData, UpdateCommandError, UpdateCommandResponse, ListCommandConflictsError, ListCommandConflictsResponse, ListToolsData, ListToolsError, ListToolsResponse, SetToolEnabledData, SetToolEnabledError, SetToolEnabledResponse, SetToolPermissionData, SetToolPermissionError, SetToolPermissionResponse, ListMcpServersError, ListMcpServersResponse, CreateMcpServerData, CreateMcpServerError, CreateMcpServerResponse, UpdateMcpServerByNameData, UpdateMcpServerByNameError, UpdateMcpServerByNameResponse, DeleteMcpServerByNameData, DeleteMcpServerByNameError, DeleteMcpServerByNameResponse, SetMcpServerEnabledByNameData, SetMcpServerEnabledByNameError, SetMcpServerEnabledByNameResponse, TestMcpServerByNameData, TestMcpServerByNameError, TestMcpServerByNameResponse, UpdateMcpServerData, UpdateMcpServerError, UpdateMcpServerResponse, DeleteMcpServerData, DeleteMcpServerError, DeleteMcpServerResponse, SetMcpServerEnabledData, SetMcpServerEnabledError, SetMcpServerEnabledResponse, TestMcpServerData, TestMcpServerError, TestMcpServerResponse, SyncModelScopeMcpServersData, SyncModelScopeMcpServersError, SyncModelScopeMcpServersResponse, ListSkillsData, ListSkillsError, ListSkillsResponse, UploadSkillData, UploadSkillError, UploadSkillResponse, UploadSkillsBatchData, UploadSkillsBatchError, UploadSkillsBatchResponse, UpdateSkillByNameData, UpdateSkillByNameError, UpdateSkillByNameResponse, DeleteSkillByNameData, DeleteSkillByNameError, DeleteSkillByNameResponse, DownloadSkillByNameData, DownloadSkillByNameError, DownloadSkillByNameResponse, ListSkillFilesByNameData, ListSkillFilesByNameError, ListSkillFilesByNameResponse, GetSkillFileByNameData, GetSkillFileByNameError, GetSkillFileByNameResponse, UpdateSkillFileByNameData, UpdateSkillFileByNameError, UpdateSkillFileByNameResponse, UpdateSkillData, UpdateSkillError, UpdateSkillResponse, DeleteSkillData, DeleteSkillError, DeleteSkillResponse, DownloadSkillData, DownloadSkillError, DownloadSkillResponse, ListSkillFilesData, ListSkillFilesError, ListSkillFilesResponse, GetSkillFileData, GetSkillFileError, GetSkillFileResponse, UpdateSkillFileData, UpdateSkillFileError, UpdateSkillFileResponse, ListNeoSkillCandidatesData, ListNeoSkillCandidatesError, ListNeoSkillCandidatesResponse, ListNeoSkillReleasesData, ListNeoSkillReleasesError, ListNeoSkillReleasesResponse, GetNeoSkillPayloadData, GetNeoSkillPayloadError, GetNeoSkillPayloadResponse, EvaluateNeoSkillCandidateData, EvaluateNeoSkillCandidateError, EvaluateNeoSkillCandidateResponse, PromoteNeoSkillCandidateData, PromoteNeoSkillCandidateError, PromoteNeoSkillCandidateResponse, RollbackNeoSkillReleaseData, RollbackNeoSkillReleaseError, RollbackNeoSkillReleaseResponse, SyncNeoSkillReleaseData, SyncNeoSkillReleaseError, SyncNeoSkillReleaseResponse, DeleteNeoSkillCandidateData, DeleteNeoSkillCandidateError, DeleteNeoSkillCandidateResponse, DeleteNeoSkillReleaseData, DeleteNeoSkillReleaseError, DeleteNeoSkillReleaseResponse, ListKnowledgeBasesData, ListKnowledgeBasesError, ListKnowledgeBasesResponse, CreateKnowledgeBaseData, CreateKnowledgeBaseError, CreateKnowledgeBaseResponse, GetKnowledgeBaseData, GetKnowledgeBaseError, GetKnowledgeBaseResponse, UpdateKnowledgeBaseData, UpdateKnowledgeBaseError, UpdateKnowledgeBaseResponse, DeleteKnowledgeBaseData, DeleteKnowledgeBaseError, DeleteKnowledgeBaseResponse, GetKnowledgeBaseStatsData, GetKnowledgeBaseStatsError, GetKnowledgeBaseStatsResponse, ListKnowledgeDocumentsData, ListKnowledgeDocumentsError, ListKnowledgeDocumentsResponse, UploadKnowledgeDocumentData, UploadKnowledgeDocumentError, UploadKnowledgeDocumentResponse, ImportKnowledgeDocumentsData, ImportKnowledgeDocumentsError, ImportKnowledgeDocumentsResponse, ImportKnowledgeDocumentFromUrlData, ImportKnowledgeDocumentFromUrlError, ImportKnowledgeDocumentFromUrlResponse, GetKnowledgeDocumentData, GetKnowledgeDocumentError, GetKnowledgeDocumentResponse, DeleteKnowledgeDocumentData, DeleteKnowledgeDocumentError, DeleteKnowledgeDocumentResponse, ListKnowledgeChunksData, ListKnowledgeChunksError, ListKnowledgeChunksResponse, DeleteKnowledgeChunkData, DeleteKnowledgeChunkError, DeleteKnowledgeChunkResponse, RetrieveKnowledgeBaseData, RetrieveKnowledgeBaseError, RetrieveKnowledgeBaseResponse, GetKnowledgeTaskData, GetKnowledgeTaskError, GetKnowledgeTaskResponse, GetPersonaTreeError, GetPersonaTreeResponse, ListPersonasData, ListPersonasError, ListPersonasResponse, CreatePersonaData, CreatePersonaError, CreatePersonaResponse, GetPersonaByIdData, GetPersonaByIdError, GetPersonaByIdResponse, UpdatePersonaByIdData, UpdatePersonaByIdError, UpdatePersonaByIdResponse, DeletePersonaByIdData, DeletePersonaByIdError, DeletePersonaByIdResponse, GetPersonaData, GetPersonaError, GetPersonaResponse, UpdatePersonaData, UpdatePersonaError, UpdatePersonaResponse, DeletePersonaData, DeletePersonaError, DeletePersonaResponse, ListPersonaFoldersData, ListPersonaFoldersError, ListPersonaFoldersResponse, CreatePersonaFolderData, CreatePersonaFolderError, CreatePersonaFolderResponse, UpdatePersonaFolderData, UpdatePersonaFolderError, UpdatePersonaFolderResponse, DeletePersonaFolderData, DeletePersonaFolderError, DeletePersonaFolderResponse, MovePersonaItemData, MovePersonaItemError, MovePersonaItemResponse, ReorderPersonaItemsData, ReorderPersonaItemsError, ReorderPersonaItemsResponse, ListSessionsData, ListSessionsError, ListSessionsResponse, ListActiveUmosError, ListActiveUmosResponse, ListSessionRulesData, ListSessionRulesError, ListSessionRulesResponse, UpsertSessionRuleData, UpsertSessionRuleError, UpsertSessionRuleResponse, DeleteSessionRulesData, DeleteSessionRulesError, DeleteSessionRulesResponse, BatchUpdateSessionProviderData, BatchUpdateSessionProviderError, BatchUpdateSessionProviderResponse, BatchUpdateSessionServiceData, BatchUpdateSessionServiceError, BatchUpdateSessionServiceResponse, ListSessionGroupsError, ListSessionGroupsResponse, CreateSessionGroupData, CreateSessionGroupError, CreateSessionGroupResponse, UpdateSessionGroupData, UpdateSessionGroupError, UpdateSessionGroupResponse, DeleteSessionGroupData, DeleteSessionGroupError, DeleteSessionGroupResponse, ListConversationsData, ListConversationsError, ListConversationsResponse, BatchDeleteConversationsData, BatchDeleteConversationsError, BatchDeleteConversationsResponse, GetConversationData, GetConversationError, GetConversationResponse, UpdateConversationData, UpdateConversationError, UpdateConversationResponse, DeleteConversationData, DeleteConversationError, DeleteConversationResponse, ReplaceConversationMessagesData, ReplaceConversationMessagesError, ReplaceConversationMessagesResponse, ExportConversationsData, ExportConversationsError, ExportConversationsResponse, GetStatsData, GetStatsError, GetStatsResponse, GetProviderTokenStatsData, GetProviderTokenStatsError, GetProviderTokenStatsResponse, GetVersionError, GetVersionResponse, GetFirstNoticeData, GetFirstNoticeError, GetFirstNoticeResponse, TestGhproxyConnectionData, TestGhproxyConnectionError, TestGhproxyConnectionResponse, ListChangelogVersionsError, ListChangelogVersionsResponse, GetChangelogData, GetChangelogError, GetChangelogResponse, GetStartTimeError, GetStartTimeResponse, GetStorageStatusError, GetStorageStatusResponse, CleanupStorageData, CleanupStorageError, CleanupStorageResponse, RestartCoreError, RestartCoreResponse, ListBackupsData, ListBackupsError, ListBackupsResponse, CreateBackupData, CreateBackupError, CreateBackupResponse, UploadBackupData, UploadBackupError, UploadBackupResponse, InitBackupUploadData, InitBackupUploadError, InitBackupUploadResponse, UploadBackupChunkData, UploadBackupChunkError, UploadBackupChunkResponse, CompleteBackupUploadData, CompleteBackupUploadError, CompleteBackupUploadResponse, AbortBackupUploadData, AbortBackupUploadError, AbortBackupUploadResponse, GetBackupProgressData, GetBackupProgressError, GetBackupProgressResponse, DownloadBackupData, DownloadBackupError, DownloadBackupResponse, RenameBackupData, RenameBackupError, RenameBackupResponse, DeleteBackupData, DeleteBackupError, DeleteBackupResponse, CheckBackupData, CheckBackupError, CheckBackupResponse, ImportBackupData, ImportBackupError, ImportBackupResponse, CheckUpdateError, CheckUpdateResponse, ListReleasesData, ListReleasesError, ListReleasesResponse, UpdateCoreData, UpdateCoreError, UpdateCoreResponse, UpdateDashboardData, UpdateDashboardError, UpdateDashboardResponse, GetUpdateProgressData, GetUpdateProgressError, GetUpdateProgressResponse, InstallPipPackageData, InstallPipPackageError, InstallPipPackageResponse, ListCronJobsData, ListCronJobsError, ListCronJobsResponse, CreateCronJobData, CreateCronJobError, CreateCronJobResponse, UpdateCronJobData, UpdateCronJobError, UpdateCronJobResponse, DeleteCronJobData, DeleteCronJobError, DeleteCronJobResponse, RunCronJobData, RunCronJobError, RunCronJobResponse, StreamLiveLogsError, StreamLiveLogsResponse, GetLogHistoryError, GetLogHistoryResponse, GetTraceSettingsError, GetTraceSettingsResponse, UpdateTraceSettingsData, UpdateTraceSettingsError, UpdateTraceSettingsResponse, ListT2iTemplatesError, ListT2iTemplatesResponse, CreateT2iTemplateData, CreateT2iTemplateError, CreateT2iTemplateResponse, GetActiveT2iTemplateError, GetActiveT2iTemplateResponse, SetActiveT2iTemplateData, SetActiveT2iTemplateError, SetActiveT2iTemplateResponse, ResetDefaultT2iTemplateError, ResetDefaultT2iTemplateResponse, GetT2iTemplateData, GetT2iTemplateError, GetT2iTemplateResponse, UpdateT2iTemplateData, UpdateT2iTemplateError, UpdateT2iTemplateResponse, DeleteT2iTemplateData, DeleteT2iTemplateError, DeleteT2iTemplateResponse, GetSubagentConfigError, GetSubagentConfigResponse, UpdateSubagentConfigData, UpdateSubagentConfigError, UpdateSubagentConfigResponse, ListSubagentAvailableToolsError, ListSubagentAvailableToolsResponse, VerifyPlatformWebhookData, VerifyPlatformWebhookError, VerifyPlatformWebhookResponse, ReceivePlatformWebhookData, ReceivePlatformWebhookError, ReceivePlatformWebhookResponse } from './types.gen'; +import type { LoginData, LoginError, LoginResponse, LogoutError, LogoutResponse, GetAuthSetupStatusError, GetAuthSetupStatusResponse, SetupAuthData, SetupAuthError, SetupAuthResponse, SetupTotpData, SetupTotpError, SetupTotpResponse, RecoverTotpError, RecoverTotpResponse, UpdateAuthAccountData, UpdateAuthAccountError, UpdateAuthAccountResponse, ListApiKeysError, ListApiKeysResponse, CreateApiKeyData, CreateApiKeyError, CreateApiKeyResponse, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyResponse, DeleteApiKeyData, DeleteApiKeyError, DeleteApiKeyResponse, GetSystemConfigSchemaError, GetSystemConfigSchemaResponse, GetSystemConfigError, GetSystemConfigResponse, UpdateSystemConfigData, UpdateSystemConfigError, UpdateSystemConfigResponse, GetSystemConfigRuntimeError, GetSystemConfigRuntimeResponse, GetConfigProfileSchemaError, GetConfigProfileSchemaResponse, ListConfigProfilesError, ListConfigProfilesResponse, CreateConfigProfileData, CreateConfigProfileError, CreateConfigProfileResponse, GetConfigProfileData, GetConfigProfileError, GetConfigProfileResponse, UpdateConfigProfileContentData, UpdateConfigProfileContentError, UpdateConfigProfileContentResponse, RenameConfigProfileData, RenameConfigProfileError, RenameConfigProfileResponse, DeleteConfigProfileData, DeleteConfigProfileError, DeleteConfigProfileResponse, ListConfigRoutesError, ListConfigRoutesResponse, ReplaceConfigRoutesData, ReplaceConfigRoutesError, ReplaceConfigRoutesResponse, UpsertConfigRouteData, UpsertConfigRouteError, UpsertConfigRouteResponse, DeleteConfigRouteData, DeleteConfigRouteError, DeleteConfigRouteResponse, ListBotTypesError, ListBotTypesResponse, RegisterBotTypeData, RegisterBotTypeError, RegisterBotTypeResponse, ListBotsData, ListBotsError, ListBotsResponse, CreateBotData, CreateBotError, CreateBotResponse, ListBotStatsError, ListBotStatsResponse, GetBotByIdData, GetBotByIdError, GetBotByIdResponse, UpdateBotByIdData, UpdateBotByIdError, UpdateBotByIdResponse, DeleteBotByIdData, DeleteBotByIdError, DeleteBotByIdResponse, SetBotEnabledByIdData, SetBotEnabledByIdError, SetBotEnabledByIdResponse, TestBotByIdData, TestBotByIdError, TestBotByIdResponse, GetBotData, GetBotError, GetBotResponse, UpdateBotData, UpdateBotError, UpdateBotResponse, DeleteBotData, DeleteBotError, DeleteBotResponse, SetBotEnabledData, SetBotEnabledError, SetBotEnabledResponse, TestBotData, TestBotError, TestBotResponse, GetProviderSchemaError, GetProviderSchemaResponse, ListProviderSourcesError, ListProviderSourcesResponse, CreateProviderSourceData, CreateProviderSourceError, CreateProviderSourceResponse, GetProviderSourceByIdData, GetProviderSourceByIdError, GetProviderSourceByIdResponse, UpsertProviderSourceByIdData, UpsertProviderSourceByIdError, UpsertProviderSourceByIdResponse, DeleteProviderSourceByIdData, DeleteProviderSourceByIdError, DeleteProviderSourceByIdResponse, ListProviderSourceModelsByIdData, ListProviderSourceModelsByIdError, ListProviderSourceModelsByIdResponse, ListProvidersBySourceIdData, ListProvidersBySourceIdError, ListProvidersBySourceIdResponse, CreateProviderInSourceByIdData, CreateProviderInSourceByIdError, CreateProviderInSourceByIdResponse, GetProviderSourceData, GetProviderSourceError, GetProviderSourceResponse, UpsertProviderSourceData, UpsertProviderSourceError, UpsertProviderSourceResponse, DeleteProviderSourceData, DeleteProviderSourceError, DeleteProviderSourceResponse, ListProviderSourceModelsData, ListProviderSourceModelsError, ListProviderSourceModelsResponse, ListProvidersBySourceData, ListProvidersBySourceError, ListProvidersBySourceResponse, CreateProviderInSourceData, CreateProviderInSourceError, CreateProviderInSourceResponse, ListProvidersData, ListProvidersError, ListProvidersResponse, CreateProviderData, CreateProviderError, CreateProviderResponse, GetProviderByIdData, GetProviderByIdError, GetProviderByIdResponse, UpdateProviderByIdData, UpdateProviderByIdError, UpdateProviderByIdResponse, DeleteProviderByIdData, DeleteProviderByIdError, DeleteProviderByIdResponse, SetProviderEnabledByIdData, SetProviderEnabledByIdError, SetProviderEnabledByIdResponse, TestProviderByIdData, TestProviderByIdError, TestProviderByIdResponse, GetProviderEmbeddingDimensionByIdData, GetProviderEmbeddingDimensionByIdError, GetProviderEmbeddingDimensionByIdResponse, GetProviderData, GetProviderError, GetProviderResponse, UpdateProviderData, UpdateProviderError, UpdateProviderResponse, DeleteProviderData, DeleteProviderError, DeleteProviderResponse, SetProviderEnabledData, SetProviderEnabledError, SetProviderEnabledResponse, TestProviderData, TestProviderError, TestProviderResponse, GetProviderEmbeddingDimensionData, GetProviderEmbeddingDimensionError, GetProviderEmbeddingDimensionResponse, SendChatMessageData, SendChatMessageError, SendChatMessageResponse, OpenChatWebSocketData, OpenLiveChatWebSocketData, OpenUnifiedChatWebSocketData, ListChatSessionsData, ListChatSessionsError, ListChatSessionsResponse, CreateChatSessionData, CreateChatSessionError, CreateChatSessionResponse, BatchDeleteChatSessionsData, BatchDeleteChatSessionsError, BatchDeleteChatSessionsResponse, GetChatSessionData, GetChatSessionError, GetChatSessionResponse, UpdateChatSessionData, UpdateChatSessionError, UpdateChatSessionResponse, DeleteChatSessionData, DeleteChatSessionError, DeleteChatSessionResponse, StopChatSessionData, StopChatSessionError, StopChatSessionResponse, UpdateChatMessageData, UpdateChatMessageError, UpdateChatMessageResponse, RegenerateChatMessageData, RegenerateChatMessageError, RegenerateChatMessageResponse, ListChatConfigsError, ListChatConfigsResponse, CreateChatThreadData, CreateChatThreadError, CreateChatThreadResponse, GetChatThreadData, GetChatThreadError, GetChatThreadResponse, DeleteChatThreadData, DeleteChatThreadError, DeleteChatThreadResponse, SendChatThreadMessageData, SendChatThreadMessageError, SendChatThreadMessageResponse, ListChatProjectsError, ListChatProjectsResponse, CreateChatProjectData, CreateChatProjectError, CreateChatProjectResponse, GetChatProjectData, GetChatProjectError, GetChatProjectResponse, UpdateChatProjectData, UpdateChatProjectError, UpdateChatProjectResponse, DeleteChatProjectData, DeleteChatProjectError, DeleteChatProjectResponse, ListChatProjectSessionsData, ListChatProjectSessionsError, ListChatProjectSessionsResponse, AddChatProjectSessionData, AddChatProjectSessionError, AddChatProjectSessionResponse, RemoveChatProjectSessionData, RemoveChatProjectSessionError, RemoveChatProjectSessionResponse, SendImMessageData, SendImMessageError, SendImMessageResponse, ListImBotsError, ListImBotsResponse, UploadFileData, UploadFileError, UploadFileResponse, UploadOpenApiFileData, UploadOpenApiFileError, UploadOpenApiFileResponse, DownloadOpenApiFileData, DownloadOpenApiFileError, DownloadOpenApiFileResponse, GetFileByNameData, GetFileByNameError, GetFileByNameResponse, GetTokenFileData, GetTokenFileError, GetTokenFileResponse, GetAttachmentData, GetAttachmentError, GetAttachmentResponse, DeleteAttachmentData, DeleteAttachmentError, DeleteAttachmentResponse, DownloadAttachmentData, DownloadAttachmentError, DownloadAttachmentResponse, ListPluginsData, ListPluginsError, ListPluginsResponse, GetPluginByIdData, GetPluginByIdError, GetPluginByIdResponse, UninstallPluginByIdData, UninstallPluginByIdError, UninstallPluginByIdResponse, GetPluginConfigByIdData, GetPluginConfigByIdError, GetPluginConfigByIdResponse, UpdatePluginConfigByIdData, UpdatePluginConfigByIdError, UpdatePluginConfigByIdResponse, GetPluginConfigSchemaByIdData, GetPluginConfigSchemaByIdError, GetPluginConfigSchemaByIdResponse, ListPluginConfigFilesByIdData, ListPluginConfigFilesByIdError, ListPluginConfigFilesByIdResponse, UploadPluginConfigFilesByIdData, UploadPluginConfigFilesByIdError, UploadPluginConfigFilesByIdResponse, DeletePluginConfigFileByIdData, DeletePluginConfigFileByIdError, DeletePluginConfigFileByIdResponse, GetPluginReadmeByIdData, GetPluginReadmeByIdError, GetPluginReadmeByIdResponse, GetPluginChangelogByIdData, GetPluginChangelogByIdError, GetPluginChangelogByIdResponse, ReloadPluginByIdData, ReloadPluginByIdError, ReloadPluginByIdResponse, SetPluginEnabledByIdData, SetPluginEnabledByIdError, SetPluginEnabledByIdResponse, ListPluginPagesByIdData, ListPluginPagesByIdError, ListPluginPagesByIdResponse, GetPluginPageByIdData, GetPluginPageByIdError, GetPluginPageByIdResponse, GetPluginPageAssetByIdData, GetPluginPageAssetByIdError, GetPluginPageAssetByIdResponse, GetPluginData, GetPluginError, GetPluginResponse, UninstallPluginData, UninstallPluginError, UninstallPluginResponse, GetPluginConfigData, GetPluginConfigError, GetPluginConfigResponse, UpdatePluginConfigData, UpdatePluginConfigError, UpdatePluginConfigResponse, GetPluginConfigSchemaData, GetPluginConfigSchemaError, GetPluginConfigSchemaResponse, ListPluginConfigFilesData, ListPluginConfigFilesError, ListPluginConfigFilesResponse, UploadPluginConfigFilesData, UploadPluginConfigFilesError, UploadPluginConfigFilesResponse, DeletePluginConfigFileData, DeletePluginConfigFileError, DeletePluginConfigFileResponse, GetPluginReadmeData, GetPluginReadmeError, GetPluginReadmeResponse, GetPluginChangelogData, GetPluginChangelogError, GetPluginChangelogResponse, ReloadPluginData, ReloadPluginError, ReloadPluginResponse, SetPluginEnabledData, SetPluginEnabledError, SetPluginEnabledResponse, UpdatePluginData, UpdatePluginError, UpdatePluginResponse, UpdatePluginsData, UpdatePluginsError, UpdatePluginsResponse, CheckPluginVersionSupportData, CheckPluginVersionSupportError, CheckPluginVersionSupportResponse, ListFailedPluginsError, ListFailedPluginsResponse, UninstallFailedPluginData, UninstallFailedPluginError, UninstallFailedPluginResponse, ReloadFailedPluginData, ReloadFailedPluginError, ReloadFailedPluginResponse, InstallPluginFromGithubData, InstallPluginFromGithubError, InstallPluginFromGithubResponse, InstallPluginFromUrlData, InstallPluginFromUrlError, InstallPluginFromUrlResponse, InstallPluginFromUploadData, InstallPluginFromUploadError, InstallPluginFromUploadResponse, ListPluginMarketData, ListPluginMarketError, ListPluginMarketResponse, ListPluginMarketCategoriesError, ListPluginMarketCategoriesResponse, ListPluginSourcesError, ListPluginSourcesResponse, CreatePluginSourceData, CreatePluginSourceError, CreatePluginSourceResponse, ReplacePluginSourcesData, ReplacePluginSourcesError, ReplacePluginSourcesResponse, DeletePluginSourceData, DeletePluginSourceError, DeletePluginSourceResponse, DeletePluginSourceByIdData, DeletePluginSourceByIdError, DeletePluginSourceByIdResponse, ListPluginPagesData, ListPluginPagesError, ListPluginPagesResponse, GetPluginPageData, GetPluginPageError, GetPluginPageResponse, GetPluginPageAssetData, GetPluginPageAssetError, GetPluginPageAssetResponse, GetPluginPageBridgeSdkError, GetPluginPageBridgeSdkResponse, GetPluginExtensionRouteData, GetPluginExtensionRouteError, GetPluginExtensionRouteResponse, PostPluginExtensionRouteData, PostPluginExtensionRouteError, PostPluginExtensionRouteResponse, PutPluginExtensionRouteData, PutPluginExtensionRouteError, PutPluginExtensionRouteResponse, PatchPluginExtensionRouteData, PatchPluginExtensionRouteError, PatchPluginExtensionRouteResponse, DeletePluginExtensionRouteData, DeletePluginExtensionRouteError, DeletePluginExtensionRouteResponse, ListCommandsData, ListCommandsError, ListCommandsResponse, UpdateCommandData, UpdateCommandError, UpdateCommandResponse, ListCommandConflictsError, ListCommandConflictsResponse, ListToolsData, ListToolsError, ListToolsResponse, SetToolEnabledData, SetToolEnabledError, SetToolEnabledResponse, SetToolPermissionData, SetToolPermissionError, SetToolPermissionResponse, ListMcpServersError, ListMcpServersResponse, CreateMcpServerData, CreateMcpServerError, CreateMcpServerResponse, UpdateMcpServerByNameData, UpdateMcpServerByNameError, UpdateMcpServerByNameResponse, DeleteMcpServerByNameData, DeleteMcpServerByNameError, DeleteMcpServerByNameResponse, SetMcpServerEnabledByNameData, SetMcpServerEnabledByNameError, SetMcpServerEnabledByNameResponse, TestMcpServerByNameData, TestMcpServerByNameError, TestMcpServerByNameResponse, UpdateMcpServerData, UpdateMcpServerError, UpdateMcpServerResponse, DeleteMcpServerData, DeleteMcpServerError, DeleteMcpServerResponse, SetMcpServerEnabledData, SetMcpServerEnabledError, SetMcpServerEnabledResponse, TestMcpServerData, TestMcpServerError, TestMcpServerResponse, SyncModelScopeMcpServersData, SyncModelScopeMcpServersError, SyncModelScopeMcpServersResponse, ListSkillsData, ListSkillsError, ListSkillsResponse, UploadSkillData, UploadSkillError, UploadSkillResponse, UploadSkillsBatchData, UploadSkillsBatchError, UploadSkillsBatchResponse, UpdateSkillByNameData, UpdateSkillByNameError, UpdateSkillByNameResponse, DeleteSkillByNameData, DeleteSkillByNameError, DeleteSkillByNameResponse, DownloadSkillByNameData, DownloadSkillByNameError, DownloadSkillByNameResponse, ListSkillFilesByNameData, ListSkillFilesByNameError, ListSkillFilesByNameResponse, GetSkillFileByNameData, GetSkillFileByNameError, GetSkillFileByNameResponse, UpdateSkillFileByNameData, UpdateSkillFileByNameError, UpdateSkillFileByNameResponse, UpdateSkillData, UpdateSkillError, UpdateSkillResponse, DeleteSkillData, DeleteSkillError, DeleteSkillResponse, DownloadSkillData, DownloadSkillError, DownloadSkillResponse, ListSkillFilesData, ListSkillFilesError, ListSkillFilesResponse, GetSkillFileData, GetSkillFileError, GetSkillFileResponse, UpdateSkillFileData, UpdateSkillFileError, UpdateSkillFileResponse, ListNeoSkillCandidatesData, ListNeoSkillCandidatesError, ListNeoSkillCandidatesResponse, ListNeoSkillReleasesData, ListNeoSkillReleasesError, ListNeoSkillReleasesResponse, GetNeoSkillPayloadData, GetNeoSkillPayloadError, GetNeoSkillPayloadResponse, EvaluateNeoSkillCandidateData, EvaluateNeoSkillCandidateError, EvaluateNeoSkillCandidateResponse, PromoteNeoSkillCandidateData, PromoteNeoSkillCandidateError, PromoteNeoSkillCandidateResponse, RollbackNeoSkillReleaseData, RollbackNeoSkillReleaseError, RollbackNeoSkillReleaseResponse, SyncNeoSkillReleaseData, SyncNeoSkillReleaseError, SyncNeoSkillReleaseResponse, DeleteNeoSkillCandidateData, DeleteNeoSkillCandidateError, DeleteNeoSkillCandidateResponse, DeleteNeoSkillReleaseData, DeleteNeoSkillReleaseError, DeleteNeoSkillReleaseResponse, ListKnowledgeBasesData, ListKnowledgeBasesError, ListKnowledgeBasesResponse, CreateKnowledgeBaseData, CreateKnowledgeBaseError, CreateKnowledgeBaseResponse, GetKnowledgeBaseData, GetKnowledgeBaseError, GetKnowledgeBaseResponse, UpdateKnowledgeBaseData, UpdateKnowledgeBaseError, UpdateKnowledgeBaseResponse, DeleteKnowledgeBaseData, DeleteKnowledgeBaseError, DeleteKnowledgeBaseResponse, GetKnowledgeBaseStatsData, GetKnowledgeBaseStatsError, GetKnowledgeBaseStatsResponse, ListKnowledgeDocumentsData, ListKnowledgeDocumentsError, ListKnowledgeDocumentsResponse, UploadKnowledgeDocumentData, UploadKnowledgeDocumentError, UploadKnowledgeDocumentResponse, ImportKnowledgeDocumentsData, ImportKnowledgeDocumentsError, ImportKnowledgeDocumentsResponse, ImportKnowledgeDocumentFromUrlData, ImportKnowledgeDocumentFromUrlError, ImportKnowledgeDocumentFromUrlResponse, PreviewKnowledgeTableData, PreviewKnowledgeTableError, PreviewKnowledgeTableResponse, ImportKnowledgeTableData, ImportKnowledgeTableError, ImportKnowledgeTableResponse, GetKnowledgeDocumentData, GetKnowledgeDocumentError, GetKnowledgeDocumentResponse, DeleteKnowledgeDocumentData, DeleteKnowledgeDocumentError, DeleteKnowledgeDocumentResponse, ListKnowledgeChunksData, ListKnowledgeChunksError, ListKnowledgeChunksResponse, DeleteKnowledgeChunkData, DeleteKnowledgeChunkError, DeleteKnowledgeChunkResponse, RetrieveKnowledgeBaseData, RetrieveKnowledgeBaseError, RetrieveKnowledgeBaseResponse, GetKnowledgeTaskData, GetKnowledgeTaskError, GetKnowledgeTaskResponse, GetPersonaTreeError, GetPersonaTreeResponse, ListPersonasData, ListPersonasError, ListPersonasResponse, CreatePersonaData, CreatePersonaError, CreatePersonaResponse, GetPersonaByIdData, GetPersonaByIdError, GetPersonaByIdResponse, UpdatePersonaByIdData, UpdatePersonaByIdError, UpdatePersonaByIdResponse, DeletePersonaByIdData, DeletePersonaByIdError, DeletePersonaByIdResponse, GetPersonaData, GetPersonaError, GetPersonaResponse, UpdatePersonaData, UpdatePersonaError, UpdatePersonaResponse, DeletePersonaData, DeletePersonaError, DeletePersonaResponse, ListPersonaFoldersData, ListPersonaFoldersError, ListPersonaFoldersResponse, CreatePersonaFolderData, CreatePersonaFolderError, CreatePersonaFolderResponse, UpdatePersonaFolderData, UpdatePersonaFolderError, UpdatePersonaFolderResponse, DeletePersonaFolderData, DeletePersonaFolderError, DeletePersonaFolderResponse, MovePersonaItemData, MovePersonaItemError, MovePersonaItemResponse, ReorderPersonaItemsData, ReorderPersonaItemsError, ReorderPersonaItemsResponse, ListSessionsData, ListSessionsError, ListSessionsResponse, ListActiveUmosError, ListActiveUmosResponse, ListSessionRulesData, ListSessionRulesError, ListSessionRulesResponse, UpsertSessionRuleData, UpsertSessionRuleError, UpsertSessionRuleResponse, DeleteSessionRulesData, DeleteSessionRulesError, DeleteSessionRulesResponse, BatchUpdateSessionProviderData, BatchUpdateSessionProviderError, BatchUpdateSessionProviderResponse, BatchUpdateSessionServiceData, BatchUpdateSessionServiceError, BatchUpdateSessionServiceResponse, ListSessionGroupsError, ListSessionGroupsResponse, CreateSessionGroupData, CreateSessionGroupError, CreateSessionGroupResponse, UpdateSessionGroupData, UpdateSessionGroupError, UpdateSessionGroupResponse, DeleteSessionGroupData, DeleteSessionGroupError, DeleteSessionGroupResponse, ListConversationsData, ListConversationsError, ListConversationsResponse, BatchDeleteConversationsData, BatchDeleteConversationsError, BatchDeleteConversationsResponse, GetConversationData, GetConversationError, GetConversationResponse, UpdateConversationData, UpdateConversationError, UpdateConversationResponse, DeleteConversationData, DeleteConversationError, DeleteConversationResponse, ReplaceConversationMessagesData, ReplaceConversationMessagesError, ReplaceConversationMessagesResponse, ExportConversationsData, ExportConversationsError, ExportConversationsResponse, GetStatsData, GetStatsError, GetStatsResponse, GetProviderTokenStatsData, GetProviderTokenStatsError, GetProviderTokenStatsResponse, GetVersionError, GetVersionResponse, GetFirstNoticeData, GetFirstNoticeError, GetFirstNoticeResponse, TestGhproxyConnectionData, TestGhproxyConnectionError, TestGhproxyConnectionResponse, ListChangelogVersionsError, ListChangelogVersionsResponse, GetChangelogData, GetChangelogError, GetChangelogResponse, GetStartTimeError, GetStartTimeResponse, GetStorageStatusError, GetStorageStatusResponse, CleanupStorageData, CleanupStorageError, CleanupStorageResponse, RestartCoreError, RestartCoreResponse, ListBackupsData, ListBackupsError, ListBackupsResponse, CreateBackupData, CreateBackupError, CreateBackupResponse, UploadBackupData, UploadBackupError, UploadBackupResponse, InitBackupUploadData, InitBackupUploadError, InitBackupUploadResponse, UploadBackupChunkData, UploadBackupChunkError, UploadBackupChunkResponse, CompleteBackupUploadData, CompleteBackupUploadError, CompleteBackupUploadResponse, AbortBackupUploadData, AbortBackupUploadError, AbortBackupUploadResponse, GetBackupProgressData, GetBackupProgressError, GetBackupProgressResponse, DownloadBackupData, DownloadBackupError, DownloadBackupResponse, RenameBackupData, RenameBackupError, RenameBackupResponse, DeleteBackupData, DeleteBackupError, DeleteBackupResponse, CheckBackupData, CheckBackupError, CheckBackupResponse, ImportBackupData, ImportBackupError, ImportBackupResponse, CheckUpdateError, CheckUpdateResponse, ListReleasesData, ListReleasesError, ListReleasesResponse, UpdateCoreData, UpdateCoreError, UpdateCoreResponse, UpdateDashboardData, UpdateDashboardError, UpdateDashboardResponse, GetUpdateProgressData, GetUpdateProgressError, GetUpdateProgressResponse, InstallPipPackageData, InstallPipPackageError, InstallPipPackageResponse, ListCronJobsData, ListCronJobsError, ListCronJobsResponse, CreateCronJobData, CreateCronJobError, CreateCronJobResponse, UpdateCronJobData, UpdateCronJobError, UpdateCronJobResponse, DeleteCronJobData, DeleteCronJobError, DeleteCronJobResponse, RunCronJobData, RunCronJobError, RunCronJobResponse, StreamLiveLogsError, StreamLiveLogsResponse, GetLogHistoryError, GetLogHistoryResponse, GetTraceSettingsError, GetTraceSettingsResponse, UpdateTraceSettingsData, UpdateTraceSettingsError, UpdateTraceSettingsResponse, ListT2iTemplatesError, ListT2iTemplatesResponse, CreateT2iTemplateData, CreateT2iTemplateError, CreateT2iTemplateResponse, GetActiveT2iTemplateError, GetActiveT2iTemplateResponse, SetActiveT2iTemplateData, SetActiveT2iTemplateError, SetActiveT2iTemplateResponse, ResetDefaultT2iTemplateError, ResetDefaultT2iTemplateResponse, GetT2iTemplateData, GetT2iTemplateError, GetT2iTemplateResponse, UpdateT2iTemplateData, UpdateT2iTemplateError, UpdateT2iTemplateResponse, DeleteT2iTemplateData, DeleteT2iTemplateError, DeleteT2iTemplateResponse, GetSubagentConfigError, GetSubagentConfigResponse, UpdateSubagentConfigData, UpdateSubagentConfigError, UpdateSubagentConfigResponse, ListSubagentAvailableToolsError, ListSubagentAvailableToolsResponse, VerifyPlatformWebhookData, VerifyPlatformWebhookError, VerifyPlatformWebhookResponse, ReceivePlatformWebhookData, ReceivePlatformWebhookError, ReceivePlatformWebhookResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -2145,6 +2145,36 @@ export const importKnowledgeDocumentFromUrl = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/api/v1/knowledge-bases/{kb_id}/documents/preview-table' + }); +}; + +/** + * Import a table file with column configuration + */ +export const importKnowledgeTable = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/api/v1/knowledge-bases/{kb_id}/documents/import-table' + }); +}; + /** * Get a knowledge base document */ diff --git a/dashboard/src/api/generated/openapi-v1/types.gen.ts b/dashboard/src/api/generated/openapi-v1/types.gen.ts index a75cf49314..f595f3a3f2 100644 --- a/dashboard/src/api/generated/openapi-v1/types.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/types.gen.ts @@ -86,6 +86,9 @@ export type ChatProjectRequest = { }; export type ChatRequest = { + /** + * Caller-declared WebChat sender/session owner. This value is used as the message sender identity and may participate in sender-ID-based command permission checks. Treat chat-scoped API keys as trusted backend credentials and map or validate usernames before accepting end-user input. + */ username?: string; session_id?: string; /** @@ -255,12 +258,15 @@ export type JsonSchema = { export type KnowledgeBaseRequest = { name: string; description?: string; + kb_type?: 'text' | 'table'; embedding_provider_id?: string; rerank_provider_id?: string; chunking?: DynamicConfig; metadata?: DynamicConfig; }; +export type kb_type = 'text' | 'table'; + export type KnowledgeDocumentImportRequest = { paths: Array<(string)>; parser?: string; @@ -282,6 +288,24 @@ export type KnowledgeRetrieveRequest = { score_threshold?: number; }; +export type KnowledgeTableImportRequest = { + file: (Blob | File); + /** + * JSON string array of column configuration objects. + */ + columns_config: string; + header_row?: number; + batch_size?: number; + tasks_limit?: number; + max_retries?: number; +}; + +export type KnowledgeTablePreviewRequest = { + file: (Blob | File); + header_row?: number; + preview_rows?: number; +}; + export type LoginRequest = { username: string; password: string; @@ -2661,6 +2685,28 @@ export type ImportKnowledgeDocumentFromUrlResponse = (SuccessEnvelope); export type ImportKnowledgeDocumentFromUrlError = unknown; +export type PreviewKnowledgeTableData = { + body: KnowledgeTablePreviewRequest; + path: { + kb_id: string; + }; +}; + +export type PreviewKnowledgeTableResponse = (SuccessEnvelope); + +export type PreviewKnowledgeTableError = unknown; + +export type ImportKnowledgeTableData = { + body: KnowledgeTableImportRequest; + path: { + kb_id: string; + }; +}; + +export type ImportKnowledgeTableResponse = (SuccessEnvelope); + +export type ImportKnowledgeTableError = unknown; + export type GetKnowledgeDocumentData = { path: { document_id: string; diff --git a/dashboard/src/api/v1.ts b/dashboard/src/api/v1.ts index b0b248de50..a30a619048 100644 --- a/dashboard/src/api/v1.ts +++ b/dashboard/src/api/v1.ts @@ -1387,6 +1387,22 @@ export const knowledgeApi = { }), ); }, + previewTable(kbId: string, formData: FormData) { + return typed( + openApiV1.previewKnowledgeTable({ + path: { kb_id: kbId }, + body: generatedFormData(formData), + }), + ); + }, + importTable(kbId: string, formData: FormData) { + return typed( + openApiV1.importKnowledgeTable({ + path: { kb_id: kbId }, + body: generatedFormData(formData), + }), + ); + }, task(taskId: string) { return typed( openApiV1.getKnowledgeTask({ path: { task_id: taskId } }), diff --git a/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json index 78a00669e3..e8ffbe4ceb 100644 --- a/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/en-US/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "Name", "description": "Description", "emoji": "Icon", + "type": "Type", + "typeText": "Text Knowledge Base", + "typeTable": "Table Knowledge Base", "createdAt": "Created At", "updatedAt": "Updated At", "stats": "Statistics", @@ -28,6 +31,7 @@ "documents": { "title": "Documents", "upload": "Upload Document", + "importTable": "Import Table", "empty": "No documents", "name": "Name", "type": "Type", @@ -77,6 +81,33 @@ "urlHint": "The main content will be automatically extracted from the target URL as a document. Currently supports {supported} pages. Before use, please ensure that the target web page allows crawler access.", "beta": "Beta" }, + "table": { + "title": "Import Table", + "dropzone": "Drop a csv/xls/xlsx file here or click to select", + "supportedFormats": "Supported formats: .csv, .xls, .xlsx", + "headerRow": "Header Row", + "headerRowHint": "0 means the first row is the header", + "preview": "Parse & Preview", + "previewing": "Parsing...", + "reselect": "Reselect", + "totalRows": "{count} rows in total", + "columnConfig": "Column Configuration", + "columnConfigHint": "Choose index columns for semantic retrieval and the columns returned on a hit.", + "columnName": "Column", + "indexColumn": "Index", + "returnColumn": "Return", + "selectAll": "All", + "dataPreview": "Data Preview", + "back": "Back", + "import": "Start Import", + "importing": "Importing...", + "cancel": "Cancel", + "fileRequired": "Please select a table file", + "indexRequired": "Please select at least one index column", + "previewFailed": "Failed to preview table", + "importStarted": "Importing table in the background...", + "importFailed": "Failed to import table" + }, "retrieval": { "title": "Retrieval", "subtitle": "Test the knowledge base using dense and sparse retrieval methods", diff --git a/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json b/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json index 67bb4d5717..0b3170560e 100644 --- a/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/en-US/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "Description", "descriptionPlaceholder": "Describe the purpose of this knowledge base...", "emojiLabel": "Icon", + "typeText": "Text Knowledge Base", + "typeTable": "Table Knowledge Base", + "typeHint": "Text knowledge bases handle unstructured content like documents and web pages; table knowledge bases treat each row as an independent unit with per-column preprocessing. The type cannot be changed after creation.", "embeddingModelLabel": "Embedding Model", "rerankModelLabel": "Rerank Model (Optional)", "providerInfo": "Provider: {id} | Dimensions: {dimensions}", diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json index 5145d5c285..32e6b3d6d2 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "Название", "description": "Описание", "emoji": "Иконка", + "type": "Тип", + "typeText": "Текстовая база знаний", + "typeTable": "Табличная база знаний", "createdAt": "Создана", "updatedAt": "Обновлена", "stats": "Статистика", @@ -28,6 +31,7 @@ "documents": { "title": "Список документов", "upload": "Загрузить", + "importTable": "Импорт таблицы", "empty": "Документов нет", "name": "Имя файла", "type": "Тип", @@ -77,6 +81,33 @@ "urlHint": "Контент будет автоматически извлечен со страницы. Убедитесь, что сайт разрешает доступ роботам.", "beta": "Бета-версия" }, + "table": { + "title": "Импорт таблицы", + "dropzone": "Перетащите файл csv/xls/xlsx сюда или нажмите для выбора", + "supportedFormats": "Форматы: .csv, .xls, .xlsx", + "headerRow": "Строка заголовка", + "headerRowHint": "0 означает, что первая строка является заголовком", + "preview": "Разобрать и просмотреть", + "previewing": "Разбор...", + "reselect": "Выбрать заново", + "totalRows": "Всего строк: {count}", + "columnConfig": "Настройка столбцов", + "columnConfigHint": "Выберите индексные столбцы для семантического поиска и столбцы для возврата.", + "columnName": "Столбец", + "indexColumn": "Индекс", + "returnColumn": "Возврат", + "selectAll": "Все", + "dataPreview": "Предпросмотр данных", + "back": "Назад", + "import": "Начать импорт", + "importing": "Импорт...", + "cancel": "Отмена", + "fileRequired": "Пожалуйста, выберите файл таблицы", + "indexRequired": "Выберите хотя бы один индексный столбец", + "previewFailed": "Не удалось разобрать таблицу", + "importStarted": "Импорт таблицы в фоновом режиме...", + "importFailed": "Ошибка импорта таблицы" + }, "retrieval": { "title": "Поиск и проверка", "subtitle": "Проверьте качество поиска (Dense & Sparse) по вашей базе знаний", diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json index 4eb99d5f06..9406c35a4d 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "Описание", "descriptionPlaceholder": "Для чего нужна эта база?", "emojiLabel": "Иконка", + "typeText": "Текстовая база знаний", + "typeTable": "Табличная база знаний", + "typeHint": "Текстовая база знаний подходит для неструктурированного контента (документы, веб-страницы); табличная база знаний рассматривает каждую строку как отдельную единицу с настройкой по столбцам. Тип нельзя изменить после создания.", "embeddingModelLabel": "Embedding модель", "rerankModelLabel": "Rerank модель (опционально)", "providerInfo": "Провайдер: {id} | Размерность: {dimensions}", diff --git a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json index 54bc60b7a7..e7d85dabab 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json +++ b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/detail.json @@ -16,6 +16,9 @@ "name": "名称", "description": "描述", "emoji": "图标", + "type": "类型", + "typeText": "文本知识库", + "typeTable": "表格知识库", "createdAt": "创建时间", "updatedAt": "更新时间", "stats": "统计信息", @@ -28,6 +31,7 @@ "documents": { "title": "文档列表", "upload": "上传文档", + "importTable": "导入表格", "empty": "暂无文档", "name": "文档名称", "type": "类型", @@ -77,6 +81,33 @@ "urlHint": "将自动从目标 URL 提取主要内容作为文档。目前支持 {supported} 页面,请确保目标网页允许爬虫访问。", "beta": "测试版" }, + "table": { + "title": "导入表格", + "dropzone": "拖放 csv/xls/xlsx 文件到这里或点击选择", + "supportedFormats": "支持的格式: .csv, .xls, .xlsx", + "headerRow": "表头所在行", + "headerRowHint": "0 表示第一行为表头", + "preview": "解析预览", + "previewing": "解析中...", + "reselect": "重新选择", + "totalRows": "共 {count} 行", + "columnConfig": "列配置", + "columnConfigHint": "选择用于语义检索的索引列,以及命中后返回的列。", + "columnName": "列名", + "indexColumn": "索引列", + "returnColumn": "返回列", + "selectAll": "全选", + "dataPreview": "数据预览", + "back": "上一步", + "import": "开始导入", + "importing": "导入中...", + "cancel": "取消", + "fileRequired": "请选择表格文件", + "indexRequired": "请至少选择一个索引列", + "previewFailed": "表格预览失败", + "importStarted": "正在后台导入表格...", + "importFailed": "表格导入失败" + }, "retrieval": { "title": "知识库检索", "subtitle": "使用稠密检索和稀疏检索测试知识库内容", diff --git a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json index cac88bacd1..82f2a491aa 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json +++ b/dashboard/src/i18n/locales/zh-CN/features/knowledge-base/index.json @@ -27,6 +27,9 @@ "descriptionLabel": "描述", "descriptionPlaceholder": "简单描述这个知识库的用途...", "emojiLabel": "图标", + "typeText": "文本知识库", + "typeTable": "表格知识库", + "typeHint": "文本知识库适用于文档/网页等非结构化内容;表格知识库将每一行作为独立知识单元,支持按列预处理。类型创建后不可修改。", "embeddingModelLabel": "嵌入模型 (Embedding Model)", "rerankModelLabel": "重排序模型 (Rerank Model, 可选)", "providerInfo": "提供商: {id} | 维度: {dimensions}", diff --git a/dashboard/src/views/knowledge-base/KBDetail.vue b/dashboard/src/views/knowledge-base/KBDetail.vue index 13797939ed..fad3606f52 100644 --- a/dashboard/src/views/knowledge-base/KBDetail.vue +++ b/dashboard/src/views/knowledge-base/KBDetail.vue @@ -62,6 +62,16 @@ {{ kb.emoji || '📚' }} + + + {{ t('overview.type') }} + + {{ kb.kb_type === 'table' ? t('overview.typeTable') : t('overview.typeText') }} + + + + + diff --git a/openspec/openapi-v1.yaml b/openspec/openapi-v1.yaml index 80cbeef3b1..d968742b81 100644 --- a/openspec/openapi-v1.yaml +++ b/openspec/openapi-v1.yaml @@ -3468,6 +3468,42 @@ paths: "200": $ref: "#/components/responses/Ok" + /api/v1/knowledge-bases/{kb_id}/documents/preview-table: + post: + tags: [Knowledge Base] + summary: Preview a table file headers and sample rows + operationId: previewKnowledgeTable + x-astrbot-scope: kb + parameters: + - $ref: "#/components/parameters/KbId" + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/KnowledgeTablePreviewRequest" + responses: + "200": + $ref: "#/components/responses/Ok" + + /api/v1/knowledge-bases/{kb_id}/documents/import-table: + post: + tags: [Knowledge Base] + summary: Import a table file with column configuration + operationId: importKnowledgeTable + x-astrbot-scope: kb + parameters: + - $ref: "#/components/parameters/KbId" + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/KnowledgeTableImportRequest" + responses: + "200": + $ref: "#/components/responses/Ok" + /api/v1/knowledge-bases/{kb_id}/documents/{document_id}: get: tags: [Knowledge Base] @@ -5619,6 +5655,10 @@ components: type: string description: type: string + kb_type: + type: string + enum: [text, table] + default: text embedding_provider_id: type: string rerank_provider_id: @@ -5675,6 +5715,43 @@ components: type: number additionalProperties: false + KnowledgeTablePreviewRequest: + type: object + required: [file] + properties: + file: + type: string + format: binary + header_row: + type: integer + default: 0 + preview_rows: + type: integer + default: 20 + + KnowledgeTableImportRequest: + type: object + required: [file, columns_config] + properties: + file: + type: string + format: binary + columns_config: + type: string + description: JSON string array of column configuration objects. + header_row: + type: integer + default: 0 + batch_size: + type: integer + default: 32 + tasks_limit: + type: integer + default: 3 + max_retries: + type: integer + default: 3 + PersonaRequest: type: object required: [persona_id, system_prompt] From 3a62505abeffa35ed862a2344355061f1d1ae277 Mon Sep 17 00:00:00 2001 From: wxd <1363812980@qq.com> Date: Sun, 21 Jun 2026 18:50:32 +0800 Subject: [PATCH 3/4] fix: harden table knowledge base parsing and upload validation --- astrbot/core/knowledge_base/kb_helper.py | 2 +- .../knowledge_base/parsers/table_parser.py | 17 ++-- .../services/knowledge_base_service.py | 96 ++++++++++++++----- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/astrbot/core/knowledge_base/kb_helper.py b/astrbot/core/knowledge_base/kb_helper.py index d23abcdae0..b818315ba8 100644 --- a/astrbot/core/knowledge_base/kb_helper.py +++ b/astrbot/core/knowledge_base/kb_helper.py @@ -481,7 +481,7 @@ def _format_row_text(columns: list[dict], row: dict[str, str]) -> str: name = col.get("name") if not name: continue - value = str(row.get(name, "")).strip() + value = str(row.get(name) or "").strip() if value: lines.append(f"{name}: {value}") return "\n".join(lines) diff --git a/astrbot/core/knowledge_base/parsers/table_parser.py b/astrbot/core/knowledge_base/parsers/table_parser.py index cade6d616a..0fd1f2b208 100644 --- a/astrbot/core/knowledge_base/parsers/table_parser.py +++ b/astrbot/core/knowledge_base/parsers/table_parser.py @@ -42,16 +42,19 @@ def _normalize_headers(raw_headers: list[object]) -> list[str]: A list of non-empty, de-duplicated header strings. """ headers: list[str] = [] - seen: dict[str, int] = {} + seen: set[str] = set() for idx, raw in enumerate(raw_headers): name = str(raw).strip() if raw is not None else "" if not name or name.lower().startswith("unnamed:"): name = f"column_{idx + 1}" - if name in seen: - seen[name] += 1 - name = f"{name}_{seen[name]}" - else: - seen[name] = 0 + + base_name = name + counter = 1 + while name in seen: + name = f"{base_name}_{counter}" + counter += 1 + + seen.add(name) headers.append(name) return headers @@ -90,7 +93,7 @@ def _read_dataframe( keep_default_na=False, encoding=encoding, ) - except (UnicodeDecodeError, ValueError) as exc: + except Exception as exc: last_error = exc continue raise ValueError(f"无法解析 CSV 文件,可能是编码问题: {last_error}") diff --git a/astrbot/dashboard/services/knowledge_base_service.py b/astrbot/dashboard/services/knowledge_base_service.py index c9a2eaa24b..68aa6428b5 100644 --- a/astrbot/dashboard/services/knowledge_base_service.py +++ b/astrbot/dashboard/services/knowledge_base_service.py @@ -630,6 +630,28 @@ def _read_single_upload(form_data, files) -> tuple[str, bytes]: raise KnowledgeBaseServiceError("缺少文件") return file_list[0] + @staticmethod + async def _save_and_read_upload_file(file) -> tuple[str, bytes]: + """Persist an uploaded file to a temp path and read it back into memory. + + Args: + file: The uploaded file object from a multipart request. + + Returns: + A tuple of ``(file_name, file_content)``. + """ + file_name = file.filename + temp_file_path = ( + Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" + ) + try: + await file.save(temp_file_path) + async with aiofiles.open(temp_file_path, "rb") as file_obj: + file_content = await file_obj.read() + return file_name, file_content + finally: + temp_file_path.unlink(missing_ok=True) + async def preview_table( self, *, @@ -654,20 +676,23 @@ async def preview_table( raise KnowledgeBaseServiceError("Content-Type 须为 multipart/form-data") if not form_data.get("kb_id"): raise KnowledgeBaseServiceError("缺少参数 kb_id") - header_row = int(form_data.get("header_row", 0)) - preview_rows = int(form_data.get("preview_rows", 20)) - file = self._read_single_upload(form_data, files) - file_name = file.filename - temp_file_path = ( - Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" - ) - await file.save(temp_file_path) try: - async with aiofiles.open(temp_file_path, "rb") as file_obj: - file_content = await file_obj.read() - finally: - temp_file_path.unlink(missing_ok=True) + header_row = int(form_data.get("header_row", 0)) + if header_row < 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError("表头行数必须是大于等于 0 的整数") from exc + + try: + preview_rows = int(form_data.get("preview_rows", 20)) + if preview_rows <= 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError("预览行数必须是大于 0 的整数") from exc + + file = self._read_single_upload(form_data, files) + file_name, file_content = await self._save_and_read_upload_file(file) from astrbot.core.knowledge_base.parsers.table_parser import ( is_table_file, @@ -716,10 +741,36 @@ async def import_table( kb_id = form_data.get("kb_id") if not kb_id: raise KnowledgeBaseServiceError("缺少参数 kb_id") - header_row = int(form_data.get("header_row", 0)) - batch_size = int(form_data.get("batch_size", 32)) - tasks_limit = int(form_data.get("tasks_limit", 3)) - max_retries = int(form_data.get("max_retries", 3)) + + try: + header_row = int(form_data.get("header_row", 0)) + if header_row < 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError("表头行数必须是大于等于 0 的整数") from exc + + try: + batch_size = int(form_data.get("batch_size", 32)) + if batch_size <= 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError("批处理大小必须是大于 0 的整数") from exc + + try: + tasks_limit = int(form_data.get("tasks_limit", 3)) + if tasks_limit <= 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError("并发限制必须是大于 0 的整数") from exc + + try: + max_retries = int(form_data.get("max_retries", 3)) + if max_retries < 0: + raise ValueError() + except (ValueError, TypeError) as exc: + raise KnowledgeBaseServiceError( + "最大重试次数必须是大于等于 0 的整数" + ) from exc columns_config_raw = form_data.get("columns_config") if not columns_config_raw: @@ -730,20 +781,13 @@ async def import_table( raise KnowledgeBaseServiceError("columns_config 格式错误") from exc if not isinstance(columns_config, list) or not columns_config: raise KnowledgeBaseServiceError("columns_config 必须是非空列表") + if not all(isinstance(c, dict) for c in columns_config): + raise KnowledgeBaseServiceError("columns_config 格式错误:元素必须是对象") if not any(c.get("is_index") for c in columns_config): raise KnowledgeBaseServiceError("至少需要选择一个索引列") file = self._read_single_upload(form_data, files) - file_name = file.filename - temp_file_path = ( - Path(get_astrbot_temp_path()) / f"kb_table_{uuid.uuid4()}_{file_name}" - ) - await file.save(temp_file_path) - try: - async with aiofiles.open(temp_file_path, "rb") as file_obj: - file_content = await file_obj.read() - finally: - temp_file_path.unlink(missing_ok=True) + file_name, file_content = await self._save_and_read_upload_file(file) file_type = file_name.rsplit(".", 1)[-1].lower() if "." in file_name else "" file_size = len(file_content) From e3c5d193a50a2492d8e3571467dc768bea22d95f Mon Sep 17 00:00:00 2001 From: wxd <1363812980@qq.com> Date: Sun, 21 Jun 2026 19:10:39 +0800 Subject: [PATCH 4/4] fix: enforce table KB type on import and fix upload helper return typing --- astrbot/dashboard/services/knowledge_base_service.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/astrbot/dashboard/services/knowledge_base_service.py b/astrbot/dashboard/services/knowledge_base_service.py index 68aa6428b5..bfcef6ce1f 100644 --- a/astrbot/dashboard/services/knowledge_base_service.py +++ b/astrbot/dashboard/services/knowledge_base_service.py @@ -609,15 +609,16 @@ async def import_documents(self, data: object) -> dict[str, Any]: } @staticmethod - def _read_single_upload(form_data, files) -> tuple[str, bytes]: - """Read the first uploaded file from a multipart request into memory. + def _read_single_upload(form_data, files): + """Return the first uploaded file object from a multipart request. Args: form_data: Parsed multipart form fields. files: Parsed multipart file fields. Returns: - A tuple of ``(file_name, file_content)``. + The first uploaded file object (e.g. a Quart ``FileStorage``). Use + :meth:`_save_and_read_upload_file` to obtain its name and bytes. Raises: KnowledgeBaseServiceError: If no file is present. @@ -794,6 +795,8 @@ async def import_table( kb_helper = await self.get_kb_manager().get_kb(kb_id) if not kb_helper: raise KnowledgeBaseServiceError("知识库不存在") + if kb_helper.kb.kb_type != "table": + raise KnowledgeBaseServiceError("只能向表格知识库导入表格数据") task_id = str(uuid.uuid4()) self.init_task(task_id, status="pending")