From c217b8f3d886ca5e650091ba0b7fff2465bae1b0 Mon Sep 17 00:00:00 2001 From: Heyang Wang Date: Fri, 6 Mar 2026 21:13:23 +0800 Subject: [PATCH] =?UTF-8?q?Feat:=20add=20=20DingTalk=20AI=20Table=20connec?= =?UTF-8?q?tor=20and=20integration=20for=20data=20synch=E2=80=A6=20(#13413?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What problem does this PR solve? Add DingTalk AI Table connector and integration for data synchronization Issue #13400 ### Type of change - [x] New Feature (non-breaking change which adds functionality) Co-authored-by: wangheyang --- common/constants.py | 1 + common/data_source/__init__.py | 2 + common/data_source/config.py | 1 + .../dingtalk_ai_table_connector.py | 433 ++++++++++++++++++ pyproject.toml | 1 + rag/svr/sync_data_source.py | 45 ++ uv.lock | 29 ++ .../svg/data-source/dingtalk-ai-table.svg | 27 ++ web/src/locales/bg.ts | 2 + web/src/locales/de.ts | 2 + web/src/locales/en.ts | 2 + web/src/locales/es.ts | 2 + web/src/locales/fr.ts | 2 + web/src/locales/id.ts | 2 + web/src/locales/it.ts | 2 + web/src/locales/ja.ts | 2 + web/src/locales/pt-br.ts | 2 + web/src/locales/ru.ts | 2 + web/src/locales/vi.ts | 2 + web/src/locales/zh-traditional.ts | 2 + web/src/locales/zh.ts | 2 + .../data-source/constant/index.tsx | 37 ++ 22 files changed, 602 insertions(+) create mode 100644 common/data_source/dingtalk_ai_table_connector.py create mode 100644 web/src/assets/svg/data-source/dingtalk-ai-table.svg diff --git a/common/constants.py b/common/constants.py index 6a939cf4cf..cbc2f534c9 100644 --- a/common/constants.py +++ b/common/constants.py @@ -138,6 +138,7 @@ class FileSource(StrEnum): SEAFILE = "seafile" MYSQL = "mysql" POSTGRESQL = "postgresql" + DINGTALK_AI_TABLE = "dingtalk_ai_table" class PipelineTaskType(StrEnum): diff --git a/common/data_source/__init__.py b/common/data_source/__init__.py index 099f3d7b3b..022a107613 100644 --- a/common/data_source/__init__.py +++ b/common/data_source/__init__.py @@ -36,6 +36,7 @@ from .sharepoint_connector import SharePointConnector from .teams_connector import TeamsConnector from .moodle_connector import MoodleConnector from .airtable_connector import AirtableConnector +from .dingtalk_ai_table_connector import DingTalkAITableConnector from .asana_connector import AsanaConnector from .imap_connector import ImapConnector from .zendesk_connector import ZendeskConnector @@ -83,4 +84,5 @@ __all__ = [ "SeaFileConnector", "RDBMSConnector", "WebDAVConnector", + "DingTalkAITableConnector", ] diff --git a/common/data_source/config.py b/common/data_source/config.py index b05d8af24a..65338f34a6 100644 --- a/common/data_source/config.py +++ b/common/data_source/config.py @@ -66,6 +66,7 @@ class DocumentSource(str, Enum): SEAFILE = "seafile" MYSQL = "mysql" POSTGRESQL = "postgresql" + DINGTALK_AI_TABLE = "dingtalk_ai_table" class FileOrigin(str, Enum): diff --git a/common/data_source/dingtalk_ai_table_connector.py b/common/data_source/dingtalk_ai_table_connector.py new file mode 100644 index 0000000000..66588d4d30 --- /dev/null +++ b/common/data_source/dingtalk_ai_table_connector.py @@ -0,0 +1,433 @@ +"""DingTalk AI Table connector for RAGFlow. By the way, "notable" is a reference to the DingTalk AI Table. + +This connector ingests records from DingTalk AI Table as documents. +It first retrieves all sheets from a specified table, then fetches all records +from each sheet. + +API Documentation: +- GetAllSheets: https://open.dingtalk.com/document/development/api-notable-getallsheets +- ListRecords: https://open.dingtalk.com/document/development/api-notable-listrecords +""" + +import json +import logging +from datetime import datetime, timezone +from typing import Any + +from alibabacloud_dingtalk.notable_1_0.client import Client as NotableClient +from alibabacloud_dingtalk.notable_1_0 import models as notable_models +from alibabacloud_tea_openapi import models as open_api_models +from alibabacloud_tea_util import models as util_models +from alibabacloud_tea_util.client import Client as UtilClient + +from common.data_source.config import INDEX_BATCH_SIZE, DocumentSource +from common.data_source.exceptions import ConnectorMissingCredentialError, ConnectorValidationError +from common.data_source.interfaces import LoadConnector, PollConnector, SecondsSinceUnixEpoch +from common.data_source.models import Document, GenerateDocumentsOutput + +logger = logging.getLogger(__name__) + +# Document ID prefix for DingTalk Notable +_DINGTALK_AI_TABLE_DOC_ID_PREFIX = "dingtalk_ai_table:" + + +class DingTalkAITableClientNotSetUpError(PermissionError): + """Exception raised when DingTalk Notable client is not initialized.""" + + def __init__(self) -> None: + super().__init__("DingTalk Notable client is not set up. Did you forget to call load_credentials()?") + + +class DingTalkAITableConnector(LoadConnector, PollConnector): + """ + DingTalk AI Table (Notable) connector for accessing table records. + + This connector: + 1. Retrieves all sheets from a specified Notable table using GetAllSheets API + 2. For each sheet, fetches all records using ListRecords API with pagination + 3. Converts each record into a Document for RAGFlow ingestion + + Required credentials: + - access_token: DingTalk access token (x-acs-dingtalk-access-token) + - operator_id: User's unionId for API calls + + Configuration: + - table_id: The Notable table ID (e.g., 'qnYxxx') + """ + + def __init__( + self, + table_id: str, + operator_id: str, + batch_size: int = INDEX_BATCH_SIZE, + ) -> None: + """ + Initialize the DingTalk Notable connector. + + Args: + table_id: The Notable table ID + operator_id: User's unionId for API calls + batch_size: Number of records per batch for document generation + """ + self.table_id = table_id + self.operator_id = operator_id + self.batch_size = batch_size + self._client: NotableClient | None = None + self._access_token: str | None = None + + def _create_client(self) -> NotableClient: + """Create DingTalk Notable API client.""" + config = open_api_models.Config() + config.protocol = "https" + config.region_id = "central" + return NotableClient(config) + + def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None: + """ + Load DingTalk credentials. + + Args: + credentials: Dictionary containing 'access_token' + + Returns: + None + """ + access_token = credentials.get("access_token") + if not access_token: + raise ConnectorMissingCredentialError("DingTalk access_token is required") + + self._access_token = access_token + self._client = self._create_client() + return None + + @property + def client(self) -> NotableClient: + """Get the DingTalk AITable client.""" + if self._client is None: + raise DingTalkAITableClientNotSetUpError() + return self._client + + @property + def access_token(self) -> str: + """Get the access token.""" + if self._access_token is None: + raise ConnectorMissingCredentialError("DingTalk access_token not loaded") + return self._access_token + + def validate_connector_settings(self) -> None: + """Validate DingTalk connector settings by trying to get all sheets.""" + if self._client is None or self._access_token is None: + raise ConnectorMissingCredentialError("DingTalk Notable") + + try: + # Try to get sheets to validate credentials + headers = notable_models.GetAllSheetsHeaders() + headers.x_acs_dingtalk_access_token = self._access_token + + request = notable_models.GetAllSheetsRequest( + operator_id=self.operator_id, + ) + + self.client.get_all_sheets_with_options( + self.table_id, + request, + headers, + util_models.RuntimeOptions(), + ) + except Exception as e: + logger.exception("[DingTalk Notable]: Failed to validate credentials") + raise ConnectorValidationError(f"DingTalk Notable credential validation failed: {e}") + + def _get_all_sheets(self) -> list[dict[str, Any]]: + """ + Retrieve all sheets from the Notable table. + + Returns: + List of sheet information dictionaries + """ + headers = notable_models.GetAllSheetsHeaders() + headers.x_acs_dingtalk_access_token = self._access_token + + request = notable_models.GetAllSheetsRequest( + operator_id=self.operator_id, + ) + + try: + response = self.client.get_all_sheets_with_options( + self.table_id, + request, + headers, + util_models.RuntimeOptions(), + ) + + sheets = [] + if response.body and response.body.value: + for sheet in response.body.value: + sheets.append( + { + "id": sheet.id, + "name": sheet.name, + } + ) + + logger.info(f"[DingTalk Notable]: Found {len(sheets)} sheets in table {self.table_id}") + return sheets + + except Exception as e: + logger.exception(f"[DingTalk Notable]: Failed to get sheets: {e}") + raise + + def _list_records( + self, + sheet_id: str, + next_token: str | None = None, + max_results: int = 100, + ) -> tuple[list[dict[str, Any]], str | None]: + """ + List records from a specific sheet with pagination. + + Args: + sheet_id: The sheet ID + next_token: Token for pagination + max_results: Maximum number of results per page + + Returns: + Tuple of (records list, next_token or None if no more) + """ + headers = notable_models.ListRecordsHeaders() + headers.x_acs_dingtalk_access_token = self._access_token + + request = notable_models.ListRecordsRequest( + operator_id=self.operator_id, + max_results=max_results, + next_token=next_token or "", + ) + + try: + response = self.client.list_records_with_options( + self.table_id, + sheet_id, + request, + headers, + util_models.RuntimeOptions(), + ) + + records = [] + new_next_token = None + + if response.body: + if response.body.records: + for record in response.body.records: + records.append( + { + "id": record.id, + "fields": record.fields, + } + ) + if response.body.next_token: + new_next_token = response.body.next_token + + return records, new_next_token + + except Exception as e: + if not UtilClient.empty(getattr(e, "code", None)) and not UtilClient.empty(getattr(e, "message", None)): + logger.error(f"[DingTalk AITable]: API error - code: {e.code}, message: {e.message}") + raise + + def _get_all_records(self, sheet_id: str) -> list[dict[str, Any]]: + """ + Retrieve all records from a sheet with pagination. + + Args: + sheet_id: The sheet ID + + Returns: + List of all records + """ + all_records = [] + next_token = None + + while True: + records, next_token = self._list_records( + sheet_id=sheet_id, + next_token=next_token, + ) + all_records.extend(records) + + if not next_token: + break + + logger.info(f"[DingTalk Notable]: Retrieved {len(all_records)} records from sheet {sheet_id}") + return all_records + + def _convert_record_to_document( + self, + record: dict[str, Any], + sheet_id: str, + sheet_name: str, + ) -> Document: + """ + Convert a Notable record to a Document. + + Args: + record: The record dictionary + sheet_id: The sheet ID + sheet_name: The sheet name + + Returns: + Document object + """ + record_id = record.get("id", "unknown") + fields = record.get("fields", {}) + + # Convert fields to JSON string for blob content + content = json.dumps(fields, ensure_ascii=False, indent=2) + blob = content.encode("utf-8") + + # Create semantic identifier from record fields + # Try to find a meaningful title/name field + semantic_identifier = f"{sheet_name} - Record {record_id}" + + # Try to find a title-like field + for key, value in fields.items(): + if isinstance(value, str) and len(value) > 0 and len(value) < 100: + semantic_identifier = f"{sheet_name} - {value[:50]}" + break + + # Metadata + metadata: dict[str, str | list[str]] = { + "table_id": self.table_id, + "sheet_id": sheet_id, + "sheet_name": sheet_name, + "record_id": record_id, + } + + # Create document + doc = Document( + id=f"{_DINGTALK_AI_TABLE_DOC_ID_PREFIX}{self.table_id}:{sheet_id}:{record_id}", + source=DocumentSource.DINGTALK_AI_TABLE, + semantic_identifier=semantic_identifier, + extension=".json", + blob=blob, + size_bytes=len(blob), + doc_updated_at=datetime.now(timezone.utc), + metadata=metadata, + ) + + return doc + + def _yield_documents_from_table( + self, + start: SecondsSinceUnixEpoch | None = None, + end: SecondsSinceUnixEpoch | None = None, + ) -> GenerateDocumentsOutput: + """ + Yield documents from all sheets in the table. + + Args: + start: Optional start timestamp for filtering + end: Optional end timestamp for filtering + + Yields: + Lists of Document objects + """ + # Get all sheets + sheets = self._get_all_sheets() + + batch: list[Document] = [] + + for sheet in sheets: + sheet_id = sheet["id"] + sheet_name = sheet["name"] + + # Get all records from this sheet + records = self._get_all_records(sheet_id) + + for record in records: + doc = self._convert_record_to_document( + record=record, + sheet_id=sheet_id, + sheet_name=sheet_name, + ) + + # Apply time filtering if specified + if start is not None or end is not None: + doc_time = doc.doc_updated_at.timestamp() if doc.doc_updated_at else None + if doc_time is not None: + if start is not None and doc_time < start: + continue + if end is not None and doc_time > end: + continue + + batch.append(doc) + + if len(batch) >= self.batch_size: + yield batch + batch = [] + + if batch: + yield batch + + def load_from_state(self) -> GenerateDocumentsOutput: + """ + Load all documents from the DingTalk Notable table. + + Yields: + Lists of Document objects + """ + return self._yield_documents_from_table() + + def poll_source( + self, + start: SecondsSinceUnixEpoch, + end: SecondsSinceUnixEpoch, + ) -> GenerateDocumentsOutput: + """ + Poll for documents within a time range. + + Args: + start: Start timestamp + end: End timestamp + + Yields: + Lists of Document objects + """ + return self._yield_documents_from_table(start=start, end=end) + + +if __name__ == "__main__": + import os + + logging.basicConfig(level=logging.DEBUG) + + # Example usage + table_id = os.environ.get("DINGTALK_AI_TABLE_BASE_ID", "") + operator_id = os.environ.get("DINGTALK_OPERATOR_ID", "") + access_token = os.environ.get("DINGTALK_ACCESS_TOKEN", "") + + if not all([table_id, operator_id, access_token]): + print("Please set DINGTALK_AI_TABLE_BASE_ID, DINGTALK_OPERATOR_ID, and DINGTALK_ACCESS_TOKEN environment variables") + exit(1) + + connector = DingTalkAITableConnector( + table_id=table_id, + operator_id=operator_id, + ) + connector.load_credentials({"access_token": access_token}) + + try: + connector.validate_connector_settings() + print("Connector settings validated successfully") + except Exception as e: + print(f"Validation failed: {e}") + exit(1) + + document_batches = connector.load_from_state() + try: + first_batch = next(document_batches) + print(f"Loaded {len(first_batch)} documents in first batch.") + for doc in first_batch[:5]: # Print first 5 docs + print(f"- {doc.semantic_identifier} ({doc.size_bytes} bytes)") + print(f" Metadata: {doc.metadata}") + except StopIteration: + print("No documents available in DingTalk Notable table.") diff --git a/pyproject.toml b/pyproject.toml index 53dc38cf8c..0665a1c536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,6 +156,7 @@ dependencies = [ "pygithub>=2.8.1", "asana>=5.2.2", "python-gitlab>=7.0.0", + "alibabacloud-dingtalk>=2.0.0", "quart-schema==0.23.0", ] diff --git a/rag/svr/sync_data_source.py b/rag/svr/sync_data_source.py index ac317d418e..044c7484df 100644 --- a/rag/svr/sync_data_source.py +++ b/rag/svr/sync_data_source.py @@ -55,6 +55,7 @@ from common.data_source import ( ZendeskConnector, SeaFileConnector, RDBMSConnector, + DingTalkAITableConnector, ) from common.constants import FileSource, TaskStatus from common.data_source.config import INDEX_BATCH_SIZE @@ -1221,6 +1222,49 @@ class SeaFile(SyncBase): return document_generator +class DingTalkAITable(SyncBase): + SOURCE_NAME: str = FileSource.DINGTALK_AI_TABLE + + async def _generate(self, task: dict): + """ + Sync records from DingTalk AI Table (Notable). + """ + self.connector = DingTalkAITableConnector( + table_id=self.conf.get("table_id"), + operator_id=self.conf.get("operator_id"), + batch_size=self.conf.get("batch_size", INDEX_BATCH_SIZE), + ) + + credentials = self.conf.get("credentials", {}) + if "access_token" not in credentials: + raise ValueError("Missing access_token in credentials") + + self.connector.load_credentials( + {"access_token": credentials["access_token"]} + ) + + poll_start = task.get("poll_range_start") + + if task.get("reindex") == "1" or poll_start is None: + document_generator = self.connector.load_from_state() + begin_info = "totally" + else: + document_generator = self.connector.poll_source( + poll_start.timestamp(), + datetime.now(timezone.utc).timestamp(), + ) + begin_info = f"from {poll_start}" + + logging.info( + "Connect to DingTalk AI Table: table_id(%s), operator_id(%s) %s", + self.conf.get("table_id"), + self.conf.get("operator_id"), + begin_info, + ) + + return document_generator + + class MySQL(SyncBase): SOURCE_NAME: str = FileSource.MYSQL @@ -1321,6 +1365,7 @@ func_factory = { FileSource.SEAFILE: SeaFile, FileSource.MYSQL: MySQL, FileSource.POSTGRESQL: PostgreSQL, + FileSource.DINGTALK_AI_TABLE: DingTalkAITable, } diff --git a/uv.lock b/uv.lock index 6c54506565..0b1423a014 100644 --- a/uv.lock +++ b/uv.lock @@ -319,12 +319,39 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/c6/7d375cc1b1cab0f46950f556b70a2b17235747429a0889b73f3d46ff6023/alibabacloud_devs20230714-2.4.1-py3-none-any.whl", hash = "sha256:dbd260718e6db50021d804218b40bc99ee9c7e40b1def382aef8e542f5921113", size = 59307, upload-time = "2025-08-08T07:40:28.504Z" }, ] +[[package]] +name = "alibabacloud-dingtalk" +version = "2.2.38" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "alibabacloud-endpoint-util" }, + { name = "alibabacloud-gateway-dingtalk" }, + { name = "alibabacloud-gateway-spi" }, + { name = "alibabacloud-openapi-util" }, + { name = "alibabacloud-tea-openapi" }, + { name = "alibabacloud-tea-util" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/95/ad71f7cb1a2814d17f3a731b37c27eb71df0895daa912e2436d2f0dd06d4/alibabacloud_dingtalk-2.2.38.tar.gz", hash = "sha256:39cba6ff3accf0a5c7fe7de651a65a5a784c4ef63e442750fd822c19864ed6f1", size = 1954698, upload-time = "2026-01-08T11:38:04.913Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/77/f6679f78becf73bfbf3468367ffa6fe694120e0cd677eacbc454dbb379a1/alibabacloud_dingtalk-2.2.38-py3-none-any.whl", hash = "sha256:c3dfc918c45f49fe61469230c0808fd6f316341594a2895564511b5542f50019", size = 2074155, upload-time = "2026-01-08T11:38:03.395Z" }, +] + [[package]] name = "alibabacloud-endpoint-util" version = "0.0.4" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/7d/8cc92a95c920e344835b005af6ea45a0db98763ad6ad19299d26892e6c8d/alibabacloud_endpoint_util-0.0.4.tar.gz", hash = "sha256:a593eb8ddd8168d5dc2216cd33111b144f9189fcd6e9ca20e48f358a739bbf90", size = 2813, upload-time = "2025-06-12T07:20:52.572Z" } +[[package]] +name = "alibabacloud-gateway-dingtalk" +version = "1.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "alibabacloud-gateway-spi" }, + { name = "alibabacloud-tea-util" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/40/751d8bdf133d7fcf053f10c98e8e506810e7bee06458a02eaaa14d30ac26/alibabacloud_gateway_dingtalk-1.0.2.tar.gz", hash = "sha256:acea8b0b1d11e0394913f0b0899ddd19c0bfceab716060449b57fcc250ceb300", size = 2938, upload-time = "2023-04-25T09:48:42.249Z" } + [[package]] name = "alibabacloud-gateway-spi" version = "0.0.3" @@ -6218,6 +6245,7 @@ dependencies = [ { name = "agentrun-sdk" }, { name = "aiosmtplib" }, { name = "akshare" }, + { name = "alibabacloud-dingtalk" }, { name = "anthropic" }, { name = "arxiv" }, { name = "asana" }, @@ -6357,6 +6385,7 @@ requires-dist = [ { name = "agentrun-sdk", specifier = ">=0.0.16,<1.0.0" }, { name = "aiosmtplib", specifier = ">=5.0.0" }, { name = "akshare", specifier = ">=1.15.78,<2.0.0" }, + { name = "alibabacloud-dingtalk", specifier = ">=2.0.0" }, { name = "anthropic", specifier = "==0.34.1" }, { name = "arxiv", specifier = "==2.1.3" }, { name = "asana", specifier = ">=5.2.2" }, diff --git a/web/src/assets/svg/data-source/dingtalk-ai-table.svg b/web/src/assets/svg/data-source/dingtalk-ai-table.svg new file mode 100644 index 0000000000..589602c480 --- /dev/null +++ b/web/src/assets/svg/data-source/dingtalk-ai-table.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/locales/bg.ts b/web/src/locales/bg.ts index 03b391cf97..ef4fc7822e 100644 --- a/web/src/locales/bg.ts +++ b/web/src/locales/bg.ts @@ -998,6 +998,8 @@ The above is the content you need to summarize.`, 'Свържете GitHub за синхронизиране на pull requests и issues за извличане.', airtableDescription: 'Свържете се с Airtable и синхронизирайте файлове от определена таблица в определено работно пространство.', + dingtalkAITableDescription: + 'Свържете се с Dingtalk AI Table и синхронизирайте записи от определена таблица.', gitlabDescription: 'Свържете GitLab за синхронизиране на хранилища, issues, merge requests и свързана документация.', asanaDescription: diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 508115b186..d549af3fb0 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -1016,6 +1016,8 @@ Beispiel: Virtual Hosted Style`, 'Verbinden Sie GitHub, um Pull Requests und Issues zur Recherche zu synchronisieren.', airtableDescription: 'Verbinden Sie sich mit Airtable und synchronisieren Sie Dateien aus einer bestimmten Tabelle in einem vorgesehenen Arbeitsbereich.', + dingtalkAITableDescription: + 'Verbinden Sie sich mit Dingtalk AI Table und synchronisieren Sie Datensätze aus einer bestimmten Tabelle.', asanaDescription: 'Verbinden Sie sich mit Asana und synchronisieren Sie Dateien aus einem bestimmten Arbeitsbereich.', imapDescription: diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 4df8e286b5..4257d5a0e1 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1034,6 +1034,8 @@ Example: Virtual Hosted Style`, 'Connect GitHub to sync pull requests and issues for retrieval.', airtableDescription: 'Connect to Airtable and synchronize files from a specified table within a designated workspace.', + dingtalkAITableDescription: + 'Connect to Dingtalk AI Table and synchronize records from a specified table.', gitlabDescription: 'Connect GitLab to sync repositories, issues, merge requests, and related documentation.', asanaDescription: diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index 816353495f..1ea94962dc 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -472,6 +472,8 @@ export default { apiVersionMessage: '¡Por favor ingresa la versión de la API!', modelsToBeAddedTooltip: 'Si tu proveedor de modelos no aparece en la lista pero afirma ser compatible con OpenAI, selecciona la tarjeta OpenAI-API-compatible para añadir el/los modelo(s) correspondiente(s).', + dingtalkAITableDescription: + 'Conéctese a Dingtalk AI Table y sincronice registros de una tabla especificada.', }, message: { registered: '¡Registrado!', diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts index ccb735c410..f19f1b6d05 100644 --- a/web/src/locales/fr.ts +++ b/web/src/locales/fr.ts @@ -680,6 +680,8 @@ export default { modelsToBeAddedTooltip: 'Si votre fournisseur de modèle n\'est pas listé mais prétend être "compatible OpenAI", sélectionnez la carte compatible OpenAI-API pour ajouter le(s) modèle(s) pertinent(s).', mcp: 'MCP', + dingtalkAITableDescription: + 'Connectez-vous à Dingtalk AI Table et synchronisez les enregistrements d\'une table spécifiée.', }, message: { registered: 'Enregistré !', diff --git a/web/src/locales/id.ts b/web/src/locales/id.ts index 7969d85666..09aa6a29d1 100644 --- a/web/src/locales/id.ts +++ b/web/src/locales/id.ts @@ -669,6 +669,8 @@ export default { apiVersionMessage: 'Silakan masukkan versi API', modelsToBeAddedTooltip: 'Jika penyedia model Anda tidak tercantum tetapi mengklaim kompatibel dengan OpenAI, pilih kartu OpenAI-API-compatible untuk menambahkan model yang relevan.', + dingtalkAITableDescription: + 'Hubungkan ke Dingtalk AI Table dan sinkronkan catatan dari tabel yang ditentukan.', }, message: { registered: 'Terdaftar!', diff --git a/web/src/locales/it.ts b/web/src/locales/it.ts index 04222f4607..05627ea1dc 100644 --- a/web/src/locales/it.ts +++ b/web/src/locales/it.ts @@ -840,6 +840,8 @@ Quanto sopra è il contenuto che devi riassumere.`, configuration: 'Configurazione', view: 'Visualizza', mcp: 'MCP', + dingtalkAITableDescription: + 'Connettiti a Dingtalk AI Table e sincronizza i record da una tabella specificata.', }, message: { registered: 'Registrato!', diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts index 3eb93aae5e..5b32e0fbf2 100644 --- a/web/src/locales/ja.ts +++ b/web/src/locales/ja.ts @@ -691,6 +691,8 @@ export default { sureQuit: '参加したチームから退出してもよろしいですか?', modelsToBeAddedTooltip: 'モデルプロバイダーがリストにないが「OpenAI互換」を謳っている場合は、OpenAI-API-compatible カードを選択して関連モデルを追加してください。', + dingtalkAITableDescription: + 'Dingtalk AI Table に接続し、指定されたテーブルからレコードを同期します。', }, message: { registered: '登録完了!', diff --git a/web/src/locales/pt-br.ts b/web/src/locales/pt-br.ts index 1ce96814ca..719f21a97f 100644 --- a/web/src/locales/pt-br.ts +++ b/web/src/locales/pt-br.ts @@ -639,6 +639,8 @@ export default { sureQuit: 'Tem certeza de que deseja sair da equipe que você ingressou?', modelsToBeAddedTooltip: 'Se o seu provedor de modelo não estiver listado, mas afirmar ser compatível com a OpenAI, selecione o card OpenAI-API-compatible para adicionar o(s) modelo(s) relevante(s). ', + dingtalkAITableDescription: + 'Conecte-se ao Dingtalk AI Table e sincronize registros de uma tabela especificada.', }, message: { registered: 'Registrado!', diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index 60b8d8ab1c..923314ef5a 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -778,6 +778,8 @@ export default { 'Подключите GitHub для синхронизации содержимого Pull Request и Issue для поиска.', airtableDescription: 'Подключите Airtable и синхронизируйте файлы из указанной таблицы в заданном рабочем пространстве.', + dingtalkAITableDescription: + 'Подключите Dingtalk AI Table и синхронизируйте записи из указанной таблицы.', gitlabDescription: 'Подключите GitLab для синхронизации репозиториев, задач, merge requests и связанной документации.', asanaDescription: diff --git a/web/src/locales/vi.ts b/web/src/locales/vi.ts index ffe7312131..c3f933c30a 100644 --- a/web/src/locales/vi.ts +++ b/web/src/locales/vi.ts @@ -723,6 +723,8 @@ export default { FishAudioRefIDMessage: `Vui lòng nhập ID của model tham chiếu (để trống để sử dụng model mặc định)`, modelsToBeAddedTooltip: 'Nếu nhà cung cấp mô hình của bạn không có trong danh sách nhưng tuyên bố tương thích với "OpenAI", hãy chọn thẻ OpenAI-API-compatible để thêm mô hình liên quan.', + dingtalkAITableDescription: + 'Kết nối với Dingtalk AI Table và đồng bộ hóa bản ghi từ một bảng được chỉ định.', }, message: { registered: 'Đã đăng ký!', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index 229c6bea5f..611dddf007 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -563,6 +563,8 @@ export default { avatar: '头像', avatarTip: '這會在你的個人主頁展示', profileDescription: '在此更新您的照片和個人詳細信息。', + dingtalkAITableDescription: + '連接釘釘AI表格,同步指定表格中的記錄。', gitlabDescription: '連接 GitLab,同步儲存庫、Issue、合併請求(MR)及相關文件內容。', bedrockCredentialsHint: diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 8a278177f5..3a624d42eb 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -919,6 +919,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 githubDescription: '连接 GitHub,可同步 Pull Request 与 Issue 内容用于检索。', airtableDescription: '连接 Airtable,同步指定工作区下指定表格中的文件。', + dingtalkAITableDescription: + '连接钉钉AI表格,同步指定表格中的记录。', gitlabDescription: '连接 GitLab,同步仓库、Issue、合并请求(MR)及相关文档内容。', asanaDescription: '连接 Asana,同步工作区中的文件。', diff --git a/web/src/pages/user-setting/data-source/constant/index.tsx b/web/src/pages/user-setting/data-source/constant/index.tsx index 96f6987323..b06d424b4c 100644 --- a/web/src/pages/user-setting/data-source/constant/index.tsx +++ b/web/src/pages/user-setting/data-source/constant/index.tsx @@ -30,6 +30,7 @@ export enum DataSourceKey { OCI_STORAGE = 'oci_storage', GOOGLE_CLOUD_STORAGE = 'google_cloud_storage', AIRTABLE = 'airtable', + DINGTALK_AI_TABLE = 'dingtalk_ai_table', GITLAB = 'gitlab', ASANA = 'asana', IMAP = 'imap', @@ -123,6 +124,11 @@ export const generateDataSourceInfo = (t: TFunction) => { description: t(`setting.${DataSourceKey.AIRTABLE}Description`), icon: , }, + [DataSourceKey.DINGTALK_AI_TABLE]: { + name: 'Dingtalk AI Table', + description: t(`setting.dingtalkAITableDescription`), + icon: , + }, [DataSourceKey.GITLAB]: { name: 'GitLab', description: t(`setting.${DataSourceKey.GITLAB}Description`), @@ -658,6 +664,26 @@ export const DataSourceFormFields = { required: true, }, ], + [DataSourceKey.DINGTALK_AI_TABLE]: [ + { + label: 'Access Token', + name: 'config.credentials.access_token', + type: FormFieldType.Password, + required: true, + }, + { + label: 'Base ID', + name: 'config.table_id', + type: FormFieldType.Text, + required: true, + }, + { + label: 'Operator ID', + name: 'config.operator_id', + type: FormFieldType.Text, + required: true, + }, + ], [DataSourceKey.GITLAB]: [ { label: 'Project Owner', @@ -1135,6 +1161,17 @@ export const DataSourceFormDefaultValues = { }, }, }, + [DataSourceKey.DINGTALK_AI_TABLE]: { + name: '', + source: DataSourceKey.DINGTALK_AI_TABLE, + config: { + table_id: '', + operator_id: '', + credentials: { + access_token: '', + }, + }, + }, [DataSourceKey.GITLAB]: { name: '', source: DataSourceKey.GITLAB,