Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f93e28d75 | ||
|
|
6ffedcd8f0 | ||
|
|
26358e971d | ||
|
|
effd5449bc | ||
|
|
a608e23613 | ||
|
|
be122ac974 | ||
|
|
1fe585fcdd | ||
|
|
2d034c57da | ||
|
|
2c102c9b34 | ||
|
|
4f3113febb | ||
|
|
dfd85c6d11 | ||
|
|
3f330c0b80 | ||
|
|
c379448673 | ||
|
|
05ed049cc8 | ||
|
|
37d47983a1 |
@@ -76,16 +76,16 @@ jobs:
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
cache: yarn
|
||||
cache-dependency-path: ./web/package.json
|
||||
|
||||
- name: Web dependencies
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Web style check
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: pnpm run lint
|
||||
run: yarn run lint
|
||||
|
||||
|
||||
superlinter:
|
||||
|
||||
@@ -32,10 +32,10 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: ''
|
||||
cache-dependency-path: 'pnpm-lock.yaml'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
run: yarn install
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
run: yarn test
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run npm script
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
cache: yarn
|
||||
cache-dependency-path: ./web/package.json
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: pnpm test
|
||||
run: yarn test
|
||||
|
||||
@@ -175,6 +175,8 @@ docker/volumes/pgvector/data/*
|
||||
docker/volumes/pgvecto_rs/data/*
|
||||
|
||||
docker/nginx/conf.d/default.conf
|
||||
docker/nginx/ssl/*
|
||||
!docker/nginx/ssl/.gitkeep
|
||||
docker/middleware.env
|
||||
|
||||
sdks/python-client/build
|
||||
|
||||
@@ -6,8 +6,9 @@ Dify is licensed under the Apache License 2.0, with the following additional con
|
||||
|
||||
a. Multi-tenant service: Unless explicitly authorized by Dify in writing, you may not use the Dify source code to operate a multi-tenant environment.
|
||||
- Tenant Definition: Within the context of Dify, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations.
|
||||
|
||||
b. LOGO and copyright information: In the process of using Dify's frontend components, you may not remove or modify the LOGO or copyright information in the Dify console or applications. This restriction is inapplicable to uses of Dify that do not involve its frontend components.
|
||||
|
||||
b. LOGO and copyright information: In the process of using Dify's frontend, you may not remove or modify the LOGO or copyright information in the Dify console or applications. This restriction is inapplicable to uses of Dify that do not involve its frontend.
|
||||
- Frontend Definition: For the purposes of this license, the "frontend" of Dify includes all components located in the `web/` directory when running Dify from the raw source code, or the "web" image when running Dify with Docker.
|
||||
|
||||
Please contact business@dify.ai by email to inquire about licensing matters.
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ DB_DATABASE=dify
|
||||
|
||||
# Storage configuration
|
||||
# use for store upload files, private keys...
|
||||
# storage type: local, s3, azure-blob, google-storage, tencent-cos, huawei-obs, volcengine-tos, baidu-obs, supabase
|
||||
# storage type: local, s3, aliyun-oss, azure-blob, baidu-obs, google-storage, huawei-obs, oci-storage, tencent-cos, volcengine-tos, supabase
|
||||
STORAGE_TYPE=local
|
||||
STORAGE_LOCAL_PATH=storage
|
||||
S3_USE_AWS_MANAGED_IAM=false
|
||||
|
||||
@@ -20,6 +20,7 @@ from app_factory import create_app
|
||||
|
||||
# DO NOT REMOVE BELOW
|
||||
from events import event_handlers # noqa: F401
|
||||
from extensions.ext_database import db
|
||||
|
||||
# TODO: Find a way to avoid importing models here
|
||||
from models import account, dataset, model, source, task, tool, tools, web # noqa: F401
|
||||
|
||||
@@ -35,7 +35,8 @@ from configs.middleware.vdb.weaviate_config import WeaviateConfig
|
||||
class StorageConfig(BaseSettings):
|
||||
STORAGE_TYPE: str = Field(
|
||||
description="Type of storage to use."
|
||||
" Options: 'local', 's3', 'azure-blob', 'aliyun-oss', 'google-storage'. Default is 'local'.",
|
||||
" Options: 'local', 's3', 'aliyun-oss', 'azure-blob', 'baidu-obs', 'google-storage', 'huawei-obs', "
|
||||
"'oci-storage', 'tencent-cos', 'volcengine-tos', 'supabase'. Default is 'local'.",
|
||||
default="local",
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ help:
|
||||
en_US: https://console.groq.com/
|
||||
supported_model_types:
|
||||
- llm
|
||||
- speech2text
|
||||
configurate_methods:
|
||||
- predefined-model
|
||||
provider_credential_schema:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
model: llama-3.2-11b-vision-preview
|
||||
label:
|
||||
zh_Hans: Llama 3.2 11B Vision (Preview)
|
||||
en_US: Llama 3.2 11B Vision (Preview)
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 131072
|
||||
parameter_rules:
|
||||
- name: temperature
|
||||
use_template: temperature
|
||||
- name: top_p
|
||||
use_template: top_p
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
default: 512
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.05'
|
||||
output: '0.1'
|
||||
unit: '0.000001'
|
||||
currency: USD
|
||||
@@ -0,0 +1,26 @@
|
||||
model: llama-3.2-90b-vision-preview
|
||||
label:
|
||||
zh_Hans: Llama 3.2 90B Vision (Preview)
|
||||
en_US: Llama 3.2 90B Vision (Preview)
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 131072
|
||||
parameter_rules:
|
||||
- name: temperature
|
||||
use_template: temperature
|
||||
- name: top_p
|
||||
use_template: top_p
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
default: 512
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.05'
|
||||
output: '0.1'
|
||||
unit: '0.000001'
|
||||
currency: USD
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
model: distil-whisper-large-v3-en
|
||||
model_type: speech2text
|
||||
model_properties:
|
||||
file_upload_limit: 1
|
||||
supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm
|
||||
@@ -0,0 +1,30 @@
|
||||
from typing import IO, Optional
|
||||
|
||||
from core.model_runtime.model_providers.openai_api_compatible.speech2text.speech2text import OAICompatSpeech2TextModel
|
||||
|
||||
|
||||
class GroqSpeech2TextModel(OAICompatSpeech2TextModel):
|
||||
"""
|
||||
Model class for Groq Speech to text model.
|
||||
"""
|
||||
|
||||
def _invoke(self, model: str, credentials: dict, file: IO[bytes], user: Optional[str] = None) -> str:
|
||||
"""
|
||||
Invoke speech2text model
|
||||
|
||||
:param model: model name
|
||||
:param credentials: model credentials
|
||||
:param file: audio file
|
||||
:param user: unique user id
|
||||
:return: text for given audio file
|
||||
"""
|
||||
self._add_custom_parameters(credentials)
|
||||
return super()._invoke(model, credentials, file)
|
||||
|
||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
||||
self._add_custom_parameters(credentials)
|
||||
return super().validate_credentials(model, credentials)
|
||||
|
||||
@classmethod
|
||||
def _add_custom_parameters(cls, credentials: dict) -> None:
|
||||
credentials["endpoint_url"] = "https://api.groq.com/openai/v1"
|
||||
@@ -0,0 +1,5 @@
|
||||
model: whisper-large-v3-turbo
|
||||
model_type: speech2text
|
||||
model_properties:
|
||||
file_upload_limit: 1
|
||||
supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm
|
||||
@@ -0,0 +1,5 @@
|
||||
model: whisper-large-v3
|
||||
model_type: speech2text
|
||||
model_properties:
|
||||
file_upload_limit: 1
|
||||
supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm
|
||||
+14
@@ -8,6 +8,7 @@ supported_model_types:
|
||||
- llm
|
||||
- text-embedding
|
||||
- speech2text
|
||||
- rerank
|
||||
configurate_methods:
|
||||
- customizable-model
|
||||
model_credential_schema:
|
||||
@@ -83,6 +84,19 @@ model_credential_schema:
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的模型上下文长度
|
||||
en_US: Enter your Model context size
|
||||
- variable: context_size
|
||||
label:
|
||||
zh_Hans: 模型上下文长度
|
||||
en_US: Model context size
|
||||
required: true
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: rerank
|
||||
type: text-input
|
||||
default: '4096'
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的模型上下文长度
|
||||
en_US: Enter your Model context size
|
||||
- variable: max_tokens_to_sample
|
||||
label:
|
||||
zh_Hans: 最大 token 上限
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
from json import dumps
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from requests import post
|
||||
from yarl import URL
|
||||
|
||||
from core.model_runtime.entities.common_entities import I18nObject
|
||||
from core.model_runtime.entities.model_entities import AIModelEntity, FetchFrom, ModelType
|
||||
from core.model_runtime.entities.rerank_entities import RerankDocument, RerankResult
|
||||
from core.model_runtime.errors.invoke import (
|
||||
InvokeAuthorizationError,
|
||||
InvokeBadRequestError,
|
||||
InvokeConnectionError,
|
||||
InvokeError,
|
||||
InvokeRateLimitError,
|
||||
InvokeServerUnavailableError,
|
||||
)
|
||||
from core.model_runtime.errors.validate import CredentialsValidateFailedError
|
||||
from core.model_runtime.model_providers.__base.rerank_model import RerankModel
|
||||
|
||||
|
||||
class OAICompatRerankModel(RerankModel):
|
||||
"""
|
||||
rerank model API is compatible with Jina rerank model API. So copy the JinaRerankModel class code here.
|
||||
we need enhance for llama.cpp , which return raw score, not normalize score 0~1. It seems Dify need it
|
||||
"""
|
||||
|
||||
def _invoke(
|
||||
self,
|
||||
model: str,
|
||||
credentials: dict,
|
||||
query: str,
|
||||
docs: list[str],
|
||||
score_threshold: Optional[float] = None,
|
||||
top_n: Optional[int] = None,
|
||||
user: Optional[str] = None,
|
||||
) -> RerankResult:
|
||||
"""
|
||||
Invoke rerank model
|
||||
|
||||
:param model: model name
|
||||
:param credentials: model credentials
|
||||
:param query: search query
|
||||
:param docs: docs for reranking
|
||||
:param score_threshold: score threshold
|
||||
:param top_n: top n documents to return
|
||||
:param user: unique user id
|
||||
:return: rerank result
|
||||
"""
|
||||
if len(docs) == 0:
|
||||
return RerankResult(model=model, docs=[])
|
||||
|
||||
server_url = credentials["endpoint_url"]
|
||||
model_name = model
|
||||
|
||||
if not server_url:
|
||||
raise CredentialsValidateFailedError("server_url is required")
|
||||
if not model_name:
|
||||
raise CredentialsValidateFailedError("model_name is required")
|
||||
|
||||
url = server_url
|
||||
headers = {"Authorization": f"Bearer {credentials.get('api_key')}", "Content-Type": "application/json"}
|
||||
|
||||
# TODO: Do we need truncate docs to avoid llama.cpp return error?
|
||||
|
||||
data = {"model": model_name, "query": query, "documents": docs, "top_n": top_n}
|
||||
|
||||
try:
|
||||
response = post(str(URL(url) / "rerank"), headers=headers, data=dumps(data), timeout=60)
|
||||
response.raise_for_status()
|
||||
results = response.json()
|
||||
|
||||
rerank_documents = []
|
||||
scores = [result["relevance_score"] for result in results["results"]]
|
||||
|
||||
# Min-Max Normalization: Normalize scores to 0 ~ 1.0 range
|
||||
min_score = min(scores)
|
||||
max_score = max(scores)
|
||||
score_range = max_score - min_score if max_score != min_score else 1.0 # Avoid division by zero
|
||||
|
||||
for result in results["results"]:
|
||||
index = result["index"]
|
||||
|
||||
# Retrieve document text (fallback if llama.cpp rerank doesn't return it)
|
||||
text = result.get("document", {}).get("text", docs[index])
|
||||
|
||||
# Normalize the score
|
||||
normalized_score = (result["relevance_score"] - min_score) / score_range
|
||||
|
||||
# Create RerankDocument object with normalized score
|
||||
rerank_document = RerankDocument(
|
||||
index=index,
|
||||
text=text,
|
||||
score=normalized_score,
|
||||
)
|
||||
|
||||
# Apply threshold (if defined)
|
||||
if score_threshold is None or normalized_score >= score_threshold:
|
||||
rerank_documents.append(rerank_document)
|
||||
|
||||
# Sort rerank_documents by normalized score in descending order
|
||||
rerank_documents.sort(key=lambda doc: doc.score, reverse=True)
|
||||
|
||||
return RerankResult(model=model, docs=rerank_documents)
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise InvokeServerUnavailableError(str(e))
|
||||
|
||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
||||
"""
|
||||
Validate model credentials
|
||||
|
||||
:param model: model name
|
||||
:param credentials: model credentials
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
self._invoke(
|
||||
model=model,
|
||||
credentials=credentials,
|
||||
query="What is the capital of the United States?",
|
||||
docs=[
|
||||
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
|
||||
"Census, Carson City had a population of 55,274.",
|
||||
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
|
||||
"are a political division controlled by the United States. Its capital is Saipan.",
|
||||
],
|
||||
score_threshold=0.8,
|
||||
)
|
||||
except Exception as ex:
|
||||
raise CredentialsValidateFailedError(str(ex))
|
||||
|
||||
@property
|
||||
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
|
||||
"""
|
||||
Map model invoke error to unified error
|
||||
"""
|
||||
return {
|
||||
InvokeConnectionError: [httpx.ConnectError],
|
||||
InvokeServerUnavailableError: [httpx.RemoteProtocolError],
|
||||
InvokeRateLimitError: [],
|
||||
InvokeAuthorizationError: [httpx.HTTPStatusError],
|
||||
InvokeBadRequestError: [httpx.RequestError],
|
||||
}
|
||||
|
||||
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity:
|
||||
"""
|
||||
generate custom model entities from credentials
|
||||
"""
|
||||
entity = AIModelEntity(
|
||||
model=model,
|
||||
label=I18nObject(en_US=model),
|
||||
model_type=ModelType.RERANK,
|
||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
||||
model_properties={},
|
||||
)
|
||||
|
||||
return entity
|
||||
@@ -2,20 +2,15 @@ from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from core.model_runtime.entities.common_entities import I18nObject
|
||||
from core.model_runtime.entities.model_entities import AIModelEntity, FetchFrom, ModelPropertyKey, ModelType
|
||||
from core.model_runtime.entities.rerank_entities import RerankDocument, RerankResult
|
||||
from core.model_runtime.errors.invoke import (
|
||||
InvokeAuthorizationError,
|
||||
InvokeBadRequestError,
|
||||
InvokeConnectionError,
|
||||
InvokeError,
|
||||
InvokeRateLimitError,
|
||||
InvokeServerUnavailableError,
|
||||
)
|
||||
from core.model_runtime.errors.invoke import InvokeError
|
||||
from core.model_runtime.errors.validate import CredentialsValidateFailedError
|
||||
from core.model_runtime.model_providers.__base.rerank_model import RerankModel
|
||||
from core.model_runtime.model_providers.wenxin._common import _CommonWenxin
|
||||
from core.model_runtime.model_providers.wenxin.wenxin_errors import (
|
||||
InternalServerError,
|
||||
invoke_error_mapping,
|
||||
)
|
||||
|
||||
|
||||
class WenxinRerank(_CommonWenxin):
|
||||
@@ -32,7 +27,7 @@ class WenxinRerank(_CommonWenxin):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise InvokeServerUnavailableError(str(e))
|
||||
raise InternalServerError(str(e))
|
||||
|
||||
|
||||
class WenxinRerankModel(RerankModel):
|
||||
@@ -93,7 +88,7 @@ class WenxinRerankModel(RerankModel):
|
||||
|
||||
return RerankResult(model=model, docs=rerank_documents)
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise InvokeServerUnavailableError(str(e))
|
||||
raise InternalServerError(str(e))
|
||||
|
||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
||||
"""
|
||||
@@ -124,24 +119,4 @@ class WenxinRerankModel(RerankModel):
|
||||
"""
|
||||
Map model invoke error to unified error
|
||||
"""
|
||||
return {
|
||||
InvokeConnectionError: [httpx.ConnectError],
|
||||
InvokeServerUnavailableError: [httpx.RemoteProtocolError],
|
||||
InvokeRateLimitError: [],
|
||||
InvokeAuthorizationError: [httpx.HTTPStatusError],
|
||||
InvokeBadRequestError: [httpx.RequestError],
|
||||
}
|
||||
|
||||
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity:
|
||||
"""
|
||||
generate custom model entities from credentials
|
||||
"""
|
||||
entity = AIModelEntity(
|
||||
model=model,
|
||||
label=I18nObject(en_US=model),
|
||||
model_type=ModelType.RERANK,
|
||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
||||
model_properties={ModelPropertyKey.CONTEXT_SIZE: int(credentials.get("context_size"))},
|
||||
)
|
||||
|
||||
return entity
|
||||
return invoke_error_mapping()
|
||||
|
||||
@@ -4,12 +4,22 @@ from urllib.parse import urlparse
|
||||
|
||||
import tiktoken
|
||||
|
||||
from core.model_runtime.entities.llm_entities import LLMResult
|
||||
from core.model_runtime.entities.common_entities import I18nObject
|
||||
from core.model_runtime.entities.llm_entities import LLMMode, LLMResult
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
PromptMessage,
|
||||
PromptMessageTool,
|
||||
SystemPromptMessage,
|
||||
)
|
||||
from core.model_runtime.entities.model_entities import (
|
||||
AIModelEntity,
|
||||
FetchFrom,
|
||||
ModelFeature,
|
||||
ModelPropertyKey,
|
||||
ModelType,
|
||||
ParameterRule,
|
||||
ParameterType,
|
||||
)
|
||||
from core.model_runtime.model_providers.openai.llm.llm import OpenAILargeLanguageModel
|
||||
|
||||
|
||||
@@ -125,3 +135,58 @@ class YiLargeLanguageModel(OpenAILargeLanguageModel):
|
||||
else:
|
||||
parsed_url = urlparse(credentials["endpoint_url"])
|
||||
credentials["openai_api_base"] = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
|
||||
return AIModelEntity(
|
||||
model=model,
|
||||
label=I18nObject(en_US=model, zh_Hans=model),
|
||||
model_type=ModelType.LLM,
|
||||
features=[ModelFeature.TOOL_CALL, ModelFeature.MULTI_TOOL_CALL, ModelFeature.STREAM_TOOL_CALL]
|
||||
if credentials.get("function_calling_type") == "tool_call"
|
||||
else [],
|
||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
||||
model_properties={
|
||||
ModelPropertyKey.CONTEXT_SIZE: int(credentials.get("context_size", 8000)),
|
||||
ModelPropertyKey.MODE: LLMMode.CHAT.value,
|
||||
},
|
||||
parameter_rules=[
|
||||
ParameterRule(
|
||||
name="temperature",
|
||||
use_template="temperature",
|
||||
label=I18nObject(en_US="Temperature", zh_Hans="温度"),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
ParameterRule(
|
||||
name="max_tokens",
|
||||
use_template="max_tokens",
|
||||
default=512,
|
||||
min=1,
|
||||
max=int(credentials.get("max_tokens", 8192)),
|
||||
label=I18nObject(
|
||||
en_US="Max Tokens", zh_Hans="指定生成结果长度的上限。如果生成结果截断,可以调大该参数"
|
||||
),
|
||||
type=ParameterType.INT,
|
||||
),
|
||||
ParameterRule(
|
||||
name="top_p",
|
||||
use_template="top_p",
|
||||
label=I18nObject(
|
||||
en_US="Top P",
|
||||
zh_Hans="控制生成结果的随机性。数值越小,随机性越弱;数值越大,随机性越强。",
|
||||
),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
ParameterRule(
|
||||
name="top_k",
|
||||
use_template="top_k",
|
||||
label=I18nObject(en_US="Top K", zh_Hans="取样数量"),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
ParameterRule(
|
||||
name="frequency_penalty",
|
||||
use_template="frequency_penalty",
|
||||
label=I18nObject(en_US="Frequency Penalty", zh_Hans="重复惩罚"),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ supported_model_types:
|
||||
- llm
|
||||
configurate_methods:
|
||||
- predefined-model
|
||||
- customizable-model
|
||||
provider_credential_schema:
|
||||
credential_form_schemas:
|
||||
- variable: api_key
|
||||
@@ -39,3 +40,57 @@ provider_credential_schema:
|
||||
placeholder:
|
||||
zh_Hans: Base URL, e.g. https://api.lingyiwanwu.com/v1
|
||||
en_US: Base URL, e.g. https://api.lingyiwanwu.com/v1
|
||||
model_credential_schema:
|
||||
model:
|
||||
label:
|
||||
en_US: Model Name
|
||||
zh_Hans: 模型名称
|
||||
placeholder:
|
||||
en_US: Enter your model name
|
||||
zh_Hans: 输入模型名称
|
||||
credential_form_schemas:
|
||||
- variable: api_key
|
||||
label:
|
||||
en_US: API Key
|
||||
type: secret-input
|
||||
required: true
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
- variable: context_size
|
||||
label:
|
||||
zh_Hans: 模型上下文长度
|
||||
en_US: Model context size
|
||||
required: true
|
||||
type: text-input
|
||||
default: '4096'
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的模型上下文长度
|
||||
en_US: Enter your Model context size
|
||||
- variable: max_tokens
|
||||
label:
|
||||
zh_Hans: 最大 token 上限
|
||||
en_US: Upper bound for max tokens
|
||||
default: '4096'
|
||||
type: text-input
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- variable: function_calling_type
|
||||
label:
|
||||
en_US: Function calling
|
||||
type: select
|
||||
required: false
|
||||
default: no_call
|
||||
options:
|
||||
- value: no_call
|
||||
label:
|
||||
en_US: Not Support
|
||||
zh_Hans: 不支持
|
||||
- value: function_call
|
||||
label:
|
||||
en_US: Support
|
||||
zh_Hans: 支持
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
- vectorizer
|
||||
- qrcode
|
||||
- tianditu
|
||||
- aliyuque
|
||||
- google_translate
|
||||
- hap
|
||||
- json_process
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,19 @@
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class AliYuqueProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
token = credentials.get("token")
|
||||
if not token:
|
||||
raise ToolProviderCredentialValidationError("token is required")
|
||||
|
||||
try:
|
||||
resp = AliYuqueTool.auth(token)
|
||||
if resp and resp.get("data", {}).get("id"):
|
||||
return
|
||||
|
||||
raise ToolProviderCredentialValidationError(resp)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
@@ -0,0 +1,29 @@
|
||||
identity:
|
||||
author: 佐井
|
||||
name: aliyuque
|
||||
label:
|
||||
en_US: yuque
|
||||
zh_Hans: 语雀
|
||||
pt_BR: yuque
|
||||
description:
|
||||
en_US: Yuque, https://www.yuque.com.
|
||||
zh_Hans: 语雀,https://www.yuque.com。
|
||||
pt_BR: Yuque, https://www.yuque.com.
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- productivity
|
||||
- search
|
||||
credentials_for_provider:
|
||||
token:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Yuque Team Token
|
||||
zh_Hans: 语雀团队Token
|
||||
placeholder:
|
||||
en_US: Please input your Yuque team token
|
||||
zh_Hans: 请输入你的语雀团队Token
|
||||
help:
|
||||
en_US: Get Alibaba Yuque team token
|
||||
zh_Hans: 先获取语雀团队Token
|
||||
url: https://www.yuque.com/settings/tokens
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
语雀客户端
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-01 09:45:20"
|
||||
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class AliYuqueTool:
|
||||
# yuque service url
|
||||
server_url = "https://www.yuque.com"
|
||||
|
||||
@staticmethod
|
||||
def auth(token):
|
||||
session = requests.Session()
|
||||
session.headers.update({"Accept": "application/json", "X-Auth-Token": token})
|
||||
login = session.request("GET", AliYuqueTool.server_url + "/api/v2/user")
|
||||
login.raise_for_status()
|
||||
resp = login.json()
|
||||
return resp
|
||||
|
||||
def request(self, method: str, token, tool_parameters: dict[str, Any], path: str) -> str:
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
session = requests.Session()
|
||||
session.headers.update({"accept": "application/json", "X-Auth-Token": token})
|
||||
new_params = {**tool_parameters}
|
||||
# 找出需要替换的变量
|
||||
replacements = {k: v for k, v in new_params.items() if f"{{{k}}}" in path}
|
||||
|
||||
# 替换 path 中的变量
|
||||
for key, value in replacements.items():
|
||||
path = path.replace(f"{{{key}}}", str(value))
|
||||
del new_params[key] # 从 kwargs 中删除已经替换的变量
|
||||
# 请求接口
|
||||
if method.upper() in {"POST", "PUT"}:
|
||||
session.headers.update(
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
)
|
||||
response = session.request(method.upper(), self.server_url + path, json=new_params)
|
||||
else:
|
||||
response = session.request(method, self.server_url + path, params=new_params)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
创建文档
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-01 10:45:20"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueCreateDocumentTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(self.request("POST", token, tool_parameters, "/api/v2/repos/{book_id}/docs"))
|
||||
@@ -0,0 +1,99 @@
|
||||
identity:
|
||||
name: aliyuque_create_document
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Create Document
|
||||
zh_Hans: 创建文档
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Creates a new document within a knowledge base without automatic addition to the table of contents. Requires a subsequent call to the "knowledge base directory update API". Supports setting visibility, format, and content. # 接口英文描述
|
||||
zh_Hans: 在知识库中创建新文档,但不会自动加入目录,需额外调用“知识库目录更新接口”。允许设置公开性、格式及正文内容。
|
||||
llm: Creates docs in a KB.
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Knowledge Base ID
|
||||
zh_Hans: 知识库ID
|
||||
human_description:
|
||||
en_US: The unique identifier of the knowledge base where the document will be created.
|
||||
zh_Hans: 文档将被创建的知识库的唯一标识。
|
||||
llm_description: ID of the target knowledge base.
|
||||
|
||||
- name: title
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Title
|
||||
zh_Hans: 标题
|
||||
human_description:
|
||||
en_US: The title of the document, defaults to 'Untitled' if not provided.
|
||||
zh_Hans: 文档标题,默认为'无标题'如未提供。
|
||||
llm_description: Title of the document, defaults to 'Untitled'.
|
||||
|
||||
- name: public
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
options:
|
||||
- value: 0
|
||||
label:
|
||||
en_US: Private
|
||||
zh_Hans: 私密
|
||||
- value: 1
|
||||
label:
|
||||
en_US: Public
|
||||
zh_Hans: 公开
|
||||
- value: 2
|
||||
label:
|
||||
en_US: Enterprise-only
|
||||
zh_Hans: 企业内公开
|
||||
label:
|
||||
en_US: Visibility
|
||||
zh_Hans: 公开性
|
||||
human_description:
|
||||
en_US: Document visibility (0 Private, 1 Public, 2 Enterprise-only).
|
||||
zh_Hans: 文档可见性(0 私密, 1 公开, 2 企业内公开)。
|
||||
llm_description: Doc visibility options, 0-private, 1-public, 2-enterprise.
|
||||
|
||||
- name: format
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
options:
|
||||
- value: markdown
|
||||
label:
|
||||
en_US: markdown
|
||||
zh_Hans: markdown
|
||||
- value: html
|
||||
label:
|
||||
en_US: html
|
||||
zh_Hans: html
|
||||
- value: lake
|
||||
label:
|
||||
en_US: lake
|
||||
zh_Hans: lake
|
||||
label:
|
||||
en_US: Content Format
|
||||
zh_Hans: 内容格式
|
||||
human_description:
|
||||
en_US: Format of the document content (markdown, HTML, Lake).
|
||||
zh_Hans: 文档内容格式(markdown, HTML, Lake)。
|
||||
llm_description: Content format choices, markdown, HTML, Lake.
|
||||
|
||||
- name: body
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Body Content
|
||||
zh_Hans: 正文内容
|
||||
human_description:
|
||||
en_US: The actual content of the document.
|
||||
zh_Hans: 文档的实际内容。
|
||||
llm_description: Content of the document.
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
删除文档
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-09-17 22:04"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueDeleteDocumentTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(
|
||||
self.request("DELETE", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
identity:
|
||||
name: aliyuque_delete_document
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Delete Document
|
||||
zh_Hans: 删除文档
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Delete Document
|
||||
zh_Hans: 根据id删除文档
|
||||
llm: Delete document.
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Knowledge Base ID
|
||||
zh_Hans: 知识库ID
|
||||
human_description:
|
||||
en_US: The unique identifier of the knowledge base where the document will be created.
|
||||
zh_Hans: 文档将被创建的知识库的唯一标识。
|
||||
llm_description: ID of the target knowledge base.
|
||||
|
||||
- name: id
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Document ID or Path
|
||||
zh_Hans: 文档 ID or 路径
|
||||
human_description:
|
||||
en_US: Document ID or path.
|
||||
zh_Hans: 文档 ID or 路径。
|
||||
llm_description: Document ID or path.
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
获取知识库首页
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-01 22:57:14"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueDescribeBookIndexPageTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(
|
||||
self.request("GET", token, tool_parameters, "/api/v2/repos/{group_login}/{book_slug}/index_page")
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
identity:
|
||||
name: aliyuque_describe_book_index_page
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Get Repo Index Page
|
||||
zh_Hans: 获取知识库首页
|
||||
icon: icon.svg
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Retrieves the homepage of a knowledge base within a group, supporting both book ID and group login with book slug access.
|
||||
zh_Hans: 获取团队中知识库的首页信息,可通过书籍ID或团队登录名与书籍路径访问。
|
||||
llm: Fetches the knowledge base homepage using group and book identifiers with support for alternate access paths.
|
||||
|
||||
parameters:
|
||||
- name: group_login
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Group Login
|
||||
zh_Hans: 团队登录名
|
||||
human_description:
|
||||
en_US: The login name of the group that owns the knowledge base.
|
||||
zh_Hans: 拥有该知识库的团队登录名。
|
||||
llm_description: Team login identifier for the knowledge base owner.
|
||||
|
||||
- name: book_slug
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Book Slug
|
||||
zh_Hans: 知识库路径
|
||||
human_description:
|
||||
en_US: The unique slug representing the path of the knowledge base.
|
||||
zh_Hans: 知识库的唯一路径标识。
|
||||
llm_description: Unique path identifier for the knowledge base.
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
获取知识库目录
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-09-17 15:17:11"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class YuqueDescribeBookTableOfContentsTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> (Union)[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(self.request("GET", token, tool_parameters, "/api/v2/repos/{book_id}/toc"))
|
||||
@@ -0,0 +1,25 @@
|
||||
identity:
|
||||
name: aliyuque_describe_book_table_of_contents
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Get Book's Table of Contents
|
||||
zh_Hans: 获取知识库的目录
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Get Book's Table of Contents.
|
||||
zh_Hans: 获取知识库的目录。
|
||||
llm: Get Book's Table of Contents.
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Book ID
|
||||
zh_Hans: 知识库 ID
|
||||
human_description:
|
||||
en_US: Book ID.
|
||||
zh_Hans: 知识库 ID。
|
||||
llm_description: Book ID.
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
获取文档
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-02 07:11:45"
|
||||
|
||||
import json
|
||||
from typing import Any, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueDescribeDocumentContentTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
new_params = {**tool_parameters}
|
||||
token = new_params.pop("token")
|
||||
if not token or token.lower() == "none":
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
new_params = {**tool_parameters}
|
||||
url = new_params.pop("url")
|
||||
if not url or not url.startswith("http"):
|
||||
raise Exception("url is not valid")
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
path_parts = parsed_url.path.strip("/").split("/")
|
||||
if len(path_parts) < 3:
|
||||
raise Exception("url is not correct")
|
||||
doc_id = path_parts[-1]
|
||||
book_slug = path_parts[-2]
|
||||
group_id = path_parts[-3]
|
||||
|
||||
# 1. 请求首页信息,获取book_id
|
||||
new_params["group_login"] = group_id
|
||||
new_params["book_slug"] = book_slug
|
||||
index_page = json.loads(
|
||||
self.request("GET", token, new_params, "/api/v2/repos/{group_login}/{book_slug}/index_page")
|
||||
)
|
||||
book_id = index_page.get("data", {}).get("book", {}).get("id")
|
||||
if not book_id:
|
||||
raise Exception(f"can not parse book_id from {index_page}")
|
||||
# 2. 获取文档内容
|
||||
new_params["book_id"] = book_id
|
||||
new_params["id"] = doc_id
|
||||
data = self.request("GET", token, new_params, "/api/v2/repos/{book_id}/docs/{id}")
|
||||
data = json.loads(data)
|
||||
body_only = tool_parameters.get("body_only") or ""
|
||||
if body_only.lower() == "true":
|
||||
return self.create_text_message(data.get("data").get("body"))
|
||||
else:
|
||||
raw = data.get("data")
|
||||
del raw["body_lake"]
|
||||
del raw["body_html"]
|
||||
return self.create_text_message(json.dumps(data))
|
||||
@@ -0,0 +1,50 @@
|
||||
identity:
|
||||
name: aliyuque_describe_document_content
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Fetch Document Content
|
||||
zh_Hans: 获取文档内容
|
||||
icon: icon.svg
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Retrieves document content from Yuque based on the provided document URL, which can be a normal or shared link.
|
||||
zh_Hans: 根据提供的语雀文档地址(支持正常链接或分享链接)获取文档内容。
|
||||
llm: Fetches Yuque document content given a URL.
|
||||
|
||||
parameters:
|
||||
- name: url
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Document URL
|
||||
zh_Hans: 文档地址
|
||||
human_description:
|
||||
en_US: The URL of the document to retrieve content from, can be normal or shared.
|
||||
zh_Hans: 需要获取内容的文档地址,可以是正常链接或分享链接。
|
||||
llm_description: URL of the Yuque document to fetch content.
|
||||
|
||||
- name: body_only
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: return body content only
|
||||
zh_Hans: 仅返回body内容
|
||||
human_description:
|
||||
en_US: true:Body content only, false:Full response with metadata.
|
||||
zh_Hans: true:仅返回body内容,不返回其他元数据,false:返回所有元数据。
|
||||
llm_description: true:Body content only, false:Full response with metadata.
|
||||
|
||||
- name: token
|
||||
type: secret-input
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Yuque API Token
|
||||
zh_Hans: 语雀接口Token
|
||||
human_description:
|
||||
en_US: The token for calling the Yuque API defaults to the Yuque token bound to the current tool if not provided.
|
||||
zh_Hans: 调用语雀接口的token,如果不传则默认为当前工具绑定的语雀Token。
|
||||
llm_description: If the token for calling the Yuque API is not provided, it will default to the Yuque token bound to the current tool.
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
获取文档
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-01 10:45:20"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueDescribeDocumentsTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(
|
||||
self.request("GET", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
identity:
|
||||
name: aliyuque_describe_documents
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Get Doc Detail
|
||||
zh_Hans: 获取文档详情
|
||||
icon: icon.svg
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Retrieves detailed information of a specific document identified by its ID or path within a knowledge base.
|
||||
zh_Hans: 根据知识库ID和文档ID或路径获取文档详细信息。
|
||||
llm: Fetches detailed doc info using ID/path from a knowledge base; supports doc lookup in Yuque.
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Knowledge Base ID
|
||||
zh_Hans: 知识库 ID
|
||||
human_description:
|
||||
en_US: Identifier for the knowledge base where the document resides.
|
||||
zh_Hans: 文档所属知识库的唯一标识。
|
||||
llm_description: ID of the knowledge base holding the document.
|
||||
|
||||
- name: id
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Document ID or Path
|
||||
zh_Hans: 文档 ID 或路径
|
||||
human_description:
|
||||
en_US: The unique identifier or path of the document to retrieve.
|
||||
zh_Hans: 需要获取的文档的ID或其在知识库中的路径。
|
||||
llm_description: Unique doc ID or its path for retrieval.
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
获取知识库目录
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-09-17 15:17:11"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class YuqueDescribeBookTableOfContentsTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> (Union)[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
|
||||
doc_ids = tool_parameters.get("doc_ids")
|
||||
if doc_ids:
|
||||
doc_ids = [int(doc_id.strip()) for doc_id in doc_ids.split(",")]
|
||||
tool_parameters["doc_ids"] = doc_ids
|
||||
|
||||
return self.create_text_message(self.request("PUT", token, tool_parameters, "/api/v2/repos/{book_id}/toc"))
|
||||
@@ -0,0 +1,222 @@
|
||||
identity:
|
||||
name: aliyuque_update_book_table_of_contents
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Update Book's Table of Contents
|
||||
zh_Hans: 更新知识库目录
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Update Book's Table of Contents.
|
||||
zh_Hans: 更新知识库目录。
|
||||
llm: Update Book's Table of Contents.
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Book ID
|
||||
zh_Hans: 知识库 ID
|
||||
human_description:
|
||||
en_US: Book ID.
|
||||
zh_Hans: 知识库 ID。
|
||||
llm_description: Book ID.
|
||||
|
||||
- name: action
|
||||
type: select
|
||||
required: true
|
||||
form: llm
|
||||
options:
|
||||
- value: appendNode
|
||||
label:
|
||||
en_US: appendNode
|
||||
zh_Hans: appendNode
|
||||
pt_BR: appendNode
|
||||
- value: prependNode
|
||||
label:
|
||||
en_US: prependNode
|
||||
zh_Hans: prependNode
|
||||
pt_BR: prependNode
|
||||
- value: editNode
|
||||
label:
|
||||
en_US: editNode
|
||||
zh_Hans: editNode
|
||||
pt_BR: editNode
|
||||
- value: editNode
|
||||
label:
|
||||
en_US: removeNode
|
||||
zh_Hans: removeNode
|
||||
pt_BR: removeNode
|
||||
label:
|
||||
en_US: Action Type
|
||||
zh_Hans: 操作
|
||||
human_description:
|
||||
en_US: In the operation scenario, sibling node prepending is not supported, deleting a node doesn't remove associated documents, and node deletion has two modes, 'sibling' (delete current node) and 'child' (delete current node and its children).
|
||||
zh_Hans: 操作,创建场景下不支持同级头插 prependNode,删除节点不会删除关联文档,删除节点时action_mode=sibling (删除当前节点), action_mode=child (删除当前节点及子节点)
|
||||
llm_description: In the operation scenario, sibling node prepending is not supported, deleting a node doesn't remove associated documents, and node deletion has two modes, 'sibling' (delete current node) and 'child' (delete current node and its children).
|
||||
|
||||
|
||||
- name: action_mode
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
options:
|
||||
- value: sibling
|
||||
label:
|
||||
en_US: sibling
|
||||
zh_Hans: 同级
|
||||
pt_BR: sibling
|
||||
- value: child
|
||||
label:
|
||||
en_US: child
|
||||
zh_Hans: 子集
|
||||
pt_BR: child
|
||||
label:
|
||||
en_US: Action Type
|
||||
zh_Hans: 操作
|
||||
human_description:
|
||||
en_US: Operation mode (sibling:same level, child:child level).
|
||||
zh_Hans: 操作模式 (sibling:同级, child:子级)。
|
||||
llm_description: Operation mode (sibling:same level, child:child level).
|
||||
|
||||
- name: target_uuid
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Target node UUID
|
||||
zh_Hans: 目标节点 UUID
|
||||
human_description:
|
||||
en_US: Target node UUID, defaults to root node if left empty.
|
||||
zh_Hans: 目标节点 UUID, 不填默认为根节点。
|
||||
llm_description: Target node UUID, defaults to root node if left empty.
|
||||
|
||||
- name: node_uuid
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Node UUID
|
||||
zh_Hans: 操作节点 UUID
|
||||
human_description:
|
||||
en_US: Operation node UUID [required for move/update/delete].
|
||||
zh_Hans: 操作节点 UUID [移动/更新/删除必填]。
|
||||
llm_description: Operation node UUID [required for move/update/delete].
|
||||
|
||||
- name: doc_ids
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Document IDs
|
||||
zh_Hans: 文档id列表
|
||||
human_description:
|
||||
en_US: Document IDs [required for creating documents], separate multiple IDs with ','.
|
||||
zh_Hans: 文档 IDs [创建文档必填],多个用','分隔。
|
||||
llm_description: Document IDs [required for creating documents], separate multiple IDs with ','.
|
||||
|
||||
|
||||
- name: type
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
default: DOC
|
||||
options:
|
||||
- value: DOC
|
||||
label:
|
||||
en_US: DOC
|
||||
zh_Hans: 文档
|
||||
pt_BR: DOC
|
||||
- value: LINK
|
||||
label:
|
||||
en_US: LINK
|
||||
zh_Hans: 链接
|
||||
pt_BR: LINK
|
||||
- value: TITLE
|
||||
label:
|
||||
en_US: TITLE
|
||||
zh_Hans: 分组
|
||||
pt_BR: TITLE
|
||||
label:
|
||||
en_US: Node type
|
||||
zh_Hans: 操节点类型
|
||||
human_description:
|
||||
en_US: Node type [required for creation] (DOC:document, LINK:external link, TITLE:group).
|
||||
zh_Hans: 操节点类型 [创建必填] (DOC:文档, LINK:外链, TITLE:分组)。
|
||||
llm_description: Node type [required for creation] (DOC:document, LINK:external link, TITLE:group).
|
||||
|
||||
- name: title
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Node Name
|
||||
zh_Hans: 节点名称
|
||||
human_description:
|
||||
en_US: Node name [required for creating groups/external links].
|
||||
zh_Hans: 节点名称 [创建分组/外链必填]。
|
||||
llm_description: Node name [required for creating groups/external links].
|
||||
|
||||
- name: url
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Node URL
|
||||
zh_Hans: 节点URL
|
||||
human_description:
|
||||
en_US: Node URL [required for creating external links].
|
||||
zh_Hans: 节点 URL [创建外链必填]。
|
||||
llm_description: Node URL [required for creating external links].
|
||||
|
||||
|
||||
- name: open_window
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
default: 0
|
||||
options:
|
||||
- value: 0
|
||||
label:
|
||||
en_US: DOC
|
||||
zh_Hans: Current Page
|
||||
pt_BR: DOC
|
||||
- value: 1
|
||||
label:
|
||||
en_US: LINK
|
||||
zh_Hans: New Page
|
||||
pt_BR: LINK
|
||||
label:
|
||||
en_US: Open in new window
|
||||
zh_Hans: 是否新窗口打开
|
||||
human_description:
|
||||
en_US: Open in new window [optional for external links] (0:open in current page, 1:open in new window).
|
||||
zh_Hans: 是否新窗口打开 [外链选填] (0:当前页打开, 1:新窗口打开)。
|
||||
llm_description: Open in new window [optional for external links] (0:open in current page, 1:open in new window).
|
||||
|
||||
|
||||
- name: visible
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
default: 1
|
||||
options:
|
||||
- value: 0
|
||||
label:
|
||||
en_US: Invisible
|
||||
zh_Hans: 隐藏
|
||||
pt_BR: Invisible
|
||||
- value: 1
|
||||
label:
|
||||
en_US: Visible
|
||||
zh_Hans: 可见
|
||||
pt_BR: Visible
|
||||
label:
|
||||
en_US: Visibility
|
||||
zh_Hans: 是否可见
|
||||
human_description:
|
||||
en_US: Visibility (0:invisible, 1:visible).
|
||||
zh_Hans: 是否可见 (0:不可见, 1:可见)。
|
||||
llm_description: Visibility (0:invisible, 1:visible).
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
更新文档
|
||||
"""
|
||||
|
||||
__author__ = "佐井"
|
||||
__created__ = "2024-06-19 16:50:07"
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.aliyuque.tools.base import AliYuqueTool
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AliYuqueUpdateDocumentTool(AliYuqueTool, BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
token = self.runtime.credentials.get("token", None)
|
||||
if not token:
|
||||
raise Exception("token is required")
|
||||
return self.create_text_message(
|
||||
self.request("PUT", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
identity:
|
||||
name: aliyuque_update_document
|
||||
author: 佐井
|
||||
label:
|
||||
en_US: Update Document
|
||||
zh_Hans: 更新文档
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Update an existing document within a specified knowledge base by providing the document ID or path.
|
||||
zh_Hans: 通过提供文档ID或路径,更新指定知识库中的现有文档。
|
||||
llm: Update doc in a knowledge base via ID/path.
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Knowledge Base ID
|
||||
zh_Hans: 知识库 ID
|
||||
human_description:
|
||||
en_US: The unique identifier of the knowledge base where the document resides.
|
||||
zh_Hans: 文档所属知识库的ID。
|
||||
llm_description: ID of the knowledge base holding the doc.
|
||||
- name: id
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Document ID or Path
|
||||
zh_Hans: 文档 ID 或 路径
|
||||
human_description:
|
||||
en_US: The unique identifier or the path of the document to be updated.
|
||||
zh_Hans: 要更新的文档的唯一ID或路径。
|
||||
llm_description: Doc's ID or path for update.
|
||||
|
||||
- name: title
|
||||
type: string
|
||||
required: false
|
||||
form: llm
|
||||
label:
|
||||
en_US: Title
|
||||
zh_Hans: 标题
|
||||
human_description:
|
||||
en_US: The title of the document, defaults to 'Untitled' if not provided.
|
||||
zh_Hans: 文档标题,默认为'无标题'如未提供。
|
||||
llm_description: Title of the document, defaults to 'Untitled'.
|
||||
|
||||
- name: format
|
||||
type: select
|
||||
required: false
|
||||
form: llm
|
||||
options:
|
||||
- value: markdown
|
||||
label:
|
||||
en_US: markdown
|
||||
zh_Hans: markdown
|
||||
pt_BR: markdown
|
||||
- value: html
|
||||
label:
|
||||
en_US: html
|
||||
zh_Hans: html
|
||||
pt_BR: html
|
||||
- value: lake
|
||||
label:
|
||||
en_US: lake
|
||||
zh_Hans: lake
|
||||
pt_BR: lake
|
||||
label:
|
||||
en_US: Content Format
|
||||
zh_Hans: 内容格式
|
||||
human_description:
|
||||
en_US: Format of the document content (markdown, HTML, Lake).
|
||||
zh_Hans: 文档内容格式(markdown, HTML, Lake)。
|
||||
llm_description: Content format choices, markdown, HTML, Lake.
|
||||
|
||||
- name: body
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
en_US: Body Content
|
||||
zh_Hans: 正文内容
|
||||
human_description:
|
||||
en_US: The actual content of the document.
|
||||
zh_Hans: 文档的实际内容。
|
||||
llm_description: Content of the document.
|
||||
@@ -21,7 +21,6 @@ class DiscordWebhookTool(BuiltinTool):
|
||||
return self.create_text_message("Invalid parameter content")
|
||||
|
||||
webhook_url = tool_parameters.get("webhook_url", "")
|
||||
|
||||
if not webhook_url.startswith("https://discord.com/api/webhooks/"):
|
||||
return self.create_text_message(
|
||||
f"Invalid parameter webhook_url ${webhook_url}, \
|
||||
@@ -31,13 +30,14 @@ class DiscordWebhookTool(BuiltinTool):
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
params = {}
|
||||
payload = {
|
||||
"username": tool_parameters.get("username") or user_id,
|
||||
"content": content,
|
||||
"avatar_url": tool_parameters.get("avatar_url") or None,
|
||||
}
|
||||
|
||||
try:
|
||||
res = httpx.post(webhook_url, headers=headers, params=params, json=payload)
|
||||
res = httpx.post(webhook_url, headers=headers, json=payload)
|
||||
if res.is_success:
|
||||
return self.create_text_message("Text message was sent successfully")
|
||||
else:
|
||||
|
||||
@@ -38,3 +38,28 @@ parameters:
|
||||
pt_BR: Content to sent to the channel or person.
|
||||
llm_description: Content of the message
|
||||
form: llm
|
||||
- name: username
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Discord Webhook Username
|
||||
zh_Hans: Discord Webhook用户名
|
||||
pt_BR: Discord Webhook Username
|
||||
human_description:
|
||||
en_US: Discord Webhook Username
|
||||
zh_Hans: Discord Webhook用户名
|
||||
pt_BR: Discord Webhook Username
|
||||
llm_description: Discord Webhook Username
|
||||
form: llm
|
||||
- name: avatar_url
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Discord Webhook Avatar
|
||||
zh_Hans: Discord Webhook头像
|
||||
pt_BR: Discord Webhook Avatar
|
||||
human_description:
|
||||
en_US: Discord Webhook Avatar URL
|
||||
zh_Hans: Discord Webhook头像地址
|
||||
pt_BR: Discord Webhook Avatar URL
|
||||
form: form
|
||||
|
||||
@@ -289,7 +289,7 @@ class WorkflowEntry:
|
||||
new_value.append(file)
|
||||
|
||||
if new_value:
|
||||
value = new_value
|
||||
input_value = new_value
|
||||
|
||||
# append variable and value to variable pool
|
||||
variable_pool.add([variable_node_id] + variable_key_list, input_value)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import os
|
||||
from typing import Union
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from tos import TosClientV2
|
||||
from tos.clientv2 import DeleteObjectOutput, GetObjectOutput, HeadObjectOutput, PutObjectOutput
|
||||
|
||||
|
||||
class AttrDict(dict):
|
||||
def __getattr__(self, item):
|
||||
return self.get(item)
|
||||
|
||||
|
||||
def get_example_bucket() -> str:
|
||||
return "dify"
|
||||
|
||||
|
||||
def get_example_filename() -> str:
|
||||
return "test.txt"
|
||||
|
||||
|
||||
def get_example_data() -> bytes:
|
||||
return b"test"
|
||||
|
||||
|
||||
def get_example_filepath() -> str:
|
||||
return "/test"
|
||||
|
||||
|
||||
class MockVolcengineTosClass:
|
||||
def __init__(self, ak="", sk="", endpoint="", region=""):
|
||||
self.bucket_name = get_example_bucket()
|
||||
self.key = get_example_filename()
|
||||
self.content = get_example_data()
|
||||
self.filepath = get_example_filepath()
|
||||
self.resp = AttrDict(
|
||||
{
|
||||
"x-tos-server-side-encryption": "kms",
|
||||
"x-tos-server-side-encryption-kms-key-id": "trn:kms:cn-beijing:****:keyrings/ring-test/keys/key-test",
|
||||
"x-tos-server-side-encryption-customer-algorithm": "AES256",
|
||||
"x-tos-version-id": "test",
|
||||
"x-tos-hash-crc64ecma": 123456,
|
||||
"request_id": "test",
|
||||
"headers": {
|
||||
"x-tos-id-2": "test",
|
||||
"ETag": "123456",
|
||||
},
|
||||
"status": 200,
|
||||
}
|
||||
)
|
||||
|
||||
def put_object(self, bucket: str, key: str, content=None) -> PutObjectOutput:
|
||||
assert bucket == self.bucket_name
|
||||
assert key == self.key
|
||||
assert content == self.content
|
||||
return PutObjectOutput(self.resp)
|
||||
|
||||
def get_object(self, bucket: str, key: str) -> GetObjectOutput:
|
||||
assert bucket == self.bucket_name
|
||||
assert key == self.key
|
||||
|
||||
get_object_output = MagicMock(GetObjectOutput)
|
||||
get_object_output.read.return_value = self.content
|
||||
return get_object_output
|
||||
|
||||
def get_object_to_file(self, bucket: str, key: str, file_path: str):
|
||||
assert bucket == self.bucket_name
|
||||
assert key == self.key
|
||||
assert file_path == self.filepath
|
||||
|
||||
def head_object(self, bucket: str, key: str) -> HeadObjectOutput:
|
||||
assert bucket == self.bucket_name
|
||||
assert key == self.key
|
||||
return HeadObjectOutput(self.resp)
|
||||
|
||||
def delete_object(self, bucket: str, key: str):
|
||||
assert bucket == self.bucket_name
|
||||
assert key == self.key
|
||||
return DeleteObjectOutput(self.resp)
|
||||
|
||||
|
||||
MOCK = os.getenv("MOCK_SWITCH", "false").lower() == "true"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_volcengine_tos_mock(monkeypatch: MonkeyPatch):
|
||||
if MOCK:
|
||||
monkeypatch.setattr(TosClientV2, "__init__", MockVolcengineTosClass.__init__)
|
||||
monkeypatch.setattr(TosClientV2, "put_object", MockVolcengineTosClass.put_object)
|
||||
monkeypatch.setattr(TosClientV2, "get_object", MockVolcengineTosClass.get_object)
|
||||
monkeypatch.setattr(TosClientV2, "get_object_to_file", MockVolcengineTosClass.get_object_to_file)
|
||||
monkeypatch.setattr(TosClientV2, "head_object", MockVolcengineTosClass.head_object)
|
||||
monkeypatch.setattr(TosClientV2, "delete_object", MockVolcengineTosClass.delete_object)
|
||||
|
||||
yield
|
||||
|
||||
if MOCK:
|
||||
monkeypatch.undo()
|
||||
@@ -0,0 +1,67 @@
|
||||
from collections.abc import Generator
|
||||
|
||||
from flask import Flask
|
||||
from tos import TosClientV2
|
||||
from tos.clientv2 import GetObjectOutput, HeadObjectOutput, PutObjectOutput
|
||||
|
||||
from extensions.storage.volcengine_tos_storage import VolcengineTosStorage
|
||||
from tests.unit_tests.oss.__mock.volcengine_tos import (
|
||||
get_example_bucket,
|
||||
get_example_data,
|
||||
get_example_filename,
|
||||
get_example_filepath,
|
||||
setup_volcengine_tos_mock,
|
||||
)
|
||||
|
||||
|
||||
class VolcengineTosTest:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance == None:
|
||||
cls._instance = object.__new__(cls)
|
||||
return cls._instance
|
||||
else:
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
self.storage = VolcengineTosStorage(app=Flask(__name__))
|
||||
self.storage.bucket_name = get_example_bucket()
|
||||
self.storage.client = TosClientV2(
|
||||
ak="dify",
|
||||
sk="dify",
|
||||
endpoint="https://xxx.volces.com",
|
||||
region="cn-beijing",
|
||||
)
|
||||
|
||||
|
||||
def test_save(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
volc_tos.storage.save(get_example_filename(), get_example_data())
|
||||
|
||||
|
||||
def test_load_once(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
assert volc_tos.storage.load_once(get_example_filename()) == get_example_data()
|
||||
|
||||
|
||||
def test_load_stream(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
generator = volc_tos.storage.load_stream(get_example_filename())
|
||||
assert isinstance(generator, Generator)
|
||||
assert next(generator) == get_example_data()
|
||||
|
||||
|
||||
def test_download(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
volc_tos.storage.download(get_example_filename(), get_example_filepath())
|
||||
|
||||
|
||||
def test_exists(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
assert volc_tos.storage.exists(get_example_filename())
|
||||
|
||||
|
||||
def test_delete(setup_volcengine_tos_mock):
|
||||
volc_tos = VolcengineTosTest()
|
||||
volc_tos.storage.delete(get_example_filename())
|
||||
@@ -96,6 +96,10 @@ x-shared-env: &shared-api-worker-env
|
||||
VOLCENGINE_TOS_ACCESS_KEY: ${VOLCENGINE_TOS_ACCESS_KEY:-}
|
||||
VOLCENGINE_TOS_ENDPOINT: ${VOLCENGINE_TOS_ENDPOINT:-}
|
||||
VOLCENGINE_TOS_REGION: ${VOLCENGINE_TOS_REGION:-}
|
||||
BAIDU_OBS_BUCKET_NAME: ${BAIDU_OBS_BUCKET_NAME:-}
|
||||
BAIDU_OBS_SECRET_KEY: ${BAIDU_OBS_SECRET_KEY:-}
|
||||
BAIDU_OBS_ACCESS_KEY: ${BAIDU_OBS_ACCESS_KEY:-}
|
||||
BAIDU_OBS_ENDPOINT: ${BAIDU_OBS_ENDPOINT:-}
|
||||
VECTOR_STORE: ${VECTOR_STORE:-weaviate}
|
||||
WEAVIATE_ENDPOINT: ${WEAVIATE_ENDPOINT:-http://weaviate:8080}
|
||||
WEAVIATE_API_KEY: ${WEAVIATE_API_KEY:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**/node_modules/*
|
||||
node_modules/
|
||||
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
.next/
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"next",
|
||||
"@antfu",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-definitions": [
|
||||
"error",
|
||||
"type"
|
||||
],
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-console": "off",
|
||||
"indent": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"error",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1,
|
||||
"flatTernaryExpressions": false,
|
||||
"ignoredNodes": [
|
||||
"PropertyDefinition[decorators]",
|
||||
"TSUnionType",
|
||||
"FunctionExpression[params]:has(Identifier[decorators])"
|
||||
]
|
||||
}
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"react/display-name": "warn"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import type { Preview } from '@storybook/react'
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes'
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import I18nServer from '../app/components/i18n-server'
|
||||
|
||||
import '../app/styles/globals.css'
|
||||
@@ -16,12 +16,12 @@ export const decorators = [
|
||||
defaultTheme: 'light',
|
||||
attributeName: 'data-theme',
|
||||
}),
|
||||
(Story) => {
|
||||
Story => {
|
||||
return <I18nServer>
|
||||
<Story />
|
||||
</I18nServer>
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
|
||||
+6
-8
@@ -6,9 +6,6 @@ LABEL maintainer="takatost@gmail.com"
|
||||
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
RUN npm install -g pnpm@9.12.2
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
|
||||
# install packages
|
||||
@@ -17,12 +14,12 @@ FROM base AS packages
|
||||
WORKDIR /app/web
|
||||
|
||||
COPY package.json .
|
||||
COPY pnpm-lock.yaml .
|
||||
COPY yarn.lock .
|
||||
|
||||
# if you located in China, you can use taobao registry to speed up
|
||||
# RUN pnpm install --frozen-lockfile --registry https://registry.npmmirror.com/
|
||||
# RUN yarn install --frozen-lockfile --registry https://registry.npmmirror.com/
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# build resources
|
||||
FROM base AS builder
|
||||
@@ -30,7 +27,7 @@ WORKDIR /app/web
|
||||
COPY --from=packages /app/web/ .
|
||||
COPY . .
|
||||
|
||||
RUN pnpm build
|
||||
RUN yarn build
|
||||
|
||||
|
||||
# production stage
|
||||
@@ -60,7 +57,8 @@ COPY docker/entrypoint.sh ./entrypoint.sh
|
||||
|
||||
|
||||
# global runtime packages
|
||||
RUN pnpm add -g pm2 \
|
||||
RUN yarn global add pm2 \
|
||||
&& yarn cache clean \
|
||||
&& mkdir /.pm2 \
|
||||
&& chown -R 1001:0 /.pm2 /app/web \
|
||||
&& chmod -R g=u /.pm2 /app/web
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ This project uses [Storybook](https://storybook.js.org/) for UI component develo
|
||||
To start the storybook server, run:
|
||||
|
||||
```bash
|
||||
pnpm storybook
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import type { Locale } from '@/i18n'
|
||||
import { type Locale } from '@/i18n'
|
||||
import DevelopMain from '@/app/components/develop'
|
||||
|
||||
export type IDevelopProps = {
|
||||
|
||||
@@ -8,11 +8,12 @@ import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
// CommandLineIcon,
|
||||
Squares2X2Icon,
|
||||
// eslint-disable-next-line sort-imports
|
||||
PuzzlePieceIcon,
|
||||
DocumentTextIcon,
|
||||
PaperClipIcon,
|
||||
PuzzlePieceIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
Squares2X2Icon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import {
|
||||
Cog8ToothIcon as Cog8ToothSolidIcon,
|
||||
|
||||
@@ -4,9 +4,12 @@ import { customTool, extensionDallE, modelGPT4, toolNotion } from '@/app/compone
|
||||
import PluginItem from '@/app/components/plugins/plugin-item'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||
import { getLocaleOnServer } from '@/i18n/server'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
const PluginList = async () => {
|
||||
const locale = getLocaleOnServer()
|
||||
const pluginList = [toolNotion, extensionDallE, modelGPT4, customTool]
|
||||
|
||||
return (
|
||||
@@ -63,6 +66,9 @@ const PluginList = async () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<PluginDetailPanel
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import ConfirmAddVar from './confirm-add-var'
|
||||
import s from './style.module.css'
|
||||
import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
import { type PromptVariable } from '@/models/debug'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { CompletionParams } from '@/types/app'
|
||||
import { AppType } from '@/types/app'
|
||||
|
||||
+15
-14
@@ -106,22 +106,23 @@ const SettingBuiltInTool: FC<Props> = ({
|
||||
</div>
|
||||
|
||||
{infoSchemas.length > 0 && (
|
||||
<div className='my-2'>
|
||||
<div className='pt-3 text-text-secondary system-sm-semibold-uppercase'>
|
||||
{t('tools.setBuiltInTools.parameters')}
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>
|
||||
<div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div>
|
||||
<div className='grow w-0 h-px bg-[#f3f4f6]'></div>
|
||||
</div>
|
||||
<div className='py-2 space-y-3'>
|
||||
<div className='space-y-4'>
|
||||
{infoSchemas.map((item: any, index) => (
|
||||
<div key={index} className='py-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-text-secondary code-sm-semibold'>{item.label[language]}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
|
||||
<div key={index}>
|
||||
<div className='flex items-center space-x-2 leading-[18px]'>
|
||||
<div className='text-[13px] font-semibold text-gray-900'>{item.label[language]}</div>
|
||||
<div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
|
||||
{item.required && (
|
||||
<div className='text-text-warning-secondary system-xs-medium'>{t('tools.setBuiltInTools.required')}</div>
|
||||
<div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div>
|
||||
)}
|
||||
</div>
|
||||
{item.human_description && (
|
||||
<div className='mt-0.5 text-text-tertiary system-xs-regular'>
|
||||
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
|
||||
{item.human_description?.[language]}
|
||||
</div>
|
||||
)}
|
||||
@@ -191,9 +192,9 @@ const SettingBuiltInTool: FC<Props> = ({
|
||||
</>)}
|
||||
</div>
|
||||
)}
|
||||
panelClassName='mt-[64px] mb-2 !w-[420px]'
|
||||
maxWidthClassName='!max-w-[420px]'
|
||||
height='calc(100vh - 64px)'
|
||||
panelClassName='mt-[65px] !w-[405px]'
|
||||
maxWidthClassName='!max-w-[405px]'
|
||||
height='calc(100vh - 65px)'
|
||||
headerClassName='!border-b-black/5'
|
||||
body={
|
||||
<div className='h-full pt-3'>
|
||||
@@ -202,7 +203,7 @@ const SettingBuiltInTool: FC<Props> = ({
|
||||
<Loading type='app' />
|
||||
</div>
|
||||
: (<div className='flex flex-col h-full'>
|
||||
<div className='grow h-0 overflow-y-auto px-4'>
|
||||
<div className='grow h-0 overflow-y-auto px-6'>
|
||||
{isInfoActive ? infoUI : settingUI}
|
||||
</div>
|
||||
{!readonly && !isInfoActive && (
|
||||
|
||||
@@ -19,7 +19,7 @@ import AgentTools from './agent/agent-tools'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||
import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig, TextToSpeechConfig } from '@/models/debug'
|
||||
import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig, type TextToSpeechConfig } from '@/models/debug'
|
||||
import type { AppType } from '@/types/app'
|
||||
import { ModelModeType } from '@/types/app'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
@@ -134,11 +134,11 @@ const Config: FC = () => {
|
||||
annotation: annotationConfig.enabled,
|
||||
setAnnotation: async (value) => {
|
||||
if (value) {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
setIsShowAnnotationConfigInit(true)
|
||||
}
|
||||
else {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
await handleDisableAnnotation(annotationConfig.embedding_model)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ import ModelParameterModal from '@/app/components/header/account-setting/model-p
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import type { Collection } from '@/app/components/tools/types'
|
||||
import { type Collection } from '@/app/components/tools/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import {
|
||||
getMultipleRetrievalConfig,
|
||||
@@ -142,7 +142,7 @@ const Configuration: FC = () => {
|
||||
const setCompletionParams = (value: FormValue) => {
|
||||
const params = { ...value }
|
||||
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) {
|
||||
params.stop = getTempStop()
|
||||
setTempStop([])
|
||||
@@ -331,7 +331,7 @@ const Configuration: FC = () => {
|
||||
const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true)
|
||||
const setPromptMode = async (mode: PromptMode) => {
|
||||
if (mode === PromptMode.advanced) {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
await migrateToDefaultPrompt()
|
||||
setCanReturnToSimpleMode(true)
|
||||
}
|
||||
@@ -523,6 +523,7 @@ const Configuration: FC = () => {
|
||||
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
||||
external_data_tools: modelConfig.external_data_tools,
|
||||
dataSets: datasets || [],
|
||||
// eslint-disable-next-line multiline-ternary
|
||||
agentConfig: res.mode === 'agent-chat' ? {
|
||||
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
|
||||
...modelConfig.agent_mode,
|
||||
|
||||
@@ -20,7 +20,7 @@ const Progress: FC<IProgressProps> = ({
|
||||
className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')}
|
||||
style={{ width: `${value}%` }}
|
||||
/>
|
||||
{Array.from({ length: 10 }).fill(0).map((i, k) => (
|
||||
{Array(10).fill(0).map((i, k) => (
|
||||
<div key={k} className={s['bar-item']} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AudioPlayer from '@/app/components/base/audio-btn/audio'
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/consistent-type-definitions
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface AudioPlayerManager {
|
||||
instance: AudioPlayerManager
|
||||
}
|
||||
@@ -12,7 +12,6 @@ export class AudioPlayerManager {
|
||||
private audioPlayers: AudioPlayer | null = null
|
||||
private msgId: string | undefined
|
||||
|
||||
// eslint-disable-next-line
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Toast from '@/app/components/base/toast'
|
||||
import { textToAudioStream } from '@/service/share'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/consistent-type-definitions
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface Window {
|
||||
ManagedMediaSource: any
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({ src }) => {
|
||||
audio.load()
|
||||
|
||||
// Delayed generation of waveform data
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const timer = setTimeout(() => generateWaveformData(src), 1000)
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -49,6 +49,4 @@ const AutoHeightTextarea = forwardRef<HTMLTextAreaElement, AutoHeightTextareaPro
|
||||
},
|
||||
)
|
||||
|
||||
AutoHeightTextarea.displayName = 'AutoHeightTextarea'
|
||||
|
||||
export default AutoHeightTextarea
|
||||
|
||||
@@ -12,9 +12,8 @@ import Divider from '@/app/components/base/divider'
|
||||
import { searchEmoji } from '@/utils/emoji'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
namespace JSX {
|
||||
// eslint-disable-next-line ts/consistent-type-definitions
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface IntrinsicElements {
|
||||
'em-emoji': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement>, HTMLElement >
|
||||
}
|
||||
|
||||
@@ -28,6 +28,4 @@ const IconBase = forwardRef<React.MutableRefObject<HTMLOrSVGElement>, IconBasePr
|
||||
})
|
||||
})
|
||||
|
||||
IconBase.displayName = 'IconBase'
|
||||
|
||||
export default IconBase
|
||||
|
||||
@@ -75,6 +75,7 @@ export function PreCode(props: { children: any }) {
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
const useLazyLoad = (ref: RefObject<Element>): boolean => {
|
||||
const [isIntersecting, setIntersecting] = useState<boolean>(false)
|
||||
|
||||
@@ -296,11 +297,11 @@ export default class ErrorBoundary extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
// eslint-disable-next-line ts/ban-ts-comment
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
if (this.state.hasError)
|
||||
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
|
||||
// eslint-disable-next-line ts/ban-ts-comment
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
return this.props.children
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const Flowchart = React.forwardRef((props: {
|
||||
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
|
||||
const prevPrimitiveCode = usePrevious(props.PrimitiveCode)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const timeRef = useRef<number>()
|
||||
const timeRef = useRef<NodeJS.Timeout>()
|
||||
const [errMsg, setErrMsg] = useState('')
|
||||
|
||||
const renderFlowchart = async (PrimitiveCode: string) => {
|
||||
@@ -74,15 +74,15 @@ const Flowchart = React.forwardRef((props: {
|
||||
return
|
||||
}
|
||||
if (timeRef.current)
|
||||
window.clearTimeout(timeRef.current)
|
||||
clearTimeout(timeRef.current)
|
||||
|
||||
timeRef.current = window.setTimeout(() => {
|
||||
timeRef.current = setTimeout(() => {
|
||||
renderFlowchart(props.PrimitiveCode)
|
||||
}, 300)
|
||||
}, [props.PrimitiveCode])
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line ts/ban-ts-comment
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
<div ref={ref}>
|
||||
{
|
||||
@@ -108,6 +108,4 @@ const Flowchart = React.forwardRef((props: {
|
||||
)
|
||||
})
|
||||
|
||||
Flowchart.displayName = 'Flowchart'
|
||||
|
||||
export default Flowchart
|
||||
|
||||
@@ -34,15 +34,15 @@ export default function CustomPopover({
|
||||
disabled = false,
|
||||
}: IPopover) {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const timeOutRef = useRef<number | null>(null)
|
||||
const timeOutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const onMouseEnter = (isOpen: boolean) => {
|
||||
timeOutRef.current && window.clearTimeout(timeOutRef.current)
|
||||
timeOutRef.current && clearTimeout(timeOutRef.current)
|
||||
!isOpen && buttonRef.current?.click()
|
||||
}
|
||||
|
||||
const onMouseLeave = (isOpen: boolean) => {
|
||||
timeOutRef.current = window.setTimeout(() => {
|
||||
timeOutRef.current = setTimeout(() => {
|
||||
isOpen && buttonRef.current?.click()
|
||||
}, timeoutDuration)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EditorConfig, SerializedTextNode } from 'lexical'
|
||||
import type { EditorConfig, NodeKey, SerializedTextNode } from 'lexical'
|
||||
import { $createTextNode, TextNode } from 'lexical'
|
||||
|
||||
export class CustomTextNode extends TextNode {
|
||||
@@ -10,9 +10,9 @@ export class CustomTextNode extends TextNode {
|
||||
return new CustomTextNode(node.__text, node.__key)
|
||||
}
|
||||
|
||||
// constructor(text: string, key?: NodeKey) {
|
||||
// super(text, key)
|
||||
// }
|
||||
constructor(text: string, key?: NodeKey) {
|
||||
super(text, key)
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig) {
|
||||
const dom = super.createDOM(config)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
NodeKey,
|
||||
SerializedTextNode,
|
||||
} from 'lexical'
|
||||
import {
|
||||
@@ -17,9 +18,9 @@ export class VariableValueBlockNode extends TextNode {
|
||||
return new VariableValueBlockNode(node.__text, node.__key)
|
||||
}
|
||||
|
||||
// constructor(text: string, key?: NodeKey) {
|
||||
// super(text, key)
|
||||
// }
|
||||
constructor(text: string, key?: NodeKey) {
|
||||
super(text, key)
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = super.createDOM(config)
|
||||
|
||||
@@ -30,7 +30,7 @@ const TagManagementModal = ({ show, type }: TagManagementModalProps) => {
|
||||
setTagList(res)
|
||||
}
|
||||
|
||||
const [pending, setPending] = useState<boolean>(false)
|
||||
const [pending, setPending] = useState<Boolean>(false)
|
||||
const [name, setName] = useState<string>('')
|
||||
const createNewTag = async () => {
|
||||
if (!name)
|
||||
|
||||
@@ -54,7 +54,7 @@ const Panel = (props: PanelProps) => {
|
||||
return tagList.filter(tag => tag.type === type && !value.includes(tag.id) && tag.name.includes(keywords))
|
||||
}, [type, tagList, value, keywords])
|
||||
|
||||
const [creating, setCreating] = useState<boolean>(false)
|
||||
const [creating, setCreating] = useState<Boolean>(false)
|
||||
const createNewTag = async () => {
|
||||
if (!keywords)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ const TagItemEditor: FC<TagItemEditorProps> = ({
|
||||
}
|
||||
}
|
||||
const [showRemoveModal, setShowRemoveModal] = useState(false)
|
||||
const [pending, setPending] = useState<boolean>(false)
|
||||
const [pending, setPending] = useState<Boolean>(false)
|
||||
const removeTag = async (tagID: string) => {
|
||||
if (pending)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ import VectorSpaceFull from '@/app/components/billing/vector-space-full'
|
||||
type IStepOneProps = {
|
||||
datasetId?: string
|
||||
dataSourceType?: DataSourceType
|
||||
dataSourceTypeDisable: boolean
|
||||
dataSourceTypeDisable: Boolean
|
||||
hasConnection: boolean
|
||||
onSetting: () => void
|
||||
files: FileItem[]
|
||||
|
||||
@@ -28,7 +28,7 @@ import Loading from '@/app/components/base/loading'
|
||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import { type RetrievalConfig } from '@/types/app'
|
||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@@ -202,7 +202,7 @@ const StepTwo = ({
|
||||
}
|
||||
|
||||
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
|
||||
if (segmentationType === SegmentType.CUSTOM)
|
||||
setCustomFileIndexingEstimate(res)
|
||||
@@ -344,7 +344,7 @@ const StepTwo = ({
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
process_rule: getProcessRule(),
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
|
||||
embedding_model: embeddingModel.model, // Readonly
|
||||
embedding_model_provider: embeddingModel.provider, // Readonly
|
||||
@@ -357,7 +357,7 @@ const StepTwo = ({
|
||||
rerankDefaultModel,
|
||||
isRerankDefaultModelValid: !!isRerankDefaultModelValid,
|
||||
rerankModelList,
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
retrievalConfig,
|
||||
indexMethod: indexMethod as string,
|
||||
})
|
||||
@@ -367,7 +367,7 @@ const StepTwo = ({
|
||||
}
|
||||
const postRetrievalConfig = ensureRerankModelSelected({
|
||||
rerankDefaultModel: rerankDefaultModel!,
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
retrievalConfig,
|
||||
indexMethod: indexMethod as string,
|
||||
})
|
||||
|
||||
@@ -87,6 +87,4 @@ const Form: FC<FormProps> = React.memo(({
|
||||
)
|
||||
})
|
||||
|
||||
Form.displayName = 'Form'
|
||||
|
||||
export default Form
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/d
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
import type { DataSetListResponse } from '@/models/datasets'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import { type RetrievalConfig } from '@/types/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
RiBrain2Fill,
|
||||
RiBrain2Line,
|
||||
RiBrainFill,
|
||||
RiBrainLine,
|
||||
RiColorFilterFill,
|
||||
RiColorFilterLine,
|
||||
RiDatabase2Fill,
|
||||
@@ -63,8 +63,8 @@ export default function AccountSetting({
|
||||
{
|
||||
key: 'provider',
|
||||
name: t('common.settings.provider'),
|
||||
icon: <RiBrain2Line className={iconClassName} />,
|
||||
activeIcon: <RiBrain2Fill className={iconClassName} />,
|
||||
icon: <RiBrainLine className={iconClassName} />,
|
||||
activeIcon: <RiBrainFill className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'members',
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
RiRobot2Line,
|
||||
} from '@remixicon/react'
|
||||
import Nav from '../nav'
|
||||
import type { NavItem } from '../nav/nav-selector'
|
||||
import { type NavItem } from '../nav/nav-selector'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import type { Plugin } from '../types'
|
||||
import Icon from '../card/base/card-icon'
|
||||
@@ -9,7 +10,7 @@ import OrgInfo from './base/org-info'
|
||||
import Description from './base/description'
|
||||
import Placeholder from './base/placeholder'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import I18n from '@/context/i18n'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -34,7 +35,7 @@ const Card = ({
|
||||
isLoading = false,
|
||||
loadingFileName,
|
||||
}: Props) => {
|
||||
const locale = useGetLanguage()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const { type, name, org, label, brief, icon } = payload
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import type { GitHubRepoReleaseResponse } from '@/app/components/plugins/types'
|
||||
import { installPackageFromGitHub } from '@/service/plugins'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
type InstallFromGitHubProps = {
|
||||
onClose: () => void
|
||||
@@ -15,104 +12,38 @@ type InstallFromGitHubProps = {
|
||||
|
||||
type InstallStep = 'url' | 'version' | 'package' | 'installed'
|
||||
|
||||
type GitHubUrlInfo = {
|
||||
isValid: boolean
|
||||
owner?: string
|
||||
repo?: string
|
||||
}
|
||||
|
||||
const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ onClose }) => {
|
||||
const [step, setStep] = useState<InstallStep>('url')
|
||||
const [repoUrl, setRepoUrl] = useState('')
|
||||
const [selectedVersion, setSelectedVersion] = useState('')
|
||||
const [selectedPackage, setSelectedPackage] = useState('')
|
||||
const [releases, setReleases] = useState<GitHubRepoReleaseResponse[]>([])
|
||||
|
||||
const versions: Item[] = releases.map(release => ({
|
||||
value: release.tag_name,
|
||||
name: release.tag_name,
|
||||
}))
|
||||
// Mock data - replace with actual data fetched from the backend
|
||||
const versions: Item[] = [
|
||||
{ value: '1.0.1', name: '1.0.1' },
|
||||
{ value: '1.2.0', name: '1.2.0' },
|
||||
{ value: '1.2.1', name: '1.2.1' },
|
||||
{ value: '1.3.2', name: '1.3.2' },
|
||||
]
|
||||
const packages: Item[] = [
|
||||
{ value: 'package1', name: 'Package 1' },
|
||||
{ value: 'package2', name: 'Package 2' },
|
||||
{ value: 'package3', name: 'Package 3' },
|
||||
]
|
||||
|
||||
const packages: Item[] = selectedVersion
|
||||
? (releases
|
||||
.find(release => release.tag_name === selectedVersion)
|
||||
?.assets.map(asset => ({
|
||||
value: asset.browser_download_url,
|
||||
name: asset.name,
|
||||
})) || [])
|
||||
: []
|
||||
|
||||
const parseGitHubUrl = (url: string): GitHubUrlInfo => {
|
||||
const githubUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/
|
||||
const match = url.match(githubUrlRegex)
|
||||
|
||||
if (match) {
|
||||
return {
|
||||
isValid: true,
|
||||
owner: match[1],
|
||||
repo: match[2],
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: false }
|
||||
}
|
||||
|
||||
const handleInstall = async () => {
|
||||
try {
|
||||
const response = await installPackageFromGitHub({ repo: repoUrl, version: selectedVersion, package: selectedPackage })
|
||||
if (response.plugin_unique_identifier) {
|
||||
setStep('installed')
|
||||
console.log('Package installed:')
|
||||
}
|
||||
else {
|
||||
console.error('Failed to install package:')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error installing package:')
|
||||
}
|
||||
}
|
||||
|
||||
const handleNext = async () => {
|
||||
const handleNext = () => {
|
||||
switch (step) {
|
||||
case 'url': {
|
||||
const { isValid, owner, repo } = parseGitHubUrl(repoUrl)
|
||||
if (!isValid || !owner || !repo) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: 'Invalid GitHub URL. Please enter a valid URL in the format: https://github.com/owner/repo',
|
||||
})
|
||||
break
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`)
|
||||
if (!res.ok)
|
||||
throw new Error('Failed to fetch releases')
|
||||
const data = await res.json()
|
||||
const formattedReleases = data.map((release: any) => ({
|
||||
tag_name: release.tag_name,
|
||||
assets: release.assets.map((asset: any) => ({
|
||||
browser_download_url: asset.browser_download_url,
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
})),
|
||||
}))
|
||||
setReleases(formattedReleases)
|
||||
setStep('version')
|
||||
}
|
||||
catch (error) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: 'Failed to fetch repository release',
|
||||
})
|
||||
}
|
||||
case 'url':
|
||||
// TODO: Validate URL and fetch versions
|
||||
setStep('version')
|
||||
break
|
||||
}
|
||||
case 'version':
|
||||
// TODO: Validate version and fetch packages
|
||||
setStep('package')
|
||||
break
|
||||
case 'package':
|
||||
handleInstall()
|
||||
// TODO: Handle installation
|
||||
setStep('installed')
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -250,7 +181,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ onClose }) => {
|
||||
className='min-w-[72px]'
|
||||
onClick={onClose}
|
||||
>
|
||||
{step === 'url' ? 'Cancel' : 'Back'}
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='primary'
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
RiBugLine,
|
||||
RiCloseLine,
|
||||
RiHardDrive3Line,
|
||||
RiVerifiedBadgeLine,
|
||||
} from '@remixicon/react'
|
||||
import type { PluginDetail } from '../types'
|
||||
import { PluginSource } from '../types'
|
||||
import Description from '../card/base/description'
|
||||
import Icon from '../card/base/card-icon'
|
||||
import Title from '../card/base/title'
|
||||
import OrgInfo from '../card/base/org-info'
|
||||
import OperationDropdown from './operation-dropdown'
|
||||
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import { Github } from '@/app/components/base/icons/src/public/common'
|
||||
import I18n from '@/context/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'plugin.action'
|
||||
|
||||
type Props = {
|
||||
detail: PluginDetail
|
||||
onHide: () => void
|
||||
onDelete: () => void
|
||||
}
|
||||
|
||||
const DetailHeader = ({
|
||||
detail,
|
||||
onHide,
|
||||
onDelete,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const hasNewVersion = useMemo(() => {
|
||||
if (!detail)
|
||||
return false
|
||||
return false
|
||||
// return pluginDetail.latest_version !== pluginDetail.version
|
||||
}, [detail])
|
||||
|
||||
const handleUpdate = () => {}
|
||||
|
||||
const [isShowPluginInfo, {
|
||||
setTrue: showPluginInfo,
|
||||
setFalse: hidePluginInfo,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const [isShowDeleteConfirm, {
|
||||
setTrue: showDeleteConfirm,
|
||||
setFalse: hideDeleteConfirm,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const usedInApps = 3
|
||||
|
||||
return (
|
||||
<div className={cn('shrink-0 p-4 pb-3 border-b border-divider-subtle bg-components-panel-bg')}>
|
||||
<div className="flex">
|
||||
<Icon src={detail.declaration.icon} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={detail.declaration.label[locale]} />
|
||||
{detail.declaration.verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />}
|
||||
<Badge
|
||||
className='mx-1'
|
||||
text={detail.version}
|
||||
hasRedCornerMark={hasNewVersion}
|
||||
/>
|
||||
{hasNewVersion && (
|
||||
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-1 flex justify-between items-center h-4'>
|
||||
<div className='flex items-center'>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
packageNameClassName='w-auto'
|
||||
orgName={detail.declaration.author}
|
||||
packageName={detail.declaration.name}
|
||||
/>
|
||||
<div className='ml-1 mr-0.5 text-text-quaternary system-xs-regular'>·</div>
|
||||
{detail.source === PluginSource.marketplace && (
|
||||
<Tooltip popupContent={t('plugin.detailPanel.categoryTip.marketplace')} >
|
||||
<BoxSparkleFill className='w-3.5 h-3.5 text-text-tertiary hover:text-text-accent' />
|
||||
</Tooltip>
|
||||
)}
|
||||
{detail.source === PluginSource.github && (
|
||||
<Tooltip popupContent={t('plugin.detailPanel.categoryTip.github')} >
|
||||
<Github className='w-3.5 h-3.5 text-text-secondary hover:text-text-primary' />
|
||||
</Tooltip>
|
||||
)}
|
||||
{detail.source === PluginSource.local && (
|
||||
<Tooltip popupContent={t('plugin.detailPanel.categoryTip.local')} >
|
||||
<RiHardDrive3Line className='w-3.5 h-3.5 text-text-tertiary' />
|
||||
</Tooltip>
|
||||
)}
|
||||
{detail.source === PluginSource.debugging && (
|
||||
<Tooltip popupContent={t('plugin.detailPanel.categoryTip.debugging')} >
|
||||
<RiBugLine className='w-3.5 h-3.5 text-text-tertiary hover:text-text-warning' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1'>
|
||||
<OperationDropdown
|
||||
onInfo={showPluginInfo}
|
||||
onRemove={showDeleteConfirm}
|
||||
/>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<Description className='mt-3' text={detail.declaration.description[locale]} descriptionLineRows={2}></Description>
|
||||
{isShowPluginInfo && (
|
||||
<PluginInfo
|
||||
repository={detail.meta?.repo}
|
||||
release={detail.version}
|
||||
packageName={detail.meta?.package}
|
||||
onHide={hidePluginInfo}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t(`${i18nPrefix}.delete`)}
|
||||
content={
|
||||
<div>
|
||||
{t(`${i18nPrefix}.deleteContentLeft`)}<span className='system-md-semibold'>{detail.declaration.label[locale]}</span>{t(`${i18nPrefix}.deleteContentRight`)}<br />
|
||||
{usedInApps > 0 && t(`${i18nPrefix}.usedInApps`, { num: usedInApps })}
|
||||
</div>
|
||||
}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={onDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailHeader
|
||||
@@ -1,198 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import type { EndpointListItem } from '../types'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyBtn from '@/app/components/base/copy-btn'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import {
|
||||
deleteEndpoint,
|
||||
disableEndpoint,
|
||||
enableEndpoint,
|
||||
updateEndpoint,
|
||||
} from '@/service/plugins'
|
||||
|
||||
type Props = {
|
||||
data: EndpointListItem
|
||||
}
|
||||
|
||||
const EndpointCard = ({
|
||||
data,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [active, setActive] = useState(data.enabled)
|
||||
const endpointID = data.id
|
||||
|
||||
const [isShowDisableConfirm, {
|
||||
setTrue: showDisableConfirm,
|
||||
setFalse: hideDisableConfirm,
|
||||
}] = useBoolean(false)
|
||||
const activeEndpoint = async () => {
|
||||
try {
|
||||
await enableEndpoint({
|
||||
url: '/workspaces/current/endpoints/enable',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
const inactiveEndpoint = async () => {
|
||||
try {
|
||||
await disableEndpoint({
|
||||
url: '/workspaces/current/endpoints/disable',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
setActive(true)
|
||||
}
|
||||
}
|
||||
const handleSwitch = (state: boolean) => {
|
||||
if (state) {
|
||||
setActive(true)
|
||||
activeEndpoint()
|
||||
}
|
||||
else {
|
||||
setActive(false)
|
||||
showDisableConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
const [isShowDeleteConfirm, {
|
||||
setTrue: showDeleteConfirm,
|
||||
setFalse: hideDeleteConfirm,
|
||||
}] = useBoolean(false)
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteEndpoint({
|
||||
url: '/workspaces/current/endpoints/delete',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModalConfirm,
|
||||
setFalse: hideEndpointModalConfirm,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return toolCredentialToFormSchemas(data.declaration.settings)
|
||||
}, [data.declaration.settings])
|
||||
const formValue = useMemo(() => {
|
||||
return addDefaultValue(data.settings, formSchemas)
|
||||
}, [data.settings, formSchemas])
|
||||
|
||||
const handleUpdate = (state: any) => {
|
||||
try {
|
||||
updateEndpoint({
|
||||
url: '/workspaces/current/endpoints',
|
||||
body: {
|
||||
endpoint_id: data.id,
|
||||
settings: state,
|
||||
name: state.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||
<div className='group p-2.5 pl-3 bg-components-panel-on-panel-item-bg rounded-[10px] border-[0.5px] border-components-panel-border'>
|
||||
<div className='flex items-center'>
|
||||
<div className='grow mb-1 h-6 flex items-center gap-1 text-text-secondary system-md-semibold'>
|
||||
<RiLoginCircleLine className='w-4 h-4' />
|
||||
<div>{data.name}</div>
|
||||
</div>
|
||||
<div className='hidden group-hover:flex items-center'>
|
||||
<ActionButton onClick={showEndpointModalConfirm}>
|
||||
<RiEditLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
<ActionButton onClick={showDeleteConfirm} className='hover:bg-state-destructive-hover text-text-tertiary hover:text-text-destructive'>
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{data.declaration.endpoints.map((endpoint, index) => (
|
||||
<div key={index} className='h-6 flex items-center'>
|
||||
<div className='shrink-0 w-12 text-text-tertiary system-xs-regular'>{endpoint.method}</div>
|
||||
<div className='group/item grow flex items-center text-text-secondary system-xs-regular truncate'>
|
||||
<div title={`${data.url}${endpoint.path}`} className='truncate'>{`${data.url}${endpoint.path}`}</div>
|
||||
<CopyBtn
|
||||
className='hidden shrink-0 ml-2 group-hover/item:block'
|
||||
value={`${data.url}${endpoint.path}`}
|
||||
isPlain
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className='p-2 pl-3 flex items-center justify-between'>
|
||||
{active && (
|
||||
<div className='flex items-center gap-1 system-xs-semibold-uppercase text-util-colors-green-green-600'>
|
||||
<Indicator color='green' />
|
||||
{t('plugin.detailPanel.serviceOk')}
|
||||
</div>
|
||||
)}
|
||||
{!active && (
|
||||
<div className='flex items-center gap-1 system-xs-semibold-uppercase text-text-tertiary'>
|
||||
<Indicator color='gray' />
|
||||
{t('plugin.detailPanel.disabled')}
|
||||
</div>
|
||||
)}
|
||||
<Switch
|
||||
className='ml-3'
|
||||
defaultValue={active}
|
||||
onChange={handleSwitch}
|
||||
size='sm'
|
||||
/>
|
||||
</div>
|
||||
{isShowDisableConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('plugin.detailPanel.endpointDisableTip')}
|
||||
content={<div>{t('plugin.detailPanel.endpointDisableContent', { name: data.name })}</div>}
|
||||
onCancel={() => {
|
||||
hideDisableConfirm()
|
||||
setActive(true)
|
||||
}}
|
||||
onConfirm={inactiveEndpoint}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('plugin.detailPanel.endpointDeleteTip')}
|
||||
content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
)}
|
||||
{isShowEndpointModal && (
|
||||
<EndpointModal
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={formValue}
|
||||
onCancel={hideEndpointModalConfirm}
|
||||
onSaved={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EndpointCard
|
||||
@@ -1,55 +1,66 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import type { EndpointListItem, PluginEndpointDeclaration } from '../types'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import EndpointCard from './endpoint-card'
|
||||
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import { RiAddLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyBtn from '@/app/components/base/copy-btn'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
createEndpoint,
|
||||
} from '@/service/plugins'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
|
||||
type Props = {
|
||||
pluginUniqueID: string
|
||||
declaration: PluginEndpointDeclaration
|
||||
list: EndpointListItem[]
|
||||
const EndpointCard = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||
<div className='p-2.5 pl-3 bg-components-panel-on-panel-item-bg rounded-[10px] border-[0.5px] border-components-panel-border'>
|
||||
<div className='mb-1 h-6 flex items-center gap-1 text-text-secondary system-md-semibold'>
|
||||
<RiLoginCircleLine className='w-4 h-4' />
|
||||
<div>Endpoint for Unreal workspace</div>
|
||||
</div>
|
||||
<div className='h-6 flex items-center'>
|
||||
<div className='shrink-0 w-24 text-text-tertiary system-xs-regular'>Start Callback</div>
|
||||
<div className='group grow flex items-center text-text-secondary system-xs-regular truncate'>
|
||||
<div className='truncate'>https://extension.dify.ai/a1b2c3d4/onStart</div>
|
||||
<CopyBtn
|
||||
className='hidden shrink-0 ml-2 group-hover:block'
|
||||
value={'https://extension.dify.ai/a1b2c3d4/onStart'}
|
||||
isPlain
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-6 flex items-center'>
|
||||
<div className='shrink-0 w-24 text-text-tertiary system-xs-regular'>Finish Callback</div>
|
||||
<div className='group grow flex items-center text-text-secondary system-xs-regular truncate'>
|
||||
<div className='truncate'>https://extension.dify.ai/a1b2c3d4/onFinish</div>
|
||||
<CopyBtn
|
||||
className='hidden shrink-0 ml-2 group-hover:block'
|
||||
value={'https://extension.dify.ai/a1b2c3d4/onFinish'}
|
||||
isPlain
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-3 py-2 flex items-center justify-between'>
|
||||
<div className='flex items-center gap-1 system-xs-semibold-uppercase text-util-colors-green-green-600'>
|
||||
<Indicator color='green' />
|
||||
{t('plugin.detailPanel.serviceOk')}
|
||||
</div>
|
||||
{/* <div className='flex items-center gap-1 system-xs-semibold-uppercase text-text-tertiary'>
|
||||
<Indicator color='gray' />
|
||||
{t('plugin.detailPanel.disabled')}
|
||||
</div> */}
|
||||
<Switch
|
||||
className='ml-3'
|
||||
defaultValue={true}
|
||||
onChange={() => {}}
|
||||
size='sm'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EndpointList = ({
|
||||
pluginUniqueID,
|
||||
declaration,
|
||||
list,
|
||||
}: Props) => {
|
||||
const EndpointList = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModal,
|
||||
setFalse: hideEndpointModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return toolCredentialToFormSchemas(declaration.settings)
|
||||
}, [declaration.settings])
|
||||
|
||||
const handleCreate = (state: any) => {
|
||||
try {
|
||||
createEndpoint({
|
||||
url: '/workspaces/current/endpoints',
|
||||
body: {
|
||||
plugin_unique_identifier: pluginUniqueID,
|
||||
settings: state,
|
||||
name: state.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='px-4 py-2 border-t border-divider-subtle'>
|
||||
<div className='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold-uppercase'>
|
||||
@@ -57,32 +68,24 @@ const EndpointList = ({
|
||||
{t('plugin.detailPanel.endpoints')}
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]'>TODO</div>
|
||||
<div className='w-[180px]'>
|
||||
{t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ActionButton onClick={showEndpointModal}>
|
||||
<ActionButton>
|
||||
<RiAddLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
{list.length === 0 && (
|
||||
<div className='mb-1 p-3 flex justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsEmpty')}</div>
|
||||
)}
|
||||
<div className='mb-1 p-3 flex justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsEmpty')}</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
{list.map((item, index) => (
|
||||
<EndpointCard
|
||||
key={index}
|
||||
data={item}
|
||||
/>
|
||||
))}
|
||||
<EndpointCard />
|
||||
<EndpointCard />
|
||||
<EndpointCard />
|
||||
</div>
|
||||
{isShowEndpointModal && (
|
||||
<EndpointModal
|
||||
formSchemas={formSchemas}
|
||||
onCancel={hideEndpointModal}
|
||||
onSaved={handleCreate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightUpLine, RiCloseLine } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
formSchemas: any
|
||||
defaultValues?: any
|
||||
onCancel: () => void
|
||||
onSaved: (value: Record<string, any>) => void
|
||||
}
|
||||
|
||||
const EndpointModal: FC<Props> = ({
|
||||
formSchemas,
|
||||
defaultValues = {},
|
||||
onCancel,
|
||||
onSaved,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const [tempCredential, setTempCredential] = React.useState<any>(defaultValues)
|
||||
|
||||
const handleSave = () => {
|
||||
for (const field of formSchemas) {
|
||||
if (field.required && !tempCredential[field.name]) {
|
||||
Toast.notify({ type: 'error', message: t('common.errorMsg.fieldRequired', { field: field.label[language] || field.label.en_US }) })
|
||||
return
|
||||
}
|
||||
}
|
||||
onSaved(tempCredential)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen
|
||||
clickOutsideNotOpen={false}
|
||||
onClose={onCancel}
|
||||
footer={null}
|
||||
mask
|
||||
positionCenter={false}
|
||||
panelClassname={cn('justify-start mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')}
|
||||
>
|
||||
<>
|
||||
<div className='p-4 pb-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='text-text-primary system-xl-semibold'>{t('plugin.detailPanel.endpointModalTitle')}</div>
|
||||
<ActionButton onClick={onCancel}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='mt-0.5 text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointModalDesc')}</div>
|
||||
</div>
|
||||
<div className='grow overflow-y-auto'>
|
||||
<div className='px-4 py-2'>
|
||||
<Form
|
||||
value={tempCredential}
|
||||
onChange={(v) => {
|
||||
setTempCredential(v)
|
||||
}}
|
||||
formSchemas={formSchemas}
|
||||
isEditMode={true}
|
||||
showOnVariableMap={{}}
|
||||
validating={false}
|
||||
inputClassName='!bg-gray-50'
|
||||
fieldMoreInfo={item => item.url
|
||||
? (<a
|
||||
href={item.url}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='inline-flex items-center body-xs-regular text-text-accent-secondary'
|
||||
>
|
||||
{t('tools.howToGet')}
|
||||
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
|
||||
</a>)
|
||||
: null}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn('p-4 pt-0 flex justify-end')} >
|
||||
<div className='flex gap-2'>
|
||||
<Button onClick={onCancel}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
export default React.memo(EndpointModal)
|
||||
@@ -1,60 +1,132 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { EndpointListItem, PluginDetail } from '../types'
|
||||
import DetailHeader from './detail-header'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { RiCloseLine, RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import type { Plugin } from '../types'
|
||||
// import { PluginType } from '../types'
|
||||
import Badge from '../../base/badge'
|
||||
import Description from '../card/base/description'
|
||||
import Icon from '../card/base/card-icon'
|
||||
import Title from '../card/base/title'
|
||||
import OperationDropdown from './operation-dropdown'
|
||||
import EndpointList from './endpoint-list'
|
||||
import ActionList from './action-list'
|
||||
import ModelList from './model-list'
|
||||
import type { Locale } from '@/i18n'
|
||||
import { fetchPluginDetail } from '@/app/(commonLayout)/plugins/test/card/actions'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
// extensionDallE,
|
||||
// modelGPT4,
|
||||
toolNotion,
|
||||
} from '@/app/components/plugins/card/card-mock'
|
||||
|
||||
type Props = {
|
||||
pluginDetail: PluginDetail | undefined
|
||||
endpointList: EndpointListItem[]
|
||||
onHide: () => void
|
||||
locale: Locale // The component is used in both client and server side, so we can't get the locale from both side(getLocaleOnServer and useContext)
|
||||
}
|
||||
|
||||
const PluginDetailPanel: FC<Props> = ({
|
||||
pluginDetail,
|
||||
endpointList = [],
|
||||
onHide,
|
||||
locale,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
const org = searchParams.get('org')
|
||||
const name = searchParams.get('name')
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [pluginDetail, setPluginDetail] = useState<Plugin>()
|
||||
|
||||
const handleDelete = () => {}
|
||||
const hasNewVersion = useMemo(() => {
|
||||
if (!pluginDetail)
|
||||
return false
|
||||
return pluginDetail.latest_version !== pluginDetail.version
|
||||
}, [pluginDetail])
|
||||
|
||||
if (!pluginDetail)
|
||||
const getPluginDetail = async (org: string, name: string) => {
|
||||
console.log('organization: ', org)
|
||||
console.log('plugin name: ', name)
|
||||
setLoading(true)
|
||||
const detail = await fetchPluginDetail(org, name)
|
||||
setPluginDetail({
|
||||
...detail,
|
||||
...toolNotion,
|
||||
} as any)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setPluginDetail(undefined)
|
||||
router.replace(pathname)
|
||||
}
|
||||
|
||||
const handleUpdate = () => {}
|
||||
|
||||
useEffect(() => {
|
||||
if (org && name)
|
||||
getPluginDetail(org, name)
|
||||
}, [org, name])
|
||||
|
||||
if (!org || !name)
|
||||
return null
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={!!pluginDetail}
|
||||
clickOutsideNotOpen={false}
|
||||
onClose={onHide}
|
||||
onClose={handleClose}
|
||||
footer={null}
|
||||
mask={false}
|
||||
positionCenter={false}
|
||||
panelClassname={cn('justify-start mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')}
|
||||
>
|
||||
{pluginDetail && (
|
||||
{loading && <Loading type='area' />}
|
||||
{!loading && pluginDetail && (
|
||||
<>
|
||||
<DetailHeader
|
||||
detail={pluginDetail}
|
||||
onHide={onHide}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
<div className={cn('shrink-0 p-4 pb-3 border-b border-divider-subtle bg-components-panel-bg')}>
|
||||
<div className="flex">
|
||||
<Icon src={pluginDetail.icon} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={pluginDetail.label[locale]} />
|
||||
<RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />
|
||||
<Badge
|
||||
className='mx-1'
|
||||
text={pluginDetail.version}
|
||||
hasRedCornerMark={hasNewVersion}
|
||||
/>
|
||||
{hasNewVersion && (
|
||||
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-1 flex justify-between items-center h-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='text-text-tertiary system-xs-regular'>{pluginDetail.org}</div>
|
||||
<div className='ml-1 text-text-quaternary system-xs-regular'>·</div>
|
||||
<BoxSparkleFill className='w-3.5 h-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1'>
|
||||
<OperationDropdown />
|
||||
<ActionButton onClick={handleClose}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<Description className='mt-3' text={pluginDetail.brief[locale]} descriptionLineRows={2}></Description>
|
||||
</div>
|
||||
<div className='grow overflow-y-auto'>
|
||||
{!!pluginDetail.declaration.endpoint && (
|
||||
<EndpointList
|
||||
pluginUniqueID={pluginDetail.plugin_unique_identifier}
|
||||
list={endpointList}
|
||||
declaration={pluginDetail.declaration.endpoint}
|
||||
/>
|
||||
)}
|
||||
{!!pluginDetail.declaration.tool && <ActionList />}
|
||||
{!!pluginDetail.declaration.model && <ModelList />}
|
||||
<ActionList />
|
||||
<EndpointList />
|
||||
<ModelList />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { PluginSource, PluginType } from '../types'
|
||||
|
||||
export const toolNotion = {
|
||||
id: 'dlfajkgjdga-dfjalksjfglkds-dfjakld',
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
updated_at: '2024-10-16 16:05:33',
|
||||
name: 'notion page search',
|
||||
plugin_id: 'Notion/notion-page-search',
|
||||
plugin_unique_identifier: 'Notion/notion-page-search:1.2.0@fldsjflkdsajfldsakajfkls',
|
||||
declaration: {
|
||||
version: '1.2.0',
|
||||
author: 'Notion',
|
||||
name: 'notion page search',
|
||||
category: PluginType.tool,
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
label: {
|
||||
'en-US': 'Notion Page Search',
|
||||
'zh-Hans': 'Notion 页面搜索',
|
||||
},
|
||||
description: {
|
||||
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||
},
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
resource: {},
|
||||
plugins: {},
|
||||
endpoint: {
|
||||
settings: [
|
||||
{
|
||||
type: 'secret-input',
|
||||
name: 'api-key',
|
||||
required: true,
|
||||
default: null,
|
||||
options: null,
|
||||
label: {
|
||||
en_US: 'API-key',
|
||||
zh_Hans: 'API-key',
|
||||
},
|
||||
help: null,
|
||||
url: null,
|
||||
placeholder: {
|
||||
en_US: 'Please input your API key',
|
||||
zh_Hans: '请输入你的 API key',
|
||||
},
|
||||
},
|
||||
],
|
||||
endpoints: [
|
||||
{ path: '/duck/<app_id>', method: 'GET' },
|
||||
{ path: '/neko', method: 'GET' },
|
||||
],
|
||||
},
|
||||
tool: null, // TODO
|
||||
verified: true,
|
||||
},
|
||||
installation_id: 'jflkdsjoewingljlsadjgoijg-dkfjldajglkajglask-dlfkajdg',
|
||||
tenant_id: 'jflkdsjoewingljlsadjgoijg',
|
||||
endpoints_setups: 2,
|
||||
endpoints_active: 1,
|
||||
version: '1.2.0',
|
||||
source: PluginSource.marketplace,
|
||||
meta: null,
|
||||
}
|
||||
|
||||
export const toolNotionEndpoints = [
|
||||
{
|
||||
id: 'dlfajkgjdga-dfjalksjfglkds-dfjakld',
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
updated_at: '2024-10-16 16:05:33',
|
||||
settings: {
|
||||
'api-key': '*******',
|
||||
},
|
||||
tenant_id: 'jflkdsjoewingljlsadjgoijg',
|
||||
plugin_id: 'Notion/notion-page-search',
|
||||
expired_at: '2024-10-16 16:05:33',
|
||||
declaration: {
|
||||
settings: [
|
||||
{
|
||||
type: 'secret-input',
|
||||
name: 'api-key',
|
||||
required: true,
|
||||
default: null,
|
||||
options: null,
|
||||
label: {
|
||||
en_US: 'API-key',
|
||||
zh_Hans: 'API-key',
|
||||
},
|
||||
help: null,
|
||||
url: null,
|
||||
placeholder: {
|
||||
en_US: 'Please input your API key',
|
||||
zh_Hans: '请输入你的 API key',
|
||||
},
|
||||
},
|
||||
],
|
||||
endpoints: [
|
||||
{ path: '/duck/<app_id>', method: 'GET' },
|
||||
{ path: '/neko', method: 'GET' },
|
||||
],
|
||||
},
|
||||
name: 'default',
|
||||
enabled: true,
|
||||
url: 'http://localhost:5002/e/45rj9V4TRxAjL0I2wXRZgZdXjdHEKBh8',
|
||||
hook_id: '45rj9V4TRxAjL0I2wXRZgZdXjdHEKBh8',
|
||||
},
|
||||
]
|
||||
@@ -13,14 +13,9 @@ import {
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
onInfo: () => void
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = ({
|
||||
onInfo,
|
||||
onRemove,
|
||||
}) => {
|
||||
const OperationDropdown: FC<Props> = () => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
@@ -50,16 +45,14 @@ const OperationDropdown: FC<Props> = ({
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div onClick={onInfo} className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.info')}</div>
|
||||
{/* ##plugin TODO## check update */}
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.info')}</div>
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||
{/* ##plugin TODO## router action */}
|
||||
<div className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
|
||||
<div className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</div>
|
||||
<RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='my-1 h-px bg-divider-subtle'></div>
|
||||
<div onClick={onRemove} className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.remove')}</div>
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.remove')}</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import type { EndpointListItem, PluginDetail } from '../types'
|
||||
|
||||
import type { FilterState } from './filter-management'
|
||||
import FilterManagement from './filter-management'
|
||||
import List from './list'
|
||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||
import { toolNotion, toolNotionEndpoints } from '@/app/components/plugins/plugin-detail-panel/mock'
|
||||
|
||||
const PluginsPanel = () => {
|
||||
const handleFilterChange = (filters: FilterState) => {
|
||||
//
|
||||
}
|
||||
|
||||
const [currentPluginDetail, setCurrentPluginDetail] = useState<PluginDetail | undefined>(toolNotion as any)
|
||||
const [currentPluginEndpoints, setCurrentEndpoints] = useState<EndpointListItem[]>(toolNotionEndpoints as any)
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col pt-1 pb-3 px-12 justify-center items-start gap-3 self-stretch'>
|
||||
@@ -27,14 +22,6 @@ const PluginsPanel = () => {
|
||||
<List />
|
||||
</div>
|
||||
</div>
|
||||
<PluginDetailPanel
|
||||
pluginDetail={currentPluginDetail}
|
||||
endpointList={currentPluginEndpoints}
|
||||
onHide={() => {
|
||||
setCurrentPluginDetail(undefined)
|
||||
setCurrentEndpoints([])
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CredentialFormSchemaBase } from '../header/account-setting/model-provider-page/declarations'
|
||||
import type { ToolCredential } from '@/app/components/tools/types'
|
||||
import type { Locale } from '@/i18n'
|
||||
|
||||
export enum PluginType {
|
||||
@@ -8,84 +7,6 @@ export enum PluginType {
|
||||
extension = 'extension',
|
||||
}
|
||||
|
||||
export enum PluginSource {
|
||||
marketplace = 'marketplace',
|
||||
github = 'github',
|
||||
local = 'package',
|
||||
debugging = 'remote',
|
||||
}
|
||||
|
||||
export type PluginToolDeclaration = {
|
||||
identity: {
|
||||
author: string
|
||||
name: string
|
||||
description: Record<Locale, string>
|
||||
icon: string
|
||||
label: Record<Locale, string>
|
||||
tags: string[]
|
||||
}
|
||||
credentials_schema: ToolCredential[] // TODO
|
||||
}
|
||||
|
||||
export type PluginEndpointDeclaration = {
|
||||
settings: ToolCredential[]
|
||||
endpoints: EndpointItem[]
|
||||
}
|
||||
|
||||
export type EndpointItem = {
|
||||
path: string
|
||||
method: string
|
||||
}
|
||||
|
||||
export type EndpointListItem = {
|
||||
id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
settings: Record<string, any>
|
||||
tenant_id: string
|
||||
plugin_id: string
|
||||
expired_at: string
|
||||
declaration: PluginEndpointDeclaration
|
||||
name: string
|
||||
enabled: boolean
|
||||
url: string
|
||||
hook_id: string
|
||||
}
|
||||
|
||||
export type PluginDeclaration = {
|
||||
version: string
|
||||
author: string
|
||||
icon: string
|
||||
name: string
|
||||
category: PluginType
|
||||
label: Record<Locale, string>
|
||||
description: Record<Locale, string>
|
||||
created_at: string
|
||||
resource: any // useless in frontend
|
||||
plugins: any // useless in frontend
|
||||
verified: boolean
|
||||
endpoint: PluginEndpointDeclaration
|
||||
tool: PluginToolDeclaration
|
||||
model: any // TODO
|
||||
}
|
||||
|
||||
export type PluginDetail = {
|
||||
id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
name: string
|
||||
plugin_id: string
|
||||
plugin_unique_identifier: string
|
||||
declaration: PluginDeclaration
|
||||
installation_id: string
|
||||
tenant_id: string
|
||||
endpoints_setups: number
|
||||
endpoints_active: number
|
||||
version: string
|
||||
source: PluginSource
|
||||
meta?: any
|
||||
}
|
||||
|
||||
export type Plugin = {
|
||||
'type': PluginType
|
||||
'org': string
|
||||
@@ -115,45 +36,3 @@ export type Permissions = {
|
||||
canManagement: PermissionType
|
||||
canDebugger: PermissionType
|
||||
}
|
||||
|
||||
// endpoint
|
||||
export type CreateEndpointRequest = {
|
||||
plugin_unique_identifier: string
|
||||
settings: Record<string, any>
|
||||
name: string
|
||||
}
|
||||
export type EndpointOperationResponse = {
|
||||
result: 'success' | 'error'
|
||||
}
|
||||
export type EndpointsRequest = {
|
||||
limit: number
|
||||
page: number
|
||||
plugin_id: string
|
||||
}
|
||||
export type EndpointsResponse = {
|
||||
endpoints: EndpointListItem[]
|
||||
has_more: boolean
|
||||
limit: number
|
||||
total: number
|
||||
page: number
|
||||
}
|
||||
export type UpdateEndpointRequest = {
|
||||
endpoint_id: string
|
||||
settings: Record<string, any>
|
||||
name: string
|
||||
}
|
||||
|
||||
export type GitHubAsset = {
|
||||
id: number
|
||||
name: string
|
||||
browser_download_url: string
|
||||
}
|
||||
|
||||
export type GitHubRepoReleaseResponse = {
|
||||
tag_name: string
|
||||
assets: GitHubAsset[]
|
||||
}
|
||||
|
||||
export type InstallPackageResponse = {
|
||||
plugin_unique_identifier: string
|
||||
}
|
||||
|
||||
@@ -132,9 +132,9 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
const handleSend = () => {
|
||||
setIsCallBatchAPI(false)
|
||||
setControlSend(Date.now())
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
setAllTaskList([]) // clear batch task running status
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
showResSidebar()
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setControlSend(Date.now())
|
||||
// clear run once task status
|
||||
setControlStopResponding(Date.now())
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
showResSidebar()
|
||||
}
|
||||
const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import type { Collection } from './types'
|
||||
import Marketplace from './marketplace'
|
||||
import cn from '@/utils/classnames'
|
||||
@@ -8,15 +9,21 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||
import TabSliderNew from '@/app/components/base/tab-slider-new'
|
||||
import LabelFilter from '@/app/components/tools/labels/filter'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import { DotsGrid } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Colors } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { Route } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
|
||||
import CustomCreateCard from '@/app/components/tools/provider/custom-create-card'
|
||||
import ProviderDetail from '@/app/components/tools/provider/detail'
|
||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
|
||||
const ProviderList = () => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||
|
||||
@@ -24,9 +31,9 @@ const ProviderList = () => {
|
||||
defaultTab: 'builtin',
|
||||
})
|
||||
const options = [
|
||||
{ value: 'builtin', text: t('tools.type.builtIn') },
|
||||
{ value: 'api', text: t('tools.type.custom') },
|
||||
{ value: 'workflow', text: t('tools.type.workflow') },
|
||||
{ value: 'builtin', text: t('tools.type.builtIn'), icon: <DotsGrid className='w-[14px] h-[14px] mr-1' /> },
|
||||
{ value: 'api', text: t('tools.type.custom'), icon: <Colors className='w-[14px] h-[14px] mr-1' /> },
|
||||
{ value: 'workflow', text: t('tools.type.workflow'), icon: <Route className='w-[14px] h-[14px] mr-1' /> },
|
||||
]
|
||||
const [tagFilterValue, setTagFilterValue] = useState<string[]>([])
|
||||
const handleTagsChange = (value: string[]) => {
|
||||
@@ -91,7 +98,9 @@ const ProviderList = () => {
|
||||
</div>
|
||||
<div className={cn(
|
||||
'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0',
|
||||
currentProvider && 'pr-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
)}>
|
||||
{activeTab === 'api' && <CustomCreateCard onRefreshData={getProviderList} />}
|
||||
{filteredCollectionList.map(collection => (
|
||||
<div
|
||||
key={collection.id}
|
||||
@@ -125,13 +134,13 @@ const ProviderList = () => {
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<ProviderDetail
|
||||
collection={currentProvider}
|
||||
onHide={() => setCurrentProvider(undefined)}
|
||||
onRefreshData={getProviderList}
|
||||
/>
|
||||
)}
|
||||
<div className={cn(
|
||||
'shrink-0 w-0 border-l-[0.5px] border-black/8 overflow-y-auto transition-all duration-200 ease-in-out',
|
||||
currentProvider && 'w-[420px]',
|
||||
)}>
|
||||
{currentProvider && <ProviderDetail collection={currentProvider} onRefreshData={getProviderList} />}
|
||||
</div>
|
||||
<div className='absolute top-5 right-5 p-1 cursor-pointer' onClick={() => setCurrentProvider(undefined)}><RiCloseLine className='w-4 h-4' /></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import {
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react'
|
||||
import { AuthHeaderPrefix, AuthType, CollectionType } from '../types'
|
||||
import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types'
|
||||
import ToolItem from './tool-item'
|
||||
@@ -12,20 +9,14 @@ import cn from '@/utils/classnames'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
import Title from '@/app/components/plugins/card/base/title'
|
||||
import OrgInfo from '@/app/components/plugins/card/base/org-info'
|
||||
import Description from '@/app/components/plugins/card/base/description'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
|
||||
import WorkflowToolModal from '@/app/components/tools/workflow-tool'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
import {
|
||||
deleteWorkflowTool,
|
||||
fetchBuiltInToolList,
|
||||
@@ -47,13 +38,11 @@ import { useAppContext } from '@/context/app-context'
|
||||
|
||||
type Props = {
|
||||
collection: Collection
|
||||
onHide: () => void
|
||||
onRefreshData: () => void
|
||||
}
|
||||
|
||||
const ProviderDetail = ({
|
||||
collection,
|
||||
onHide,
|
||||
onRefreshData,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -224,176 +213,164 @@ const ProviderDetail = ({
|
||||
}, [collection.name, collection.type, getCustomProvider, getProviderToolList, getWorkflowToolProvider])
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={!!collection}
|
||||
clickOutsideNotOpen={false}
|
||||
onClose={onHide}
|
||||
footer={null}
|
||||
mask={false}
|
||||
positionCenter={false}
|
||||
panelClassname={cn('justify-start mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')}
|
||||
>
|
||||
<div className='p-4'>
|
||||
<div className='flex'>
|
||||
<Icon src={collection.icon} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={collection.label[language]} />
|
||||
</div>
|
||||
<div className='mb-1 flex justify-between items-center h-4'>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
packageNameClassName='w-auto'
|
||||
orgName={collection.author}
|
||||
packageName={collection.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1'>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
<div className='px-6 py-3'>
|
||||
<div className='flex items-center py-1 gap-2'>
|
||||
<div className='relative shrink-0'>
|
||||
{typeof collection.icon === 'string' && (
|
||||
<div className='w-8 h-8 bg-center bg-cover bg-no-repeat rounded-md' style={{ backgroundImage: `url(${collection.icon})` }} />
|
||||
)}
|
||||
{typeof collection.icon !== 'string' && (
|
||||
<AppIcon
|
||||
size='small'
|
||||
icon={collection.icon.content}
|
||||
background={collection.icon.background}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='grow w-0 py-[1px]'>
|
||||
<div className='flex items-center text-md leading-6 font-semibold text-gray-900'>
|
||||
<div className='truncate' title={collection.label[language]}>{collection.label[language]}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Description className='mt-3' text={collection.description[language]} descriptionLineRows={2}></Description>
|
||||
<div className='flex gap-1 border-b-[0.5px] border-black/5'>
|
||||
{(collection.type === CollectionType.builtIn) && needAuth && (
|
||||
</div>
|
||||
<div className='mt-2 min-h-[36px] text-gray-500 text-sm leading-[18px]'>{collection.description[language]}</div>
|
||||
<div className='flex gap-1 border-b-[0.5px] border-black/5'>
|
||||
{(collection.type === CollectionType.builtIn) && needAuth && (
|
||||
<Button
|
||||
variant={isAuthed ? 'secondary' : 'primary'}
|
||||
className={cn('shrink-0 my-3 w-full', isAuthed && 'bg-white')}
|
||||
onClick={() => {
|
||||
if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model)
|
||||
showSettingAuthModal()
|
||||
}}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
{isAuthed && <Indicator className='mr-2' color={'green'} />}
|
||||
<div className={cn('text-white leading-[18px] text-[13px] font-medium', isAuthed && '!text-gray-700')}>
|
||||
{isAuthed ? t('tools.auth.authorized') : t('tools.auth.unauthorized')}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
{collection.type === CollectionType.custom && !isDetailLoading && (
|
||||
<Button
|
||||
className={cn('shrink-0 my-3 w-full')}
|
||||
onClick={() => setIsShowEditCustomCollectionModal(true)}
|
||||
>
|
||||
<Settings01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
<div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div>
|
||||
</Button>
|
||||
)}
|
||||
{collection.type === CollectionType.workflow && !isDetailLoading && customCollection && (
|
||||
<>
|
||||
<Button
|
||||
variant={isAuthed ? 'secondary' : 'primary'}
|
||||
className={cn('shrink-0 my-3 w-full', isAuthed && 'bg-white')}
|
||||
onClick={() => {
|
||||
if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model)
|
||||
showSettingAuthModal()
|
||||
}}
|
||||
variant='primary'
|
||||
className={cn('shrink-0 my-3 w-[183px]')}
|
||||
>
|
||||
<a className='flex items-center text-white' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
|
||||
<div className='leading-5 text-sm font-medium'>{t('tools.openInStudio')}</div>
|
||||
<LinkExternal02 className='ml-1 w-4 h-4' />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
className={cn('shrink-0 my-3 w-[183px]')}
|
||||
onClick={() => setIsShowEditWorkflowToolModal(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
{isAuthed && <Indicator className='mr-2' color={'green'} />}
|
||||
<div className={cn('text-white leading-[18px] text-[13px] font-medium', isAuthed && '!text-gray-700')}>
|
||||
{isAuthed ? t('tools.auth.authorized') : t('tools.auth.unauthorized')}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
{collection.type === CollectionType.custom && !isDetailLoading && (
|
||||
<Button
|
||||
className={cn('shrink-0 my-3 w-full')}
|
||||
onClick={() => setIsShowEditCustomCollectionModal(true)}
|
||||
>
|
||||
<Settings01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
<div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div>
|
||||
</Button>
|
||||
)}
|
||||
{collection.type === CollectionType.workflow && !isDetailLoading && customCollection && (
|
||||
<>
|
||||
<Button
|
||||
variant='primary'
|
||||
className={cn('shrink-0 my-3 w-[183px]')}
|
||||
>
|
||||
<a className='flex items-center text-white' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
|
||||
<div className='leading-5 text-sm font-medium'>{t('tools.openInStudio')}</div>
|
||||
<LinkExternal02 className='ml-1 w-4 h-4' />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
className={cn('shrink-0 my-3 w-[183px]')}
|
||||
onClick={() => setIsShowEditWorkflowToolModal(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
<div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* Tools */}
|
||||
<div className='pt-3'>
|
||||
{isDetailLoading && <div className='flex h-[200px]'><Loading type='app' /></div>}
|
||||
{!isDetailLoading && (
|
||||
<div className='text-text-secondary system-sm-semibold-uppercase'>
|
||||
{collection.type === CollectionType.workflow && <span className=''>{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span>}
|
||||
{collection.type !== CollectionType.workflow && <span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span>}
|
||||
{needAuth && (isBuiltIn || isModel) && !isAuthed && (
|
||||
<>
|
||||
<span className='px-1'>·</span>
|
||||
<span className='text-[#DC6803]'>{t('tools.auth.setup').toLocaleUpperCase()}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isDetailLoading && (
|
||||
<div className='mt-1 py-2'>
|
||||
{collection.type !== CollectionType.workflow && toolList.map(tool => (
|
||||
<ToolItem
|
||||
key={tool.name}
|
||||
disabled={needAuth && (isBuiltIn || isModel) && !isAuthed}
|
||||
collection={collection}
|
||||
tool={tool}
|
||||
isBuiltIn={isBuiltIn}
|
||||
isModel={isModel}
|
||||
/>
|
||||
))}
|
||||
{collection.type === CollectionType.workflow && (customCollection as WorkflowToolProviderResponse)?.tool?.parameters.map(item => (
|
||||
<div key={item.name} className='mb-1 py-1'>
|
||||
<div className='mb-1 flex items-center gap-2'>
|
||||
<span className='text-text-secondary code-sm-semibold'>{item.name}</span>
|
||||
<span className='text-text-tertiary system-xs-regular'>{item.type}</span>
|
||||
<span className='text-text-warning-secondary system-xs-medium'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span>
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{item.llm_description}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showSettingAuth && (
|
||||
<ConfigCredential
|
||||
collection={collection}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async (value) => {
|
||||
await updateBuiltInToolCredential(collection.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onRemove={async () => {
|
||||
await removeBuiltInToolCredential(collection.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isShowEditCollectionToolModal && (
|
||||
<EditCustomToolModal
|
||||
payload={customCollection}
|
||||
onHide={() => setIsShowEditCustomCollectionModal(false)}
|
||||
onEdit={doUpdateCustomToolCollection}
|
||||
onRemove={onClickCustomToolDelete}
|
||||
/>
|
||||
)}
|
||||
{isShowEditWorkflowToolModal && (
|
||||
<WorkflowToolModal
|
||||
payload={customCollection}
|
||||
onHide={() => setIsShowEditWorkflowToolModal(false)}
|
||||
onRemove={onClickWorkflowToolDelete}
|
||||
onSave={updateWorkflowToolProvider}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('tools.createTool.deleteToolConfirmTitle')}
|
||||
content={t('tools.createTool.deleteToolConfirmContent')}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Drawer>
|
||||
{/* Tools */}
|
||||
<div className='pt-3'>
|
||||
{isDetailLoading && <div className='flex h-[200px]'><Loading type='app' /></div>}
|
||||
{!isDetailLoading && (
|
||||
<div className='text-xs font-medium leading-6 text-gray-500'>
|
||||
{collection.type === CollectionType.workflow && <span className=''>{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span>}
|
||||
{collection.type !== CollectionType.workflow && <span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span>}
|
||||
{needAuth && (isBuiltIn || isModel) && !isAuthed && (
|
||||
<>
|
||||
<span className='px-1'>·</span>
|
||||
<span className='text-[#DC6803]'>{t('tools.auth.setup').toLocaleUpperCase()}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isDetailLoading && (
|
||||
<div className='mt-1'>
|
||||
{collection.type !== CollectionType.workflow && toolList.map(tool => (
|
||||
<ToolItem
|
||||
key={tool.name}
|
||||
disabled={needAuth && (isBuiltIn || isModel) && !isAuthed}
|
||||
collection={collection}
|
||||
tool={tool}
|
||||
isBuiltIn={isBuiltIn}
|
||||
isModel={isModel}
|
||||
/>
|
||||
))}
|
||||
{collection.type === CollectionType.workflow && (customCollection as WorkflowToolProviderResponse)?.tool?.parameters.map(item => (
|
||||
<div key={item.name} className='mb-2 px-4 py-3 rounded-xl bg-gray-25 border-[0.5px] border-gray-200'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='font-medium text-sm text-gray-900'>{item.name}</span>
|
||||
<span className='text-xs leading-[18px] text-gray-500'>{item.type}</span>
|
||||
<span className='font-medium text-xs leading-[18px] text-[#ec4a0a]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span>
|
||||
</div>
|
||||
<div className='h-[18px] leading-[18px] text-gray-500 text-xs'>{item.llm_description}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showSettingAuth && (
|
||||
<ConfigCredential
|
||||
collection={collection}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async (value) => {
|
||||
await updateBuiltInToolCredential(collection.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onRemove={async () => {
|
||||
await removeBuiltInToolCredential(collection.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isShowEditCollectionToolModal && (
|
||||
<EditCustomToolModal
|
||||
payload={customCollection}
|
||||
onHide={() => setIsShowEditCustomCollectionModal(false)}
|
||||
onEdit={doUpdateCustomToolCollection}
|
||||
onRemove={onClickCustomToolDelete}
|
||||
/>
|
||||
)}
|
||||
{isShowEditWorkflowToolModal && (
|
||||
<WorkflowToolModal
|
||||
payload={customCollection}
|
||||
onHide={() => setIsShowEditWorkflowToolModal(false)}
|
||||
onRemove={onClickWorkflowToolDelete}
|
||||
onSave={updateWorkflowToolProvider}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('tools.createTool.deleteToolConfirmTitle')}
|
||||
content={t('tools.createTool.deleteToolConfirmContent')}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ProviderDetail
|
||||
|
||||
@@ -29,11 +29,11 @@ const ToolItem = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('mb-2 px-4 py-3 bg-components-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover', disabled && 'opacity-50 !cursor-not-allowed')}
|
||||
className={cn('mb-2 px-4 py-3 rounded-xl bg-gray-25 border-[0.5px] border-gary-200 shadow-xs cursor-pointer', disabled && 'opacity-50 !cursor-not-allowed')}
|
||||
onClick={() => !disabled && setShowDetail(true)}
|
||||
>
|
||||
<div className='pb-0.5 text-text-secondary system-md-semibold'>{tool.label[language]}</div>
|
||||
<div className='text-text-tertiary system-xs-regular line-clamp-2' title={tool.description[language]}>{tool.description[language]}</div>
|
||||
<div className='text-gray-800 font-semibold text-sm leading-5'>{tool.label[language]}</div>
|
||||
<div className='mt-0.5 text-xs leading-[18px] text-gray-500 line-clamp-2' title={tool.description[language]}>{tool.description[language]}</div>
|
||||
</div>
|
||||
{showDetail && (
|
||||
<SettingBuiltInTool
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user