mirror of
https://github.com/langgenius/dify.git
synced 2026-02-06 03:35:36 +08:00
Compare commits
1 Commits
fix/notion
...
fix/toolti
| Author | SHA1 | Date | |
|---|---|---|---|
| 2474dbdff0 |
@ -1,52 +0,0 @@
|
||||
name: Check i18n Files and Create PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
check-and-update:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for file changes in i18n/en-US
|
||||
id: check_files
|
||||
run: |
|
||||
changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'i18n/en-US/*.ts')
|
||||
echo "Changed files: $changed_files"
|
||||
if [ -n "$changed_files" ]; then
|
||||
echo "FILES_CHANGED=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "FILES_CHANGED=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Set up Node.js
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
- name: Install dependencies
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run npm script
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
run: npm run auto-gen-i18n
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.FILES_CHANGED == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update i18n files based on en-US changes
|
||||
title: 'chore: translate i18n files'
|
||||
body: This PR was automatically created to update i18n files based on changes in en-US locale.
|
||||
branch: chore/automated-i18n-updates
|
||||
@ -8,7 +8,7 @@ In terms of licensing, please take a minute to read our short [License and Contr
|
||||
|
||||
## Before you jump in
|
||||
|
||||
[Find](https://github.com/langgenius/dify/issues?q=is:issue+is:open) an existing issue, or [open](https://github.com/langgenius/dify/issues/new/choose) a new one. We categorize issues into 2 types:
|
||||
[Find](https://github.com/langgenius/dify/issues?q=is:issue+is:closed) an existing issue, or [open](https://github.com/langgenius/dify/issues/new/choose) a new one. We categorize issues into 2 types:
|
||||
|
||||
### Feature requests:
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
## 在开始之前
|
||||
|
||||
[查找](https://github.com/langgenius/dify/issues?q=is:issue+is:open)现有问题,或 [创建](https://github.com/langgenius/dify/issues/new/choose) 一个新问题。我们将问题分为两类:
|
||||
[查找](https://github.com/langgenius/dify/issues?q=is:issue+is:closed)现有问题,或 [创建](https://github.com/langgenius/dify/issues/new/choose) 一个新问题。我们将问题分为两类:
|
||||
|
||||
### 功能请求:
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ Dify にコントリビュートしたいとお考えなのですね。それは
|
||||
|
||||
## 飛び込む前に
|
||||
|
||||
[既存の Issue](https://github.com/langgenius/dify/issues?q=is:issue+is:open) を探すか、[新しい Issue](https://github.com/langgenius/dify/issues/new/choose) を作成してください。私たちは Issue を 2 つのタイプに分類しています。
|
||||
[既存の Issue](https://github.com/langgenius/dify/issues?q=is:issue+is:closed) を探すか、[新しい Issue](https://github.com/langgenius/dify/issues/new/choose) を作成してください。私たちは Issue を 2 つのタイプに分類しています。
|
||||
|
||||
### 機能リクエスト
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ Về vấn đề cấp phép, xin vui lòng dành chút thời gian đọc qua [
|
||||
|
||||
## Trước khi bắt đầu
|
||||
|
||||
[Tìm kiếm](https://github.com/langgenius/dify/issues?q=is:issue+is:open) một vấn đề hiện có, hoặc [tạo mới](https://github.com/langgenius/dify/issues/new/choose) một vấn đề. Chúng tôi phân loại các vấn đề thành 2 loại:
|
||||
[Tìm kiếm](https://github.com/langgenius/dify/issues?q=is:issue+is:closed) một vấn đề hiện có, hoặc [tạo mới](https://github.com/langgenius/dify/issues/new/choose) một vấn đề. Chúng tôi phân loại các vấn đề thành 2 loại:
|
||||
|
||||
### Yêu cầu tính năng:
|
||||
|
||||
|
||||
@ -60,8 +60,7 @@ ALIYUN_OSS_SECRET_KEY=your-secret-key
|
||||
ALIYUN_OSS_ENDPOINT=your-endpoint
|
||||
ALIYUN_OSS_AUTH_VERSION=v1
|
||||
ALIYUN_OSS_REGION=your-region
|
||||
# Don't start with '/'. OSS doesn't support leading slash in object names.
|
||||
ALIYUN_OSS_PATH=your-path
|
||||
|
||||
# Google Storage configuration
|
||||
GOOGLE_STORAGE_BUCKET_NAME=yout-bucket-name
|
||||
GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64=your-google-service-account-json-base64-string
|
||||
|
||||
@ -55,7 +55,7 @@ RUN apt-get update \
|
||||
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
|
||||
&& apt-get update \
|
||||
# For Security
|
||||
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-2 libldap-2.5-0=2.5.18+dfsg-3 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
|
||||
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-1 libldap-2.5-0=2.5.18+dfsg-3 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@ -559,9 +559,8 @@ def add_qdrant_doc_id_index(field: str):
|
||||
|
||||
@click.command("create-tenant", help="Create account and tenant.")
|
||||
@click.option("--email", prompt=True, help="The email address of the tenant account.")
|
||||
@click.option("--name", prompt=True, help="The workspace name of the tenant account.")
|
||||
@click.option("--language", prompt=True, help="Account language, default: en-US.")
|
||||
def create_tenant(email: str, language: Optional[str] = None, name: Optional[str] = None):
|
||||
def create_tenant(email: str, language: Optional[str] = None):
|
||||
"""
|
||||
Create tenant account
|
||||
"""
|
||||
@ -581,15 +580,13 @@ def create_tenant(email: str, language: Optional[str] = None, name: Optional[str
|
||||
if language not in languages:
|
||||
language = "en-US"
|
||||
|
||||
name = name.strip()
|
||||
|
||||
# generate random password
|
||||
new_password = secrets.token_urlsafe(16)
|
||||
|
||||
# register account
|
||||
account = RegisterService.register(email=email, name=account_name, password=new_password, language=language)
|
||||
|
||||
TenantService.create_owner_tenant_if_not_exist(account, name)
|
||||
TenantService.create_owner_tenant_if_not_exist(account)
|
||||
|
||||
click.echo(
|
||||
click.style(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Annotated, Optional
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import AliasChoices, Field, HttpUrl, NegativeInt, NonNegativeInt, PositiveInt, computed_field
|
||||
from pydantic_settings import BaseSettings
|
||||
@ -217,17 +217,20 @@ class HttpConfig(BaseSettings):
|
||||
def WEB_API_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
||||
return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(",")
|
||||
|
||||
HTTP_REQUEST_MAX_CONNECT_TIMEOUT: Annotated[
|
||||
PositiveInt, Field(ge=10, description="connect timeout in seconds for HTTP request")
|
||||
] = 10
|
||||
HTTP_REQUEST_MAX_CONNECT_TIMEOUT: NonNegativeInt = Field(
|
||||
description="",
|
||||
default=300,
|
||||
)
|
||||
|
||||
HTTP_REQUEST_MAX_READ_TIMEOUT: Annotated[
|
||||
PositiveInt, Field(ge=60, description="read timeout in seconds for HTTP request")
|
||||
] = 60
|
||||
HTTP_REQUEST_MAX_READ_TIMEOUT: NonNegativeInt = Field(
|
||||
description="",
|
||||
default=600,
|
||||
)
|
||||
|
||||
HTTP_REQUEST_MAX_WRITE_TIMEOUT: Annotated[
|
||||
PositiveInt, Field(ge=10, description="read timeout in seconds for HTTP request")
|
||||
] = 20
|
||||
HTTP_REQUEST_MAX_WRITE_TIMEOUT: NonNegativeInt = Field(
|
||||
description="",
|
||||
default=600,
|
||||
)
|
||||
|
||||
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: PositiveInt = Field(
|
||||
description="",
|
||||
|
||||
@ -38,8 +38,3 @@ class AliyunOSSStorageConfig(BaseSettings):
|
||||
description="Aliyun OSS authentication version",
|
||||
default=None,
|
||||
)
|
||||
|
||||
ALIYUN_OSS_PATH: Optional[str] = Field(
|
||||
description="Aliyun OSS path",
|
||||
default=None,
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
||||
|
||||
CURRENT_VERSION: str = Field(
|
||||
description="Dify version",
|
||||
default="0.7.3",
|
||||
default="0.7.2",
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
||||
@ -174,7 +174,6 @@ class AppApi(Resource):
|
||||
parser.add_argument("icon", type=str, location="json")
|
||||
parser.add_argument("icon_background", type=str, location="json")
|
||||
parser.add_argument("max_active_requests", type=int, location="json")
|
||||
parser.add_argument("use_icon_as_answer_icon", type=bool, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
app_service = AppService()
|
||||
|
||||
@ -173,18 +173,21 @@ class ChatConversationApi(Resource):
|
||||
|
||||
if args["keyword"]:
|
||||
keyword_filter = "%{}%".format(args["keyword"])
|
||||
message_subquery = (
|
||||
db.session.query(Message.conversation_id)
|
||||
.filter(or_(Message.query.ilike(keyword_filter), Message.answer.ilike(keyword_filter)))
|
||||
.subquery()
|
||||
)
|
||||
query = query.join(subquery, subquery.c.conversation_id == Conversation.id).filter(
|
||||
or_(
|
||||
Conversation.id.in_(message_subquery),
|
||||
Conversation.name.ilike(keyword_filter),
|
||||
Conversation.introduction.ilike(keyword_filter),
|
||||
subquery.c.from_end_user_session_id.ilike(keyword_filter),
|
||||
),
|
||||
query = (
|
||||
query.join(
|
||||
Message,
|
||||
Message.conversation_id == Conversation.id,
|
||||
)
|
||||
.join(subquery, subquery.c.conversation_id == Conversation.id)
|
||||
.filter(
|
||||
or_(
|
||||
Message.query.ilike(keyword_filter),
|
||||
Message.answer.ilike(keyword_filter),
|
||||
Conversation.name.ilike(keyword_filter),
|
||||
Conversation.introduction.ilike(keyword_filter),
|
||||
subquery.c.from_end_user_session_id.ilike(keyword_filter),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
account = current_user
|
||||
|
||||
@ -34,7 +34,6 @@ def parse_app_site_args():
|
||||
)
|
||||
parser.add_argument("prompt_public", type=bool, required=False, location="json")
|
||||
parser.add_argument("show_workflow_steps", type=bool, required=False, location="json")
|
||||
parser.add_argument("use_icon_as_answer_icon", type=bool, required=False, location="json")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@ -69,7 +68,6 @@ class AppSite(Resource):
|
||||
"customize_token_strategy",
|
||||
"prompt_public",
|
||||
"show_workflow_steps",
|
||||
"use_icon_as_answer_icon",
|
||||
]:
|
||||
value = args.get(attr_name)
|
||||
if value is not None:
|
||||
|
||||
@ -122,7 +122,6 @@ class DatasetListApi(Resource):
|
||||
name=args["name"],
|
||||
indexing_technique=args["indexing_technique"],
|
||||
account=current_user,
|
||||
permission=DatasetPermissionEnum.ONLY_ME,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
raise DatasetNameDuplicateError()
|
||||
|
||||
@ -39,7 +39,7 @@ class FileApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@marshal_with(file_fields)
|
||||
@cloud_edition_billing_resource_check("documents")
|
||||
@cloud_edition_billing_resource_check(resource="documents")
|
||||
def post(self):
|
||||
# get file from request
|
||||
file = request.files["file"]
|
||||
|
||||
@ -35,7 +35,6 @@ class InstalledAppsListApi(Resource):
|
||||
"uninstallable": current_tenant_id == installed_app.app_owner_tenant_id,
|
||||
}
|
||||
for installed_app in installed_apps
|
||||
if installed_app.app is not None
|
||||
]
|
||||
installed_apps.sort(
|
||||
key=lambda app: (
|
||||
|
||||
@ -46,7 +46,9 @@ def only_edition_self_hosted(view):
|
||||
return decorated
|
||||
|
||||
|
||||
def cloud_edition_billing_resource_check(resource: str):
|
||||
def cloud_edition_billing_resource_check(
|
||||
resource: str, error_msg: str = "You have reached the limit of your subscription."
|
||||
):
|
||||
def interceptor(view):
|
||||
@wraps(view)
|
||||
def decorated(*args, **kwargs):
|
||||
@ -58,22 +60,22 @@ def cloud_edition_billing_resource_check(resource: str):
|
||||
documents_upload_quota = features.documents_upload_quota
|
||||
annotation_quota_limit = features.annotation_quota_limit
|
||||
if resource == "members" and 0 < members.limit <= members.size:
|
||||
abort(403, "The number of members has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
elif resource == "apps" and 0 < apps.limit <= apps.size:
|
||||
abort(403, "The number of apps has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
|
||||
abort(403, "The capacity of the vector space has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
|
||||
# The api of file upload is used in the multiple places, so we need to check the source of the request from datasets
|
||||
source = request.args.get("source")
|
||||
if source == "datasets":
|
||||
abort(403, "The number of documents has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
else:
|
||||
return view(*args, **kwargs)
|
||||
elif resource == "workspace_custom" and not features.can_replace_logo:
|
||||
abort(403, "The workspace custom feature has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
elif resource == "annotation" and 0 < annotation_quota_limit.limit < annotation_quota_limit.size:
|
||||
abort(403, "The annotation quota has reached the limit of your subscription.")
|
||||
abort(403, error_msg)
|
||||
else:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
@ -84,7 +86,10 @@ def cloud_edition_billing_resource_check(resource: str):
|
||||
return interceptor
|
||||
|
||||
|
||||
def cloud_edition_billing_knowledge_limit_check(resource: str):
|
||||
def cloud_edition_billing_knowledge_limit_check(
|
||||
resource: str,
|
||||
error_msg: str = "To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
|
||||
):
|
||||
def interceptor(view):
|
||||
@wraps(view)
|
||||
def decorated(*args, **kwargs):
|
||||
@ -92,10 +97,7 @@ def cloud_edition_billing_knowledge_limit_check(resource: str):
|
||||
if features.billing.enabled:
|
||||
if resource == "add_segment":
|
||||
if features.billing.subscription.plan == "sandbox":
|
||||
abort(
|
||||
403,
|
||||
"To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
|
||||
)
|
||||
abort(403, error_msg)
|
||||
else:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
|
||||
@ -36,10 +36,6 @@ class SegmentApi(DatasetApiResource):
|
||||
document = DocumentService.get_document(dataset.id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
if document.indexing_status != "completed":
|
||||
raise NotFound("Document is already completed.")
|
||||
if not document.enabled:
|
||||
raise NotFound("Document is disabled.")
|
||||
# check embedding model setting
|
||||
if dataset.indexing_technique == "high_quality":
|
||||
try:
|
||||
|
||||
@ -83,7 +83,9 @@ def validate_app_token(view: Optional[Callable] = None, *, fetch_user_arg: Optio
|
||||
return decorator(view)
|
||||
|
||||
|
||||
def cloud_edition_billing_resource_check(resource: str, api_token_type: str):
|
||||
def cloud_edition_billing_resource_check(
|
||||
resource: str, api_token_type: str, error_msg: str = "You have reached the limit of your subscription."
|
||||
):
|
||||
def interceptor(view):
|
||||
def decorated(*args, **kwargs):
|
||||
api_token = validate_and_get_api_token(api_token_type)
|
||||
@ -96,13 +98,13 @@ def cloud_edition_billing_resource_check(resource: str, api_token_type: str):
|
||||
documents_upload_quota = features.documents_upload_quota
|
||||
|
||||
if resource == "members" and 0 < members.limit <= members.size:
|
||||
raise Forbidden("The number of members has reached the limit of your subscription.")
|
||||
raise Forbidden(error_msg)
|
||||
elif resource == "apps" and 0 < apps.limit <= apps.size:
|
||||
raise Forbidden("The number of apps has reached the limit of your subscription.")
|
||||
raise Forbidden(error_msg)
|
||||
elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
|
||||
raise Forbidden("The capacity of the vector space has reached the limit of your subscription.")
|
||||
raise Forbidden(error_msg)
|
||||
elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
|
||||
raise Forbidden("The number of documents has reached the limit of your subscription.")
|
||||
raise Forbidden(error_msg)
|
||||
else:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
@ -113,7 +115,11 @@ def cloud_edition_billing_resource_check(resource: str, api_token_type: str):
|
||||
return interceptor
|
||||
|
||||
|
||||
def cloud_edition_billing_knowledge_limit_check(resource: str, api_token_type: str):
|
||||
def cloud_edition_billing_knowledge_limit_check(
|
||||
resource: str,
|
||||
api_token_type: str,
|
||||
error_msg: str = "To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
|
||||
):
|
||||
def interceptor(view):
|
||||
@wraps(view)
|
||||
def decorated(*args, **kwargs):
|
||||
@ -122,9 +128,7 @@ def cloud_edition_billing_knowledge_limit_check(resource: str, api_token_type: s
|
||||
if features.billing.enabled:
|
||||
if resource == "add_segment":
|
||||
if features.billing.subscription.plan == "sandbox":
|
||||
raise Forbidden(
|
||||
"To unlock this feature and elevate your Dify experience, please upgrade to a paid plan."
|
||||
)
|
||||
raise Forbidden(error_msg)
|
||||
else:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
|
||||
@ -39,7 +39,6 @@ class AppSiteApi(WebApiResource):
|
||||
"default_language": fields.String,
|
||||
"prompt_public": fields.Boolean,
|
||||
"show_workflow_steps": fields.Boolean,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
}
|
||||
|
||||
app_fields = {
|
||||
|
||||
@ -93,7 +93,7 @@ class DatasetConfigManager:
|
||||
reranking_model=dataset_configs.get('reranking_model'),
|
||||
weights=dataset_configs.get('weights'),
|
||||
reranking_enabled=dataset_configs.get('reranking_enabled', True),
|
||||
rerank_mode=dataset_configs.get('reranking_mode', 'reranking_model'),
|
||||
rerank_mode=dataset_configs.get('rerank_mode', 'reranking_model'),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import os
|
||||
import threading
|
||||
import uuid
|
||||
from collections.abc import Generator
|
||||
from typing import Literal, Union, overload
|
||||
from typing import Union
|
||||
|
||||
from flask import Flask, current_app
|
||||
from pydantic import ValidationError
|
||||
@ -39,26 +39,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[True] = True,
|
||||
) -> Generator[str, None, None]: ...
|
||||
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[False] = False,
|
||||
) -> dict: ...
|
||||
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
import threading
|
||||
import uuid
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal, Union, overload
|
||||
from typing import Any, Union
|
||||
|
||||
from flask import Flask, current_app
|
||||
from pydantic import ValidationError
|
||||
@ -28,24 +28,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentChatAppGenerator(MessageBasedAppGenerator):
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[True] = True,
|
||||
) -> Generator[dict, None, None]: ...
|
||||
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[False] = False,
|
||||
) -> dict: ...
|
||||
|
||||
def generate(self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: Any,
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
import threading
|
||||
import uuid
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal, Union, overload
|
||||
from typing import Any, Union
|
||||
|
||||
from flask import Flask, current_app
|
||||
from pydantic import ValidationError
|
||||
@ -28,31 +28,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ChatAppGenerator(MessageBasedAppGenerator):
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[True] = True,
|
||||
) -> Generator[str, None, None]: ...
|
||||
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[False] = False,
|
||||
) -> dict: ...
|
||||
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: bool = True,
|
||||
) -> Union[dict, Generator[str, None, None]]:
|
||||
) -> Union[dict, Generator[dict, None, None]]:
|
||||
"""
|
||||
Generate App response.
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
import threading
|
||||
import uuid
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal, Union, overload
|
||||
from typing import Any, Union
|
||||
|
||||
from flask import Flask, current_app
|
||||
from pydantic import ValidationError
|
||||
@ -30,30 +30,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CompletionAppGenerator(MessageBasedAppGenerator):
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[True] = True,
|
||||
) -> Generator[str, None, None]: ...
|
||||
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[False] = False,
|
||||
) -> dict: ...
|
||||
|
||||
def generate(self, app_model: App,
|
||||
user: Union[Account, EndUser],
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: bool = True) \
|
||||
-> Union[dict, Generator[str, None, None]]:
|
||||
-> Union[dict, Generator[dict, None, None]]:
|
||||
"""
|
||||
Generate App response.
|
||||
|
||||
@ -221,7 +203,7 @@ class CompletionAppGenerator(MessageBasedAppGenerator):
|
||||
user: Union[Account, EndUser],
|
||||
invoke_from: InvokeFrom,
|
||||
stream: bool = True) \
|
||||
-> Union[dict, Generator[str, None, None]]:
|
||||
-> Union[dict, Generator[dict, None, None]]:
|
||||
"""
|
||||
Generate App response.
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import os
|
||||
import threading
|
||||
import uuid
|
||||
from collections.abc import Generator
|
||||
from typing import Literal, Union, overload
|
||||
from typing import Union
|
||||
|
||||
from flask import Flask, current_app
|
||||
from pydantic import ValidationError
|
||||
@ -32,26 +32,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WorkflowAppGenerator(BaseAppGenerator):
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[True] = True,
|
||||
) -> Generator[str, None, None]: ...
|
||||
|
||||
@overload
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
user: Union[Account, EndUser],
|
||||
args: dict,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: Literal[False] = False,
|
||||
) -> dict: ...
|
||||
|
||||
def generate(
|
||||
self, app_model: App,
|
||||
workflow: Workflow,
|
||||
@ -127,7 +107,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
application_generate_entity: WorkflowAppGenerateEntity,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: bool = True,
|
||||
) -> Union[dict, Generator[str, None, None]]:
|
||||
) -> Union[dict, Generator[dict, None, None]]:
|
||||
"""
|
||||
Generate App response.
|
||||
|
||||
|
||||
@ -150,9 +150,9 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
except json.JSONDecodeError as e:
|
||||
raise CredentialsValidateFailedError('Credentials validation failed: JSON decode error')
|
||||
|
||||
if (completion_type is LLMMode.CHAT and json_result.get('object','') == ''):
|
||||
if (completion_type is LLMMode.CHAT and json_result['object'] == ''):
|
||||
json_result['object'] = 'chat.completion'
|
||||
elif (completion_type is LLMMode.COMPLETION and json_result.get('object','') == ''):
|
||||
elif (completion_type is LLMMode.COMPLETION and json_result['object'] == ''):
|
||||
json_result['object'] = 'text_completion'
|
||||
|
||||
if (completion_type is LLMMode.CHAT
|
||||
|
||||
@ -71,24 +71,11 @@ class ArkClientV3:
|
||||
args = {
|
||||
"base_url": credentials['api_endpoint_host'],
|
||||
"region": credentials['volc_region'],
|
||||
"ak": credentials['volc_access_key_id'],
|
||||
"sk": credentials['volc_secret_access_key'],
|
||||
}
|
||||
if credentials.get("auth_method") == "api_key":
|
||||
args = {
|
||||
**args,
|
||||
"api_key": credentials['volc_api_key'],
|
||||
}
|
||||
else:
|
||||
args = {
|
||||
**args,
|
||||
"ak": credentials['volc_access_key_id'],
|
||||
"sk": credentials['volc_secret_access_key'],
|
||||
}
|
||||
|
||||
if cls.is_compatible_with_legacy(credentials):
|
||||
args = {
|
||||
**args,
|
||||
"base_url": DEFAULT_V3_ENDPOINT
|
||||
}
|
||||
args["base_url"] = DEFAULT_V3_ENDPOINT
|
||||
|
||||
client = ArkClientV3(
|
||||
**args
|
||||
|
||||
@ -30,28 +30,8 @@ model_credential_schema:
|
||||
en_US: Enter your Model Name
|
||||
zh_Hans: 输入模型名称
|
||||
credential_form_schemas:
|
||||
- variable: auth_method
|
||||
required: true
|
||||
label:
|
||||
en_US: Authentication Method
|
||||
zh_Hans: 鉴权方式
|
||||
type: select
|
||||
default: aksk
|
||||
options:
|
||||
- label:
|
||||
en_US: API Key
|
||||
value: api_key
|
||||
- label:
|
||||
en_US: Access Key / Secret Access Key
|
||||
value: aksk
|
||||
placeholder:
|
||||
en_US: Enter your Authentication Method
|
||||
zh_Hans: 选择鉴权方式
|
||||
- variable: volc_access_key_id
|
||||
required: true
|
||||
show_on:
|
||||
- variable: auth_method
|
||||
value: aksk
|
||||
label:
|
||||
en_US: Access Key
|
||||
zh_Hans: Access Key
|
||||
@ -61,9 +41,6 @@ model_credential_schema:
|
||||
zh_Hans: 输入您的 Access Key
|
||||
- variable: volc_secret_access_key
|
||||
required: true
|
||||
show_on:
|
||||
- variable: auth_method
|
||||
value: aksk
|
||||
label:
|
||||
en_US: Secret Access Key
|
||||
zh_Hans: Secret Access Key
|
||||
@ -71,17 +48,6 @@ model_credential_schema:
|
||||
placeholder:
|
||||
en_US: Enter your Secret Access Key
|
||||
zh_Hans: 输入您的 Secret Access Key
|
||||
- variable: volc_api_key
|
||||
required: true
|
||||
show_on:
|
||||
- variable: auth_method
|
||||
value: api_key
|
||||
label:
|
||||
en_US: API Key
|
||||
type: secret-input
|
||||
placeholder:
|
||||
en_US: Enter your API Key
|
||||
zh_Hans: 输入您的 API Key
|
||||
- variable: volc_region
|
||||
required: true
|
||||
label:
|
||||
|
||||
@ -38,7 +38,7 @@ parameter_rules:
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0'
|
||||
output: '0'
|
||||
input: '0.0001'
|
||||
output: '0.0001'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
|
||||
@ -37,8 +37,3 @@ parameter_rules:
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.001'
|
||||
output: '0.001'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
|
||||
@ -37,8 +37,3 @@ parameter_rules:
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.1'
|
||||
output: '0.1'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
|
||||
@ -30,9 +30,4 @@ parameter_rules:
|
||||
use_template: max_tokens
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.001'
|
||||
output: '0.001'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
max: 4096
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
model: glm-4-plus
|
||||
label:
|
||||
en_US: glm-4-plus
|
||||
model_type: llm
|
||||
features:
|
||||
- multi-tool-call
|
||||
- agent-thought
|
||||
- stream-tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
parameter_rules:
|
||||
- name: temperature
|
||||
use_template: temperature
|
||||
default: 0.95
|
||||
min: 0.0
|
||||
max: 1.0
|
||||
help:
|
||||
zh_Hans: 采样温度,控制输出的随机性,必须为正数取值范围是:(0.0,1.0],不能等于 0,默认值为 0.95 值越大,会使输出更随机,更具创造性;值越小,输出会更加稳定或确定建议您根据应用场景调整 top_p 或 temperature 参数,但不要同时调整两个参数。
|
||||
en_US: Sampling temperature, controls the randomness of the output, must be a positive number. The value range is (0.0,1.0], which cannot be equal to 0. The default value is 0.95. The larger the value, the more random and creative the output will be; the smaller the value, The output will be more stable or certain. It is recommended that you adjust the top_p or temperature parameters according to the application scenario, but do not adjust both parameters at the same time.
|
||||
- name: top_p
|
||||
use_template: top_p
|
||||
default: 0.7
|
||||
help:
|
||||
zh_Hans: 用温度取样的另一种方法,称为核取样取值范围是:(0.0, 1.0) 开区间,不能等于 0 或 1,默认值为 0.7 模型考虑具有 top_p 概率质量tokens的结果例如:0.1 意味着模型解码器只考虑从前 10% 的概率的候选集中取 tokens 建议您根据应用场景调整 top_p 或 temperature 参数,但不要同时调整两个参数。
|
||||
en_US: Another method of temperature sampling is called kernel sampling. The value range is (0.0, 1.0) open interval, which cannot be equal to 0 or 1. The default value is 0.7. The model considers the results with top_p probability mass tokens. For example 0.1 means The model decoder only considers tokens from the candidate set with the top 10% probability. It is recommended that you adjust the top_p or temperature parameters according to the application scenario, but do not adjust both parameters at the same time.
|
||||
- name: incremental
|
||||
label:
|
||||
zh_Hans: 增量返回
|
||||
en_US: Incremental
|
||||
type: boolean
|
||||
help:
|
||||
zh_Hans: SSE接口调用时,用于控制每次返回内容方式是增量还是全量,不提供此参数时默认为增量返回,true 为增量返回,false 为全量返回。
|
||||
en_US: When the SSE interface is called, it is used to control whether the content is returned incrementally or in full. If this parameter is not provided, the default is incremental return. true means incremental return, false means full return.
|
||||
required: false
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 8192
|
||||
pricing:
|
||||
input: '0.05'
|
||||
output: '0.05'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
@ -34,9 +34,4 @@ parameter_rules:
|
||||
use_template: max_tokens
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 1024
|
||||
pricing:
|
||||
input: '0.05'
|
||||
output: '0.05'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
max: 8192
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
model: glm-4v-plus
|
||||
label:
|
||||
en_US: glm-4v-plus
|
||||
model_type: llm
|
||||
model_properties:
|
||||
mode: chat
|
||||
features:
|
||||
- vision
|
||||
parameter_rules:
|
||||
- name: temperature
|
||||
use_template: temperature
|
||||
default: 0.95
|
||||
min: 0.0
|
||||
max: 1.0
|
||||
help:
|
||||
zh_Hans: 采样温度,控制输出的随机性,必须为正数取值范围是:(0.0,1.0],不能等于 0,默认值为 0.95 值越大,会使输出更随机,更具创造性;值越小,输出会更加稳定或确定建议您根据应用场景调整 top_p 或 temperature 参数,但不要同时调整两个参数。
|
||||
en_US: Sampling temperature, controls the randomness of the output, must be a positive number. The value range is (0.0,1.0], which cannot be equal to 0. The default value is 0.95. The larger the value, the more random and creative the output will be; the smaller the value, The output will be more stable or certain. It is recommended that you adjust the top_p or temperature parameters according to the application scenario, but do not adjust both parameters at the same time.
|
||||
- name: top_p
|
||||
use_template: top_p
|
||||
default: 0.7
|
||||
help:
|
||||
zh_Hans: 用温度取样的另一种方法,称为核取样取值范围是:(0.0, 1.0) 开区间,不能等于 0 或 1,默认值为 0.7 模型考虑具有 top_p 概率质量tokens的结果例如:0.1 意味着模型解码器只考虑从前 10% 的概率的候选集中取 tokens 建议您根据应用场景调整 top_p 或 temperature 参数,但不要同时调整两个参数。
|
||||
en_US: Another method of temperature sampling is called kernel sampling. The value range is (0.0, 1.0) open interval, which cannot be equal to 0 or 1. The default value is 0.7. The model considers the results with top_p probability mass tokens. For example 0.1 means The model decoder only considers tokens from the candidate set with the top 10% probability. It is recommended that you adjust the top_p or temperature parameters according to the application scenario, but do not adjust both parameters at the same time.
|
||||
- name: incremental
|
||||
label:
|
||||
zh_Hans: 增量返回
|
||||
en_US: Incremental
|
||||
type: boolean
|
||||
help:
|
||||
zh_Hans: SSE接口调用时,用于控制每次返回内容方式是增量还是全量,不提供此参数时默认为增量返回,true 为增量返回,false 为全量返回。
|
||||
en_US: When the SSE interface is called, it is used to control whether the content is returned incrementally or in full. If this parameter is not provided, the default is incremental return. true means incremental return, false means full return.
|
||||
required: false
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
default: 1024
|
||||
min: 1
|
||||
max: 1024
|
||||
pricing:
|
||||
input: '0.01'
|
||||
output: '0.01'
|
||||
unit: '0.001'
|
||||
currency: RMB
|
||||
@ -153,8 +153,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
|
||||
:return: full response or stream response chunk generator result
|
||||
"""
|
||||
extra_model_kwargs = {}
|
||||
# request to glm-4v-plus with stop words will always response "finish_reason":"network_error"
|
||||
if stop and model!= 'glm-4v-plus':
|
||||
if stop:
|
||||
extra_model_kwargs['stop'] = stop
|
||||
|
||||
client = ZhipuAI(
|
||||
@ -175,7 +174,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
|
||||
if copy_prompt_message.role in [PromptMessageRole.USER, PromptMessageRole.SYSTEM, PromptMessageRole.TOOL]:
|
||||
if isinstance(copy_prompt_message.content, list):
|
||||
# check if model is 'glm-4v'
|
||||
if model not in ('glm-4v', 'glm-4v-plus'):
|
||||
if model != 'glm-4v':
|
||||
# not support list message
|
||||
continue
|
||||
# get image and
|
||||
@ -208,7 +207,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
|
||||
else:
|
||||
new_prompt_messages.append(copy_prompt_message)
|
||||
|
||||
if model == 'glm-4v' or model == 'glm-4v-plus':
|
||||
if model == 'glm-4v':
|
||||
params = self._construct_glm_4v_parameter(model, new_prompt_messages, model_parameters)
|
||||
else:
|
||||
params = {
|
||||
@ -305,7 +304,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
|
||||
|
||||
return params
|
||||
|
||||
def _construct_glm_4v_messages(self, prompt_message: Union[str, list[PromptMessageContent]]) -> list[dict]:
|
||||
def _construct_glm_4v_messages(self, prompt_message: Union[str | list[PromptMessageContent]]) -> list[dict]:
|
||||
if isinstance(prompt_message, str):
|
||||
return [{'type': 'text', 'text': prompt_message}]
|
||||
|
||||
|
||||
@ -281,25 +281,20 @@ class NotionExtractor(BaseExtractor):
|
||||
for table_header_cell_text in tabel_header_cell:
|
||||
text = table_header_cell_text["text"]["content"]
|
||||
table_header_cell_texts.append(text)
|
||||
else:
|
||||
table_header_cell_texts.append('')
|
||||
# Initialize Markdown table with headers
|
||||
markdown_table = "| " + " | ".join(table_header_cell_texts) + " |\n"
|
||||
markdown_table += "| " + " | ".join(['---'] * len(table_header_cell_texts)) + " |\n"
|
||||
|
||||
# Process data to format each row in Markdown table format
|
||||
# get table columns text and format
|
||||
results = data["results"]
|
||||
for i in range(len(results) - 1):
|
||||
column_texts = []
|
||||
table_column_cells = data["results"][i + 1]['table_row']['cells']
|
||||
for j in range(len(table_column_cells)):
|
||||
if table_column_cells[j]:
|
||||
for table_column_cell_text in table_column_cells[j]:
|
||||
tabel_column_cells = data["results"][i + 1]['table_row']['cells']
|
||||
for j in range(len(tabel_column_cells)):
|
||||
if tabel_column_cells[j]:
|
||||
for table_column_cell_text in tabel_column_cells[j]:
|
||||
column_text = table_column_cell_text["text"]["content"]
|
||||
column_texts.append(column_text)
|
||||
# Add row to Markdown table
|
||||
markdown_table += "| " + " | ".join(column_texts) + " |\n"
|
||||
result_lines_arr.append(markdown_table)
|
||||
column_texts.append(f'{table_header_cell_texts[j]}:{column_text}')
|
||||
|
||||
cur_result_text = "\n".join(column_texts)
|
||||
result_lines_arr.append(cur_result_text)
|
||||
|
||||
if data["next_cursor"] is None:
|
||||
done = True
|
||||
break
|
||||
|
||||
@ -170,8 +170,6 @@ class WordExtractor(BaseExtractor):
|
||||
if run.element.xpath('.//a:blip'):
|
||||
for blip in run.element.xpath('.//a:blip'):
|
||||
image_id = blip.get("{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed")
|
||||
if not image_id:
|
||||
continue
|
||||
image_part = paragraph.part.rels[image_id].target_part
|
||||
|
||||
if image_part in image_map:
|
||||
@ -258,6 +256,6 @@ class WordExtractor(BaseExtractor):
|
||||
content.append(parsed_paragraph)
|
||||
elif isinstance(element.tag, str) and element.tag.endswith('tbl'): # table
|
||||
table = tables.pop(0)
|
||||
content.append(self._table_to_markdown(table, image_map))
|
||||
content.append(self._table_to_markdown(table,image_map))
|
||||
return '\n'.join(content)
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
- google
|
||||
- bing
|
||||
- perplexity
|
||||
- duckduckgo
|
||||
- searchapi
|
||||
- serper
|
||||
@ -11,7 +10,6 @@
|
||||
- wikipedia
|
||||
- nominatim
|
||||
- yahoo
|
||||
- alphavantage
|
||||
- arxiv
|
||||
- pubmed
|
||||
- stablediffusion
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="0 0 56 56" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>形状结合</title>
|
||||
<g id="设计规范" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M56,0 L56,56 L0,56 L0,0 L56,0 Z M31.6063018,12 L24.3936982,12 L24.1061064,12.7425499 L12.6071308,42.4324141 L12,44 L19.7849972,44 L20.0648488,43.2391815 L22.5196173,36.5567427 L33.4780427,36.5567427 L35.9351512,43.2391815 L36.2150028,44 L44,44 L43.3928692,42.4324141 L31.8938936,12.7425499 L31.6063018,12 Z M28.0163803,21.5755126 L31.1613993,30.2523823 L24.8432808,30.2523823 L28.0163803,21.5755126 Z" id="形状结合" fill="#2F4F4F"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 780 B |
@ -1,22 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.alphavantage.tools.query_stock import QueryStockTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class AlphaVantageProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
QueryStockTool().fork_tool_runtime(
|
||||
runtime={
|
||||
"credentials": credentials,
|
||||
}
|
||||
).invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
"code": "AAPL", # Apple Inc.
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
@ -1,31 +0,0 @@
|
||||
identity:
|
||||
author: zhuhao
|
||||
name: alphavantage
|
||||
label:
|
||||
en_US: AlphaVantage
|
||||
zh_Hans: AlphaVantage
|
||||
pt_BR: AlphaVantage
|
||||
description:
|
||||
en_US: AlphaVantage is an online platform that provides financial market data and APIs, making it convenient for individual investors and developers to access stock quotes, technical indicators, and stock analysis.
|
||||
zh_Hans: AlphaVantage是一个在线平台,它提供金融市场数据和API,便于个人投资者和开发者获取股票报价、技术指标和股票分析。
|
||||
pt_BR: AlphaVantage is an online platform that provides financial market data and APIs, making it convenient for individual investors and developers to access stock quotes, technical indicators, and stock analysis.
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- finance
|
||||
credentials_for_provider:
|
||||
api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: AlphaVantage API key
|
||||
zh_Hans: AlphaVantage API key
|
||||
pt_BR: AlphaVantage API key
|
||||
placeholder:
|
||||
en_US: Please input your AlphaVantage API key
|
||||
zh_Hans: 请输入你的 AlphaVantage API key
|
||||
pt_BR: Please input your AlphaVantage API key
|
||||
help:
|
||||
en_US: Get your AlphaVantage API key from AlphaVantage
|
||||
zh_Hans: 从 AlphaVantage 获取您的 AlphaVantage API key
|
||||
pt_BR: Get your AlphaVantage API key from AlphaVantage
|
||||
url: https://www.alphavantage.co/support/#api-key
|
||||
@ -1,49 +0,0 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
ALPHAVANTAGE_API_URL = "https://www.alphavantage.co/query"
|
||||
|
||||
|
||||
class QueryStockTool(BuiltinTool):
|
||||
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
stock_code = tool_parameters.get('code', '')
|
||||
if not stock_code:
|
||||
return self.create_text_message('Please tell me your stock code')
|
||||
|
||||
if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'):
|
||||
return self.create_text_message("Alpha Vantage API key is required.")
|
||||
|
||||
params = {
|
||||
"function": "TIME_SERIES_DAILY",
|
||||
"symbol": stock_code,
|
||||
"outputsize": "compact",
|
||||
"datatype": "json",
|
||||
"apikey": self.runtime.credentials['api_key']
|
||||
}
|
||||
response = requests.get(url=ALPHAVANTAGE_API_URL, params=params)
|
||||
response.raise_for_status()
|
||||
result = self._handle_response(response.json())
|
||||
return self.create_json_message(result)
|
||||
|
||||
def _handle_response(self, response: dict[str, Any]) -> dict[str, Any]:
|
||||
result = response.get('Time Series (Daily)', {})
|
||||
if not result:
|
||||
return {}
|
||||
stock_result = {}
|
||||
for k, v in result.items():
|
||||
stock_result[k] = {}
|
||||
stock_result[k]['open'] = v.get('1. open')
|
||||
stock_result[k]['high'] = v.get('2. high')
|
||||
stock_result[k]['low'] = v.get('3. low')
|
||||
stock_result[k]['close'] = v.get('4. close')
|
||||
stock_result[k]['volume'] = v.get('5. volume')
|
||||
return stock_result
|
||||
@ -1,27 +0,0 @@
|
||||
identity:
|
||||
name: query_stock
|
||||
author: zhuhao
|
||||
label:
|
||||
en_US: query_stock
|
||||
zh_Hans: query_stock
|
||||
pt_BR: query_stock
|
||||
description:
|
||||
human:
|
||||
en_US: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol.
|
||||
zh_Hans: 获取指定股票代码的每日开盘价、每日最高价、每日最低价、每日收盘价和每日交易量等信息。
|
||||
pt_BR: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol
|
||||
llm: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol
|
||||
parameters:
|
||||
- name: code
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: stock code
|
||||
zh_Hans: 股票代码
|
||||
pt_BR: stock code
|
||||
human_description:
|
||||
en_US: stock code
|
||||
zh_Hans: 股票代码
|
||||
pt_BR: stock code
|
||||
llm_description: stock code for query from alphavantage
|
||||
form: llm
|
||||
@ -1,3 +0,0 @@
|
||||
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.008 42L190.99 124.905L190.99 124.886L190.99 42.1913H208.506L208.506 125.276L298.891 42V136.524L336 136.524V272.866H299.005V357.035L208.506 277.525L208.506 357.948H190.99L190.99 278.836L101.11 358V272.866H64V136.524H101.008V42ZM177.785 153.826H81.5159V255.564H101.088V223.472L177.785 153.826ZM118.625 231.149V319.392L190.99 255.655L190.99 165.421L118.625 231.149ZM209.01 254.812V165.336L281.396 231.068V272.866H281.489V318.491L209.01 254.812ZM299.005 255.564H318.484V153.826L222.932 153.826L299.005 222.751V255.564ZM281.375 136.524V81.7983L221.977 136.524L281.375 136.524ZM177.921 136.524H118.524V81.7983L177.921 136.524Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 798 B |
@ -1,46 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.perplexity.tools.perplexity_search import PERPLEXITY_API_URL
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class PerplexityProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {credentials.get('perplexity_api_key')}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "llama-3.1-sonar-small-128k-online",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello"
|
||||
}
|
||||
],
|
||||
"max_tokens": 5,
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.9,
|
||||
"stream": False
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(PERPLEXITY_API_URL, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
raise ToolProviderCredentialValidationError(
|
||||
f"Failed to validate Perplexity API key: {str(e)}"
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise ToolProviderCredentialValidationError(
|
||||
f"Perplexity API key is invalid. Status code: {response.status_code}"
|
||||
)
|
||||
@ -1,26 +0,0 @@
|
||||
identity:
|
||||
author: Dify
|
||||
name: perplexity
|
||||
label:
|
||||
en_US: Perplexity
|
||||
zh_Hans: Perplexity
|
||||
description:
|
||||
en_US: Perplexity.AI
|
||||
zh_Hans: Perplexity.AI
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- search
|
||||
credentials_for_provider:
|
||||
perplexity_api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Perplexity API key
|
||||
zh_Hans: Perplexity API key
|
||||
placeholder:
|
||||
en_US: Please input your Perplexity API key
|
||||
zh_Hans: 请输入你的 Perplexity API key
|
||||
help:
|
||||
en_US: Get your Perplexity API key from Perplexity
|
||||
zh_Hans: 从 Perplexity 获取您的 Perplexity API key
|
||||
url: https://www.perplexity.ai/settings/api
|
||||
@ -1,72 +0,0 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions"
|
||||
|
||||
class PerplexityAITool(BuiltinTool):
|
||||
def _parse_response(self, response: dict) -> dict:
|
||||
"""Parse the response from Perplexity AI API"""
|
||||
if 'choices' in response and len(response['choices']) > 0:
|
||||
message = response['choices'][0]['message']
|
||||
return {
|
||||
'content': message.get('content', ''),
|
||||
'role': message.get('role', ''),
|
||||
'citations': response.get('citations', [])
|
||||
}
|
||||
else:
|
||||
return {'content': 'Unable to get a valid response', 'role': 'assistant', 'citations': []}
|
||||
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.runtime.credentials['perplexity_api_key']}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": tool_parameters.get('model', 'llama-3.1-sonar-small-128k-online'),
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Be precise and concise."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": tool_parameters['query']
|
||||
}
|
||||
],
|
||||
"max_tokens": tool_parameters.get('max_tokens', 4096),
|
||||
"temperature": tool_parameters.get('temperature', 0.7),
|
||||
"top_p": tool_parameters.get('top_p', 1),
|
||||
"top_k": tool_parameters.get('top_k', 5),
|
||||
"presence_penalty": tool_parameters.get('presence_penalty', 0),
|
||||
"frequency_penalty": tool_parameters.get('frequency_penalty', 1),
|
||||
"stream": False
|
||||
}
|
||||
|
||||
if 'search_recency_filter' in tool_parameters:
|
||||
payload['search_recency_filter'] = tool_parameters['search_recency_filter']
|
||||
if 'return_citations' in tool_parameters:
|
||||
payload['return_citations'] = tool_parameters['return_citations']
|
||||
if 'search_domain_filter' in tool_parameters:
|
||||
if isinstance(tool_parameters['search_domain_filter'], str):
|
||||
payload['search_domain_filter'] = [tool_parameters['search_domain_filter']]
|
||||
elif isinstance(tool_parameters['search_domain_filter'], list):
|
||||
payload['search_domain_filter'] = tool_parameters['search_domain_filter']
|
||||
|
||||
|
||||
response = requests.post(url=PERPLEXITY_API_URL, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
valuable_res = self._parse_response(response.json())
|
||||
|
||||
return [
|
||||
self.create_json_message(valuable_res),
|
||||
self.create_text_message(json.dumps(valuable_res, ensure_ascii=False, indent=2))
|
||||
]
|
||||
@ -1,178 +0,0 @@
|
||||
identity:
|
||||
name: perplexity
|
||||
author: Dify
|
||||
label:
|
||||
en_US: Perplexity Search
|
||||
description:
|
||||
human:
|
||||
en_US: Search information using Perplexity AI's language models.
|
||||
llm: This tool is used to search information using Perplexity AI's language models.
|
||||
parameters:
|
||||
- name: query
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Query
|
||||
zh_Hans: 查询
|
||||
human_description:
|
||||
en_US: The text query to be processed by the AI model.
|
||||
zh_Hans: 要由 AI 模型处理的文本查询。
|
||||
form: llm
|
||||
- name: model
|
||||
type: select
|
||||
required: false
|
||||
label:
|
||||
en_US: Model Name
|
||||
zh_Hans: 模型名称
|
||||
human_description:
|
||||
en_US: The Perplexity AI model to use for generating the response.
|
||||
zh_Hans: 用于生成响应的 Perplexity AI 模型。
|
||||
form: form
|
||||
default: "llama-3.1-sonar-small-128k-online"
|
||||
options:
|
||||
- value: llama-3.1-sonar-small-128k-online
|
||||
label:
|
||||
en_US: llama-3.1-sonar-small-128k-online
|
||||
zh_Hans: llama-3.1-sonar-small-128k-online
|
||||
- value: llama-3.1-sonar-large-128k-online
|
||||
label:
|
||||
en_US: llama-3.1-sonar-large-128k-online
|
||||
zh_Hans: llama-3.1-sonar-large-128k-online
|
||||
- value: llama-3.1-sonar-huge-128k-online
|
||||
label:
|
||||
en_US: llama-3.1-sonar-huge-128k-online
|
||||
zh_Hans: llama-3.1-sonar-huge-128k-online
|
||||
- name: max_tokens
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Max Tokens
|
||||
zh_Hans: 最大令牌数
|
||||
pt_BR: Máximo de Tokens
|
||||
human_description:
|
||||
en_US: The maximum number of tokens to generate in the response.
|
||||
zh_Hans: 在响应中生成的最大令牌数。
|
||||
pt_BR: O número máximo de tokens a serem gerados na resposta.
|
||||
form: form
|
||||
default: 4096
|
||||
min: 1
|
||||
max: 4096
|
||||
- name: temperature
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Temperature
|
||||
zh_Hans: 温度
|
||||
pt_BR: Temperatura
|
||||
human_description:
|
||||
en_US: Controls randomness in the output. Lower values make the output more focused and deterministic.
|
||||
zh_Hans: 控制输出的随机性。较低的值使输出更加集中和确定。
|
||||
form: form
|
||||
default: 0.7
|
||||
min: 0
|
||||
max: 1
|
||||
- name: top_k
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Top K
|
||||
zh_Hans: 取样数量
|
||||
human_description:
|
||||
en_US: The number of top results to consider for response generation.
|
||||
zh_Hans: 用于生成响应的顶部结果数量。
|
||||
form: form
|
||||
default: 5
|
||||
min: 1
|
||||
max: 100
|
||||
- name: top_p
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Top P
|
||||
zh_Hans: Top P
|
||||
human_description:
|
||||
en_US: Controls diversity via nucleus sampling.
|
||||
zh_Hans: 通过核心采样控制多样性。
|
||||
form: form
|
||||
default: 1
|
||||
min: 0.1
|
||||
max: 1
|
||||
step: 0.1
|
||||
- name: presence_penalty
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Presence Penalty
|
||||
zh_Hans: 存在惩罚
|
||||
human_description:
|
||||
en_US: Positive values penalize new tokens based on whether they appear in the text so far.
|
||||
zh_Hans: 正值会根据新词元是否已经出现在文本中来对其进行惩罚。
|
||||
form: form
|
||||
default: 0
|
||||
min: -1.0
|
||||
max: 1.0
|
||||
step: 0.1
|
||||
- name: frequency_penalty
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Frequency Penalty
|
||||
zh_Hans: 频率惩罚
|
||||
human_description:
|
||||
en_US: Positive values penalize new tokens based on their existing frequency in the text so far.
|
||||
zh_Hans: 正值会根据新词元在文本中已经出现的频率来对其进行惩罚。
|
||||
form: form
|
||||
default: 1
|
||||
min: 0.1
|
||||
max: 1.0
|
||||
step: 0.1
|
||||
- name: return_citations
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: Return Citations
|
||||
zh_Hans: 返回引用
|
||||
human_description:
|
||||
en_US: Whether to return citations in the response.
|
||||
zh_Hans: 是否在响应中返回引用。
|
||||
form: form
|
||||
default: true
|
||||
- name: search_domain_filter
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Search Domain Filter
|
||||
zh_Hans: 搜索域过滤器
|
||||
human_description:
|
||||
en_US: Domain to filter the search results.
|
||||
zh_Hans: 用于过滤搜索结果的域名。
|
||||
form: form
|
||||
default: ""
|
||||
- name: search_recency_filter
|
||||
type: select
|
||||
required: false
|
||||
label:
|
||||
en_US: Search Recency Filter
|
||||
zh_Hans: 搜索时间过滤器
|
||||
human_description:
|
||||
en_US: Filter for search results based on recency.
|
||||
zh_Hans: 基于时间筛选搜索结果。
|
||||
form: form
|
||||
default: "month"
|
||||
options:
|
||||
- value: day
|
||||
label:
|
||||
en_US: Day
|
||||
zh_Hans: 天
|
||||
- value: week
|
||||
label:
|
||||
en_US: Week
|
||||
zh_Hans: 周
|
||||
- value: month
|
||||
label:
|
||||
en_US: Month
|
||||
zh_Hans: 月
|
||||
- value: year
|
||||
label:
|
||||
en_US: Year
|
||||
zh_Hans: 年
|
||||
@ -5,6 +5,10 @@ from pydantic import BaseModel, ValidationInfo, field_validator
|
||||
from configs import dify_config
|
||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||
|
||||
MAX_CONNECT_TIMEOUT = dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT
|
||||
MAX_READ_TIMEOUT = dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT
|
||||
MAX_WRITE_TIMEOUT = dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT
|
||||
|
||||
|
||||
class HttpRequestNodeAuthorizationConfig(BaseModel):
|
||||
type: Literal[None, 'basic', 'bearer', 'custom']
|
||||
@ -37,9 +41,9 @@ class HttpRequestNodeBody(BaseModel):
|
||||
|
||||
|
||||
class HttpRequestNodeTimeout(BaseModel):
|
||||
connect: int = dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT
|
||||
read: int = dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT
|
||||
write: int = dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT
|
||||
connect: int = MAX_CONNECT_TIMEOUT
|
||||
read: int = MAX_READ_TIMEOUT
|
||||
write: int = MAX_WRITE_TIMEOUT
|
||||
|
||||
|
||||
class HttpRequestNodeData(BaseNodeData):
|
||||
|
||||
@ -3,7 +3,6 @@ from mimetypes import guess_extension
|
||||
from os import path
|
||||
from typing import cast
|
||||
|
||||
from configs import dify_config
|
||||
from core.app.segments import parser
|
||||
from core.file.file_obj import FileTransferMethod, FileType, FileVar
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
@ -12,6 +11,9 @@ from core.workflow.entities.node_entities import NodeRunResult, NodeType
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.nodes.base_node import BaseNode
|
||||
from core.workflow.nodes.http_request.entities import (
|
||||
MAX_CONNECT_TIMEOUT,
|
||||
MAX_READ_TIMEOUT,
|
||||
MAX_WRITE_TIMEOUT,
|
||||
HttpRequestNodeData,
|
||||
HttpRequestNodeTimeout,
|
||||
)
|
||||
@ -19,9 +21,9 @@ from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExe
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
|
||||
HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeTimeout(
|
||||
connect=dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT,
|
||||
read=dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT,
|
||||
write=dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT,
|
||||
connect=min(10, MAX_CONNECT_TIMEOUT),
|
||||
read=min(60, MAX_READ_TIMEOUT),
|
||||
write=min(20, MAX_WRITE_TIMEOUT),
|
||||
)
|
||||
|
||||
|
||||
@ -41,9 +43,9 @@ class HttpRequestNode(BaseNode):
|
||||
'body': {'type': 'none'},
|
||||
'timeout': {
|
||||
**HTTP_REQUEST_DEFAULT_TIMEOUT.model_dump(),
|
||||
'max_connect_timeout': dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT,
|
||||
'max_read_timeout': dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT,
|
||||
'max_write_timeout': dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT,
|
||||
'max_connect_timeout': MAX_CONNECT_TIMEOUT,
|
||||
'max_read_timeout': MAX_READ_TIMEOUT,
|
||||
'max_write_timeout': MAX_WRITE_TIMEOUT,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -90,15 +92,17 @@ class HttpRequestNode(BaseNode):
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_request_timeout(node_data: HttpRequestNodeData) -> HttpRequestNodeTimeout:
|
||||
def _get_request_timeout(self, node_data: HttpRequestNodeData) -> HttpRequestNodeTimeout:
|
||||
timeout = node_data.timeout
|
||||
if timeout is None:
|
||||
return HTTP_REQUEST_DEFAULT_TIMEOUT
|
||||
|
||||
timeout.connect = timeout.connect or HTTP_REQUEST_DEFAULT_TIMEOUT.connect
|
||||
timeout.connect = min(timeout.connect, MAX_CONNECT_TIMEOUT)
|
||||
timeout.read = timeout.read or HTTP_REQUEST_DEFAULT_TIMEOUT.read
|
||||
timeout.read = min(timeout.read, MAX_READ_TIMEOUT)
|
||||
timeout.write = timeout.write or HTTP_REQUEST_DEFAULT_TIMEOUT.write
|
||||
timeout.write = min(timeout.write, MAX_WRITE_TIMEOUT)
|
||||
return timeout
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -17,7 +17,7 @@ class AdvancedSettings(BaseModel):
|
||||
"""
|
||||
Group.
|
||||
"""
|
||||
output_type: Literal['string', 'number', 'object', 'array[string]', 'array[number]', 'array[object]']
|
||||
output_type: Literal['string', 'number', 'array', 'object']
|
||||
variables: list[list[str]]
|
||||
group_name: str
|
||||
|
||||
@ -30,4 +30,4 @@ class VariableAssignerNodeData(BaseNodeData):
|
||||
type: str = 'variable-assigner'
|
||||
output_type: str
|
||||
variables: list[list[str]]
|
||||
advanced_settings: Optional[AdvancedSettings] = None
|
||||
advanced_settings: Optional[AdvancedSettings] = None
|
||||
@ -1,4 +1,3 @@
|
||||
import openai
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
@ -10,7 +9,7 @@ def init_app(app):
|
||||
sentry_sdk.init(
|
||||
dsn=app.config.get("SENTRY_DSN"),
|
||||
integrations=[FlaskIntegration(), CeleryIntegration()],
|
||||
ignore_errors=[HTTPException, ValueError, openai.APIStatusError],
|
||||
ignore_errors=[HTTPException, ValueError],
|
||||
traces_sample_rate=app.config.get("SENTRY_TRACES_SAMPLE_RATE", 1.0),
|
||||
profiles_sample_rate=app.config.get("SENTRY_PROFILES_SAMPLE_RATE", 1.0),
|
||||
environment=app.config.get("DEPLOY_ENV"),
|
||||
|
||||
@ -15,7 +15,6 @@ class AliyunStorage(BaseStorage):
|
||||
|
||||
app_config = self.app.config
|
||||
self.bucket_name = app_config.get("ALIYUN_OSS_BUCKET_NAME")
|
||||
self.folder = app.config.get("ALIYUN_OSS_PATH")
|
||||
oss_auth_method = aliyun_s3.Auth
|
||||
region = None
|
||||
if app_config.get("ALIYUN_OSS_AUTH_VERSION") == "v4":
|
||||
@ -31,29 +30,15 @@ class AliyunStorage(BaseStorage):
|
||||
)
|
||||
|
||||
def save(self, filename, data):
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
self.client.put_object(filename, data)
|
||||
|
||||
def load_once(self, filename: str) -> bytes:
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
|
||||
with closing(self.client.get_object(filename)) as obj:
|
||||
data = obj.read()
|
||||
return data
|
||||
|
||||
def load_stream(self, filename: str) -> Generator:
|
||||
def generate(filename: str = filename) -> Generator:
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
|
||||
with closing(self.client.get_object(filename)) as obj:
|
||||
while chunk := obj.read(4096):
|
||||
yield chunk
|
||||
@ -61,24 +46,10 @@ class AliyunStorage(BaseStorage):
|
||||
return generate()
|
||||
|
||||
def download(self, filename, target_filepath):
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
|
||||
self.client.get_object_to_file(filename, target_filepath)
|
||||
|
||||
def exists(self, filename):
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
|
||||
return self.client.object_exists(filename)
|
||||
|
||||
def delete(self, filename):
|
||||
if not self.folder or self.folder.endswith("/"):
|
||||
filename = self.folder + filename
|
||||
else:
|
||||
filename = self.folder + "/" + filename
|
||||
self.client.delete_object(filename)
|
||||
|
||||
@ -58,7 +58,6 @@ app_detail_fields = {
|
||||
"model_config": fields.Nested(model_config_fields, attribute="app_model_config", allow_null=True),
|
||||
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
|
||||
"tracing": fields.Raw,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
"created_by": fields.String,
|
||||
"created_at": TimestampField,
|
||||
"updated_by": fields.String,
|
||||
@ -92,7 +91,6 @@ app_partial_fields = {
|
||||
"icon_url": AppIconUrlField,
|
||||
"model_config": fields.Nested(model_config_partial_fields, attribute="app_model_config", allow_null=True),
|
||||
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
"created_by": fields.String,
|
||||
"created_at": TimestampField,
|
||||
"updated_by": fields.String,
|
||||
@ -142,7 +140,6 @@ site_fields = {
|
||||
"prompt_public": fields.Boolean,
|
||||
"app_base_url": fields.String,
|
||||
"show_workflow_steps": fields.Boolean,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
"created_by": fields.String,
|
||||
"created_at": TimestampField,
|
||||
"updated_by": fields.String,
|
||||
@ -164,7 +161,6 @@ app_detail_fields_with_site = {
|
||||
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
|
||||
"site": fields.Nested(site_fields),
|
||||
"api_base_url": fields.String,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
"created_by": fields.String,
|
||||
"created_at": TimestampField,
|
||||
"updated_by": fields.String,
|
||||
@ -188,5 +184,4 @@ app_site_fields = {
|
||||
"customize_token_strategy": fields.String,
|
||||
"prompt_public": fields.Boolean,
|
||||
"show_workflow_steps": fields.Boolean,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ app_fields = {
|
||||
"icon": fields.String,
|
||||
"icon_background": fields.String,
|
||||
"icon_url": AppIconUrlField,
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
}
|
||||
|
||||
installed_app_fields = {
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
"""add use_icon_as_answer_icon fields for app and site
|
||||
|
||||
Revision ID: 030f4915f36a
|
||||
Revises: d0187d6a88dd
|
||||
Create Date: 2024-09-01 12:55:45.129687
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
import models as models
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "030f4915f36a"
|
||||
down_revision = "d0187d6a88dd"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("apps", schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("use_icon_as_answer_icon", sa.Boolean(), server_default=sa.text("false"), nullable=False)
|
||||
)
|
||||
|
||||
with op.batch_alter_table("sites", schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("use_icon_as_answer_icon", sa.Boolean(), server_default=sa.text("false"), nullable=False)
|
||||
)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
with op.batch_alter_table("sites", schema=None) as batch_op:
|
||||
batch_op.drop_column("use_icon_as_answer_icon")
|
||||
|
||||
with op.batch_alter_table("apps", schema=None) as batch_op:
|
||||
batch_op.drop_column("use_icon_as_answer_icon")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@ -86,7 +86,6 @@ class App(db.Model):
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
|
||||
updated_by = db.Column(StringUUID, nullable=True)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
|
||||
use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
|
||||
|
||||
@property
|
||||
def desc_or_prompt(self):
|
||||
@ -1115,7 +1114,6 @@ class Site(db.Model):
|
||||
copyright = db.Column(db.String(255))
|
||||
privacy_policy = db.Column(db.String(255))
|
||||
show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
|
||||
use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
|
||||
custom_disclaimer = db.Column(db.String(255), nullable=True)
|
||||
customize_domain = db.Column(db.String(255))
|
||||
customize_token_strategy = db.Column(db.String(255), nullable=False)
|
||||
|
||||
@ -6,6 +6,8 @@ requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff.lint]
|
||||
|
||||
@ -265,7 +265,7 @@ class TenantService:
|
||||
return tenant
|
||||
|
||||
@staticmethod
|
||||
def create_owner_tenant_if_not_exist(account: Account, name: Optional[str] = None):
|
||||
def create_owner_tenant_if_not_exist(account: Account):
|
||||
"""Create owner tenant if not exist"""
|
||||
available_ta = (
|
||||
TenantAccountJoin.query.filter_by(account_id=account.id).order_by(TenantAccountJoin.id.asc()).first()
|
||||
@ -274,10 +274,7 @@ class TenantService:
|
||||
if available_ta:
|
||||
return
|
||||
|
||||
if name:
|
||||
tenant = TenantService.create_tenant(name)
|
||||
else:
|
||||
tenant = TenantService.create_tenant(f"{account.name}'s Workspace")
|
||||
tenant = TenantService.create_tenant(f"{account.name}'s Workspace")
|
||||
TenantService.create_tenant_member(tenant, account, role="owner")
|
||||
account.current_tenant = tenant
|
||||
db.session.commit()
|
||||
|
||||
@ -87,7 +87,6 @@ class AppDslService:
|
||||
icon_background = (
|
||||
args.get("icon_background") if args.get("icon_background") else app_data.get("icon_background")
|
||||
)
|
||||
use_icon_as_answer_icon = app_data.get("use_icon_as_answer_icon", False)
|
||||
|
||||
# import dsl and create app
|
||||
app_mode = AppMode.value_of(app_data.get("mode"))
|
||||
@ -102,7 +101,6 @@ class AppDslService:
|
||||
icon_type=icon_type,
|
||||
icon=icon,
|
||||
icon_background=icon_background,
|
||||
use_icon_as_answer_icon=use_icon_as_answer_icon,
|
||||
)
|
||||
elif app_mode in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.COMPLETION]:
|
||||
app = cls._import_and_create_new_model_config_based_app(
|
||||
@ -115,7 +113,6 @@ class AppDslService:
|
||||
icon_type=icon_type,
|
||||
icon=icon,
|
||||
icon_background=icon_background,
|
||||
use_icon_as_answer_icon=use_icon_as_answer_icon,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Invalid app mode")
|
||||
@ -174,7 +171,6 @@ class AppDslService:
|
||||
"icon": "🤖" if app_model.icon_type == "image" else app_model.icon,
|
||||
"icon_background": "#FFEAD5" if app_model.icon_type == "image" else app_model.icon_background,
|
||||
"description": app_model.description,
|
||||
"use_icon_as_answer_icon": app_model.use_icon_as_answer_icon,
|
||||
},
|
||||
}
|
||||
|
||||
@ -222,7 +218,6 @@ class AppDslService:
|
||||
icon_type: str,
|
||||
icon: str,
|
||||
icon_background: str,
|
||||
use_icon_as_answer_icon: bool,
|
||||
) -> App:
|
||||
"""
|
||||
Import app dsl and create new workflow based app
|
||||
@ -236,7 +231,6 @@ class AppDslService:
|
||||
:param icon_type: app icon type, "emoji" or "image"
|
||||
:param icon: app icon
|
||||
:param icon_background: app icon background
|
||||
:param use_icon_as_answer_icon: use app icon as answer icon
|
||||
"""
|
||||
if not workflow_data:
|
||||
raise ValueError("Missing workflow in data argument " "when app mode is advanced-chat or workflow")
|
||||
@ -250,7 +244,6 @@ class AppDslService:
|
||||
icon_type=icon_type,
|
||||
icon=icon,
|
||||
icon_background=icon_background,
|
||||
use_icon_as_answer_icon=use_icon_as_answer_icon,
|
||||
)
|
||||
|
||||
# init draft workflow
|
||||
@ -323,7 +316,6 @@ class AppDslService:
|
||||
icon_type: str,
|
||||
icon: str,
|
||||
icon_background: str,
|
||||
use_icon_as_answer_icon: bool,
|
||||
) -> App:
|
||||
"""
|
||||
Import app dsl and create new model config based app
|
||||
@ -349,7 +341,6 @@ class AppDslService:
|
||||
icon_type=icon_type,
|
||||
icon=icon,
|
||||
icon_background=icon_background,
|
||||
use_icon_as_answer_icon=use_icon_as_answer_icon,
|
||||
)
|
||||
|
||||
app_model_config = AppModelConfig()
|
||||
@ -378,7 +369,6 @@ class AppDslService:
|
||||
icon_type: str,
|
||||
icon: str,
|
||||
icon_background: str,
|
||||
use_icon_as_answer_icon: bool,
|
||||
) -> App:
|
||||
"""
|
||||
Create new app
|
||||
@ -391,7 +381,6 @@ class AppDslService:
|
||||
:param icon_type: app icon type, "emoji" or "image"
|
||||
:param icon: app icon
|
||||
:param icon_background: app icon background
|
||||
:param use_icon_as_answer_icon: use app icon as answer icon
|
||||
"""
|
||||
app = App(
|
||||
tenant_id=tenant_id,
|
||||
@ -403,7 +392,6 @@ class AppDslService:
|
||||
icon_background=icon_background,
|
||||
enable_site=True,
|
||||
enable_api=True,
|
||||
use_icon_as_answer_icon=use_icon_as_answer_icon,
|
||||
created_by=account.id,
|
||||
updated_by=account.id,
|
||||
)
|
||||
|
||||
@ -221,7 +221,6 @@ class AppService:
|
||||
app.icon_type = args.get("icon_type", "emoji")
|
||||
app.icon = args.get("icon")
|
||||
app.icon_background = args.get("icon_background")
|
||||
app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)
|
||||
app.updated_by = current_user.id
|
||||
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
db.session.commit()
|
||||
|
||||
@ -137,7 +137,7 @@ class DatasetService:
|
||||
|
||||
@staticmethod
|
||||
def create_empty_dataset(
|
||||
tenant_id: str, name: str, indexing_technique: Optional[str], account: Account, permission: Optional[str] = None
|
||||
tenant_id: str, name: str, indexing_technique: Optional[str], account: Account, permission: Optional[str]
|
||||
):
|
||||
# check if dataset name already exists
|
||||
if Dataset.query.filter_by(name=name, tenant_id=tenant_id).first():
|
||||
|
||||
@ -19,7 +19,7 @@ def send_invite_member_mail_task(language: str, to: str, token: str, inviter_nam
|
||||
:param inviter_name
|
||||
:param workspace_name
|
||||
|
||||
Usage: send_invite_member_mail_task.delay(language, to, token, inviter_name, workspace_name)
|
||||
Usage: send_invite_member_mail_task.delay(langauge, to, token, inviter_name, workspace_name)
|
||||
"""
|
||||
if not mail.is_inited():
|
||||
return
|
||||
|
||||
@ -19,7 +19,6 @@ def example_env_file(tmp_path, monkeypatch) -> str:
|
||||
"""
|
||||
CONSOLE_API_URL=https://example.com
|
||||
CONSOLE_WEB_URL=https://example.com
|
||||
HTTP_REQUEST_MAX_WRITE_TIMEOUT=30
|
||||
"""
|
||||
)
|
||||
)
|
||||
@ -49,12 +48,6 @@ def test_dify_config(example_env_file):
|
||||
assert config.API_COMPRESSION_ENABLED is False
|
||||
assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0
|
||||
|
||||
# annotated field with default value
|
||||
assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 60
|
||||
|
||||
# annotated field with configured value
|
||||
assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 30
|
||||
|
||||
|
||||
# NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected.
|
||||
# This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`.
|
||||
|
||||
@ -2,7 +2,7 @@ version: '3'
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.7.3
|
||||
image: langgenius/dify-api:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
# Startup mode, 'api' starts the API server.
|
||||
@ -229,7 +229,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.7.3
|
||||
image: langgenius/dify-api:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_WEB_URL: ''
|
||||
@ -400,7 +400,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.7.3
|
||||
image: langgenius/dify-web:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
||||
|
||||
@ -285,8 +285,6 @@ ALIYUN_OSS_SECRET_KEY=your-secret-key
|
||||
ALIYUN_OSS_ENDPOINT=https://oss-ap-southeast-1-internal.aliyuncs.com
|
||||
ALIYUN_OSS_REGION=ap-southeast-1
|
||||
ALIYUN_OSS_AUTH_VERSION=v4
|
||||
# Don't start with '/'. OSS doesn't support leading slash in object names.
|
||||
ALIYUN_OSS_PATH=your-path
|
||||
|
||||
# Tencent COS Configuration
|
||||
# The name of the Tencent COS bucket to use for storing files.
|
||||
|
||||
@ -66,7 +66,6 @@ x-shared-env: &shared-api-worker-env
|
||||
ALIYUN_OSS_ENDPOINT: ${ALIYUN_OSS_ENDPOINT:-}
|
||||
ALIYUN_OSS_REGION: ${ALIYUN_OSS_REGION:-}
|
||||
ALIYUN_OSS_AUTH_VERSION: ${ALIYUN_OSS_AUTH_VERSION:-v4}
|
||||
ALIYUN_OSS_PATHS: ${ALIYUN_OSS_PATH:-}
|
||||
TENCENT_COS_BUCKET_NAME: ${TENCENT_COS_BUCKET_NAME:-}
|
||||
TENCENT_COS_SECRET_KEY: ${TENCENT_COS_SECRET_KEY:-}
|
||||
TENCENT_COS_SECRET_ID: ${TENCENT_COS_SECRET_ID:-}
|
||||
@ -191,7 +190,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.7.3
|
||||
image: langgenius/dify-api:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -211,7 +210,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.7.3
|
||||
image: langgenius/dify-api:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -230,7 +229,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.7.3
|
||||
image: langgenius/dify-web:0.7.2
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@ -295,7 +294,7 @@ services:
|
||||
|
||||
# ssrf_proxy server
|
||||
# for more information, please refer to
|
||||
# https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed
|
||||
# https://docs.dify.ai/learn-more/faq/self-host-faq#id-18.-why-is-ssrf_proxy-needed
|
||||
ssrf_proxy:
|
||||
image: ubuntu/squid:latest
|
||||
restart: always
|
||||
|
||||
@ -128,7 +128,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
if (e.status === 404)
|
||||
router.replace('/apps')
|
||||
})
|
||||
}, [appId, isCurrentWorkspaceEditor, systemFeatures])
|
||||
}, [appId, isCurrentWorkspaceEditor])
|
||||
|
||||
useUnmount(() => {
|
||||
setAppDetail()
|
||||
|
||||
@ -95,7 +95,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
|
||||
|
||||
if (systemFeatures.enable_web_sso_switch_component) {
|
||||
const [sso_err] = await asyncRunSafe<AppSSO>(
|
||||
updateAppSSO({ id: appId, enabled: Boolean(params.enable_sso) }) as Promise<AppSSO>,
|
||||
updateAppSSO({ id: appId, enabled: params.enable_sso }) as Promise<AppSSO>,
|
||||
)
|
||||
if (sso_err) {
|
||||
handleCallbackResult(sso_err)
|
||||
|
||||
@ -79,7 +79,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
use_icon_as_answer_icon,
|
||||
}) => {
|
||||
try {
|
||||
await updateAppInfo({
|
||||
@ -89,7 +88,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
use_icon_as_answer_icon,
|
||||
})
|
||||
setShowEditModal(false)
|
||||
notify({
|
||||
@ -257,7 +255,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
e.preventDefault()
|
||||
getRedirection(isCurrentWorkspaceEditor, app, push)
|
||||
}}
|
||||
className='relative group col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className='relative shrink-0'>
|
||||
@ -299,16 +297,17 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='title-wrapper h-[90px] px-[14px] text-xs leading-normal text-gray-500'>
|
||||
<div
|
||||
className={cn(tags.length ? 'line-clamp-2' : 'line-clamp-4', 'group-hover:line-clamp-2')}
|
||||
title={app.description}
|
||||
>
|
||||
{app.description}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-gray-500 group-hover:line-clamp-2 group-hover:max-h-[36px]',
|
||||
tags.length ? 'line-clamp-2' : 'line-clamp-4',
|
||||
)}
|
||||
title={app.description}
|
||||
>
|
||||
{app.description}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
|
||||
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
|
||||
tags.length ? 'flex' : '!hidden group-hover:!flex',
|
||||
)}>
|
||||
{isCurrentWorkspaceEditor && (
|
||||
@ -372,8 +371,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
appIconBackground={app.icon_background}
|
||||
appIconUrl={app.icon_url}
|
||||
appDescription={app.description}
|
||||
appMode={app.mode}
|
||||
appUseIconAsAnswerIcon={app.use_icon_as_answer_icon}
|
||||
show={showEditModal}
|
||||
onConfirm={onEdit}
|
||||
onHide={() => setShowEditModal(false)}
|
||||
|
||||
@ -139,7 +139,7 @@ const Apps = () => {
|
||||
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard onSuccess={mutate} />}
|
||||
{data?.map(({ data: apps }) => apps.map(app => (
|
||||
{data?.map(({ data: apps }: any) => apps.map((app: any) => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
)))}
|
||||
<CheckModal />
|
||||
|
||||
@ -63,7 +63,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
use_icon_as_answer_icon,
|
||||
}) => {
|
||||
if (!appDetail)
|
||||
return
|
||||
@ -75,7 +74,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
use_icon_as_answer_icon,
|
||||
})
|
||||
setShowEditModal(false)
|
||||
notify({
|
||||
@ -425,8 +423,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
appIconBackground={appDetail.icon_background}
|
||||
appIconUrl={appDetail.icon_url}
|
||||
appDescription={appDetail.description}
|
||||
appMode={appDetail.mode}
|
||||
appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
|
||||
show={showEditModal}
|
||||
onConfirm={onEdit}
|
||||
onHide={() => setShowEditModal(false)}
|
||||
|
||||
@ -134,8 +134,8 @@ function AppCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
`shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`}
|
||||
className={`shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''
|
||||
}`}
|
||||
>
|
||||
<div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}>
|
||||
<div className="mb-2.5 flex flex-row items-start justify-between">
|
||||
@ -176,6 +176,7 @@ function AppCard({
|
||||
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />}
|
||||
<CopyFeedback
|
||||
content={isApp ? appUrl : apiUrl}
|
||||
selectorId={randomString(8)}
|
||||
className={'hover:bg-gray-200'}
|
||||
/>
|
||||
{/* button copy link/ button regenerate */}
|
||||
@ -201,8 +202,8 @@ function AppCard({
|
||||
onClick={() => setShowConfirmDelete(true)}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
`w-full h-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`}
|
||||
className={`w-full h-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@ -43,7 +43,6 @@ export type ConfigParams = {
|
||||
icon: string
|
||||
icon_background?: string
|
||||
show_workflow_steps: boolean
|
||||
use_icon_as_answer_icon: boolean
|
||||
enable_sso?: boolean
|
||||
}
|
||||
|
||||
@ -73,7 +72,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
custom_disclaimer,
|
||||
default_language,
|
||||
show_workflow_steps,
|
||||
use_icon_as_answer_icon,
|
||||
} = appInfo.site
|
||||
const [inputInfo, setInputInfo] = useState({
|
||||
title,
|
||||
@ -84,7 +82,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
privacyPolicy: privacy_policy,
|
||||
customDisclaimer: custom_disclaimer,
|
||||
show_workflow_steps,
|
||||
use_icon_as_answer_icon,
|
||||
enable_sso: appInfo.enable_sso,
|
||||
})
|
||||
const [language, setLanguage] = useState(default_language)
|
||||
@ -97,7 +94,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
? { type: 'image', url: icon_url!, fileId: icon }
|
||||
: { type: 'emoji', icon, background: icon_background! },
|
||||
)
|
||||
const isChatBot = appInfo.mode === 'chat' || appInfo.mode === 'advanced-chat' || appInfo.mode === 'agent-chat'
|
||||
|
||||
useEffect(() => {
|
||||
setInputInfo({
|
||||
@ -109,7 +105,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
privacyPolicy: privacy_policy,
|
||||
customDisclaimer: custom_disclaimer,
|
||||
show_workflow_steps,
|
||||
use_icon_as_answer_icon,
|
||||
enable_sso: appInfo.enable_sso,
|
||||
})
|
||||
setLanguage(default_language)
|
||||
@ -162,7 +157,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId,
|
||||
icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined,
|
||||
show_workflow_steps: inputInfo.show_workflow_steps,
|
||||
use_icon_as_answer_icon: inputInfo.use_icon_as_answer_icon,
|
||||
enable_sso: inputInfo.enable_sso,
|
||||
}
|
||||
await onSave?.(params)
|
||||
@ -215,18 +209,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
onChange={onChange('desc')}
|
||||
placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
|
||||
/>
|
||||
{isChatBot && (
|
||||
<div className='w-full mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className={`font-medium ${s.settingTitle} text-gray-900 `}>{t('app.answerIcon.title')}</div>
|
||||
<Switch
|
||||
defaultValue={inputInfo.use_icon_as_answer_icon}
|
||||
onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
|
||||
/>
|
||||
</div>
|
||||
<p className='body-xs-regular text-gray-500'>{t('app.answerIcon.description')}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
|
||||
<SimpleSelect
|
||||
items={languages.filter(item => item.supported)}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { init } from 'emoji-mart'
|
||||
import data from '@emoji-mart/data'
|
||||
import classNames from '@/utils/classnames'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
|
||||
init({ data })
|
||||
|
||||
export type AnswerIconProps = {
|
||||
iconType?: AppIconType | null
|
||||
icon?: string | null
|
||||
background?: string | null
|
||||
imageUrl?: string | null
|
||||
}
|
||||
|
||||
const AnswerIcon: FC<AnswerIconProps> = ({
|
||||
iconType,
|
||||
icon,
|
||||
background,
|
||||
imageUrl,
|
||||
}) => {
|
||||
const wrapperClassName = classNames(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'w-full',
|
||||
'h-full',
|
||||
'rounded-full',
|
||||
'border-[0.5px]',
|
||||
'border-black/5',
|
||||
'text-xl',
|
||||
)
|
||||
const isValidImageIcon = iconType === 'image' && imageUrl
|
||||
return <div
|
||||
className={wrapperClassName}
|
||||
style={{ background: background || '#D5F5F6' }}
|
||||
>
|
||||
{isValidImageIcon
|
||||
? <img src={imageUrl} className="w-full h-full rounded-full" alt="answer icon" />
|
||||
: (icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default AnswerIcon
|
||||
@ -13,7 +13,6 @@ import {
|
||||
getUrl,
|
||||
stopChatMessageResponding,
|
||||
} from '@/service/share'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
|
||||
const ChatWrapper = () => {
|
||||
const {
|
||||
@ -129,15 +128,6 @@ const ChatWrapper = () => {
|
||||
isMobile,
|
||||
])
|
||||
|
||||
const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon)
|
||||
? <AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
: null
|
||||
|
||||
return (
|
||||
<Chat
|
||||
appData={appData}
|
||||
@ -153,7 +143,6 @@ const ChatWrapper = () => {
|
||||
allToolIcons={appMeta?.tool_icons || {}}
|
||||
onFeedback={handleFeedback}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
answerIcon={answerIcon}
|
||||
hideProcessDetail
|
||||
themeBuilder={themeBuilder}
|
||||
/>
|
||||
|
||||
@ -65,7 +65,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
prompt_public: false,
|
||||
copyright: '',
|
||||
show_workflow_steps: true,
|
||||
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
||||
},
|
||||
plan: 'basic',
|
||||
} as AppData
|
||||
|
||||
@ -22,7 +22,6 @@ import Citation from '@/app/components/base/chat/chat/citation'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import type { AppData } from '@/models/share'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
|
||||
type AnswerProps = {
|
||||
item: ChatItem
|
||||
@ -90,7 +89,11 @@ const Answer: FC<AnswerProps> = ({
|
||||
<div className='flex mb-2 last:mb-0'>
|
||||
<div className='shrink-0 relative w-10 h-10'>
|
||||
{
|
||||
answerIcon || <AnswerIcon />
|
||||
answerIcon || (
|
||||
<div className='flex items-center justify-center w-full h-full rounded-full bg-[#d5f5f6] border-[0.5px] border-black/5 text-xl'>
|
||||
🤖
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
responding && (
|
||||
|
||||
@ -372,16 +372,11 @@ export const useChat = (
|
||||
handleUpdateChatList(newChatList)
|
||||
}
|
||||
if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
|
||||
try {
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
responseItem.id,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
catch (e) {
|
||||
setSuggestQuestions([])
|
||||
}
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
responseItem.id,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
},
|
||||
onFile(file) {
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
stopChatMessageResponding,
|
||||
} from '@/service/share'
|
||||
import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
|
||||
const ChatWrapper = () => {
|
||||
const {
|
||||
@ -115,17 +114,6 @@ const ChatWrapper = () => {
|
||||
return null
|
||||
}, [currentConversationId, inputsForms, isMobile])
|
||||
|
||||
const answerIcon = isDify()
|
||||
? <LogoAvatar className='relative shrink-0' />
|
||||
: (appData?.site && appData.site.use_icon_as_answer_icon)
|
||||
? <AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
: null
|
||||
|
||||
return (
|
||||
<Chat
|
||||
appData={appData}
|
||||
@ -141,7 +129,7 @@ const ChatWrapper = () => {
|
||||
allToolIcons={appMeta?.tool_icons || {}}
|
||||
onFeedback={handleFeedback}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
answerIcon={answerIcon}
|
||||
answerIcon={isDify() ? <LogoAvatar className='relative shrink-0' /> : null}
|
||||
hideProcessDetail
|
||||
themeBuilder={themeBuilder}
|
||||
/>
|
||||
|
||||
@ -1,83 +1,43 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
import React from 'react'
|
||||
'use client'
|
||||
import type { SVGProps } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react'
|
||||
import { type VariantProps, cva } from 'class-variance-authority'
|
||||
import cn from '@/utils/classnames'
|
||||
import cn from 'classnames'
|
||||
|
||||
export const inputVariants = cva(
|
||||
'',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
regular: 'px-3 radius-md system-sm-regular',
|
||||
large: 'px-4 radius-lg system-md-regular',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'regular',
|
||||
},
|
||||
},
|
||||
type InputProps = {
|
||||
placeholder?: string
|
||||
value?: string
|
||||
defaultValue?: string
|
||||
onChange?: (v: string) => void
|
||||
className?: string
|
||||
wrapperClassName?: string
|
||||
type?: string
|
||||
showPrefix?: React.ReactNode
|
||||
prefixIcon?: React.ReactNode
|
||||
}
|
||||
|
||||
const GlassIcon = ({ className }: SVGProps<SVGElement>) => (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
|
||||
<path d="M12.25 12.25L10.2084 10.2083M11.6667 6.70833C11.6667 9.44675 9.44675 11.6667 6.70833 11.6667C3.96992 11.6667 1.75 9.44675 1.75 6.70833C1.75 3.96992 3.96992 1.75 6.70833 1.75C9.44675 1.75 11.6667 3.96992 11.6667 6.70833Z" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export type InputProps = {
|
||||
showLeftIcon?: boolean
|
||||
showClearIcon?: boolean
|
||||
onClear?: () => void
|
||||
disabled?: boolean
|
||||
destructive?: boolean
|
||||
wrapperClassName?: string
|
||||
styleCss?: CSSProperties
|
||||
} & React.InputHTMLAttributes<HTMLInputElement> & VariantProps<typeof inputVariants>
|
||||
|
||||
const Input = ({
|
||||
size,
|
||||
disabled,
|
||||
destructive,
|
||||
showLeftIcon,
|
||||
showClearIcon,
|
||||
onClear,
|
||||
wrapperClassName,
|
||||
className,
|
||||
styleCss,
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
const Input = ({ value, defaultValue, onChange, className = '', wrapperClassName = '', placeholder, type, showPrefix, prefixIcon }: InputProps) => {
|
||||
const [localValue, setLocalValue] = useState(value ?? defaultValue)
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className={cn('relative w-full', wrapperClassName)}>
|
||||
{showLeftIcon && <RiSearchLine className={cn('absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-components-input-text-placeholder')} />}
|
||||
<div className={`relative inline-flex w-full ${wrapperClassName}`}>
|
||||
{showPrefix && <span className='whitespace-nowrap absolute left-2 self-center'>{prefixIcon ?? <GlassIcon className='h-3.5 w-3.5 stroke-current text-gray-700 stroke-2' />}</span>}
|
||||
<input
|
||||
style={styleCss}
|
||||
className={cn(
|
||||
'w-full py-[7px] bg-components-input-bg-normal border border-transparent text-components-input-text-filled hover:bg-components-input-bg-hover hover:border-components-input-border-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:text-components-input-text-placeholder appearance-none outline-none caret-primary-600',
|
||||
inputVariants({ size }),
|
||||
showLeftIcon && 'pl-[26px]',
|
||||
showLeftIcon && size === 'large' && 'pl-7',
|
||||
showClearIcon && value && 'pr-[26px]',
|
||||
showClearIcon && value && size === 'large' && 'pr-7',
|
||||
destructive && 'pr-[26px]',
|
||||
destructive && size === 'large' && 'pr-7',
|
||||
disabled && 'bg-components-input-bg-disabled border-transparent text-components-input-text-filled-disabled cursor-not-allowed hover:bg-components-input-bg-disabled hover:border-transparent',
|
||||
destructive && 'bg-components-input-bg-destructive border-components-input-border-destructive text-components-input-text-filled hover:bg-components-input-bg-destructive hover:border-components-input-border-destructive focus:bg-components-input-bg-destructive focus:border-components-input-border-destructive',
|
||||
className,
|
||||
)}
|
||||
placeholder={placeholder ?? (showLeftIcon ? t('common.operation.search') ?? '' : 'please input')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
type={type ?? 'text'}
|
||||
className={cn('inline-flex h-7 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400', showPrefix ? '!pl-7' : '', className)}
|
||||
placeholder={placeholder ?? (showPrefix ? t('common.operation.search') ?? '' : 'please input')}
|
||||
value={localValue}
|
||||
onChange={(e) => {
|
||||
setLocalValue(e.target.value)
|
||||
onChange && onChange(e.target.value)
|
||||
}}
|
||||
/>
|
||||
{showClearIcon && value && !disabled && !destructive && (
|
||||
<div className={cn('absolute right-2 top-1/2 -translate-y-1/2 group p-[1px] cursor-pointer')} onClick={onClear}>
|
||||
<RiCloseCircleFill className='w-3.5 h-3.5 text-text-quaternary cursor-pointer group-hover:text-text-tertiary' />
|
||||
</div>
|
||||
)}
|
||||
{destructive && (
|
||||
<RiErrorWarningLine className='absolute right-2 top-1/2 -translate-y-1/2 w-4 h-4 text-text-destructive-secondary' />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import RemarkGfm from 'remark-gfm'
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter'
|
||||
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
|
||||
import type { RefObject } from 'react'
|
||||
import { Component, memo, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { memo, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
|
||||
import cn from '@/utils/classnames'
|
||||
import CopyBtn from '@/app/components/base/copy-btn'
|
||||
@ -104,7 +104,7 @@ const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
const language = match?.[1]
|
||||
const languageShowName = getCorrectCapitalizationLanguageName(language || '')
|
||||
let chartData = JSON.parse(String('{"title":{"text":"ECharts error - Wrong JSON format."}}').replace(/\n$/, ''))
|
||||
let chartData = JSON.parse(String('{"title":{"text":"Something went wrong."}}').replace(/\n$/, ''))
|
||||
if (language === 'echarts') {
|
||||
try {
|
||||
chartData = JSON.parse(String(children).replace(/\n$/, ''))
|
||||
@ -143,10 +143,10 @@ const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }
|
||||
? (<Flowchart PrimitiveCode={String(children).replace(/\n$/, '')} />)
|
||||
: (
|
||||
(language === 'echarts')
|
||||
? (<div style={{ minHeight: '250px', minWidth: '250px' }}><ErrorBoundary><ReactEcharts
|
||||
? (<div style={{ minHeight: '250px', minWidth: '250px' }}><ReactEcharts
|
||||
option={chartData}
|
||||
>
|
||||
</ReactEcharts></ErrorBoundary></div>)
|
||||
</ReactEcharts></div>)
|
||||
: (<SyntaxHighlighter
|
||||
{...props}
|
||||
style={atelierHeathLight}
|
||||
@ -211,25 +211,3 @@ export function Markdown(props: { content: string; className?: string }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// **Add an ECharts runtime error handler
|
||||
// Avoid error #7832 (Crash when ECharts accesses undefined objects)
|
||||
// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash.
|
||||
|
||||
export default class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
this.setState({ hasError: true })
|
||||
console.error(error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError)
|
||||
return <div>Oops! ECharts reported a runtime error. <br />(see the browser console for more information)</div>
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
<div className='truncate'>{option.label}</div>
|
||||
<Tooltip
|
||||
popupContent={<div className='w-[200px]'>{option.tips}</div>}
|
||||
triggerClassName='ml-0.5 w-3.5 h-3.5'
|
||||
triggerClassName='ml-0.5 w-3.5 h-4.5'
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
@ -58,7 +58,7 @@ const EmptyDatasetCreationModal = ({
|
||||
<div className={s.tip}>{t('datasetCreation.stepOne.modal.tip')}</div>
|
||||
<div className={s.form}>
|
||||
<div className={s.label}>{t('datasetCreation.stepOne.modal.input')}</div>
|
||||
<Input className='!h-8' value={inputValue} placeholder={t('datasetCreation.stepOne.modal.placeholder') || ''} onChange={e => setInputValue(e.target.value)} />
|
||||
<Input className='!h-8' value={inputValue} placeholder={t('datasetCreation.stepOne.modal.placeholder') || ''} onChange={setInputValue} />
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button className='w-24 ml-2' variant='primary' onClick={submit}>{t('datasetCreation.stepOne.modal.confirmButton')}</Button>
|
||||
|
||||
@ -9,7 +9,7 @@ type Props = {
|
||||
isNumber?: boolean
|
||||
}
|
||||
|
||||
const MIN_VALUE = 0
|
||||
const MIN_VALUE = 1
|
||||
|
||||
const Input: FC<Props> = ({
|
||||
value,
|
||||
|
||||
@ -391,7 +391,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
defaultValue={'all'}
|
||||
className={s.select}
|
||||
wrapperClassName='h-fit w-[120px] mr-2' />
|
||||
<Input showLeftIcon wrapperClassName='!w-52' className='!h-8' onChange={debounce(e => setSearchValue(e.target.value), 500)} />
|
||||
<Input showPrefix wrapperClassName='!w-52' className='!h-8' onChange={debounce(setSearchValue, 500)} />
|
||||
</div>
|
||||
<InfiniteVirtualList
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
|
||||
@ -79,7 +79,7 @@ export const FieldInfo: FC<IFieldInfoProps> = ({
|
||||
/>
|
||||
: <Input
|
||||
className={s.input}
|
||||
onChange={e => onUpdate?.(e.target.value)}
|
||||
onChange={onUpdate}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
placeholder={`${t('datasetDocuments.metadata.placeholder.add')}${label}`}
|
||||
|
||||
@ -201,10 +201,10 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
<div className='flex flex-col px-6 py-4 flex-1'>
|
||||
<div className='flex items-center justify-between flex-wrap'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showPrefix
|
||||
wrapperClassName='!w-[200px]'
|
||||
className='!h-8 !text-[13px]'
|
||||
onChange={debounce(e => setSearchValue(e.target.value), 500)}
|
||||
onChange={debounce(setSearchValue, 500)}
|
||||
value={searchValue}
|
||||
/>
|
||||
<div className='flex gap-2 justify-center items-center !h-8'>
|
||||
|
||||
@ -23,7 +23,7 @@ const AppCard = ({
|
||||
const { t } = useTranslation()
|
||||
const { app: appBasicInfo } = app
|
||||
return (
|
||||
<div className={cn('relative overflow-hidden pb-2 group col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg')}>
|
||||
<div className={cn('group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg')}>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
@ -64,13 +64,9 @@ const AppCard = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="description-wrapper h-[90px] px-[14px] text-xs leading-normal text-gray-500 ">
|
||||
<div className='line-clamp-4 group-hover:line-clamp-2'>
|
||||
{app.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mb-1 px-[14px] text-xs leading-normal text-gray-500 line-clamp-4 group-hover:line-clamp-2 group-hover:h-9'>{app.description}</div>
|
||||
{isExplore && canCreate && (
|
||||
<div className={cn('hidden items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px] bg-white group-hover:flex absolute bottom-0 left-0 right-0')}>
|
||||
<div className={cn('hidden items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px] group-hover:flex')}>
|
||||
<div className={cn('flex items-center w-full space-x-2')}>
|
||||
<Button variant='primary' className='grow h-7' onClick={() => onCreate()}>
|
||||
<PlusIcon className='w-4 h-4 mr-1' />
|
||||
@ -80,7 +76,7 @@ const AppCard = ({
|
||||
</div>
|
||||
)}
|
||||
{!isExplore && (
|
||||
<div className={cn('hidden items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px] bg-white group-hover:flex absolute bottom-0 left-0 right-0')}>
|
||||
<div className={cn('hidden items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px] group-hover:flex')}>
|
||||
<div className={cn('flex items-center w-full space-x-2')}>
|
||||
<Button variant='primary' className='grow h-7' onClick={() => onCreate()}>
|
||||
<PlusIcon className='w-4 h-4 mr-1' />
|
||||
|
||||
@ -5,7 +5,6 @@ import { RiCloseLine } from '@remixicon/react'
|
||||
import AppIconPicker from '../../base/app-icon-picker'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -21,15 +20,12 @@ export type CreateAppModalProps = {
|
||||
appIcon: string
|
||||
appIconBackground?: string | null
|
||||
appIconUrl?: string | null
|
||||
appMode?: string
|
||||
appUseIconAsAnswerIcon?: boolean
|
||||
onConfirm: (info: {
|
||||
name: string
|
||||
icon_type: AppIconType
|
||||
icon: string
|
||||
icon_background?: string
|
||||
description: string
|
||||
use_icon_as_answer_icon?: boolean
|
||||
}) => Promise<void>
|
||||
onHide: () => void
|
||||
}
|
||||
@ -43,8 +39,6 @@ const CreateAppModal = ({
|
||||
appIconUrl,
|
||||
appName,
|
||||
appDescription,
|
||||
appMode,
|
||||
appUseIconAsAnswerIcon,
|
||||
onConfirm,
|
||||
onHide,
|
||||
}: CreateAppModalProps) => {
|
||||
@ -58,7 +52,6 @@ const CreateAppModal = ({
|
||||
)
|
||||
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
|
||||
const [description, setDescription] = useState(appDescription || '')
|
||||
const [useIconAsAnswerIcon, setUseIconAsAnswerIcon] = useState(appUseIconAsAnswerIcon || false)
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
@ -74,7 +67,6 @@ const CreateAppModal = ({
|
||||
icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId,
|
||||
icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined,
|
||||
description,
|
||||
use_icon_as_answer_icon: useIconAsAnswerIcon,
|
||||
})
|
||||
onHide()
|
||||
}
|
||||
@ -127,19 +119,6 @@ const CreateAppModal = ({
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* answer icon */}
|
||||
{isEditModal && (appMode === 'chat' || appMode === 'advanced-chat' || appMode === 'agent-chat') && (
|
||||
<div className='pt-2'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.answerIcon.title')}</div>
|
||||
<Switch
|
||||
defaultValue={useIconAsAnswerIcon}
|
||||
onChange={v => setUseIconAsAnswerIcon(v)}
|
||||
/>
|
||||
</div>
|
||||
<p className='body-xs-regular text-gray-500'>{t('app.answerIcon.descriptionInExplore')}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />}
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
|
||||
@ -148,7 +148,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
{t('common.modelProvider.systemReasoningModel.tip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4 shrink-0'
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -168,7 +168,8 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
{t('common.modelProvider.embeddingModel.tip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4 shrink-0'
|
||||
needsDelay={false}
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -188,7 +189,8 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
{t('common.modelProvider.rerankModel.tip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4 shrink-0'
|
||||
needsDelay={false}
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -208,7 +210,8 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
{t('common.modelProvider.speechToTextModel.tip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4 shrink-0'
|
||||
needsDelay={false}
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -228,7 +231,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
{t('common.modelProvider.ttsModel.tip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4 shrink-0'
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -114,7 +114,7 @@ const ConfigCredential: FC<Props> = ({
|
||||
{t('tools.createTool.authMethod.keyTooltip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
triggerClassName='ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
|
||||
@ -44,7 +44,7 @@ const EditBody: FC<Props> = ({
|
||||
const { availableVars, availableNodes } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number, VarType.secret, VarType.arrayNumber, VarType.arrayString].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ const KeyValueItem: FC<Props> = ({
|
||||
<Input
|
||||
className='rounded-none bg-white border-none system-sm-regular focus:ring-0 focus:bg-gray-100! hover:bg-gray-50'
|
||||
value={payload.key}
|
||||
onChange={e => handleChange('key')(e.target.value)}
|
||||
onChange={handleChange('key')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -70,7 +70,7 @@ const RetrievalConfig: FC<Props> = ({
|
||||
}
|
||||
onMultipleRetrievalConfigChange({
|
||||
top_k: configs.top_k,
|
||||
score_threshold: configs.score_threshold_enabled ? (configs.score_threshold ?? DATASET_DEFAULT.score_threshold) : null,
|
||||
score_threshold: configs.score_threshold_enabled ? (configs.score_threshold || DATASET_DEFAULT.score_threshold) : null,
|
||||
reranking_model: payload.retrieval_mode === RETRIEVE_TYPE.oneWay
|
||||
? undefined
|
||||
: (!configs.reranking_model?.reranking_provider_name
|
||||
|
||||
@ -248,16 +248,11 @@ export const useChat = (
|
||||
}
|
||||
|
||||
if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
|
||||
try {
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
responseItem.id,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
catch (error) {
|
||||
setSuggestQuestions([])
|
||||
}
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
responseItem.id,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
},
|
||||
onMessageEnd: (messageEnd) => {
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
/* eslint-disable no-eval */
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const transpile = require('typescript').transpile
|
||||
const magicast = require('magicast')
|
||||
const { parseModule, generateCode, loadFile } = magicast
|
||||
const bingTranslate = require('bing-translate-api')
|
||||
const { translate } = bingTranslate
|
||||
const data = require('./languages.json')
|
||||
|
||||
const targetLanguage = 'en-US'
|
||||
// https://github.com/plainheart/bing-translate-api/blob/master/src/met/lang.json
|
||||
const languageKeyMap = data.languages.reduce((map, language) => {
|
||||
if (language.supported) {
|
||||
if (language.value === 'zh-Hans' || language.value === 'zh-Hant')
|
||||
map[language.value] = language.value
|
||||
else
|
||||
map[language.value] = language.value.split('-')[0]
|
||||
}
|
||||
|
||||
return map
|
||||
}, {})
|
||||
|
||||
async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) {
|
||||
await Promise.all(Object.keys(sourceObj).map(async (key) => {
|
||||
if (targetObject[key] === undefined) {
|
||||
if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = {}
|
||||
await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
}
|
||||
else {
|
||||
const { translation } = await translate(sourceObj[key], null, languageKeyMap[toLanguage])
|
||||
targetObject[key] = translation
|
||||
// console.log(translation)
|
||||
}
|
||||
}
|
||||
else if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = targetObject[key] || {}
|
||||
await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async function autoGenTrans(fileName, toGenLanguage) {
|
||||
const fullKeyFilePath = path.join(__dirname, targetLanguage, `${fileName}.ts`)
|
||||
const toGenLanguageFilePath = path.join(__dirname, toGenLanguage, `${fileName}.ts`)
|
||||
const fullKeyContent = eval(transpile(fs.readFileSync(fullKeyFilePath, 'utf8')))
|
||||
// To keep object format and format it for magicast to work: const translation = { ... } => export default {...}
|
||||
const readContent = await loadFile(toGenLanguageFilePath)
|
||||
const { code: toGenContent } = generateCode(readContent)
|
||||
const mod = await parseModule(`export default ${toGenContent.replace('export default translation', '').replace('const translation = ', '')}`)
|
||||
const toGenOutPut = mod.exports.default
|
||||
|
||||
await translateMissingKeyDeeply(fullKeyContent, toGenOutPut, toGenLanguage)
|
||||
const { code } = generateCode(mod)
|
||||
const res = `const translation =${code.replace('export default', '')}
|
||||
|
||||
export default translation
|
||||
`.replace(/,\n\n/g, ',\n').replace('};', '}')
|
||||
|
||||
fs.writeFileSync(toGenLanguageFilePath, res)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// const fileName = 'workflow'
|
||||
// Promise.all(Object.keys(languageKeyMap).map(async (toLanguage) => {
|
||||
// await autoGenTrans(fileName, toLanguage)
|
||||
// }))
|
||||
|
||||
const files = fs
|
||||
.readdirSync(path.join(__dirname, targetLanguage))
|
||||
.map(file => file.replace(/\.ts/, ''))
|
||||
.filter(f => f !== 'app-debug') // ast parse error in app-debug
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
await Promise.all(Object.keys(languageKeyMap).map(async (language) => {
|
||||
await autoGenTrans(file, language)
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user