mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-04 03:25:30 +08:00
Compare commits
7 Commits
0b5d1ebefa
...
4ec6a4e493
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ec6a4e493 | |||
| 2d5ad42128 | |||
| dccda35f65 | |||
| d142b9095e | |||
| c2c079886f | |||
| c3ae1aaecd | |||
| f099bc1236 |
@ -303,6 +303,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
Or if you are behind a proxy, you can pass proxy arguments:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 Launch service from source for development
|
||||
|
||||
1. Install `uv` and `pre-commit`, or skip this step if they are already installed:
|
||||
|
||||
@ -277,6 +277,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
Jika berada di belakang proxy, Anda dapat melewatkan argumen proxy:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 Menjalankan Aplikasi dari untuk Pengembangan
|
||||
|
||||
1. Instal `uv` dan `pre-commit`, atau lewati langkah ini jika sudah terinstal:
|
||||
|
||||
@ -277,6 +277,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
プロキシ環境下にいる場合は、プロキシ引数を指定できます:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 ソースコードからサービスを起動する方法
|
||||
|
||||
1. `uv` と `pre-commit` をインストールする。すでにインストールされている場合は、このステップをスキップしてください:
|
||||
|
||||
@ -271,6 +271,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
프록시 환경인 경우, 프록시 인수를 전달할 수 있습니다:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 소스 코드로 서비스를 시작합니다.
|
||||
|
||||
1. `uv` 와 `pre-commit` 을 설치하거나, 이미 설치된 경우 이 단계를 건너뜁니다:
|
||||
|
||||
@ -294,6 +294,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
Se você estiver atrás de um proxy, pode passar argumentos de proxy:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 Lançar o serviço a partir do código-fonte para desenvolvimento
|
||||
|
||||
1. Instale o `uv` e o `pre-commit`, ou pule esta etapa se eles já estiverem instalados:
|
||||
|
||||
@ -303,6 +303,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
若您位於代理環境,可傳遞代理參數:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 以原始碼啟動服務
|
||||
|
||||
1. 安裝 `uv` 和 `pre-commit`。如已安裝,可跳過此步驟:
|
||||
|
||||
@ -302,6 +302,15 @@ cd ragflow/
|
||||
docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
如果您处在代理环境下,可以传递代理参数:
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 \
|
||||
--build-arg http_proxy=http://YOUR_PROXY:PORT \
|
||||
--build-arg https_proxy=http://YOUR_PROXY:PORT \
|
||||
-f Dockerfile -t infiniflow/ragflow:nightly .
|
||||
```
|
||||
|
||||
## 🔨 以源代码启动服务
|
||||
|
||||
1. 安装 `uv` 和 `pre-commit`。如已经安装,可跳过本步骤:
|
||||
|
||||
@ -130,7 +130,7 @@ class FileSource(StrEnum):
|
||||
GOOGLE_CLOUD_STORAGE = "google_cloud_storage"
|
||||
AIRTABLE = "airtable"
|
||||
ASANA = "asana"
|
||||
|
||||
GITLAB = "gitlab"
|
||||
|
||||
class PipelineTaskType(StrEnum):
|
||||
PARSE = "Parse"
|
||||
|
||||
@ -55,6 +55,8 @@ class DocumentSource(str, Enum):
|
||||
BOX = "box"
|
||||
AIRTABLE = "airtable"
|
||||
ASANA = "asana"
|
||||
GITHUB = "github"
|
||||
GITLAB = "gitlab"
|
||||
|
||||
class FileOrigin(str, Enum):
|
||||
"""File origins"""
|
||||
|
||||
340
common/data_source/gitlab_connector.py
Normal file
340
common/data_source/gitlab_connector.py
Normal file
@ -0,0 +1,340 @@
|
||||
import fnmatch
|
||||
import itertools
|
||||
from collections import deque
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterator
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from typing import Any
|
||||
from typing import TypeVar
|
||||
import gitlab
|
||||
from gitlab.v4.objects import Project
|
||||
|
||||
from common.data_source.config import DocumentSource, INDEX_BATCH_SIZE
|
||||
from common.data_source.exceptions import ConnectorMissingCredentialError
|
||||
from common.data_source.exceptions import ConnectorValidationError
|
||||
from common.data_source.exceptions import CredentialExpiredError
|
||||
from common.data_source.exceptions import InsufficientPermissionsError
|
||||
from common.data_source.exceptions import UnexpectedValidationError
|
||||
from common.data_source.interfaces import GenerateDocumentsOutput
|
||||
from common.data_source.interfaces import LoadConnector
|
||||
from common.data_source.interfaces import PollConnector
|
||||
from common.data_source.interfaces import SecondsSinceUnixEpoch
|
||||
from common.data_source.models import BasicExpertInfo
|
||||
from common.data_source.models import Document
|
||||
from common.data_source.utils import get_file_ext
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
|
||||
# List of directories/Files to exclude
|
||||
exclude_patterns = [
|
||||
"logs",
|
||||
".github/",
|
||||
".gitlab/",
|
||||
".pre-commit-config.yaml",
|
||||
]
|
||||
|
||||
|
||||
def _batch_gitlab_objects(git_objs: Iterable[T], batch_size: int) -> Iterator[list[T]]:
|
||||
it = iter(git_objs)
|
||||
while True:
|
||||
batch = list(itertools.islice(it, batch_size))
|
||||
if not batch:
|
||||
break
|
||||
yield batch
|
||||
|
||||
|
||||
def get_author(author: Any) -> BasicExpertInfo:
|
||||
return BasicExpertInfo(
|
||||
display_name=author.get("name"),
|
||||
)
|
||||
|
||||
|
||||
def _convert_merge_request_to_document(mr: Any) -> Document:
|
||||
mr_text = mr.description or ""
|
||||
doc = Document(
|
||||
id=mr.web_url,
|
||||
blob=mr_text,
|
||||
source=DocumentSource.GITLAB,
|
||||
semantic_identifier=mr.title,
|
||||
extension=".md",
|
||||
# updated_at is UTC time but is timezone unaware, explicitly add UTC
|
||||
# as there is logic in indexing to prevent wrong timestamped docs
|
||||
# due to local time discrepancies with UTC
|
||||
doc_updated_at=mr.updated_at.replace(tzinfo=timezone.utc),
|
||||
size_bytes=len(mr_text.encode("utf-8")),
|
||||
primary_owners=[get_author(mr.author)],
|
||||
metadata={"state": mr.state, "type": "MergeRequest", "web_url": mr.web_url},
|
||||
)
|
||||
return doc
|
||||
|
||||
|
||||
def _convert_issue_to_document(issue: Any) -> Document:
|
||||
issue_text = issue.description or ""
|
||||
doc = Document(
|
||||
id=issue.web_url,
|
||||
blob=issue_text,
|
||||
source=DocumentSource.GITLAB,
|
||||
semantic_identifier=issue.title,
|
||||
extension=".md",
|
||||
# updated_at is UTC time but is timezone unaware, explicitly add UTC
|
||||
# as there is logic in indexing to prevent wrong timestamped docs
|
||||
# due to local time discrepancies with UTC
|
||||
doc_updated_at=issue.updated_at.replace(tzinfo=timezone.utc),
|
||||
size_bytes=len(issue_text.encode("utf-8")),
|
||||
primary_owners=[get_author(issue.author)],
|
||||
metadata={
|
||||
"state": issue.state,
|
||||
"type": issue.type if issue.type else "Issue",
|
||||
"web_url": issue.web_url,
|
||||
},
|
||||
)
|
||||
return doc
|
||||
|
||||
|
||||
def _convert_code_to_document(
|
||||
project: Project, file: Any, url: str, projectName: str, projectOwner: str
|
||||
) -> Document:
|
||||
|
||||
# Dynamically get the default branch from the project object
|
||||
default_branch = project.default_branch
|
||||
|
||||
# Fetch the file content using the correct branch
|
||||
file_content_obj = project.files.get(
|
||||
file_path=file["path"], ref=default_branch # Use the default branch
|
||||
)
|
||||
# BoxConnector uses raw bytes for blob. Keep the same here.
|
||||
file_content_bytes = file_content_obj.decode()
|
||||
file_url = f"{url}/{projectOwner}/{projectName}/-/blob/{default_branch}/{file['path']}"
|
||||
|
||||
# Try to use the last commit timestamp for incremental sync.
|
||||
# Falls back to "now" if the commit lookup fails.
|
||||
last_commit_at = None
|
||||
try:
|
||||
# Query commit history for this file on the default branch.
|
||||
commits = project.commits.list(
|
||||
ref_name=default_branch,
|
||||
path=file["path"],
|
||||
per_page=1,
|
||||
)
|
||||
if commits:
|
||||
# committed_date is ISO string like "2024-01-01T00:00:00.000+00:00"
|
||||
committed_date = commits[0].committed_date
|
||||
if isinstance(committed_date, str):
|
||||
last_commit_at = datetime.strptime(
|
||||
committed_date, "%Y-%m-%dT%H:%M:%S.%f%z"
|
||||
).astimezone(timezone.utc)
|
||||
elif isinstance(committed_date, datetime):
|
||||
last_commit_at = committed_date.astimezone(timezone.utc)
|
||||
except Exception:
|
||||
last_commit_at = None
|
||||
|
||||
# Create and return a Document object
|
||||
doc = Document(
|
||||
# Use a stable ID so reruns don't create duplicates.
|
||||
id=file_url,
|
||||
blob=file_content_bytes,
|
||||
source=DocumentSource.GITLAB,
|
||||
semantic_identifier=file.get("name"),
|
||||
extension=get_file_ext(file.get("name")),
|
||||
doc_updated_at=last_commit_at or datetime.now(tz=timezone.utc),
|
||||
size_bytes=len(file_content_bytes) if file_content_bytes is not None else 0,
|
||||
primary_owners=[], # Add owners if needed
|
||||
metadata={
|
||||
"type": "CodeFile",
|
||||
"path": file.get("path"),
|
||||
"ref": default_branch,
|
||||
"project": f"{projectOwner}/{projectName}",
|
||||
"web_url": file_url,
|
||||
},
|
||||
)
|
||||
return doc
|
||||
|
||||
|
||||
def _should_exclude(path: str) -> bool:
|
||||
"""Check if a path matches any of the exclude patterns."""
|
||||
return any(fnmatch.fnmatch(path, pattern) for pattern in exclude_patterns)
|
||||
|
||||
|
||||
class GitlabConnector(LoadConnector, PollConnector):
|
||||
def __init__(
|
||||
self,
|
||||
project_owner: str,
|
||||
project_name: str,
|
||||
batch_size: int = INDEX_BATCH_SIZE,
|
||||
state_filter: str = "all",
|
||||
include_mrs: bool = True,
|
||||
include_issues: bool = True,
|
||||
include_code_files: bool = False,
|
||||
) -> None:
|
||||
self.project_owner = project_owner
|
||||
self.project_name = project_name
|
||||
self.batch_size = batch_size
|
||||
self.state_filter = state_filter
|
||||
self.include_mrs = include_mrs
|
||||
self.include_issues = include_issues
|
||||
self.include_code_files = include_code_files
|
||||
self.gitlab_client: gitlab.Gitlab | None = None
|
||||
|
||||
def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None:
|
||||
self.gitlab_client = gitlab.Gitlab(
|
||||
credentials["gitlab_url"], private_token=credentials["gitlab_access_token"]
|
||||
)
|
||||
return None
|
||||
|
||||
def validate_connector_settings(self) -> None:
|
||||
if self.gitlab_client is None:
|
||||
raise ConnectorMissingCredentialError("GitLab")
|
||||
|
||||
try:
|
||||
self.gitlab_client.auth()
|
||||
self.gitlab_client.projects.get(
|
||||
f"{self.project_owner}/{self.project_name}",
|
||||
lazy=True,
|
||||
)
|
||||
|
||||
except gitlab.exceptions.GitlabAuthenticationError as e:
|
||||
raise CredentialExpiredError(
|
||||
"Invalid or expired GitLab credentials."
|
||||
) from e
|
||||
|
||||
except gitlab.exceptions.GitlabAuthorizationError as e:
|
||||
raise InsufficientPermissionsError(
|
||||
"Insufficient permissions to access GitLab resources."
|
||||
) from e
|
||||
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
raise ConnectorValidationError(
|
||||
"GitLab project not found or not accessible."
|
||||
) from e
|
||||
|
||||
except Exception as e:
|
||||
raise UnexpectedValidationError(
|
||||
f"Unexpected error while validating GitLab settings: {e}"
|
||||
) from e
|
||||
|
||||
def _fetch_from_gitlab(
|
||||
self, start: datetime | None = None, end: datetime | None = None
|
||||
) -> GenerateDocumentsOutput:
|
||||
if self.gitlab_client is None:
|
||||
raise ConnectorMissingCredentialError("Gitlab")
|
||||
project: Project = self.gitlab_client.projects.get(
|
||||
f"{self.project_owner}/{self.project_name}"
|
||||
)
|
||||
|
||||
start_utc = start.astimezone(timezone.utc) if start else None
|
||||
end_utc = end.astimezone(timezone.utc) if end else None
|
||||
|
||||
# Fetch code files
|
||||
if self.include_code_files:
|
||||
# Fetching using BFS as project.report_tree with recursion causing slow load
|
||||
queue = deque([""]) # Start with the root directory
|
||||
while queue:
|
||||
current_path = queue.popleft()
|
||||
files = project.repository_tree(path=current_path, all=True)
|
||||
for file_batch in _batch_gitlab_objects(files, self.batch_size):
|
||||
code_doc_batch: list[Document] = []
|
||||
for file in file_batch:
|
||||
if _should_exclude(file["path"]):
|
||||
continue
|
||||
|
||||
if file["type"] == "blob":
|
||||
|
||||
doc = _convert_code_to_document(
|
||||
project,
|
||||
file,
|
||||
self.gitlab_client.url,
|
||||
self.project_name,
|
||||
self.project_owner,
|
||||
)
|
||||
|
||||
# Apply incremental window filtering for code files too.
|
||||
if start_utc is not None and doc.doc_updated_at <= start_utc:
|
||||
continue
|
||||
if end_utc is not None and doc.doc_updated_at > end_utc:
|
||||
continue
|
||||
|
||||
code_doc_batch.append(doc)
|
||||
elif file["type"] == "tree":
|
||||
queue.append(file["path"])
|
||||
|
||||
if code_doc_batch:
|
||||
yield code_doc_batch
|
||||
|
||||
if self.include_mrs:
|
||||
merge_requests = project.mergerequests.list(
|
||||
state=self.state_filter,
|
||||
order_by="updated_at",
|
||||
sort="desc",
|
||||
iterator=True,
|
||||
)
|
||||
|
||||
for mr_batch in _batch_gitlab_objects(merge_requests, self.batch_size):
|
||||
mr_doc_batch: list[Document] = []
|
||||
for mr in mr_batch:
|
||||
mr.updated_at = datetime.strptime(
|
||||
mr.updated_at, "%Y-%m-%dT%H:%M:%S.%f%z"
|
||||
)
|
||||
if start_utc is not None and mr.updated_at <= start_utc:
|
||||
yield mr_doc_batch
|
||||
return
|
||||
if end_utc is not None and mr.updated_at > end_utc:
|
||||
continue
|
||||
mr_doc_batch.append(_convert_merge_request_to_document(mr))
|
||||
yield mr_doc_batch
|
||||
|
||||
if self.include_issues:
|
||||
issues = project.issues.list(state=self.state_filter, iterator=True)
|
||||
|
||||
for issue_batch in _batch_gitlab_objects(issues, self.batch_size):
|
||||
issue_doc_batch: list[Document] = []
|
||||
for issue in issue_batch:
|
||||
issue.updated_at = datetime.strptime(
|
||||
issue.updated_at, "%Y-%m-%dT%H:%M:%S.%f%z"
|
||||
)
|
||||
# Avoid re-syncing the last-seen item.
|
||||
if start_utc is not None and issue.updated_at <= start_utc:
|
||||
yield issue_doc_batch
|
||||
return
|
||||
if end_utc is not None and issue.updated_at > end_utc:
|
||||
continue
|
||||
issue_doc_batch.append(_convert_issue_to_document(issue))
|
||||
yield issue_doc_batch
|
||||
|
||||
def load_from_state(self) -> GenerateDocumentsOutput:
|
||||
return self._fetch_from_gitlab()
|
||||
|
||||
def poll_source(
|
||||
self, start: SecondsSinceUnixEpoch, end: SecondsSinceUnixEpoch
|
||||
) -> GenerateDocumentsOutput:
|
||||
start_datetime = datetime.fromtimestamp(start, tz=timezone.utc)
|
||||
end_datetime = datetime.fromtimestamp(end, tz=timezone.utc)
|
||||
return self._fetch_from_gitlab(start_datetime, end_datetime)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
|
||||
connector = GitlabConnector(
|
||||
# gitlab_url="https://gitlab.com/api/v4",
|
||||
project_owner=os.environ["PROJECT_OWNER"],
|
||||
project_name=os.environ["PROJECT_NAME"],
|
||||
batch_size=INDEX_BATCH_SIZE,
|
||||
state_filter="all",
|
||||
include_mrs=True,
|
||||
include_issues=True,
|
||||
include_code_files=True,
|
||||
)
|
||||
|
||||
connector.load_credentials(
|
||||
{
|
||||
"gitlab_access_token": os.environ["GITLAB_ACCESS_TOKEN"],
|
||||
"gitlab_url": os.environ["GITLAB_URL"],
|
||||
}
|
||||
)
|
||||
document_batches = connector.load_from_state()
|
||||
for f in document_batches:
|
||||
print("Batch:", f)
|
||||
print("Finished loading from state.")
|
||||
@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
|
||||
from enum import IntFlag, auto
|
||||
from types import TracebackType
|
||||
from typing import Any, Dict, Generator, TypeVar, Generic, Callable, TypeAlias
|
||||
|
||||
from collections.abc import Iterator
|
||||
from anthropic import BaseModel
|
||||
|
||||
from common.data_source.models import (
|
||||
@ -16,6 +16,7 @@ from common.data_source.models import (
|
||||
SecondsSinceUnixEpoch, GenerateSlimDocumentOutput
|
||||
)
|
||||
|
||||
GenerateDocumentsOutput = Iterator[list[Document]]
|
||||
|
||||
class LoadConnector(ABC):
|
||||
"""Load connector interface"""
|
||||
|
||||
@ -194,19 +194,18 @@ class MessageService:
|
||||
select_fields = ["message_id", "content", "content_embed"]
|
||||
_index_name = index_name(uid)
|
||||
res = settings.msgStoreConn.get_forgotten_messages(select_fields, _index_name, memory_id)
|
||||
if not res:
|
||||
return []
|
||||
message_list = settings.msgStoreConn.get_fields(res, select_fields)
|
||||
current_size = 0
|
||||
ids_to_remove = []
|
||||
for message in message_list.values():
|
||||
if current_size < size_to_delete:
|
||||
current_size += cls.calculate_message_size(message)
|
||||
ids_to_remove.append(message["message_id"])
|
||||
else:
|
||||
if res:
|
||||
message_list = settings.msgStoreConn.get_fields(res, select_fields)
|
||||
for message in message_list.values():
|
||||
if current_size < size_to_delete:
|
||||
current_size += cls.calculate_message_size(message)
|
||||
ids_to_remove.append(message["message_id"])
|
||||
else:
|
||||
return ids_to_remove, current_size
|
||||
if current_size >= size_to_delete:
|
||||
return ids_to_remove, current_size
|
||||
if current_size >= size_to_delete:
|
||||
return ids_to_remove, current_size
|
||||
|
||||
order_by = OrderByExpr()
|
||||
order_by.asc("valid_at")
|
||||
|
||||
@ -150,6 +150,7 @@ dependencies = [
|
||||
# "jinja2>=3.1.0",
|
||||
"pyairtable>=3.3.0",
|
||||
"asana>=5.2.2",
|
||||
"python-gitlab>=7.0.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
||||
@ -38,12 +38,24 @@ from api.db.services.connector_service import ConnectorService, SyncLogsService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from common import settings
|
||||
from common.config_utils import show_configs
|
||||
from common.data_source import BlobStorageConnector, NotionConnector, DiscordConnector, GoogleDriveConnector, MoodleConnector, JiraConnector, DropboxConnector, WebDAVConnector, AirtableConnector, AsanaConnector
|
||||
from common.data_source import (
|
||||
BlobStorageConnector,
|
||||
NotionConnector,
|
||||
DiscordConnector,
|
||||
GoogleDriveConnector,
|
||||
MoodleConnector,
|
||||
JiraConnector,
|
||||
DropboxConnector,
|
||||
WebDAVConnector,
|
||||
AirtableConnector,
|
||||
AsanaConnector,
|
||||
)
|
||||
from common.constants import FileSource, TaskStatus
|
||||
from common.data_source.config import INDEX_BATCH_SIZE
|
||||
from common.data_source.confluence_connector import ConfluenceConnector
|
||||
from common.data_source.gmail_connector import GmailConnector
|
||||
from common.data_source.box_connector import BoxConnector
|
||||
from common.data_source.gitlab_connector import GitlabConnector
|
||||
from common.data_source.interfaces import CheckpointOutputWrapper
|
||||
from common.log_utils import init_root_logger
|
||||
from common.signal_utils import start_tracemalloc_and_snapshot, stop_tracemalloc
|
||||
@ -843,6 +855,47 @@ class Asana(SyncBase):
|
||||
return document_generator
|
||||
|
||||
|
||||
|
||||
class Gitlab(SyncBase):
|
||||
SOURCE_NAME: str = FileSource.GITLAB
|
||||
|
||||
async def _generate(self, task: dict):
|
||||
"""
|
||||
Sync files from GitLab attachments.
|
||||
"""
|
||||
|
||||
self.connector = GitlabConnector(
|
||||
project_owner= self.conf.get("project_owner"),
|
||||
project_name= self.conf.get("project_name"),
|
||||
include_mrs = self.conf.get("include_mrs", False),
|
||||
include_issues = self.conf.get("include_issues", False),
|
||||
include_code_files= self.conf.get("include_code_files", False),
|
||||
)
|
||||
|
||||
self.connector.load_credentials(
|
||||
{
|
||||
"gitlab_access_token": self.conf.get("credentials", {}).get("gitlab_access_token"),
|
||||
"gitlab_url": self.conf.get("credentials", {}).get("gitlab_url"),
|
||||
}
|
||||
)
|
||||
|
||||
if task["reindex"] == "1" or not task["poll_range_start"]:
|
||||
document_generator = self.connector.load_from_state()
|
||||
begin_info = "totally"
|
||||
else:
|
||||
poll_start = task["poll_range_start"]
|
||||
if 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 = "from {}".format(poll_start)
|
||||
logging.info("Connect to Gitlab: ({}) {}".format(self.conf["project_name"], begin_info))
|
||||
return document_generator
|
||||
|
||||
func_factory = {
|
||||
FileSource.S3: S3,
|
||||
FileSource.R2: R2,
|
||||
@ -862,7 +915,8 @@ func_factory = {
|
||||
FileSource.WEBDAV: WebDAV,
|
||||
FileSource.BOX: BOX,
|
||||
FileSource.AIRTABLE: Airtable,
|
||||
FileSource.ASANA: Asana
|
||||
FileSource.GITLAB: Gitlab,
|
||||
FileSource.ASANA: Asana,
|
||||
}
|
||||
|
||||
|
||||
|
||||
15
uv.lock
generated
15
uv.lock
generated
@ -5871,6 +5871,19 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-gitlab"
|
||||
version = "7.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/c4/0b613303b4f0fcda69b3d2e03d0a1fb1b6b079a7c7832e03a8d92461e9fe/python_gitlab-7.0.0.tar.gz", hash = "sha256:e4d934430f64efc09e6208b782c61cc0a3389527765e03ffbef17f4323dce441", size = 400568, upload-time = "2025-10-29T15:06:02.069Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/9e/811edc46a15f8deb828cba7ef8aab3451dc11ca72d033f3df72a5af865d9/python_gitlab-7.0.0-py3-none-any.whl", hash = "sha256:712a6c8c5e79e7e66f6dabb25d8fe7831a6b238d4a5132f8231df6b3b890ceff", size = 144415, upload-time = "2025-10-29T15:06:00.232Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
@ -6178,6 +6191,7 @@ dependencies = [
|
||||
{ name = "pypdf2" },
|
||||
{ name = "python-calamine" },
|
||||
{ name = "python-docx" },
|
||||
{ name = "python-gitlab" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "pywencai" },
|
||||
{ name = "qianfan" },
|
||||
@ -6308,6 +6322,7 @@ requires-dist = [
|
||||
{ name = "pypdf2", specifier = ">=3.0.1,<4.0.0" },
|
||||
{ name = "python-calamine", specifier = ">=0.4.0" },
|
||||
{ name = "python-docx", specifier = ">=1.1.2,<2.0.0" },
|
||||
{ name = "python-gitlab", specifier = ">=7.0.0" },
|
||||
{ name = "python-pptx", specifier = ">=1.0.2,<2.0.0" },
|
||||
{ name = "pywencai", specifier = ">=0.13.1,<1.0.0" },
|
||||
{ name = "qianfan", specifier = "==0.4.6" },
|
||||
|
||||
2
web/src/assets/svg/data-source/gitlab.svg
Normal file
2
web/src/assets/svg/data-source/gitlab.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_gitlab</title><polygon points="16 28.896 16 28.896 21.156 13.029 10.844 13.029 16 28.896" style="fill:#e24329"/><polygon points="16 28.896 10.844 13.029 3.619 13.029 16 28.896" style="fill:#fc6d26"/><path d="M3.619,13.029h0L2.052,17.851a1.067,1.067,0,0,0,.388,1.193L16,28.9,3.619,13.029Z" style="fill:#fca326"/><path d="M3.619,13.029h7.225L7.739,3.473a.534.534,0,0,0-1.015,0L3.619,13.029Z" style="fill:#e24329"/><polygon points="16 28.896 21.156 13.029 28.381 13.029 16 28.896" style="fill:#fc6d26"/><path d="M28.381,13.029h0l1.567,4.822a1.067,1.067,0,0,1-.388,1.193L16,28.9,28.381,13.029Z" style="fill:#fca326"/><path d="M28.381,13.029H21.156l3.105-9.557a.534.534,0,0,1,1.015,0l3.105,9.557Z" style="fill:#e24329"/></svg>
|
||||
|
After Width: | Height: | Size: 946 B |
@ -1,6 +1,7 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
@ -44,6 +45,11 @@ const getNestedValue = (obj: any, path: string) => {
|
||||
}, obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Properties of this field will be treated as static attributes and will be filtered out during form submission.
|
||||
*/
|
||||
export const FilterFormField = 'RAG_DY_STATIC';
|
||||
|
||||
// Field type enumeration
|
||||
export enum FormFieldType {
|
||||
Text = 'text',
|
||||
@ -660,7 +666,6 @@ const DynamicForm = {
|
||||
useMemo(() => {
|
||||
setFields(originFields);
|
||||
}, [originFields]);
|
||||
const schema = useMemo(() => generateSchema(fields), [fields]);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
const value = {
|
||||
@ -729,7 +734,6 @@ const DynamicForm = {
|
||||
...fieldErrors,
|
||||
} as any;
|
||||
|
||||
console.log('combinedErrors', combinedErrors);
|
||||
for (const key in combinedErrors) {
|
||||
if (Array.isArray(combinedErrors[key])) {
|
||||
combinedErrors[key] = combinedErrors[key][0];
|
||||
@ -777,11 +781,61 @@ const DynamicForm = {
|
||||
};
|
||||
}, [fields, form]);
|
||||
|
||||
const filterActiveValues = useCallback(
|
||||
(allValues: any) => {
|
||||
const filteredValues: any = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (
|
||||
!field.shouldRender ||
|
||||
(field.shouldRender(allValues) &&
|
||||
field.name?.indexOf(FilterFormField) < 0)
|
||||
) {
|
||||
const keys = field.name.split('.');
|
||||
let current = allValues;
|
||||
let exists = true;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current && current[key] !== undefined) {
|
||||
current = current[key];
|
||||
} else {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
let target = filteredValues;
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
target = target[key];
|
||||
}
|
||||
target[keys[keys.length - 1]] = getNestedValue(
|
||||
allValues,
|
||||
field.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return filteredValues;
|
||||
},
|
||||
[fields],
|
||||
);
|
||||
|
||||
// Expose form methods via ref
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
submit: form.handleSubmit(onSubmit),
|
||||
submit: () => {
|
||||
form.handleSubmit((values) => {
|
||||
const filteredValues = filterActiveValues(values);
|
||||
onSubmit(filteredValues);
|
||||
})();
|
||||
},
|
||||
getValues: form.getValues,
|
||||
reset: (values?: T) => {
|
||||
if (values) {
|
||||
@ -824,9 +878,9 @@ const DynamicForm = {
|
||||
// }, 0);
|
||||
},
|
||||
}),
|
||||
[form],
|
||||
[form, onSubmit, filterActiveValues],
|
||||
);
|
||||
|
||||
(form as any).filterActiveValues = filterActiveValues;
|
||||
useEffect(() => {
|
||||
if (formDefaultValues && Object.keys(formDefaultValues).length > 0) {
|
||||
form.reset({
|
||||
@ -848,7 +902,10 @@ const DynamicForm = {
|
||||
className={`space-y-6 ${className}`}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
form.handleSubmit(onSubmit)(e);
|
||||
form.handleSubmit((values) => {
|
||||
const filteredValues = filterActiveValues(values);
|
||||
onSubmit(filteredValues);
|
||||
})(e);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
@ -897,10 +954,23 @@ const DynamicForm = {
|
||||
try {
|
||||
let beValid = await form.formControl.trigger();
|
||||
console.log('form valid', beValid, form, form.formControl);
|
||||
if (beValid) {
|
||||
// if (beValid) {
|
||||
// form.handleSubmit(async (values) => {
|
||||
// console.log('form values', values);
|
||||
// submitFunc?.(values);
|
||||
// })();
|
||||
// }
|
||||
|
||||
if (beValid && submitFunc) {
|
||||
form.handleSubmit(async (values) => {
|
||||
console.log('form values', values);
|
||||
submitFunc?.(values);
|
||||
const filteredValues = (form as any).filterActiveValues
|
||||
? (form as any).filterActiveValues(values)
|
||||
: values;
|
||||
console.log(
|
||||
'filtered form values in saving button',
|
||||
filteredValues,
|
||||
);
|
||||
submitFunc(filteredValues);
|
||||
})();
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@ -929,6 +929,8 @@ Beispiel: Virtual Hosted Style`,
|
||||
'Verbinden Sie Ihr Gmail über OAuth, um E-Mails zu synchronisieren.',
|
||||
webdavDescription:
|
||||
'Verbinden Sie sich mit WebDAV-Servern, um Dateien zu synchronisieren.',
|
||||
gitlabDescription:
|
||||
'Verbinden Sie GitLab, um Repositories, Issues, Merge Requests und zugehörige Dokumentation zu synchronisieren.',
|
||||
webdavRemotePathTip:
|
||||
'Optional: Geben Sie einen Ordnerpfad auf dem WebDAV-Server an (z.B. /Dokumente). Lassen Sie das Feld leer, um vom Stammverzeichnis aus zu synchronisieren.',
|
||||
google_driveTokenTip:
|
||||
|
||||
@ -933,6 +933,8 @@ Example: Virtual Hosted Style`,
|
||||
boxDescription: 'Connect your Box drive to sync files and folders.',
|
||||
airtableDescription:
|
||||
'Connect to Airtable and synchronize files from a specified table within a designated workspace.',
|
||||
gitlabDescription:
|
||||
'Connect GitLab to sync repositories, issues, merge requests, and related documentation.',
|
||||
asanaDescription:
|
||||
'Connect to Asana and synchronize files from a specified workspace.',
|
||||
dropboxAccessTokenTip:
|
||||
|
||||
@ -749,6 +749,8 @@ export default {
|
||||
'Подключите ваш диск Box для синхронизации файлов и папок.',
|
||||
airtableDescription:
|
||||
'Подключите Airtable и синхронизируйте файлы из указанной таблицы в заданном рабочем пространстве.',
|
||||
gitlabDescription:
|
||||
'Подключите GitLab для синхронизации репозиториев, задач, merge requests и связанной документации.',
|
||||
asanaDescription:
|
||||
'Подключите Asana и синхронизируйте файлы из рабочего пространства.',
|
||||
google_driveDescription:
|
||||
|
||||
@ -547,6 +547,8 @@ export default {
|
||||
avatar: '头像',
|
||||
avatarTip: '這會在你的個人主頁展示',
|
||||
profileDescription: '在此更新您的照片和個人詳細信息。',
|
||||
gitlabDescription:
|
||||
'連接 GitLab,同步儲存庫、Issue、合併請求(MR)及相關文件內容。',
|
||||
bedrockCredentialsHint:
|
||||
'提示:Access Key / Secret Key 可留空,以啟用 AWS IAM 自動驗證。',
|
||||
awsAuthModeAccessKeySecret: 'Access Key 和 Secret',
|
||||
|
||||
@ -862,6 +862,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
dropboxDescription: '连接 Dropbox,同步指定账号下的文件与文件夹。',
|
||||
boxDescription: '连接你的 Box 云盘以同步文件和文件夹。',
|
||||
airtableDescription: '连接 Airtable,同步指定工作区下指定表格中的文件。',
|
||||
gitlabDescription:
|
||||
'连接 GitLab,同步仓库、Issue、合并请求(MR)及相关文档内容。',
|
||||
asanaDescription: '连接 Asana,同步工作区中的文件。',
|
||||
r2Description: '连接你的 Cloudflare R2 存储桶以导入和同步文件。',
|
||||
dropboxAccessTokenTip:
|
||||
|
||||
@ -24,8 +24,6 @@ export const FormSchema = z.object(VariableAssignerSchema);
|
||||
|
||||
export type VariableAssignerFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
// const outputList = buildOutputList(initialVariableAssignerValues.outputs);
|
||||
|
||||
function VariableAssignerForm({ node }: INextOperatorForm) {
|
||||
const defaultValues = useFormValues(initialDataOperationsValues, node);
|
||||
|
||||
@ -41,10 +39,7 @@ function VariableAssignerForm({ node }: INextOperatorForm) {
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<DynamicVariables name="variables" label="Variables"></DynamicVariables>
|
||||
{/* <Output list={outputList} isFormRequired></Output> */}
|
||||
</FormWrapper>
|
||||
{/* <DevTool control={form.control} placement="top-left" /> */}
|
||||
{/* set up the dev tool */}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,9 +2,7 @@ import { getStructuredDatatype } from '@/utils/canvas-util';
|
||||
import { get, isPlainObject } from 'lodash';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import {
|
||||
AgentDialogueMode,
|
||||
AgentStructuredOutputField,
|
||||
BeginId,
|
||||
JsonSchemaDataType,
|
||||
Operator,
|
||||
} from '../constant';
|
||||
@ -18,94 +16,36 @@ function getNodeId(value: string) {
|
||||
}
|
||||
|
||||
export function useShowSecondaryMenu() {
|
||||
const { getOperatorTypeFromId, getNode } = useGraphStore((state) => state);
|
||||
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||
|
||||
const showSecondaryMenu = useCallback(
|
||||
(value: string, outputLabel: string) => {
|
||||
const nodeId = getNodeId(value);
|
||||
const operatorType = getOperatorTypeFromId(nodeId);
|
||||
|
||||
// For Agent nodes, show secondary menu for 'structured' field
|
||||
if (
|
||||
operatorType === Operator.Agent &&
|
||||
return (
|
||||
getOperatorTypeFromId(nodeId) === Operator.Agent &&
|
||||
outputLabel === AgentStructuredOutputField
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For Begin nodes in webhook mode, show secondary menu for schema properties (body, headers, query, etc.)
|
||||
if (operatorType === Operator.Begin) {
|
||||
const node = getNode(nodeId);
|
||||
const mode = get(node, 'data.form.mode');
|
||||
if (mode === AgentDialogueMode.Webhook) {
|
||||
// Check if this output field is from the schema
|
||||
const outputs = get(node, 'data.form.outputs', {});
|
||||
const outputField = outputs[outputLabel];
|
||||
// Show secondary menu if the field is an object or has properties
|
||||
return (
|
||||
outputField &&
|
||||
(outputField.type === 'object' ||
|
||||
(outputField.properties &&
|
||||
Object.keys(outputField.properties).length > 0))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
);
|
||||
},
|
||||
[getOperatorTypeFromId, getNode],
|
||||
[getOperatorTypeFromId],
|
||||
);
|
||||
|
||||
return showSecondaryMenu;
|
||||
}
|
||||
function useGetBeginOutputsOrSchema() {
|
||||
const { getNode } = useGraphStore((state) => state);
|
||||
|
||||
const getBeginOutputs = useCallback(() => {
|
||||
const node = getNode(BeginId);
|
||||
const outputs = get(node, 'data.form.outputs', {});
|
||||
return outputs;
|
||||
}, [getNode]);
|
||||
|
||||
const getBeginSchema = useCallback(() => {
|
||||
const node = getNode(BeginId);
|
||||
const outputs = get(node, 'data.form.schema', {});
|
||||
return outputs;
|
||||
}, [getNode]);
|
||||
|
||||
return { getBeginOutputs, getBeginSchema };
|
||||
}
|
||||
|
||||
export function useGetStructuredOutputByValue() {
|
||||
const { getNode, getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||
|
||||
const { getBeginOutputs } = useGetBeginOutputsOrSchema();
|
||||
const { getNode } = useGraphStore((state) => state);
|
||||
|
||||
const getStructuredOutput = useCallback(
|
||||
(value: string) => {
|
||||
const nodeId = getNodeId(value);
|
||||
const node = getNode(nodeId);
|
||||
const operatorType = getOperatorTypeFromId(nodeId);
|
||||
const fields = splitValue(value);
|
||||
const outputLabel = fields.at(1);
|
||||
|
||||
let structuredOutput;
|
||||
if (operatorType === Operator.Agent) {
|
||||
structuredOutput = get(
|
||||
node,
|
||||
`data.form.outputs.${AgentStructuredOutputField}`,
|
||||
);
|
||||
} else if (operatorType === Operator.Begin) {
|
||||
// For Begin nodes in webhook mode, get the specific schema property
|
||||
const outputs = getBeginOutputs();
|
||||
if (outputLabel) {
|
||||
structuredOutput = outputs[outputLabel];
|
||||
}
|
||||
}
|
||||
const node = getNode(getNodeId(value));
|
||||
const structuredOutput = get(
|
||||
node,
|
||||
`data.form.outputs.${AgentStructuredOutputField}`,
|
||||
);
|
||||
|
||||
return structuredOutput;
|
||||
},
|
||||
[getBeginOutputs, getNode, getOperatorTypeFromId],
|
||||
[getNode],
|
||||
);
|
||||
|
||||
return getStructuredOutput;
|
||||
@ -126,14 +66,13 @@ export function useFindAgentStructuredOutputLabel() {
|
||||
icon?: ReactNode;
|
||||
}>,
|
||||
) => {
|
||||
// agent structured output
|
||||
const fields = splitValue(value);
|
||||
const operatorType = getOperatorTypeFromId(fields.at(0));
|
||||
|
||||
// Handle Agent structured fields
|
||||
if (
|
||||
operatorType === Operator.Agent &&
|
||||
getOperatorTypeFromId(fields.at(0)) === Operator.Agent &&
|
||||
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
||||
) {
|
||||
// is agent structured output
|
||||
const agentOption = options.find((x) => value.includes(x.value));
|
||||
const jsonSchemaFields = fields
|
||||
.at(1)
|
||||
@ -145,19 +84,6 @@ export function useFindAgentStructuredOutputLabel() {
|
||||
value: value,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Begin webhook fields
|
||||
if (operatorType === Operator.Begin && fields.at(1)) {
|
||||
const fieldOption = options
|
||||
.filter((x) => x.parentLabel === BeginId)
|
||||
.find((x) => value.startsWith(x.value));
|
||||
|
||||
return {
|
||||
...fieldOption,
|
||||
label: fields.at(1),
|
||||
value: value,
|
||||
};
|
||||
}
|
||||
},
|
||||
[getOperatorTypeFromId],
|
||||
);
|
||||
@ -168,7 +94,6 @@ export function useFindAgentStructuredOutputLabel() {
|
||||
export function useFindAgentStructuredOutputTypeByValue() {
|
||||
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||
const filterStructuredOutput = useGetStructuredOutputByValue();
|
||||
const { getBeginSchema } = useGetBeginOutputsOrSchema();
|
||||
|
||||
const findTypeByValue = useCallback(
|
||||
(
|
||||
@ -211,12 +136,10 @@ export function useFindAgentStructuredOutputTypeByValue() {
|
||||
}
|
||||
const fields = splitValue(value);
|
||||
const nodeId = fields.at(0);
|
||||
const operatorType = getOperatorTypeFromId(nodeId);
|
||||
const jsonSchema = filterStructuredOutput(value);
|
||||
|
||||
// Handle Agent structured fields
|
||||
if (
|
||||
operatorType === Operator.Agent &&
|
||||
getOperatorTypeFromId(nodeId) === Operator.Agent &&
|
||||
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
||||
) {
|
||||
const jsonSchemaFields = fields
|
||||
@ -228,32 +151,13 @@ export function useFindAgentStructuredOutputTypeByValue() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Begin webhook fields (body, headers, query, etc.)
|
||||
if (operatorType === Operator.Begin) {
|
||||
const outputLabel = fields.at(1);
|
||||
const schema = getBeginSchema();
|
||||
if (outputLabel && schema) {
|
||||
const jsonSchemaFields = fields.at(1);
|
||||
if (jsonSchemaFields) {
|
||||
const type = findTypeByValue(schema, jsonSchemaFields);
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
filterStructuredOutput,
|
||||
findTypeByValue,
|
||||
getBeginSchema,
|
||||
getOperatorTypeFromId,
|
||||
],
|
||||
[filterStructuredOutput, findTypeByValue, getOperatorTypeFromId],
|
||||
);
|
||||
|
||||
return findAgentStructuredOutputTypeByValue;
|
||||
}
|
||||
|
||||
// TODO: Consider merging with useFindAgentStructuredOutputLabel
|
||||
export function useFindAgentStructuredOutputLabelByValue() {
|
||||
const { getNode } = useGraphStore((state) => state);
|
||||
|
||||
|
||||
@ -318,8 +318,7 @@ export function useFilterQueryVariableOptionsByTypes({
|
||||
isAgentStructured(
|
||||
y.value,
|
||||
y.value.slice(-AgentStructuredOutputField.length),
|
||||
) ||
|
||||
y.value.startsWith(BeginId), // begin node outputs
|
||||
),
|
||||
),
|
||||
};
|
||||
})
|
||||
|
||||
@ -25,6 +25,7 @@ export enum DataSourceKey {
|
||||
OCI_STORAGE = 'oci_storage',
|
||||
GOOGLE_CLOUD_STORAGE = 'google_cloud_storage',
|
||||
AIRTABLE = 'airtable',
|
||||
GITLAB = 'gitlab',
|
||||
ASANA = 'asana',
|
||||
// SHAREPOINT = 'sharepoint',
|
||||
// SLACK = 'slack',
|
||||
@ -110,6 +111,11 @@ export const generateDataSourceInfo = (t: TFunction) => {
|
||||
description: t(`setting.${DataSourceKey.AIRTABLE}Description`),
|
||||
icon: <SvgIcon name={'data-source/airtable'} width={38} />,
|
||||
},
|
||||
[DataSourceKey.GITLAB]: {
|
||||
name: 'GitLab',
|
||||
description: t(`setting.${DataSourceKey.GITLAB}Description`),
|
||||
icon: <SvgIcon name={'data-source/gitlab'} width={38} />,
|
||||
},
|
||||
[DataSourceKey.ASANA]: {
|
||||
name: 'Asana',
|
||||
description: t(`setting.${DataSourceKey.ASANA}Description`),
|
||||
@ -658,6 +664,54 @@ export const DataSourceFormFields = {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
[DataSourceKey.GITLAB]: [
|
||||
{
|
||||
label: 'Project Owner',
|
||||
name: 'config.project_owner',
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'Project Name',
|
||||
name: 'config.project_name',
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'GitLab Personal Access Token',
|
||||
name: 'config.credentials.gitlab_access_token',
|
||||
type: FormFieldType.Password,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'GitLab URL',
|
||||
name: 'config.gitlab_url',
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: 'https://gitlab.com',
|
||||
},
|
||||
{
|
||||
label: 'include Merge Requests',
|
||||
name: 'config.include_mrs',
|
||||
type: FormFieldType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
label: 'include Issues',
|
||||
name: 'config.include_issues',
|
||||
type: FormFieldType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
label: 'include Code Files',
|
||||
name: 'config.include_code_files',
|
||||
type: FormFieldType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
[DataSourceKey.ASANA]: [
|
||||
{
|
||||
label: 'API Token',
|
||||
@ -693,7 +747,6 @@ export const DataSourceFormDefaultValues = {
|
||||
config: {
|
||||
bucket_name: '',
|
||||
bucket_type: 's3',
|
||||
authMode: 'access_key',
|
||||
prefix: '',
|
||||
credentials: {
|
||||
aws_access_key_id: '',
|
||||
@ -883,6 +936,21 @@ export const DataSourceFormDefaultValues = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[DataSourceKey.GITLAB]: {
|
||||
name: '',
|
||||
source: DataSourceKey.GITLAB,
|
||||
config: {
|
||||
project_owner: '',
|
||||
project_name: '',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
include_mrs: true,
|
||||
include_issues: true,
|
||||
include_code_files: true,
|
||||
credentials: {
|
||||
gitlab_access_token: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
[DataSourceKey.ASANA]: {
|
||||
name: '',
|
||||
source: DataSourceKey.ASANA,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FormFieldType } from '@/components/dynamic-form';
|
||||
import { FilterFormField, FormFieldType } from '@/components/dynamic-form';
|
||||
import { TFunction } from 'i18next';
|
||||
import { BedrockRegionList } from '../../setting-model/constant';
|
||||
|
||||
@ -50,7 +50,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
},
|
||||
{
|
||||
label: 'Authentication',
|
||||
name: 'config.authMode',
|
||||
name: 'config.credentials.authentication_method',
|
||||
type: FormFieldType.Segmented,
|
||||
options: [
|
||||
{ label: 'Access Key', value: 'access_key' },
|
||||
@ -67,7 +67,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
label: 'AWS Access Key ID',
|
||||
type: FormFieldType.Text,
|
||||
customValidate: (val: string, formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
console.log('authMode', authMode, val);
|
||||
if (
|
||||
@ -79,7 +79,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
return true;
|
||||
},
|
||||
shouldRender: (formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
return authMode === 'access_key' || bucketType === 's3_compatible';
|
||||
},
|
||||
@ -89,7 +89,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
label: 'AWS Secret Access Key',
|
||||
type: FormFieldType.Password,
|
||||
customValidate: (val: string, formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
if (authMode === 'access_key' || bucketType === 's3_compatible') {
|
||||
return Boolean(val) || '"AWS Secret Access Key" is required';
|
||||
@ -97,7 +97,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
return true;
|
||||
},
|
||||
shouldRender: (formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
return authMode === 'access_key' || bucketType === 's3_compatible';
|
||||
},
|
||||
@ -109,7 +109,7 @@ export const S3Constant = (t: TFunction) => [
|
||||
type: FormFieldType.Text,
|
||||
placeholder: 'arn:aws:iam::123456789012:role/YourRole',
|
||||
customValidate: (val: string, formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
if (authMode === 'iam_role' || bucketType === 's3') {
|
||||
return Boolean(val) || '"AWS Secret Access Key" is required';
|
||||
@ -117,17 +117,17 @@ export const S3Constant = (t: TFunction) => [
|
||||
return true;
|
||||
},
|
||||
shouldRender: (formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
return authMode === 'iam_role' && bucketType === 's3';
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'static.tip',
|
||||
name: FilterFormField + '.tip',
|
||||
label: ' ',
|
||||
type: FormFieldType.Custom,
|
||||
shouldRender: (formValues: any) => {
|
||||
const authMode = formValues?.config?.authMode;
|
||||
const authMode = formValues?.config?.credentials?.authentication_method;
|
||||
const bucketType = formValues?.config?.bucket_type;
|
||||
return authMode === 'assume_role' && bucketType === 's3';
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user