diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 0e1c9d6927..c704ac1f7c 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -41,6 +41,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup NodeJS uses: actions/setup-node@v4 @@ -60,11 +62,10 @@ jobs: yarn run lint - name: Super-linter - uses: super-linter/super-linter/slim@v5 + uses: super-linter/super-linter/slim@v6 env: BASH_SEVERITY: warning DEFAULT_BRANCH: main - ERROR_ON_MISSING_EXEC_BIT: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} IGNORE_GENERATED_FILES: true IGNORE_GITIGNORED_FILES: true diff --git a/api/.env.example b/api/.env.example index 89d550ba5a..32d89d4287 100644 --- a/api/.env.example +++ b/api/.env.example @@ -82,7 +82,7 @@ UPLOAD_IMAGE_FILE_SIZE_LIMIT=10 MULTIMODAL_SEND_IMAGE_FORMAT=base64 # Mail configuration, support: resend, smtp -MAIL_TYPE=resend +MAIL_TYPE= MAIL_DEFAULT_SEND_FROM=no-reply RESEND_API_KEY= RESEND_API_URL=https://api.resend.com @@ -131,4 +131,4 @@ UNSTRUCTURED_API_URL= SSRF_PROXY_HTTP_URL= SSRF_PROXY_HTTPS_URL= -BATCH_UPLOAD_LIMIT=10 \ No newline at end of file +BATCH_UPLOAD_LIMIT=10 diff --git a/api/commands.py b/api/commands.py index 9923ccb8b8..45977cbb9e 100644 --- a/api/commands.py +++ b/api/commands.py @@ -131,6 +131,8 @@ def vdb_migrate(): """ click.echo(click.style('Start migrate vector db.', fg='green')) create_count = 0 + skipped_count = 0 + total_count = 0 config = current_app.config vector_type = config.get('VECTOR_STORE') page = 1 @@ -143,14 +145,19 @@ def vdb_migrate(): page += 1 for dataset in datasets: + total_count = total_count + 1 + click.echo(f'Processing the {total_count} dataset {dataset.id}. ' + + f'{create_count} created, ${skipped_count} skipped.') try: click.echo('Create dataset vdb index: {}'.format(dataset.id)) if dataset.index_struct_dict: if dataset.index_struct_dict['type'] == vector_type: + skipped_count = skipped_count + 1 continue + collection_name = '' if vector_type == "weaviate": dataset_id = dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) index_struct_dict = { "type": 'weaviate', "vector_store": {"class_prefix": collection_name} @@ -167,7 +174,7 @@ def vdb_migrate(): raise ValueError('Dataset Collection Bindings is not exist!') else: dataset_id = dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) index_struct_dict = { "type": 'qdrant', "vector_store": {"class_prefix": collection_name} @@ -176,7 +183,7 @@ def vdb_migrate(): elif vector_type == "milvus": dataset_id = dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) index_struct_dict = { "type": 'milvus', "vector_store": {"class_prefix": collection_name} @@ -186,11 +193,17 @@ def vdb_migrate(): raise ValueError(f"Vector store {config.get('VECTOR_STORE')} is not supported.") vector = Vector(dataset) - click.echo(f"vdb_migrate {dataset.id}") + click.echo(f"Start to migrate dataset {dataset.id}.") try: vector.delete() + click.echo( + click.style(f'Successfully delete vector index {collection_name} for dataset {dataset.id}.', + fg='green')) except Exception as e: + click.echo( + click.style(f'Failed to delete vector index {collection_name} for dataset {dataset.id}.', + fg='red')) raise e dataset_documents = db.session.query(DatasetDocument).filter( @@ -201,6 +214,7 @@ def vdb_migrate(): ).all() documents = [] + segments_count = 0 for dataset_document in dataset_documents: segments = db.session.query(DocumentSegment).filter( DocumentSegment.document_id == dataset_document.id, @@ -220,15 +234,22 @@ def vdb_migrate(): ) documents.append(document) + segments_count = segments_count + 1 if documents: try: + click.echo(click.style( + f'Start to created vector index with {len(documents)} documents of {segments_count} segments for dataset {dataset.id}.', + fg='green')) vector.create(documents) + click.echo( + click.style(f'Successfully created vector index for dataset {dataset.id}.', fg='green')) except Exception as e: + click.echo(click.style(f'Failed to created vector index for dataset {dataset.id}.', fg='red')) raise e - click.echo(f"Dataset {dataset.id} create successfully.") db.session.add(dataset) db.session.commit() + click.echo(f'Successfully migrated dataset {dataset.id}.') create_count += 1 except Exception as e: db.session.rollback() @@ -237,7 +258,9 @@ def vdb_migrate(): fg='red')) continue - click.echo(click.style('Congratulations! Create {} dataset indexes.'.format(create_count), fg='green')) + click.echo( + click.style(f'Congratulations! Create {create_count} dataset indexes, and skipped {skipped_count} datasets.', + fg='green')) def register_commands(app): diff --git a/api/constants/model_template.py b/api/constants/model_template.py index 5b9a09fd9b..d87f7c3926 100644 --- a/api/constants/model_template.py +++ b/api/constants/model_template.py @@ -13,30 +13,14 @@ model_templates = { 'status': 'normal' }, 'model_config': { - 'provider': 'openai', - 'model_id': 'gpt-3.5-turbo-instruct', - 'configs': { - 'prompt_template': '', - 'prompt_variables': [], - 'completion_params': { - 'max_token': 512, - 'temperature': 1, - 'top_p': 1, - 'presence_penalty': 0, - 'frequency_penalty': 0, - } - }, + 'provider': '', + 'model_id': '', + 'configs': {}, 'model': json.dumps({ "provider": "openai", "name": "gpt-3.5-turbo-instruct", "mode": "completion", - "completion_params": { - "max_tokens": 512, - "temperature": 1, - "top_p": 1, - "presence_penalty": 0, - "frequency_penalty": 0 - } + "completion_params": {} }), 'user_input_form': json.dumps([ { @@ -64,30 +48,14 @@ model_templates = { 'status': 'normal' }, 'model_config': { - 'provider': 'openai', - 'model_id': 'gpt-3.5-turbo', - 'configs': { - 'prompt_template': '', - 'prompt_variables': [], - 'completion_params': { - 'max_token': 512, - 'temperature': 1, - 'top_p': 1, - 'presence_penalty': 0, - 'frequency_penalty': 0, - } - }, + 'provider': '', + 'model_id': '', + 'configs': {}, 'model': json.dumps({ "provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", - "completion_params": { - "max_tokens": 512, - "temperature": 1, - "top_p": 1, - "presence_penalty": 0, - "frequency_penalty": 0 - } + "completion_params": {} }) } }, diff --git a/api/controllers/console/datasets/file.py b/api/controllers/console/datasets/file.py index 0eba232289..23ab224731 100644 --- a/api/controllers/console/datasets/file.py +++ b/api/controllers/console/datasets/file.py @@ -11,7 +11,7 @@ from controllers.console.datasets.error import ( UnsupportedFileTypeError, ) from controllers.console.setup import setup_required -from controllers.console.wraps import account_initialization_required +from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check from fields.file_fields import file_fields, upload_config_fields from libs.login import login_required from services.file_service import ALLOWED_EXTENSIONS, UNSTRUSTURED_ALLOWED_EXTENSIONS, FileService @@ -39,6 +39,7 @@ class FileApi(Resource): @login_required @account_initialization_required @marshal_with(file_fields) + @cloud_edition_billing_resource_check(resource='documents') def post(self): # get file from request diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index d5777a330c..84f9918470 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -56,6 +56,7 @@ def cloud_edition_billing_resource_check(resource: str, members = features.members apps = features.apps vector_space = features.vector_space + documents_upload_quota = features.documents_upload_quota annotation_quota_limit = features.annotation_quota_limit if resource == 'members' and 0 < members.limit <= members.size: @@ -64,6 +65,13 @@ def cloud_edition_billing_resource_check(resource: str, abort(403, error_msg) elif resource == 'vector_space' and 0 < vector_space.limit <= vector_space.size: 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, error_msg) + else: + return view(*args, **kwargs) elif resource == 'workspace_custom' and not features.can_replace_logo: abort(403, error_msg) elif resource == 'annotation' and 0 < annotation_quota_limit.limit < annotation_quota_limit.size: diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index cbe0517ed3..becfb81da1 100644 --- a/api/controllers/service_api/dataset/document.py +++ b/api/controllers/service_api/dataset/document.py @@ -28,6 +28,7 @@ class DocumentAddByTextApi(DatasetApiResource): """Resource for documents.""" @cloud_edition_billing_resource_check('vector_space', 'dataset') + @cloud_edition_billing_resource_check('documents', 'dataset') def post(self, tenant_id, dataset_id): """Create document by text.""" parser = reqparse.RequestParser() @@ -153,6 +154,7 @@ class DocumentUpdateByTextApi(DatasetApiResource): class DocumentAddByFileApi(DatasetApiResource): """Resource for documents.""" @cloud_edition_billing_resource_check('vector_space', 'dataset') + @cloud_edition_billing_resource_check('documents', 'dataset') def post(self, tenant_id, dataset_id): """Create document by upload file.""" args = {} diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index 9819c73d37..bdcbaecbea 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -89,6 +89,7 @@ def cloud_edition_billing_resource_check(resource: str, members = features.members apps = features.apps vector_space = features.vector_space + documents_upload_quota = features.documents_upload_quota if resource == 'members' and 0 < members.limit <= members.size: raise Unauthorized(error_msg) @@ -96,6 +97,8 @@ def cloud_edition_billing_resource_check(resource: str, raise Unauthorized(error_msg) elif resource == 'vector_space' and 0 < vector_space.limit <= vector_space.size: raise Unauthorized(error_msg) + elif resource == 'documents' and 0 < documents_upload_quota.limit <= documents_upload_quota.size: + raise Unauthorized(error_msg) else: return view(*args, **kwargs) diff --git a/api/core/model_runtime/model_providers/localai/llm/llm.py b/api/core/model_runtime/model_providers/localai/llm/llm.py index 694f5891f9..3c805682f3 100644 --- a/api/core/model_runtime/model_providers/localai/llm/llm.py +++ b/api/core/model_runtime/model_providers/localai/llm/llm.py @@ -1,6 +1,6 @@ from collections.abc import Generator -from os.path import join from typing import cast +from urllib.parse import urljoin from httpx import Timeout from openai import ( @@ -313,10 +313,13 @@ class LocalAILarguageModel(LargeLanguageModel): :param credentials: credentials dict :return: client kwargs """ + if not credentials['server_url'].endswith('/'): + credentials['server_url'] += '/' + client_kwargs = { "timeout": Timeout(315.0, read=300.0, write=10.0, connect=5.0), "api_key": "1", - "base_url": join(credentials['server_url'], 'v1'), + "base_url": urljoin(credentials['server_url'], 'v1'), } return client_kwargs diff --git a/api/core/rag/datasource/vdb/vector_factory.py b/api/core/rag/datasource/vdb/vector_factory.py index 1921de07ed..109d36583c 100644 --- a/api/core/rag/datasource/vdb/vector_factory.py +++ b/api/core/rag/datasource/vdb/vector_factory.py @@ -39,7 +39,7 @@ class Vector: collection_name = class_prefix else: dataset_id = self._dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) index_struct_dict = { "type": 'weaviate', "vector_store": {"class_prefix": collection_name} @@ -70,7 +70,7 @@ class Vector: collection_name = class_prefix else: dataset_id = self._dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) if not self._dataset.index_struct_dict: index_struct_dict = { @@ -96,7 +96,7 @@ class Vector: collection_name = class_prefix else: dataset_id = self._dataset.id - collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + collection_name = Dataset.gen_collection_name_by_id(dataset_id) index_struct_dict = { "type": 'milvus', "vector_store": {"class_prefix": collection_name} diff --git a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py index 008e54085d..6e317115b8 100644 --- a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py +++ b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py @@ -70,7 +70,7 @@ class WeaviateVector(BaseVector): return class_prefix dataset_id = dataset.id - return "Vector_index_" + dataset_id.replace("-", "_") + '_Node' + return Dataset.gen_collection_name_by_id(dataset_id) def to_index_struct(self) -> dict: return { diff --git a/api/core/tools/provider/_position.yaml b/api/core/tools/provider/_position.yaml index 40c62d710a..e796e58e13 100644 --- a/api/core/tools/provider/_position.yaml +++ b/api/core/tools/provider/_position.yaml @@ -1,16 +1,20 @@ - google - bing +- duckduckgo +- yahoo - wikipedia +- arxiv +- pubmed - dalle - azuredalle +- stablediffusion - webscraper +- youtube - wolframalpha +- maths - github - chart - time -- yahoo -- stablediffusion - vectorizer -- youtube - gaode -- maths +- wecom diff --git a/api/core/tools/provider/builtin/arxiv/_assets/icon.svg b/api/core/tools/provider/builtin/arxiv/_assets/icon.svg new file mode 100644 index 0000000000..0e60f63573 --- /dev/null +++ b/api/core/tools/provider/builtin/arxiv/_assets/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/arxiv/arxiv.py b/api/core/tools/provider/builtin/arxiv/arxiv.py new file mode 100644 index 0000000000..998128522e --- /dev/null +++ b/api/core/tools/provider/builtin/arxiv/arxiv.py @@ -0,0 +1,20 @@ +from core.tools.errors import ToolProviderCredentialValidationError +from core.tools.provider.builtin.arxiv.tools.arxiv_search import ArxivSearchTool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class ArxivProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + try: + ArxivSearchTool().fork_tool_runtime( + meta={ + "credentials": credentials, + } + ).invoke( + user_id='', + tool_parameters={ + "query": "John Doe", + }, + ) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) \ No newline at end of file diff --git a/api/core/tools/provider/builtin/arxiv/arxiv.yaml b/api/core/tools/provider/builtin/arxiv/arxiv.yaml new file mode 100644 index 0000000000..78c5c161af --- /dev/null +++ b/api/core/tools/provider/builtin/arxiv/arxiv.yaml @@ -0,0 +1,10 @@ +identity: + author: Yash Parmar + name: arxiv + label: + en_US: ArXiv + zh_Hans: ArXiv + description: + en_US: Access to a vast repository of scientific papers and articles in various fields of research. + zh_Hans: 访问各个研究领域大量科学论文和文章的存储库。 + icon: icon.svg diff --git a/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.py b/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.py new file mode 100644 index 0000000000..033d942f4d --- /dev/null +++ b/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.py @@ -0,0 +1,37 @@ +from typing import Any + +from langchain.utilities import ArxivAPIWrapper +from pydantic import BaseModel, Field + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class ArxivSearchInput(BaseModel): + query: str = Field(..., description="Search query.") + +class ArxivSearchTool(BuiltinTool): + """ + A tool for searching articles on Arxiv. + """ + def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: + """ + Invokes the Arxiv search tool with the given user ID and tool parameters. + + Args: + user_id (str): The ID of the user invoking the tool. + tool_parameters (dict[str, Any]): The parameters for the tool, including the 'query' parameter. + + Returns: + ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation, which can be a single message or a list of messages. + """ + query = tool_parameters.get('query', '') + + if not query: + return self.create_text_message('Please input query') + + arxiv = ArxivAPIWrapper() + + response = arxiv.run(query) + + return self.create_text_message(self.summary(user_id=user_id, content=response)) diff --git a/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.yaml b/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.yaml new file mode 100644 index 0000000000..7439a48658 --- /dev/null +++ b/api/core/tools/provider/builtin/arxiv/tools/arxiv_search.yaml @@ -0,0 +1,23 @@ +identity: + name: arxiv_search + author: Yash Parmar + label: + en_US: Arxiv Search + zh_Hans: Arxiv 搜索 +description: + human: + en_US: A tool for searching scientific papers and articles from the Arxiv repository. Input can be an Arxiv ID or an author's name. + zh_Hans: 一个用于从Arxiv存储库搜索科学论文和文章的工具。 输入可以是Arxiv ID或作者姓名。 + llm: A tool for searching scientific papers and articles from the Arxiv repository. Input can be an Arxiv ID or an author's name. +parameters: + - name: query + type: string + required: true + label: + en_US: Query string + zh_Hans: 查询字符串 + human_description: + en_US: The Arxiv ID or author's name used for searching. + zh_Hans: 用于搜索的Arxiv ID或作者姓名。 + llm_description: The Arxiv ID or author's name used for searching. + form: llm diff --git a/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py index 38486d4edf..028da946d1 100644 --- a/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py +++ b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py @@ -54,4 +54,4 @@ class GaodeRepositoriesTool(BuiltinTool): s.close() return self.create_text_message(f'No weather information for {city} was found.') except Exception as e: - return self.create_text_message("Github API Key and Api Version is invalid. {}".format(e)) + return self.create_text_message("Gaode API Key and Api Version is invalid. {}".format(e)) diff --git a/api/core/tools/provider/builtin/pubmed/_assets/icon.svg b/api/core/tools/provider/builtin/pubmed/_assets/icon.svg new file mode 100644 index 0000000000..6d6ff593f0 --- /dev/null +++ b/api/core/tools/provider/builtin/pubmed/_assets/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/pubmed/pubmed.py b/api/core/tools/provider/builtin/pubmed/pubmed.py new file mode 100644 index 0000000000..663617c0c1 --- /dev/null +++ b/api/core/tools/provider/builtin/pubmed/pubmed.py @@ -0,0 +1,20 @@ +from core.tools.errors import ToolProviderCredentialValidationError +from core.tools.provider.builtin.pubmed.tools.pubmed_search import PubMedSearchTool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class PubMedProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + try: + PubMedSearchTool().fork_tool_runtime( + meta={ + "credentials": credentials, + } + ).invoke( + user_id='', + tool_parameters={ + "query": "John Doe", + }, + ) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) \ No newline at end of file diff --git a/api/core/tools/provider/builtin/pubmed/pubmed.yaml b/api/core/tools/provider/builtin/pubmed/pubmed.yaml new file mode 100644 index 0000000000..971a6fb204 --- /dev/null +++ b/api/core/tools/provider/builtin/pubmed/pubmed.yaml @@ -0,0 +1,10 @@ +identity: + author: Pink Banana + name: pubmed + label: + en_US: PubMed + zh_Hans: PubMed + description: + en_US: A search engine for biomedical literature. + zh_Hans: 一款生物医学文献搜索引擎。 + icon: icon.svg diff --git a/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.py b/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.py new file mode 100644 index 0000000000..1bed1fa77c --- /dev/null +++ b/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.py @@ -0,0 +1,40 @@ +from typing import Any + +from langchain.tools import PubmedQueryRun +from pydantic import BaseModel, Field + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class PubMedInput(BaseModel): + query: str = Field(..., description="Search query.") + + +class PubMedSearchTool(BuiltinTool): + """ + Tool for performing a search using PubMed search engine. + """ + + def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: + """ + Invoke the PubMed search tool. + + Args: + user_id (str): The ID of the user invoking the tool. + tool_parameters (dict[str, Any]): The parameters for the tool invocation. + + Returns: + ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation. + """ + query = tool_parameters.get('query', '') + + if not query: + return self.create_text_message('Please input query') + + tool = PubmedQueryRun(args_schema=PubMedInput) + + result = tool.run(query) + + return self.create_text_message(self.summary(user_id=user_id, content=result)) + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.yaml b/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.yaml new file mode 100644 index 0000000000..77ab809fbc --- /dev/null +++ b/api/core/tools/provider/builtin/pubmed/tools/pubmed_search.yaml @@ -0,0 +1,23 @@ +identity: + name: pubmed_search + author: Pink Banana + label: + en_US: PubMed Search + zh_Hans: PubMed 搜索 +description: + human: + en_US: PubMed® comprises more than 35 million citations for biomedical literature from MEDLINE, life science journals, and online books. Citations may include links to full text content from PubMed Central and publisher web sites. + zh_Hans: PubMed® 包含来自 MEDLINE、生命科学期刊和在线书籍的超过 3500 万篇生物医学文献引用。引用可能包括来自 PubMed Central 和出版商网站的全文内容链接。 + llm: Perform searches on PubMed and get results. +parameters: + - name: query + type: string + required: true + label: + en_US: Query string + zh_Hans: 查询语句 + human_description: + en_US: The search query. + zh_Hans: 搜索查询语句。 + llm_description: Key words for searching + form: llm diff --git a/api/core/tools/provider/builtin/stablediffusion/tools/stable_diffusion.py b/api/core/tools/provider/builtin/stablediffusion/tools/stable_diffusion.py index 2bacfa07e7..cda89036dc 100644 --- a/api/core/tools/provider/builtin/stablediffusion/tools/stable_diffusion.py +++ b/api/core/tools/provider/builtin/stablediffusion/tools/stable_diffusion.py @@ -70,7 +70,7 @@ class StableDiffusionTool(BuiltinTool): if not base_url: return self.create_text_message('Please input base_url') - if 'model' in tool_parameters: + if 'model' in tool_parameters and tool_parameters['model']: self.runtime.credentials['model'] = tool_parameters['model'] model = self.runtime.credentials.get('model', None) diff --git a/api/core/tools/provider/builtin/wecom/_assets/icon.png b/api/core/tools/provider/builtin/wecom/_assets/icon.png new file mode 100644 index 0000000000..8588c20d57 Binary files /dev/null and b/api/core/tools/provider/builtin/wecom/_assets/icon.png differ diff --git a/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.py b/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.py new file mode 100644 index 0000000000..5a536cca50 --- /dev/null +++ b/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.py @@ -0,0 +1,46 @@ +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class WecomRepositoriesTool(BuiltinTool): + def _invoke(self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + """ + invoke tools + """ + content = tool_parameters.get('content', '') + if not content: + return self.create_text_message('Invalid parameter content') + + hook_key = tool_parameters.get('hook_key', '') + if not hook_key: + return self.create_text_message('Invalid parameter hook_key') + + msgtype = 'text' + api_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send' + headers = { + 'Content-Type': 'application/json', + } + params = { + 'key': hook_key, + } + payload = { + "msgtype": msgtype, + "text": { + "content": content, + } + } + + try: + res = httpx.post(api_url, headers=headers, params=params, json=payload) + if res.is_success: + return self.create_text_message("Text message sent successfully") + else: + return self.create_text_message( + f"Failed to send the text message, status code: {res.status_code}, response: {res.text}") + except Exception as e: + return self.create_text_message("Failed to send message to group chat bot. {}".format(e)) diff --git a/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.yaml b/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.yaml new file mode 100644 index 0000000000..52f3cf5731 --- /dev/null +++ b/api/core/tools/provider/builtin/wecom/tools/wecom_group_bot.yaml @@ -0,0 +1,40 @@ +identity: + name: wecom_group_bot + author: Bowen Liang + label: + en_US: Send Group Message + zh_Hans: 发送群消息 + pt_BR: Send Group Message + icon: icon.svg +description: + human: + en_US: Sending a group message on Wecom via the webhook of group bot + zh_Hans: 通过企业微信的群机器人webhook发送群消息 + pt_BR: Sending a group message on Wecom via the webhook of group bot + llm: A tool for sending messages to a chat group on Wecom(企业微信) . +parameters: + - name: hook_key + type: string + required: true + label: + en_US: Wecom Group bot webhook key + zh_Hans: 群机器人webhook的key + pt_BR: Wecom Group bot webhook key + human_description: + en_US: Wecom Group bot webhook key + zh_Hans: 群机器人webhook的key + pt_BR: Wecom Group bot webhook key + form: form + - name: content + type: string + required: true + label: + en_US: content + zh_Hans: 消息内容 + pt_BR: content + human_description: + en_US: Content to sent to the group. + zh_Hans: 群消息文本 + pt_BR: Content to sent to the group. + llm_description: Content of the message + form: llm diff --git a/api/core/tools/provider/builtin/wecom/wecom.py b/api/core/tools/provider/builtin/wecom/wecom.py new file mode 100644 index 0000000000..b50d3ff68a --- /dev/null +++ b/api/core/tools/provider/builtin/wecom/wecom.py @@ -0,0 +1,8 @@ +from core.tools.provider.builtin.wecom.tools.wecom_group_bot import WecomRepositoriesTool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class GaodeProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + WecomRepositoriesTool() + pass diff --git a/api/core/tools/provider/builtin/wecom/wecom.yaml b/api/core/tools/provider/builtin/wecom/wecom.yaml new file mode 100644 index 0000000000..39d00032a0 --- /dev/null +++ b/api/core/tools/provider/builtin/wecom/wecom.yaml @@ -0,0 +1,13 @@ +identity: + author: Bowen Liang + name: wecom + label: + en_US: Wecom + zh_Hans: 企业微信 + pt_BR: Wecom + description: + en_US: Wecom group bot + zh_Hans: 企业微信群机器人 + pt_BR: Wecom group bot + icon: icon.png +credentials_for_provider: diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index 781eff13b4..d5a4bf20bd 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -67,9 +67,9 @@ class ApiTool(Tool): if 'api_key_header_prefix' in credentials: api_key_header_prefix = credentials['api_key_header_prefix'] - if api_key_header_prefix == 'basic': + if api_key_header_prefix == 'basic' and credentials['api_key_value']: credentials['api_key_value'] = f'Basic {credentials["api_key_value"]}' - elif api_key_header_prefix == 'bearer': + elif api_key_header_prefix == 'bearer' and credentials['api_key_value']: credentials['api_key_value'] = f'Bearer {credentials["api_key_value"]}' elif api_key_header_prefix == 'custom': pass @@ -184,21 +184,7 @@ class ApiTool(Tool): for name, property in properties.items(): if name in parameters: # convert type - try: - value = parameters[name] - if property['type'] == 'integer': - value = int(value) - elif property['type'] == 'number': - # check if it is a float - if '.' in value: - value = float(value) - else: - value = int(value) - elif property['type'] == 'boolean': - value = bool(value) - body[name] = value - except ValueError as e: - body[name] = parameters[name] + body[name] = self._convert_body_property_type(property, parameters[name]) elif name in required: raise ToolProviderCredentialValidationError( f"Missing required parameter {name} in operation {self.api_bundle.operation_id}" @@ -228,10 +214,6 @@ class ApiTool(Tool): elif method == 'put': response = ssrf_proxy.put(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True) elif method == 'delete': - """ - request body data is unsupported for DELETE method in standard http protocol - however, OpenAPI 3.0 supports request body data for DELETE method, so we support it here by using requests - """ response = ssrf_proxy.delete(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, allow_redirects=True) elif method == 'patch': response = ssrf_proxy.patch(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True) @@ -243,6 +225,66 @@ class ApiTool(Tool): raise ValueError(f'Invalid http method {method}') return response + + def _convert_body_property_any_of(self, property: dict[str, Any], value: Any, any_of: list[dict[str, Any]], max_recursive=10) -> Any: + if max_recursive <= 0: + raise Exception("Max recursion depth reached") + for option in any_of or []: + try: + if 'type' in option: + # Attempt to convert the value based on the type. + if option['type'] == 'integer' or option['type'] == 'int': + return int(value) + elif option['type'] == 'number': + if '.' in str(value): + return float(value) + else: + return int(value) + elif option['type'] == 'string': + return str(value) + elif option['type'] == 'boolean': + if str(value).lower() in ['true', '1']: + return True + elif str(value).lower() in ['false', '0']: + return False + else: + continue # Not a boolean, try next option + elif option['type'] == 'null' and not value: + return None + else: + continue # Unsupported type, try next option + elif 'anyOf' in option and isinstance(option['anyOf'], list): + # Recursive call to handle nested anyOf + return self._convert_body_property_any_of(property, value, option['anyOf'], max_recursive - 1) + except ValueError: + continue # Conversion failed, try next option + # If no option succeeded, you might want to return the value as is or raise an error + return value # or raise ValueError(f"Cannot convert value '{value}' to any specified type in anyOf") + + def _convert_body_property_type(self, property: dict[str, Any], value: Any) -> Any: + try: + if 'type' in property: + if property['type'] == 'integer' or property['type'] == 'int': + return int(value) + elif property['type'] == 'number': + # check if it is a float + if '.' in value: + return float(value) + else: + return int(value) + elif property['type'] == 'string': + return str(value) + elif property['type'] == 'boolean': + return bool(value) + elif property['type'] == 'null': + if value is None: + return None + else: + raise ValueError(f"Invalid type {property['type']} for property {property}") + elif 'anyOf' in property and isinstance(property['anyOf'], list): + return self._convert_body_property_any_of(property, value, property['anyOf']) + except ValueError as e: + return value def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: """ diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index 889316c235..de4ecc8708 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -146,7 +146,8 @@ class ApiBasedToolSchemaParser: bundles.append(ApiBasedToolBundle( server_url=server_url + interface['path'], method=interface['method'], - summary=interface['operation']['summary'] if 'summary' in interface['operation'] else None, + summary=interface['operation']['description'] if 'description' in interface['operation'] else + interface['operation']['summary'] if 'summary' in interface['operation'] else None, operation_id=interface['operation']['operationId'], parameters=parameters, author='', @@ -249,12 +250,10 @@ class ApiBasedToolSchemaParser: if 'operationId' not in operation: raise ToolApiSchemaError(f'No operationId found in operation {method} {path}.') - if 'summary' not in operation or len(operation['summary']) == 0: - warning['missing_summary'] = f'No summary found in operation {method} {path}.' + if ('summary' not in operation or len(operation['summary']) == 0) and \ + ('description' not in operation or len(operation['description']) == 0): + warning['missing_summary'] = f'No summary or description found in operation {method} {path}.' - if 'description' not in operation or len(operation['description']) == 0: - warning['missing_description'] = f'No description found in operation {method} {path}.' - openapi['paths'][path][method] = { 'operationId': operation['operationId'], 'summary': operation.get('summary', ''), diff --git a/api/models/dataset.py b/api/models/dataset.py index 473a796be5..94664bf49a 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -116,6 +116,10 @@ class Dataset(db.Model): } return self.retrieval_model if self.retrieval_model else default_retrieval_model + @staticmethod + def gen_collection_name_by_id(dataset_id: str) -> str: + normalized_dataset_id = dataset_id.replace("-", "_") + return f'Vector_index_{normalized_dataset_id}_Node' class DatasetProcessRule(db.Model): __tablename__ = 'dataset_process_rules' diff --git a/api/requirements.txt b/api/requirements.txt index 5881c99903..fbadcdbf04 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -66,4 +66,5 @@ yfinance~=0.2.35 pydub~=0.25.1 gmpy2~=2.1.5 numexpr~=2.9.0 -duckduckgo-search==4.4.3 \ No newline at end of file +duckduckgo-search==4.4.3 +arxiv==2.1.0 \ No newline at end of file diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index b151ebada8..44a48af58b 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -37,7 +37,7 @@ from services.errors.account import NoPermissionError from services.errors.dataset import DatasetNameDuplicateError from services.errors.document import DocumentIndexingError from services.errors.file import FileNotExistsError -from services.feature_service import FeatureService +from services.feature_service import FeatureModel, FeatureService from services.vector_service import VectorService from tasks.clean_notion_document_task import clean_notion_document_task from tasks.deal_dataset_vector_index_task import deal_dataset_vector_index_task @@ -469,6 +469,9 @@ class DocumentService: batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT']) if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") + + DocumentService.check_documents_upload_quota(count, features) + # if dataset is empty, update dataset data_source_type if not dataset.data_source_type: dataset.data_source_type = document_data["data_source"]["type"] @@ -619,6 +622,12 @@ class DocumentService: return documents, batch + @staticmethod + def check_documents_upload_quota(count: int, features: FeatureModel): + can_upload_size = features.documents_upload_quota.limit - features.documents_upload_quota.size + if count > can_upload_size: + raise ValueError(f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.') + @staticmethod def build_document(dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str, document_language: str, data_source_info: dict, created_from: str, position: int, @@ -763,6 +772,8 @@ class DocumentService: if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") + DocumentService.check_documents_upload_quota(count, features) + embedding_model = None dataset_collection_binding_id = None retrieval_model = None @@ -1244,7 +1255,7 @@ class DatasetCollectionBindingService: dataset_collection_binding = DatasetCollectionBinding( provider_name=provider_name, model_name=model_name, - collection_name="Vector_index_" + str(uuid.uuid4()).replace("-", "_") + '_Node', + collection_name=Dataset.gen_collection_name_by_id(str(uuid.uuid4())), type=collection_type ) db.session.add(dataset_collection_binding) diff --git a/api/services/feature_service.py b/api/services/feature_service.py index 14d262de7c..3cf51d11a0 100644 --- a/api/services/feature_service.py +++ b/api/services/feature_service.py @@ -25,6 +25,7 @@ class FeatureModel(BaseModel): apps: LimitationModel = LimitationModel(size=0, limit=10) vector_space: LimitationModel = LimitationModel(size=0, limit=5) annotation_quota_limit: LimitationModel = LimitationModel(size=0, limit=10) + documents_upload_quota: LimitationModel = LimitationModel(size=0, limit=50) docs_processing: str = 'standard' can_replace_logo: bool = False @@ -63,6 +64,9 @@ class FeatureService: features.vector_space.size = billing_info['vector_space']['size'] features.vector_space.limit = billing_info['vector_space']['limit'] + features.documents_upload_quota.size = billing_info['documents_upload_quota']['size'] + features.documents_upload_quota.limit = billing_info['documents_upload_quota']['limit'] + features.annotation_quota_limit.size = billing_info['annotation_quota_limit']['size'] features.annotation_quota_limit.limit = billing_info['annotation_quota_limit']['limit'] diff --git a/api/services/tools_manage_service.py b/api/services/tools_manage_service.py index 9c8c0dbfac..7e305c3f7b 100644 --- a/api/services/tools_manage_service.py +++ b/api/services/tools_manage_service.py @@ -209,8 +209,8 @@ class ToolManageService: # extra info like description will be set here tool_bundles, schema_type = ToolManageService.convert_schema_to_tool_bundles(schema, extra_info) - if len(tool_bundles) > 10: - raise ValueError('the number of apis should be less than 10') + if len(tool_bundles) > 100: + raise ValueError('the number of apis should be less than 100') # create db provider db_provider = ApiToolProvider( diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 1d66f791e7..cd13636c94 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -153,18 +153,18 @@ const AgentTools: FC = () => { ) : (
- {item.provider_type === CollectionType.builtIn && ( - -
{ - setCurrentTool(item) - setIsShowSettingTool(true) - }}> - -
-
- )} + {/* {item.provider_type === CollectionType.builtIn && ( */} + +
{ + setCurrentTool(item) + setIsShowSettingTool(true) + }}> + +
+
+ {/* )} */}
{ const newModelConfig = produce(modelConfig, (draft) => { @@ -209,6 +209,7 @@ const AgentTools: FC = () => { toolName={currentTool?.tool_name as string} setting={currentTool?.tool_parameters as any} collection={currentTool?.collection as Collection} + isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn} onSave={handleToolSettingChange} onHide={() => setIsShowSettingTool(false)} />) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index 98f454bd20..9892018ab6 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -8,14 +8,17 @@ import Drawer from '@/app/components/base/drawer-plus' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import type { Collection, Tool } from '@/app/components/tools/types' -import { fetchBuiltInToolList } from '@/service/tools' +import { fetchBuiltInToolList, fetchCustomToolList } from '@/service/tools' import I18n from '@/context/i18n' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common' import { getLanguage } from '@/i18n/language' +import AppIcon from '@/app/components/base/app-icon' + type Props = { collection: Collection + isBuiltIn?: boolean toolName: string setting?: Record readonly?: boolean @@ -25,6 +28,7 @@ type Props = { const SettingBuiltInTool: FC = ({ collection, + isBuiltIn = true, toolName, setting = {}, readonly, @@ -52,7 +56,7 @@ const SettingBuiltInTool: FC = ({ (async () => { setIsLoading(true) try { - const list = await fetchBuiltInToolList(collection.name) + const list = isBuiltIn ? await fetchBuiltInToolList(collection.name) : await fetchCustomToolList(collection.name) setTools(list) const currTool = list.find(tool => tool.name === toolName) if (currTool) { @@ -135,12 +139,24 @@ const SettingBuiltInTool: FC = ({ onHide={onHide} title={(
-
+ {collection.icon === 'string' + ? ( +
+ ) + : ( + + )} +
{currTool?.label[language]}
{(hasSetting && !readonly) && (<> diff --git a/web/app/components/billing/config.ts b/web/app/components/billing/config.ts index 1456bdd081..e3f9fa65b1 100644 --- a/web/app/components/billing/config.ts +++ b/web/app/components/billing/config.ts @@ -16,6 +16,7 @@ export const ALL_PLANS: Record = { teamMembers: 1, buildApps: 10, vectorSpace: 5, + documentsUploadQuota: 50, documentProcessingPriority: Priority.standard, logHistory: 30, customTools: unAvailable, @@ -32,6 +33,7 @@ export const ALL_PLANS: Record = { teamMembers: 3, buildApps: 50, vectorSpace: 200, + documentsUploadQuota: 500, documentProcessingPriority: Priority.priority, logHistory: NUM_INFINITE, customTools: 10, @@ -48,6 +50,7 @@ export const ALL_PLANS: Record = { teamMembers: NUM_INFINITE, buildApps: NUM_INFINITE, vectorSpace: 1000, + documentsUploadQuota: 1000, documentProcessingPriority: Priority.topPriority, logHistory: NUM_INFINITE, customTools: NUM_INFINITE, @@ -64,6 +67,7 @@ export const ALL_PLANS: Record = { teamMembers: NUM_INFINITE, buildApps: NUM_INFINITE, vectorSpace: NUM_INFINITE, + documentsUploadQuota: NUM_INFINITE, documentProcessingPriority: Priority.topPriority, logHistory: NUM_INFINITE, customTools: NUM_INFINITE, diff --git a/web/app/components/billing/pricing/plan-item.tsx b/web/app/components/billing/pricing/plan-item.tsx index 9e694041b3..4e020b20e8 100644 --- a/web/app/components/billing/pricing/plan-item.tsx +++ b/web/app/components/billing/pricing/plan-item.tsx @@ -129,6 +129,9 @@ const PlanItem: FC = ({
+ {t('billing.plansCommon.supportItems.logoChange')}
+
+
+ {t('billing.plansCommon.supportItems.bulkUpload')}
+
+ @@ -264,6 +267,10 @@ const PlanItem: FC = ({ value={planInfo.vectorSpace === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : (planInfo.vectorSpace >= 1000 ? `${planInfo.vectorSpace / 1000}G` : `${planInfo.vectorSpace}MB`)} tooltip={t('billing.plansCommon.vectorSpaceBillingTooltip') as string} /> + void onFileListUpdate?: (files: FileItem[]) => void onPreview: (file: File) => void + notSupportBatchUpload?: boolean } const FileUploader = ({ @@ -32,6 +33,7 @@ const FileUploader = ({ onFileUpdate, onFileListUpdate, onPreview, + notSupportBatchUpload, }: IFileUploaderProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) @@ -40,6 +42,7 @@ const FileUploader = ({ const dropRef = useRef(null) const dragRef = useRef(null) const fileUploader = useRef(null) + const hideUpload = notSupportBatchUpload && fileList.length > 0 const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) const { data: supportFileTypesResponse } = useSWR({ url: '/files/support-type' }, fetchSupportFileTypes) @@ -131,7 +134,7 @@ const FileUploader = ({ xhr: new XMLHttpRequest(), data: formData, onprogress: onProgress, - }) + }, false, undefined, '?source=datasets') .then((res: File) => { const completeFile = { fileID: fileItem.fileID, @@ -143,8 +146,8 @@ const FileUploader = ({ onFileUpdate(completeFile, 100, fileListCopy) return Promise.resolve({ ...completeFile }) }) - .catch(() => { - notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') }) + .catch((e) => { + notify({ type: 'error', message: e?.response?.code === 'forbidden' ? e?.response?.message : t('datasetCreation.stepOne.uploader.failed') }) onFileUpdate(fileItem, -2, fileListCopy) return Promise.resolve({ ...fileItem }) }) @@ -252,30 +255,36 @@ const FileUploader = ({ return (
- + {!hideUpload && ( + + )} +
{t('datasetCreation.stepOne.uploader.title')}
-
-
- - - {t('datasetCreation.stepOne.uploader.button')} - - + {!hideUpload && ( + +
+
+ + + {t('datasetCreation.stepOne.uploader.button')} + + +
+
{t('datasetCreation.stepOne.uploader.tip', { + size: fileUploadConfig.file_size_limit, + supportTypes: supportTypesShowNames, + })}
+ {dragging &&
}
-
{t('datasetCreation.stepOne.uploader.tip', { - size: fileUploadConfig.file_size_limit, - supportTypes: supportTypesShowNames, - })}
- {dragging &&
} -
+ )}
{fileList.map((fileItem, index) => (
{ return (
- +
{t('datasetCreation.stepOne.notionSyncTitle')}
{t('datasetCreation.stepOne.notionSyncTip')}
@@ -92,7 +92,7 @@ const StepOne = ({ const hasNotin = notionPages.length > 0 const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling - + const notSupportBatchUpload = enableBilling && plan.type === 'sandbox' const nextDisabled = useMemo(() => { if (!files.length) return true @@ -169,6 +169,7 @@ const StepOne = ({ onFileListUpdate={updateFileList} onFileUpdate={updateFile} onPreview={updateCurrentFile} + notSupportBatchUpload={notSupportBatchUpload} /> {isShowVectorSpaceFull && (
diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 9572325397..d94d6aea55 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -37,7 +37,8 @@ const Apps = ({ const { isCurrentWorkspaceManager } = useAppContext() const router = useRouter() const { hasEditPermission } = useContext(ExploreContext) - const allCategoriesEn = t('explore.apps.allCategories') + const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) + const [currCategory, setCurrCategory] = useTabSearchParams({ defaultTab: allCategoriesEn, }) @@ -58,11 +59,10 @@ const Apps = ({ }, ) - const currList = (() => { - if (currCategory === allCategoriesEn) - return allList - return allList.filter(item => item.category === currCategory) - })() + const currList + = currCategory === allCategoriesEn + ? allList + : allList.filter(item => item.category === currCategory) const [currApp, setCurrApp] = React.useState(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) @@ -127,6 +127,7 @@ const Apps = ({ list={categories} value={currCategory} onChange={setCurrCategory} + allCategoriesEn={allCategoriesEn} />
void + onChange: (value: AppCategory | string) => void + /** + * default value for searchparam 'category' in en + */ + allCategoriesEn: string } const Category: FC = ({ @@ -20,8 +24,10 @@ const Category: FC = ({ list, value, onChange, + allCategoriesEn, }) => { const { t } = useTranslation() + const isAllCategories = !list.includes(value) const itemClassName = (isSelected: boolean) => cn( 'px-3 py-[5px] h-[28px] rounded-lg border-[0.5px] border-transparent text-gray-700 font-medium leading-[18px] cursor-pointer hover:bg-gray-200', @@ -31,8 +37,8 @@ const Category: FC = ({ return (
onChange('')} + className={itemClassName(isAllCategories)} + onClick={() => onChange(allCategoriesEn)} > {t('explore.apps.allCategories')}
@@ -48,4 +54,5 @@ const Category: FC = ({
) } + export default React.memo(Category) diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index 6ab0541b07..2a7ee58bcf 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -2,14 +2,11 @@ import { useTranslation } from 'react-i18next' import classNames from 'classnames' import Link from 'next/link' -import { useContext } from 'use-context-selector' import s from './index.module.css' import Modal from '@/app/components/base/modal' import { XClose } from '@/app/components/base/icons/src/vender/line/general' import type { LangGeniusVersionResponse } from '@/models/common' import { IS_CE_EDITION } from '@/config' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' import LogoSite from '@/app/components/base/logo/logo-site' type IAccountSettingProps = { @@ -25,7 +22,6 @@ export default function AccountAbout({ onCancel, }: IAccountSettingProps) { const { t } = useTranslation() - const { locale } = useContext(I18n) const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version return ( @@ -48,8 +44,8 @@ export default function AccountAbout({ IS_CE_EDITION ? Open Source License : <> - Privacy Policy, - Terms of Service + Privacy Policy, + Terms of Service }
diff --git a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx index e9e9bcccac..347572c755 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx @@ -26,7 +26,7 @@ const ModelIcon: FC = ({ return ( model-icon ) diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index db436a1547..69f50f374e 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -69,7 +69,7 @@ const Form: FC = ({ - {tooltip[language]} + {tooltip[language] || tooltip.en_US}
} > @@ -91,7 +91,7 @@ const Form: FC = ({ return (
- {label[language]} + {label[language] || label.en_US} { required && ( * @@ -104,7 +104,7 @@ const Form: FC = ({ value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]} onChange={val => handleFormChange(variable, val)} validated={validatedSuccess} - placeholder={placeholder?.[language]} + placeholder={placeholder?.[language] || placeholder?.en_US} disabled={disabed} type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'} {...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})} @@ -132,7 +132,7 @@ const Form: FC = ({ return (
- {label[language]} + {label[language] || label.en_US} { required && ( * @@ -188,7 +188,7 @@ const Form: FC = ({ return (
- {label[language]} + {label[language] || label.en_US} { required && ( @@ -230,7 +230,7 @@ const Form: FC = ({
- {label[language]} + {label[language] || label.en_US} {tooltipContent}
= ({ (provider.help && (provider.help.title || provider.help.url)) ? ( !provider.help.url && e.preventDefault()} > - {provider.help.title?.[language] || provider.help.url[language]} + {provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US} ) diff --git a/web/app/components/header/account-setting/model-provider-page/model-name/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-name/index.tsx index 87f34c08f2..d828c703db 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-name/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-name/index.tsx @@ -34,7 +34,6 @@ const ModelName: FC = ({ if (!modelItem) return null - return (
= ({ >
- {modelItem.label[language]} + {modelItem.label[language] || modelItem.label.en_US}
{ showModelType && modelItem.model_type && ( diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx index 6b383108c2..f62c1687dc 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx @@ -115,6 +115,11 @@ const ParameterItem: FC = ({ } } + useEffect(() => { + if ((parameterRule.type === 'int' || parameterRule.type === 'float') && numberInputRef.current) + numberInputRef.current.value = `${renderValue}` + }, [value]) + const renderInput = () => { const numberInputWithSlide = (parameterRule.type === 'int' || parameterRule.type === 'float') && !isNullOrUndefined(parameterRule.min) @@ -207,27 +212,22 @@ const ParameterItem: FC = ({ return null } - useEffect(() => { - if (numberInputRef.current) - numberInputRef.current.value = `${renderValue}` - }, []) - return (
- {parameterRule.label[language]} + {parameterRule.label[language] || parameterRule.label.en_US}
{ parameterRule.help && ( {parameterRule.help[language]}
+
{parameterRule.help[language] || parameterRule.help.en_US}
)} > diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx index e46f94b21f..cdc8bbd7ae 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx @@ -65,7 +65,7 @@ const PopupItem: FC = ({ return (
- {model.label[language]} + {model.label[language] || model.label.en_US}
{ model.models.map(modelItem => ( diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx index 3cb420c478..5f768962f3 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx @@ -23,7 +23,22 @@ const Popup: FC = ({ const language = useLanguage() const [searchText, setSearchText] = useState('') - const filteredModelList = modelList.filter(model => model.models.filter(modelItem => modelItem.label[language].includes(searchText)).length) + const filteredModelList = modelList.filter( + model => model.models.filter( + (modelItem) => { + if (modelItem.label[language] !== undefined) + return modelItem.label[language].includes(searchText) + + let found = false + Object.keys(modelItem.label).forEach((key) => { + if (modelItem.label[key].includes(searchText)) + found = true + }) + + return found + }, + ).length, + ) return (
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx index 82a7b9f5ab..8918c69d9c 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx @@ -69,7 +69,7 @@ const ProviderCard: FC = ({
{ provider.description && ( -
{provider.description[language]}
+
{provider.description[language] || provider.description.en_US}
) }
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx index 6b423ce75c..27e6e40490 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx @@ -16,7 +16,7 @@ const ProviderIcon: FC = ({ return ( provider-icon ) diff --git a/web/app/components/share/chat/welcome/massive-component.tsx b/web/app/components/share/chat/welcome/massive-component.tsx index b39cb414cc..55b7e676fc 100644 --- a/web/app/components/share/chat/welcome/massive-component.tsx +++ b/web/app/components/share/chat/welcome/massive-component.tsx @@ -23,9 +23,9 @@ export const AppInfo: FC<{ siteInfo: SiteInfo }> = ({ siteInfo }) => { export const PromptTemplate: FC<{ html: string }> = ({ html }) => { return (
+ /> ) } diff --git a/web/app/components/tools/edit-custom-collection-modal/index.tsx b/web/app/components/tools/edit-custom-collection-modal/index.tsx index 22416808b4..45e0a014ce 100644 --- a/web/app/components/tools/edit-custom-collection-modal/index.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/index.tsx @@ -118,6 +118,13 @@ const EditCustomCollectionModal: FC = ({ const handleSave = () => { const postData = clone(customCollection) delete postData.tools + + if (postData.credentials.auth_type === AuthType.none) { + delete postData.credentials.api_key_header + delete postData.credentials.api_key_header_prefix + delete postData.credentials.api_key_value + } + if (isAdd) { onAdd?.(postData) return diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index b1a494317e..620ceb8f48 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { Settings01 } from '../../base/icons/src/vender/line/general' import ConfigCredentials from './config-credentials' -import type { Credential, CustomCollectionBackend, CustomParamSchema } from '@/app/components/tools/types' +import { AuthType, type Credential, type CustomCollectionBackend, type CustomParamSchema } from '@/app/components/tools/types' import Button from '@/app/components/base/button' import Drawer from '@/app/components/base/drawer-plus' import I18n from '@/context/i18n' @@ -34,9 +34,16 @@ const TestApi: FC = ({ const { operation_id: toolName, parameters } = tool const [parametersValue, setParametersValue] = useState>({}) const handleTest = async () => { + // clone test schema + const credentials = JSON.parse(JSON.stringify(tempCredential)) as Credential + if (credentials.auth_type === AuthType.none) { + delete credentials.api_key_header_prefix + delete credentials.api_key_header + delete credentials.api_key_value + } const data = { tool_name: toolName, - credentials: tempCredential, + credentials, schema_type: customCollection.schema_type, schema: customCollection.schema, parameters: parametersValue, diff --git a/web/app/components/tools/index.tsx b/web/app/components/tools/index.tsx index 2f6eeb07e5..9e9bbd6802 100644 --- a/web/app/components/tools/index.tsx +++ b/web/app/components/tools/index.tsx @@ -69,9 +69,22 @@ const Tools: FC = ({ })() const [query, setQuery] = useState('') - const [collectionType, setCollectionType] = useTabSearchParams({ + const [toolPageCollectionType, setToolPageCollectionType] = useTabSearchParams({ defaultTab: collectionTypeOptions[0].value, }) + const [appPageCollectionType, setAppPageCollectionType] = useState(collectionTypeOptions[0].value) + const { collectionType, setCollectionType } = (() => { + if (isInToolsPage) { + return { + collectionType: toolPageCollectionType, + setCollectionType: setToolPageCollectionType, + } + } + return { + collectionType: appPageCollectionType, + setCollectionType: setAppPageCollectionType, + } + })() const showCollectionList = (() => { let typeFilteredList: Collection[] = [] diff --git a/web/app/components/tools/tool-list/item.tsx b/web/app/components/tools/tool-list/item.tsx index c4e856e11a..c53aba61d3 100644 --- a/web/app/components/tools/tool-list/item.tsx +++ b/web/app/components/tools/tool-list/item.tsx @@ -35,9 +35,10 @@ const Item: FC = ({ const language = getLanguage(locale) const isBuiltIn = collection.type === CollectionType.builtIn - const canShowDetail = !isBuiltIn || (isBuiltIn && isInToolsPage) + const canShowDetail = isInToolsPage const [showDetail, setShowDetail] = useState(false) const addBtn = + return ( <>
= ({ )}
- {showDetail && isBuiltIn && ( + {showDetail && ( = ({ onHide={() => { setShowDetail(false) }} + isBuiltIn={isBuiltIn} /> )} diff --git a/web/app/signin/normalForm.tsx b/web/app/signin/normalForm.tsx index d513c7382c..ad3cf4a988 100644 --- a/web/app/signin/normalForm.tsx +++ b/web/app/signin/normalForm.tsx @@ -12,7 +12,6 @@ import { IS_CE_EDITION, apiPrefix } from '@/config' import Button from '@/app/components/base/button' import { login, oauth } from '@/service/common' import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' import { getPurifyHref } from '@/utils' const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/ @@ -282,13 +281,13 @@ const NormalForm = () => { {t('login.tos')}  &  {t('login.pp')}
diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 88cde4eed6..291f3073a9 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -32,6 +32,7 @@ const translation = { vectorSpace: 'Vector Space', vectorSpaceBillingTooltip: 'Each 1MB can store about 1.2million characters of vectorized data(estimated using OpenAI Embeddings, varies across models).', vectorSpaceTooltip: 'Vector Space is the long-term memory system required for LLMs to comprehend your data.', + documentsUploadQuota: 'Documents Upload Quota', documentProcessingPriority: 'Document Processing Priority', documentProcessingPriorityTip: 'For higher document processing priority, please upgrade your plan.', documentProcessingPriorityUpgrade: 'Process more data with higher accuracy at faster speeds.', @@ -56,6 +57,7 @@ const translation = { dedicatedAPISupport: 'Dedicated API support', customIntegration: 'Custom integration and support', ragAPIRequest: 'RAG API Requests', + bulkUpload: 'Bulk upload documents', agentMode: 'Agent Mode', workflow: 'Workflow', }, diff --git a/web/i18n/ja-JP/app-annotation.ts b/web/i18n/ja-JP/app-annotation.ts new file mode 100644 index 0000000000..ec91a1448c --- /dev/null +++ b/web/i18n/ja-JP/app-annotation.ts @@ -0,0 +1,87 @@ +const translation = { + title: '注釈', + name: '注釈の返信', + editBy: '{{author}} によって編集された回答', + noData: { + title: '注釈がありません', + description: 'ここではアプリのデバッグ中に注釈を編集したり、一括で注釈をインポートしたりして高品質の応答を行うことができます。', + }, + table: { + header: { + question: '質問', + answer: '回答', + createdAt: '作成日時', + hits: 'ヒット数', + actions: 'アクション', + addAnnotation: '注釈を追加', + bulkImport: '一括インポート', + bulkExport: '一括エクスポート', + clearAll: 'すべての注釈をクリア', + }, + }, + editModal: { + title: '注釈の返信を編集', + queryName: 'ユーザーのクエリ', + answerName: 'ストーリーテラーボット', + yourAnswer: 'あなたの回答', + answerPlaceholder: 'ここに回答を入力してください', + yourQuery: 'あなたのクエリ', + queryPlaceholder: 'ここにクエリを入力してください', + removeThisCache: 'この注釈を削除', + createdAt: '作成日時', + }, + addModal: { + title: '注釈の返信を追加', + queryName: '質問', + answerName: '回答', + answerPlaceholder: 'ここに回答を入力してください', + queryPlaceholder: 'ここに質問を入力してください', + createNext: '別の注釈付きの応答を追加', + }, + batchModal: { + title: '一括インポート', + csvUploadTitle: 'CSVファイルをここにドラッグ&ドロップするか、', + browse: '参照', + tip: 'CSVファイルは以下の構造に準拠する必要があります:', + question: '質問', + answer: '回答', + contentTitle: 'チャンクの内容', + content: '内容', + template: 'テンプレートをここからダウンロード', + cancel: 'キャンセル', + run: '一括実行', + runError: '一括実行に失敗しました', + processing: '一括処理中', + completed: 'インポートが完了しました', + error: 'インポートエラー', + ok: 'OK', + }, + errorMessage: { + answerRequired: '回答は必須です', + queryRequired: '質問は必須です', + }, + viewModal: { + annotatedResponse: '注釈の返信', + hitHistory: 'ヒット履歴', + hit: 'ヒット', + hits: 'ヒット数', + noHitHistory: 'ヒット履歴はありません', + }, + hitHistoryTable: { + query: 'クエリ', + match: '一致', + response: '応答', + source: 'ソース', + score: 'スコア', + time: '時間', + }, + initSetup: { + title: '注釈の返信の初期設定', + configTitle: '注釈の返信の設定', + confirmBtn: '保存して有効にする', + configConfirmBtn: '保存', + }, + embeddingModelSwitchTip: '注釈テキストのベクトル化モデルです。モデルを切り替えると再埋め込みが行われ、追加のコストが発生します。', +} + +export default translation diff --git a/web/i18n/ja-JP/app-api.ts b/web/i18n/ja-JP/app-api.ts new file mode 100644 index 0000000000..0f86e8028a --- /dev/null +++ b/web/i18n/ja-JP/app-api.ts @@ -0,0 +1,82 @@ +const translation = { + apiServer: 'APIサーバー', + apiKey: 'APIキー', + status: 'ステータス', + disabled: '無効', + ok: '稼働中', + copy: 'コピー', + copied: 'コピー済み', + play: '再生', + pause: '一時停止', + playing: '再生中', + merMaind: { + rerender: '再レンダリング', + }, + never: 'なし', + apiKeyModal: { + apiSecretKey: 'APIシークレットキー', + apiSecretKeyTips: 'APIの悪用を防ぐために、APIキーを保護してください。フロントエンドのコードで平文として使用しないでください。:)', + createNewSecretKey: '新しいシークレットキーを作成', + secretKey: 'シークレットキー', + created: '作成日時', + lastUsed: '最終使用日時', + generateTips: 'このキーを安全でアクセス可能な場所に保管してください。', + }, + actionMsg: { + deleteConfirmTitle: 'このシークレットキーを削除しますか?', + deleteConfirmTips: 'この操作は元に戻すことはできません。', + ok: 'OK', + }, + completionMode: { + title: '補完アプリAPI', + info: '記事、要約、翻訳などの高品質なテキスト生成には、ユーザーの入力を使用した補完メッセージAPIを使用します。テキスト生成は、Dify Prompt Engineeringで設定されたモデルパラメータとプロンプトテンプレートに依存しています。', + createCompletionApi: '補完メッセージの作成', + createCompletionApiTip: '質疑応答モードをサポートするために、補完メッセージを作成します。', + inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', + queryTips: 'ユーザーの入力テキスト内容。', + blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)', + streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。', + messageFeedbackApi: 'メッセージフィードバック(いいね)', + messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。', + messageIDTip: 'メッセージID', + ratingTip: 'いいねまたはいいね、nullは元に戻す', + parametersApi: 'アプリケーションパラメータ情報の取得', + parametersApiTip: '変数名、フィールド名、タイプ、デフォルト値を含む設定済みの入力パラメータを取得します。通常、これらのフィールドをフォームに表示したり、クライアントの読み込み後にデフォルト値を入力したりするために使用されます。', + }, + chatMode: { + title: 'チャットアプリAPI', + info: '質疑応答形式を使用した多目的の対話型アプリケーションには、チャットメッセージAPIを呼び出して対話を開始します。返されたconversation_idを渡すことで、継続的な会話を維持します。応答パラメータとテンプレートは、Dify Prompt Engの設定に依存します。', + createChatApi: 'チャットメッセージの作成', + createChatApiTip: '新しい会話メッセージを作成するか、既存の対話を継続します。', + inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', + queryTips: 'ユーザーの入力/質問内容', + blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)', + streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。', + conversationIdTip: '(オプション)会話ID:初回の会話の場合は空白のままにしておき、継続する場合はコンテキストからconversation_idを渡します。', + messageFeedbackApi: 'メッセージ端末ユーザーフィードバック、いいね', + messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。', + messageIDTip: 'メッセージID', + ratingTip: 'いいねまたはいいね、nullは元に戻す', + chatMsgHistoryApi: 'チャット履歴メッセージの取得', + chatMsgHistoryApiTip: '最初のページは最新の「limit」バーを返します。逆順です。', + chatMsgHistoryConversationIdTip: '会話ID', + chatMsgHistoryFirstId: '現在のページの最初のチャットレコードのID。デフォルトはなし。', + chatMsgHistoryLimit: '1回のリクエストで返されるチャットの数', + conversationsListApi: '会話リストの取得', + conversationsListApiTip: '現在のユーザーのセッションリストを取得します。デフォルトでは、最後の20のセッションが返されます。', + conversationsListFirstIdTip: '現在のページの最後のレコードのID、デフォルトはなし。', + conversationsListLimitTip: '1回のリクエストで返されるチャットの数', + conversationRenamingApi: '会話の名前変更', + conversationRenamingApiTip: '会話の名前を変更します。名前はマルチセッションクライアントインターフェースに表示されます。', + conversationRenamingNameTip: '新しい名前', + parametersApi: 'アプリケーションパラメータ情報の取得', + parametersApiTip: '変数名、フィールド名、タイプ、デフォルト値を含む設定済みの入力パラメータを取得します。通常、これらのフィールドをフォームに表示したり、クライアントの読み込み後にデフォルト値を入力したりするために使用されます。', + }, + develop: { + requestBody: 'リクエストボディ', + pathParams: 'パスパラメータ', + query: 'クエリ', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts new file mode 100644 index 0000000000..90cab35b21 --- /dev/null +++ b/web/i18n/ja-JP/app-debug.ts @@ -0,0 +1,408 @@ +const translation = { + pageTitle: { + line1: 'プロンプト', + line2: 'エンジニアリング', + }, + orchestrate: 'Orchestrate', + promptMode: { + simple: 'エキスパートモードに切り替えて、PROMPT全体を編集します', + advanced: 'エキスパートモード', + switchBack: '基本モードに戻る', + advancedWarning: { + title: 'エキスパートモードに切り替えました。PROMPTを変更すると、基本モードに戻ることはできません。', + description: 'エキスパートモードでは、PROMPT全体を編集できます。', + learnMore: '詳細を見る', + ok: 'OK', + }, + operation: { + addMessage: 'メッセージを追加', + }, + contextMissing: 'コンテキストコンポーネントが見つかりません。プロンプトの効果が十分でない場合があります。', + }, + operation: { + applyConfig: '公開', + resetConfig: 'リセット', + debugConfig: 'デバッグ', + addFeature: '機能を追加', + automatic: '自動', + stopResponding: '応答を停止', + agree: 'いいね', + disagree: 'いいえ', + cancelAgree: 'いいねをキャンセル', + cancelDisagree: 'いいえをキャンセル', + userAction: 'ユーザー', + }, + notSetAPIKey: { + title: 'LLMプロバイダーキーが設定されていません', + trailFinished: 'トライアル終了', + description: 'LLMプロバイダーキーが設定されていません。デバッグする前に設定する必要があります。', + settingBtn: '設定に移動', + }, + + trailUseGPT4Info: { + title: '現在、gpt-4はサポートされていません', + description: 'gpt-4を使用するには、APIキーを設定してください。', + }, + feature: { + groupChat: { + title: 'チャットの強化', + description: 'アプリの事前会話設定を追加すると、ユーザーエクスペリエンスが向上します。', + }, + groupExperience: { + title: 'エクスペリエンスの強化', + }, + conversationOpener: { + title: '会話の開始', + description: 'チャットアプリでは、AIがユーザーに最初にアクティブに話しかける最初の文は、通常、歓迎メッセージとして使用されます。', + }, + suggestedQuestionsAfterAnswer: { + title: 'フォローアップ', + description: '次の質問の提案を設定すると、ユーザーにより良いチャットが提供されます。', + resDes: 'ユーザーの次の質問に関する3つの提案。', + tryToAsk: '質問してみてください', + }, + moreLikeThis: { + title: 'これに似たもの', + description: '一度に複数のテキストを生成し、編集して生成を続ける', + generateNumTip: '生成回数', + tip: 'この機能を使用すると、追加のトークンオーバーヘッドが発生します', + }, + speechToText: { + title: '音声からテキストへ', + description: '有効にすると、音声入力を使用できます。', + resDes: '音声入力が有効になっています', + }, + textToSpeech: { + title: 'テキストから音声へ', + description: '有効にすると、テキストを音声に変換できます。', + resDes: 'テキストからオーディオへの変換が有効になっています', + }, + citation: { + title: '引用と帰属', + description: '有効にすると、生成されたコンテンツのソースドキュメントと帰属セクションが表示されます。', + resDes: '引用と帰属が有効になっています', + }, + annotation: { + title: '注釈返信', + description: '類似のユーザー質問との優先一致のためにキャッシュに高品質の応答を手動で追加できます。', + resDes: '注釈応答が有効になっています', + scoreThreshold: { + title: 'スコア閾値', + description: '注釈返信の類似性閾値を設定するために使用されます。', + easyMatch: '簡単なマッチ', + accurateMatch: '正確なマッチ', + }, + matchVariable: { + title: 'マッチ変数', + choosePlaceholder: 'マッチ変数を選択', + }, + cacheManagement: '注釈', + cached: '注釈付き', + remove: '削除', + removeConfirm: 'この注釈を削除しますか?', + add: '注釈を追加', + edit: '注釈を編集', + }, + dataSet: { + title: 'コンテキスト', + noData: 'コンテキストとして知識をインポートできます', + words: '単語', + textBlocks: 'テキストブロック', + selectTitle: '参照する知識を選択', + selected: '選択された知識', + noDataSet: '知識が見つかりません', + toCreate: '作成に進む', + notSupportSelectMulti: '現在、複数の知識の選択はサポートされていません', + queryVariable: { + title: 'クエリ変数', + tip: 'この変数はコンテキストの取得のためのクエリ入力として使用され、この変数の入力に関連するコンテキスト情報を取得します。', + choosePlaceholder: 'クエリ変数を選択', + noVar: '変数なし', + noVarTip: '変数セクションの下に変数を作成してください', + unableToQueryDataSet: '知識をクエリできません', + unableToQueryDataSetTip: '知識のクエリに失敗しました。正常にクエリできなかった場合は、コンテキストセクションでコンテキストクエリ変数を選択してください。', + ok: 'OK', + contextVarNotEmpty: 'コンテキストクエリ変数は空にできません', + deleteContextVarTitle: '変数 "{{varName}}" を削除しますか?', + deleteContextVarTip: 'この変数はコンテキストクエリ変数として設定されており、削除すると知識の正常な使用に影響します。削除する場合は、コンテキストセクションで再選択してください。', + }, + }, + tools: { + title: 'ツール', + tips: 'ツールは、ユーザー入力または変数をリクエストパラメーターとして使用して外部データをコンテキストとしてクエリするための標準的なAPI呼び出し方法を提供します。', + toolsInUse: '{{count}} 個のツールが使用中', + modal: { + title: 'ツール', + toolType: { + title: 'ツールタイプ', + placeholder: 'ツールタイプを選択してください', + }, + name: { + title: '名前', + placeholder: '名前を入力してください', + }, + variableName: { + title: '変数名', + placeholder: '変数名を入力してください', + }, + }, + }, + conversationHistory: { + title: '会話履歴', + description: '会話の役割に接頭辞名を設定します', + tip: '会話履歴は有効になっていません。上記のプロンプトに を追加してください。', + learnMore: '詳細', + editModal: { + title: '会話役割名の編集', + userPrefix: 'ユーザー接頭辞', + assistantPrefix: 'アシスタント接頭辞', + }, + }, + toolbox: { + title: 'ツールボックス', + }, + + moderation: { + title: 'コンテンツのモデレーション', + description: 'モデレーションAPIを使用するか、機密語リストを維持することで、モデルの出力を安全にします。', + allEnabled: '入力/出力コンテンツが有効になっています', + inputEnabled: '入力コンテンツが有効になっています', + outputEnabled: '出力コンテンツが有効になっています', + modal: { + title: 'コンテンツのモデレーション設定', + provider: { + title: 'プロバイダ', + openai: 'OpenAIモデレーション', + openaiTip: { + prefix: 'OpenAIモデレーションには、', + suffix: 'にOpenAI APIキーが設定されている必要があります。', + }, + keywords: 'キーワード', + }, + keywords: { + tip: '1行ごとに1つ、行区切りで入力してください。1行あたり最大100文字。', + placeholder: '1行ごとに、行区切りで入力してください', + line: '行', + }, + content: { + input: '入力コンテンツをモデレート', + output: '出力コンテンツをモデレート', + preset: 'プリセット返信', + placeholder: 'ここにプリセット返信の内容を入力', + condition: '少なくとも1つの入力および出力コンテンツをモデレートする', + fromApi: 'プリセット返信はAPIによって返されます', + errorMessage: 'プリセット返信は空にできません', + supportMarkdown: 'Markdownがサポートされています', + }, + openaiNotConfig: { + before: 'OpenAIモデレーションには、', + after: 'にOpenAI APIキーが設定されている必要があります。', + }, + }, + }, + + }, + automatic: { + title: '自動アプリケーションオーケストレーション', + description: 'シナリオを説明してください。Difyがアプリケーションをあなたのためにオーケストレートします。', + intendedAudience: '誰が想定されるターゲットですか?', + intendedAudiencePlaceHolder: '例:学生', + solveProblem: 'どのような問題をAIが解決できると期待していますか?', + solveProblemPlaceHolder: '例:学業成績の評価', + generate: '生成', + audiencesRequired: 'ターゲットが必要です', + problemRequired: '問題が必要です', + resTitle: '次のアプリケーションをあなたのためにオーケストレートしました。', + apply: 'このオーケストレーションを適用する', + noData: '左側にユースケースを記述し、オーケストレーションプレビューがここに表示されます。', + loading: 'アプリケーションのオーケストレーションを実行しています...', + overwriteTitle: '既存の構成を上書きしますか?', + overwriteMessage: 'このオーケストレーションを適用すると、既存の構成が上書きされます。', + }, + resetConfig: { + title: 'リセットを確認しますか?', + message: '変更が破棄され、最後に公開された構成が復元されます。', + }, + errorMessage: { + nameOfKeyRequired: 'キーの名前: {{key}} が必要です', + valueOfVarRequired: '{{key}} の値は空にできません', + queryRequired: 'リクエストテキストが必要です。', + waitForResponse: '前のメッセージへの応答が完了するまでお待ちください。', + waitForBatchResponse: 'バッチタスクへの応答が完了するまでお待ちください。', + notSelectModel: 'モデルを選択してください', + waitForImgUpload: '画像のアップロードが完了するまでお待ちください', + }, + chatSubTitle: '手順', + completionSubTitle: '接頭辞プロンプト', + promptTip: 'プロンプトは、AIの応答を指示と制約で誘導します。 {{input}} のような変数を挿入します。このプロンプトはユーザーには表示されません。', + formattingChangedTitle: '書式が変更されました', + formattingChangedText: '書式を変更すると、デバッグ領域がリセットされます。よろしいですか?', + variableTitle: '変数', + variableTip: 'ユーザーはフォームに変数を入力し、プロンプト内の変数を自動的に置換します。', + notSetVar: '変数を使用すると、ユーザーはフォームに入力する際にプロンプトの単語や開始の言葉を導入できます。プロンプトの単語に "{{input}}" を入力してみてください。', + autoAddVar: 'プリプロンプトで参照されている未定義の変数があります。ユーザー入力フォームに追加しますか?', + variableTable: { + key: '変数キー', + name: 'ユーザー入力フィールド名', + optional: 'オプション', + type: '入力タイプ', + action: 'アクション', + typeString: '文字列', + typeSelect: '選択', + }, + varKeyError: { + canNoBeEmpty: '変数キーを空にすることはできません', + tooLong: '変数キー: {{key}} が長すぎます。30文字を超えることはできません', + notValid: '変数キー: {{key}} が無効です。文字、数字、アンダースコアのみを含めることができます', + notStartWithNumber: '変数キー: {{key}} は数字で始めることはできません', + keyAlreadyExists: '変数キー: {{key}} はすでに存在します', + }, + otherError: { + promptNoBeEmpty: 'プロンプトを空にすることはできません', + historyNoBeEmpty: 'プロンプトには会話履歴を設定する必要があります', + queryNoBeEmpty: 'プロンプトにクエリを設定する必要があります', + }, + variableConig: { + modalTitle: 'フィールド設定', + description: '変数 {{varName}} の設定', + fieldType: 'フィールドタイプ', + string: '短いテキスト', + paragraph: '段落', + select: '選択', + notSet: '未設定、接頭辞プロンプトに {{input}} を入力してみてください', + stringTitle: 'フォームテキストボックスのオプション', + maxLength: '最大長', + options: 'オプション', + addOption: 'オプションを追加', + apiBasedVar: 'APIベースの変数', + }, + + vision: { + name: 'ビジョン', + description: 'ビジョンを有効にすると、モデルが画像を受け取り、それに関する質問に答えることができます。', + settings: '設定', + visionSettings: { + title: 'ビジョン設定', + resolution: '解像度', + resolutionTooltip: `低解像度では、モデルに低解像度の 512 x 512 バージョンの画像を受け取らせ、画像を 65 トークンの予算で表現します。これにより、API がより迅速な応答を返し、高い詳細が必要なユースケースでは入力トークンを消費します。 + \n + 高解像度では、まずモデルに低解像度の画像を見せ、その後、入力画像サイズに基づいて 512px の正方形の詳細なクロップを作成します。詳細なクロップごとに 129 トークンの予算を使用します。`, + high: '高', + low: '低', + uploadMethod: 'アップロード方法', + both: '両方', + localUpload: 'ローカルアップロード', + url: 'URL', + uploadLimit: 'アップロード制限', + }, + }, + voice: { + name: '音声', + defaultDisplay: 'デフォルトの音声', + description: 'テキスト読み上げの音声設定', + settings: '設定', + voiceSettings: { + title: '音声設定', + language: '言語', + resolutionTooltip: 'テキスト読み上げの音声言語をサポートします。', + voice: '音声', + }, + }, + openingStatement: { + title: '会話開始', + add: '追加', + writeOpner: 'オープナーを書く', + placeholder: 'ここにオープナーメッセージを書いてください。変数を使用できます。{{variable}} を入力してみてください。', + openingQuestion: '開始質問', + noDataPlaceHolder: + 'ユーザーとの会話を開始すると、会話アプリケーションで彼らとのより密接な関係を築くのに役立ちます。', + varTip: '変数を使用できます。{{variable}} を入力してみてください', + tooShort: '会話の開始には少なくとも 20 単語の初期プロンプトが必要です。', + notIncludeKey: '初期プロンプトに変数 {{key}} が含まれていません。初期プロンプトに追加してください。', + }, + modelConfig: { + model: 'モデル', + setTone: '応答のトーンを設定する', + title: 'モデルとパラメータ', + modeType: { + chat: 'チャット', + completion: '完成', + }, + }, + inputs: { + title: 'デバッグとプレビュー', + noPrompt: 'プレプロンプト入力にいくつかのプロンプトを記入してみてください', + userInputField: 'ユーザー入力フィールド', + noVar: '変数の値を入力してください。新しいセッションが開始されるたびにプロンプトの単語が自動的に置換されます。', + chatVarTip: + '変数の値を入力してください。新しいセッションが開始されるたびにプロンプトの単語が自動的に置換されます。', + completionVarTip: + '変数の値を入力してください。質問が送信されるたびにプロンプトの単語が自動的に置換されます。', + previewTitle: 'プロンプトのプレビュー', + queryTitle: 'クエリ内容', + queryPlaceholder: 'リクエストテキストを入力してください。', + run: '実行', + }, + result: '出力テキスト', + datasetConfig: { + settingTitle: 'リトリーバル設定', + retrieveOneWay: { + title: 'N-to-1 リトリーバル', + description: 'ユーザーの意図とナレッジの説明に基づいて、エージェントが自律的に最適なナレッジを選択します。個々の、限られたナレッジを持つアプリケーションに最適です。', + }, + retrieveMultiWay: { + title: 'マルチパスリトリーバル', + description: 'ユーザーの意図に基づいて、すべてのナレッジをクエリし、複数のソースから関連するテキストを取得し、再順位付け後、ユーザークエリに最適な結果を選択します。再順位付けモデル API の構成が必要です。', + }, + rerankModelRequired: '再順位付けモデルが必要です', + params: 'パラメータ', + top_k: 'トップK', + top_kTip: 'ユーザーの質問に最も類似したチャンクをフィルタリングするために使用されます。システムは、選択したモデルの max_tokens に応じて、動的に Top K の値を調整します。', + score_threshold: 'スコア閾値', + score_thresholdTip: 'チャンクフィルタリングの類似性閾値を設定するために使用されます。', + retrieveChangeTip: 'インデックスモードとリトリーバルモードを変更すると、このナレッジに関連付けられたアプリケーションに影響を与える可能性があります。', + }, + + debugAsSingleModel: '単一モデルでデバッグ', + debugAsMultipleModel: '複数モデルでデバッグ', + duplicateModel: '複製', + publishAs: 'として公開', + assistantType: { + name: 'アシスタントタイプ', + chatAssistant: { + name: '基本アシスタント', + description: '大規模な言語モデルを使用してチャットベースのアシスタントを構築します', + }, + agentAssistant: { + name: 'エージェントアシスタント', + description: 'タスクを自律的に完了するためのツールを選択できるインテリジェントエージェントを構築します', + }, + }, + agent: { + agentMode: 'エージェントモード', + agentModeDes: 'エージェントの推論モードの種類を設定します', + agentModeType: { + ReACT: 'ReAct', + functionCall: '関数呼び出し', + }, + setting: { + name: 'エージェント設定', + description: 'エージェントアシスタント設定では、エージェントモードやビルトインプロンプトなどの高度な機能を設定できます。エージェントタイプのみ利用可能です。', + maximumIterations: { + name: '最大反復回数', + description: 'エージェントアシスタントが実行できる反復回数を制限します', + }, + }, + buildInPrompt: 'ビルトインプロンプト', + firstPrompt: '最初のプロンプト', + nextIteration: '次の反復', + promptPlaceholder: 'ここにプロンプトを入力してください', + tools: { + name: 'ツール', + description: 'ツールを使用すると、インターネットの検索や科学的計算など、LLMの機能を拡張できます', + enabled: '有効', + }, + }, +} + +export default translation diff --git a/web/i18n/ja-JP/app-log.ts b/web/i18n/ja-JP/app-log.ts new file mode 100644 index 0000000000..85f648c411 --- /dev/null +++ b/web/i18n/ja-JP/app-log.ts @@ -0,0 +1,69 @@ +const translation = { + title: 'ログ', + description: 'ログは、アプリケーションの実行状態を記録します。ユーザーの入力やAIの応答などが含まれます。', + dateTimeFormat: 'MM/DD/YYYY hh:mm A', + table: { + header: { + time: '時間', + endUser: 'エンドユーザー', + input: '入力', + output: '出力', + summary: 'タイトル', + messageCount: 'メッセージ数', + userRate: 'ユーザー評価', + adminRate: 'オペレータ評価', + }, + pagination: { + previous: '前へ', + next: '次へ', + }, + empty: { + noChat: 'まだ会話がありません', + noOutput: '出力なし', + element: { + title: '誰かいますか?', + content: 'ここではエンドユーザーとAIアプリケーションの相互作用を観察し、注釈を付けることでAIの精度を継続的に向上させることができます。自分自身でWebアプリを共有したりテストしたりして、このページに戻ってください。', + }, + }, + }, + detail: { + time: '時間', + conversationId: '会話ID', + promptTemplate: 'プロンプトテンプレート', + promptTemplateBeforeChat: 'チャット前のプロンプトテンプレート · システムメッセージとして', + annotationTip: '{{user}}による改善', + timeConsuming: '', + second: '秒', + tokenCost: 'トークン消費', + loading: '読み込み中', + operation: { + like: 'いいね', + dislike: 'いまいち', + addAnnotation: '改善を追加', + editAnnotation: '改善を編集', + annotationPlaceholder: 'AIが返答することを期待する回答を入力してください。これはモデルの微調整やテキスト生成品質の継続的な改善に使用されます。', + }, + variables: '変数', + uploadImages: 'アップロードされた画像', + }, + filter: { + period: { + today: '今日', + last7days: '過去7日間', + last4weeks: '過去4週間', + last3months: '過去3ヶ月', + last12months: '過去12ヶ月', + monthToDate: '今月まで', + quarterToDate: '四半期まで', + yearToDate: '今年まで', + allTime: 'すべての期間', + }, + annotation: { + all: 'すべて', + annotated: '注釈付きの改善({{count}}件)', + not_annotated: '注釈なし', + }, + }, +} + +export default translation diff --git a/web/i18n/ja-JP/app-overview.ts b/web/i18n/ja-JP/app-overview.ts new file mode 100644 index 0000000000..cced6fd670 --- /dev/null +++ b/web/i18n/ja-JP/app-overview.ts @@ -0,0 +1,139 @@ +const translation = { + welcome: { + firstStepTip: '始めるには、', + enterKeyTip: '以下にOpenAI APIキーを入力してください', + getKeyTip: 'OpenAIダッシュボードからAPIキーを取得してください', + placeholder: 'OpenAI APIキー(例:sk-xxxx)', + }, + apiKeyInfo: { + cloud: { + trial: { + title: '{{providerName}}のトライアルクオータを使用しています。', + description: 'トライアルクオータはテスト用に提供されています。トライアルクオータの使用回数が尽きる前に、独自のモデルプロバイダを設定するか、追加のクオータを購入してください。', + }, + exhausted: { + title: 'トライアルクオータが使い果たされました。APIキーを設定してください。', + description: 'トライアルクオータが使い果たされました。独自のモデルプロバイダを設定するか、追加のクオータを購入してください。', + }, + }, + selfHost: { + title: { + row1: '始めるには、', + row2: 'まずモデルプロバイダを設定してください。', + }, + }, + callTimes: '呼び出し回数', + usedToken: '使用済みトークン', + setAPIBtn: 'モデルプロバイダの設定へ', + tryCloud: 'または無料クオートでDifyのクラウドバージョンを試してみてください', + }, + overview: { + title: '概要', + appInfo: { + explanation: '使いやすいAI Webアプリ', + accessibleAddress: '公開URL', + preview: 'プレビュー', + regenerate: '再生成', + preUseReminder: '続行する前にWebアプリを有効にしてください。', + settings: { + entry: '設定', + title: 'Webアプリの設定', + webName: 'Webアプリ名', + webDesc: 'Webアプリの説明', + webDescTip: 'このテキストはクライアント側に表示され、アプリの使用方法に関する基本的なガイダンスを提供します。', + webDescPlaceholder: 'Webアプリの説明を入力してください', + language: '言語', + more: { + entry: '詳細設定を表示', + copyright: '著作権', + copyRightPlaceholder: '著作者または組織の名前を入力してください', + privacyPolicy: 'プライバシーポリシー', + privacyPolicyPlaceholder: 'プライバシーポリシーリンクを入力してください', + privacyPolicyTip: '訪問者がアプリが収集するデータを理解するのに役立ちます。Difyのプライバシーポリシーを参照してください。', + }, + }, + embedded: { + entry: '埋め込み', + title: 'ウェブサイトに埋め込む', + explanation: 'チャットアプリをウェブサイトに埋め込む方法を選択してください。', + iframe: 'ウェブサイトの任意の場所にチャットアプリを追加するには、このiframeをHTMLコードに追加してください。', + scripts: 'ウェブサイトの右下にチャットアプリを追加するには、このコードをHTMLに追加してください。', + chromePlugin: 'Dify Chatbot Chrome拡張機能をインストール', + copied: 'コピー済み', + copy: 'コピー', + }, + qrcode: { + title: '共有用QRコード', + scan: 'アプリを共有するためにスキャン', + download: 'QRコードをダウンロード', + }, + customize: { + way: '方法', + entry: 'カスタマイズ', + title: 'AI Webアプリのカスタマイズ', + explanation: 'シナリオとスタイルのニーズに合わせてWebアプリのフロントエンドをカスタマイズできます。', + way1: { + name: 'クライアントコードをフォークして変更し、Vercelにデプロイする(推奨)', + step1: 'クライアントコードをフォークして変更する', + step1Tip: 'ここをクリックしてソースコードをGitHubアカウントにフォークし、コードを変更してください', + step1Operation: 'Dify-WebClient', + step2: 'Vercelにデプロイする', + step2Tip: 'ここをクリックしてリポジトリをVercelにインポートし、デプロイしてください', + step2Operation: 'リポジトリのインポート', + step3: '環境変数を設定する', + step3Tip: 'Vercelに以下の環境変数を追加してください', + }, + way2: { + name: 'APIを呼び出すためのクライアントサイドコードを記述し、サーバーにデプロイする', + operation: 'ドキュメント', + }, + }, + }, + apiInfo: { + title: 'バックエンドサービスAPI', + explanation: 'アプリケーションに簡単に統合できます', + accessibleAddress: 'サービスAPIエンドポイント', + doc: 'APIリファレンス', + }, + status: { + running: 'サービス中', + disable: '無効化', + }, + }, + analysis: { + title: '分析', + ms: 'ミリ秒', + tokenPS: 'トークン/秒', + totalMessages: { + title: '総メッセージ数', + explanation: 'AIとのやり取りのうち、プロンプトのエンジニアリングやデバッグを除いた日次の相互作用数です。', + }, + activeUsers: { + title: 'アクティブユーザー', + explanation: 'AIとのQ&Aに参加しているユニークなユーザー数です。プロンプトのエンジニアリングやデバッグを除きます。', + }, + tokenUsage: { + title: 'トークン使用量', + explanation: 'アプリケーションの言語モデルの日次トークン使用量を反映し、コスト管理の目的に役立ちます。', + consumed: '使用済み', + }, + avgSessionInteractions: { + title: '平均セッション相互作用数', + explanation: '会話ベースのアプリケーションの連続したユーザーとAIのコミュニケーション数です。', + }, + userSatisfactionRate: { + title: 'ユーザー満足率', + explanation: '1,000メッセージあたりの「いいね」の数です。これは、ユーザーが非常に満足している回答の割合を示します。', + }, + avgResponseTime: { + title: '平均応答時間', + explanation: 'AIの処理/応答にかかる時間(ミリ秒)です。テキストベースのアプリケーションに適しています。', + }, + tps: { + title: 'トークン出力速度', + explanation: 'LLMのパフォーマンスを測定します。リクエストの開始から出力の完了までのLLMのトークン出力速度をカウントします。', + }, + }, +} + +export default translation diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts new file mode 100644 index 0000000000..a163376a42 --- /dev/null +++ b/web/i18n/ja-JP/app.ts @@ -0,0 +1,54 @@ +const translation = { + createApp: '新しいアプリを作成する', + types: { + all: 'すべて', + assistant: 'アシスタント', + completion: '補完', + }, + modes: { + completion: 'テキスト生成', + chat: '基本アシスタント', + }, + createFromConfigFile: '設定ファイルからアプリを作成する', + deleteAppConfirmTitle: 'このアプリを削除しますか?', + deleteAppConfirmContent: + 'アプリの削除は元に戻せません。ユーザーはアプリにアクセスできなくなり、プロンプトの設定とログは永久に削除されます。', + appDeleted: 'アプリが削除されました', + appDeleteFailed: 'アプリの削除に失敗しました', + join: 'コミュニティに参加する', + communityIntro: + 'チームメンバーや貢献者、開発者とさまざまなチャンネルでディスカッションを行います。', + roadmap: 'ロードマップを見る', + appNamePlaceholder: 'アプリの名前を入力してください', + newApp: { + startToCreate: '新しいアプリを作成しましょう', + captionName: 'アプリアイコンと名前', + captionAppType: 'どのタイプのアプリを作成しますか?', + previewDemo: 'デモをプレビューする', + chatApp: 'アシスタント', + chatAppIntro: + 'チャットベースのアプリケーションを構築したいです。このアプリは質問と回答の形式を使用し、複数のラウンドの連続した会話が可能です。', + agentAssistant: '新しいエージェントアシスタント', + completeApp: 'テキスト生成', + completeAppIntro: + 'プロンプトに基づいて高品質のテキストを生成するアプリケーションを作成したいです。記事、要約、翻訳などの生成が可能です。', + showTemplates: 'テンプレートから選択したいです', + hideTemplates: 'モード選択に戻る', + Create: '作成', + Cancel: 'キャンセル', + nameNotEmpty: '名前は空にできません', + appTemplateNotSelected: 'テンプレートを選択してください', + appTypeRequired: 'アプリのタイプを選択してください', + appCreated: 'アプリが作成されました', + appCreateFailed: 'アプリの作成に失敗しました', + }, + editApp: { + startToEdit: 'アプリを編集する', + }, + emoji: { + ok: 'OK', + cancel: 'キャンセル', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts new file mode 100644 index 0000000000..ace11503e7 --- /dev/null +++ b/web/i18n/ja-JP/billing.ts @@ -0,0 +1,113 @@ +const translation = { + currentPlan: '現在のプラン', + upgradeBtn: { + plain: 'プランをアップグレード', + encourage: '今すぐアップグレード', + encourageShort: 'アップグレード', + }, + viewBilling: '請求とサブスクリプションの管理', + buyPermissionDeniedTip: 'サブスクリプションするには、エンタープライズ管理者に連絡してください', + plansCommon: { + title: 'あなたに合ったプランを選択してください', + yearlyTip: '年間購読で2か月無料!', + mostPopular: '最も人気のある', + planRange: { + monthly: '月額', + yearly: '年額', + }, + month: '月', + year: '年', + save: '節約 ', + free: '無料', + currentPlan: '現在のプラン', + contractSales: '営業に連絡する', + contractOwner: 'チームマネージャーに連絡する', + startForFree: '無料で始める', + getStartedWith: '始める ', + contactSales: '営業に連絡する', + talkToSales: '営業と話す', + modelProviders: 'モデルプロバイダー', + teamMembers: 'チームメンバー', + buildApps: 'アプリを作成する', + vectorSpace: 'ベクトルスペース', + vectorSpaceBillingTooltip: '1MBあたり約120万文字のベクトル化データを保存できます(OpenAI Embeddingsを使用して推定され、モデルによって異なります)。', + vectorSpaceTooltip: 'ベクトルスペースは、LLMがデータを理解するために必要な長期記憶システムです。', + documentProcessingPriority: 'ドキュメント処理の優先度', + documentProcessingPriorityTip: 'より高いドキュメント処理の優先度をご希望の場合は、プランをアップグレードしてください。', + documentProcessingPriorityUpgrade: 'より高い精度と高速な速度でデータを処理します。', + priority: { + 'standard': '標準', + 'priority': '優先', + 'top-priority': '最優先', + }, + logsHistory: 'ログ履歴', + customTools: 'カスタムツール', + unavailable: '利用不可', + days: '日', + unlimited: '無制限', + support: 'サポート', + supportItems: { + communityForums: 'コミュニティフォーラム', + emailSupport: 'メールサポート', + priorityEmail: '優先メール&チャットサポート', + logoChange: 'ロゴ変更', + SSOAuthentication: 'SSO認証', + personalizedSupport: '個別サポート', + dedicatedAPISupport: '専用APIサポート', + customIntegration: 'カスタム統合とサポート', + ragAPIRequest: 'RAG APIリクエスト', + agentMode: 'エージェントモード', + workflow: 'ワークフロー', + }, + comingSoon: '近日公開', + member: 'メンバー', + memberAfter: 'メンバー', + messageRequest: { + title: 'メッセージクレジット', + tooltip: 'OpenAIモデルを使用したさまざまなプランのメッセージ呼び出しクォータ(gpt4を除く)。制限を超えるメッセージはOpenAI APIキーを使用します。', + }, + annotatedResponse: { + title: '注釈クォータ制限', + tooltip: '手動での編集と応答の注釈付けにより、アプリのカスタマイズ可能な高品質な質問応答機能が提供されます(チャットアプリのみ適用)。', + }, + ragAPIRequestTooltip: 'Difyのナレッジベース処理機能のみを呼び出すAPI呼び出しの数を指します。', + receiptInfo: 'チームオーナーとチーム管理者のみが購読および請求情報を表示できます', + }, + plans: { + sandbox: { + name: 'サンドボックス', + description: 'GPTの無料トライアル200回', + includesTitle: '含まれるもの:', + }, + professional: { + name: 'プロフェッショナル', + description: '個人や小規模チーム向けにより多くのパワーを手頃な価格で提供します。', + includesTitle: '無料プランに加えて、次も含まれます:', + }, + team: { + name: 'チーム', + description: '制限なく協力し、最高のパフォーマンスを楽しむ。', + includesTitle: 'プロフェッショナルプランに加えて、次も含まれます:', + }, + enterprise: { + name: 'エンタープライズ', + description: '大規模なミッションクリティカルシステムのためのフル機能とサポートを提供します。', + includesTitle: 'チームプランに加えて、次も含まれます:', + }, + }, + vectorSpace: { + fullTip: 'ベクトルスペースがいっぱいです。', + fullSolution: 'より多くのスペースを得るためにプランをアップグレードしてください。', + }, + apps: { + fullTipLine1: 'より多くのアプリを作成するには、', + fullTipLine2: 'プランをアップグレードしてください。', + }, + annotatedResponse: { + fullTipLine1: 'より多くの会話を注釈するには、', + fullTipLine2: 'プランをアップグレードしてください。', + quotaTitle: '注釈返信クォータ', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts new file mode 100644 index 0000000000..a9853461b3 --- /dev/null +++ b/web/i18n/ja-JP/common.ts @@ -0,0 +1,456 @@ +const translation = { + api: { + success: '成功', + actionSuccess: 'アクションが成功しました', + saved: '保存済み', + create: '作成済み', + remove: '削除済み', + }, + operation: { + create: '作成', + confirm: '確認', + cancel: 'キャンセル', + clear: 'クリア', + save: '保存', + edit: '編集', + add: '追加', + added: '追加済み', + refresh: 'リフレッシュ', + reset: 'リセット', + search: '検索', + change: '変更', + remove: '削除', + send: '送信', + copy: 'コピー', + lineBreak: '改行', + sure: '確認済み', + download: 'ダウンロード', + delete: '削除', + settings: '設定', + setup: 'セットアップ', + getForFree: '無料で入手', + reload: '再読み込み', + ok: 'OK', + log: 'ログ', + learnMore: '詳細を見る', + params: 'パラメータ', + }, + placeholder: { + input: '入力してください', + select: '選択してください', + }, + voice: { + language: { + zhHans: '中国語', + enUS: '英語', + deDE: 'ドイツ語', + frFR: 'フランス語', + esES: 'スペイン語', + itIT: 'イタリア語', + thTH: 'タイ語', + idID: 'インドネシア語', + jaJP: '日本語', + koKR: '韓国語', + ptBR: 'ポルトガル語', + ruRU: 'ロシア語', + ukUA: 'ウクライナ語', + }, + }, + unit: { + char: '文字', + }, + actionMsg: { + noModification: '現在は変更されていません。', + modifiedSuccessfully: '変更が正常に行われました', + modifiedUnsuccessfully: '変更が失敗しました', + copySuccessfully: 'コピーが正常に行われました', + paySucceeded: '支払いが成功しました', + payCancelled: '支払いがキャンセルされました', + generatedSuccessfully: '生成が成功しました', + generatedUnsuccessfully: '生成が失敗しました', + }, + model: { + params: { + temperature: '温度', + temperatureTip: + 'ランダム性を制御します:温度を下げると、よりランダムな完成品が得られます。温度がゼロに近づくにつれて、モデルは決定的で反復的になります。', + top_p: '上位P', + top_pTip: + 'ニュークリアスサンプリングによる多様性の制御:0.5は、すべての尤度加重オプションの半分が考慮されることを意味します。', + presence_penalty: '存在ペナルティ', + presence_penaltyTip: + 'これまでのテキストにトークンが表示されるかどうかに基づいて、新しいトークンにいくらペナルティを科すかを制御します。\nモデルが新しいトピックについて話す可能性が高まります。', + frequency_penalty: '頻度ペナルティ', + frequency_penaltyTip: + 'これまでのテキスト内のトークンの既存の頻度に基づいて、新しいトークンにどれだけペナルティを科すかを制御します。\nモデルが同じ行を文字通りに繰り返す可能性が低くなります。', + max_tokens: '最大トークン', + max_tokensTip: + '返信の最大長をトークン単位で制限するために使用されます。\n大きな値はプロンプトの単語、チャットログ、およびナレッジのために残されたスペースを制限する可能性があります。\nそれを2/3以下に設定することをお勧めします。\ngpt-4-1106-preview、gpt-4-vision-previewの最大トークン(入力128k出力4k)以下に設定することをお勧めします。', + maxTokenSettingTip: '最大トークン設定が高いため、プロンプト、クエリ、およびデータのスペースが制限される可能性があります。現在のモデルの最大トークンの80%以下に設定してください。', + setToCurrentModelMaxTokenTip: '最大トークンが現在のモデルの最大トークンの80%に更新されました {{maxToken}}.', + stop_sequences: '停止シーケンス', + stop_sequencesTip: 'APIが進行中のトークンの生成を停止する最大4つのシーケンス。返されたテキストには停止シーケンスは含まれません。', + stop_sequencesPlaceholder: 'シーケンスを入力してタブキーを押してください', + }, + tone: { + Creative: 'クリエイティブ', + Balanced: 'バランス', + Precise: '正確', + Custom: 'カスタム', + }, + addMoreModel: '設定画面から他のモデルを追加してください', + }, + menus: { + status: 'ベータ版', + explore: '探索', + apps: 'スタジオ', + plugins: 'プラグイン', + pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。', + datasets: 'ナレッジ', + datasetsTips: '近日公開:独自のテキストデータをインポートするか、Webhookを介してリアルタイムにデータを記述してLLMコンテキストを強化します。', + newApp: '新しいアプリ', + newDataset: 'ナレッジの作成', + tools: 'ツール', + }, + userProfile: { + settings: '設定', + workspace: 'ワークスペース', + createWorkspace: 'ワークスペースを作成', + helpCenter: 'ヘルプ', + roadmapAndFeedback: 'ロードマップとフィードバック', + community: 'コミュニティ', + about: 'Difyについて', + logout: 'ログアウト', + }, + settings: { + accountGroup: 'アカウント', + workplaceGroup: 'ワークスペース', + account: 'マイアカウント', + members: 'メンバー', + billing: '請求', + integrations: '統合', + language: '言語', + provider: 'モデルプロバイダー', + dataSource: 'データソース', + plugin: 'プラグイン', + apiBasedExtension: 'API拡張', + }, + account: { + avatar: 'アバター', + name: '名前', + email: 'メール', + password: 'パスワード', + passwordTip: '一時的なログインコードを使用したくない場合は、永続的なパスワードを設定できます。', + setPassword: 'パスワードを設定', + resetPassword: 'パスワードをリセット', + currentPassword: '現在のパスワード', + newPassword: '新しいパスワード', + confirmPassword: 'パスワードを確認', + notEqual: '2つのパスワードが異なります。', + langGeniusAccount: 'Difyアカウント', + langGeniusAccountTip: 'Difyアカウントと関連するユーザーデータ。', + editName: '名前を編集', + showAppLength: '{{length}}アプリを表示', + }, + members: { + team: 'チーム', + invite: '招待', + name: '名前', + lastActive: '最終アクティブ', + role: 'ロール', + pending: '保留中...', + owner: 'オーナー', + admin: '管理者', + adminTip: 'アプリの構築およびチーム設定の管理ができます', + normal: '通常', + normalTip: 'アプリの使用のみが可能で、アプリの構築はできません', + inviteTeamMember: 'チームメンバーを招待する', + inviteTeamMemberTip: '彼らはサインイン後、直接あなたのチームデータにアクセスできます。', + email: 'メール', + emailInvalid: '無効なメール形式', + emailPlaceholder: 'メールを入力してください', + sendInvite: '招待を送る', + invitedAsRole: '{{role}}ユーザーとして招待されました', + invitationSent: '招待が送信されました', + invitationSentTip: '招待が送信され、彼らはDifyにサインインしてあなたのチームデータにアクセスできます。', + invitationLink: '招待リンク', + failedinvitationEmails: '以下のユーザーは正常に招待されませんでした', + ok: 'OK', + removeFromTeam: 'チームから削除', + removeFromTeamTip: 'チームへのアクセスが削除されます', + setAdmin: '管理者に設定', + setMember: '通常のメンバーに設定', + disinvite: '招待をキャンセル', + deleteMember: 'メンバーを削除', + you: '(あなた)', + }, + integrations: { + connected: '接続済み', + google: 'Google', + googleAccount: 'Googleアカウントでログイン', + github: 'GitHub', + githubAccount: 'GitHubアカウントでログイン', + connect: '接続', + }, + language: { + displayLanguage: '表示言語', + timezone: 'タイムゾーン', + }, + provider: { + apiKey: 'APIキー', + enterYourKey: 'ここにAPIキーを入力してください', + invalidKey: '無効なOpenAI APIキー', + validatedError: '検証に失敗しました:', + validating: 'キーの検証中...', + saveFailed: 'APIキーの保存に失敗しました', + apiKeyExceedBill: 'このAPI KEYには使用可能なクォータがありません。詳細は', + addKey: 'キーを追加', + comingSoon: '近日公開', + editKey: '編集', + invalidApiKey: '無効なAPIキー', + azure: { + apiBase: 'APIベース', + apiBasePlaceholder: 'Azure OpenAIエンドポイントのAPIベースURL。', + apiKey: 'APIキー', + apiKeyPlaceholder: 'ここにAPIキーを入力してください', + helpTip: 'Azure OpenAIサービスを学ぶ', + }, + openaiHosted: { + openaiHosted: 'ホステッドOpenAI', + onTrial: 'トライアル中', + exhausted: 'クォータが使い果たされました', + desc: 'Difyが提供するOpenAIホスティングサービスを使用すると、GPT-3.5などのモデルを使用できます。トライアルクォータが使い果たされる前に、他のモデルプロバイダを設定する必要があります。', + callTimes: '通話回数', + usedUp: 'トライアルクォータが使い果たされました。独自のモデルプロバイダを追加してください。', + useYourModel: '現在、独自のモデルプロバイダを使用しています。', + close: '閉じる', + }, + anthropicHosted: { + anthropicHosted: 'アンソピッククロード', + onTrial: 'トライアル中', + exhausted: 'クォータが使い果たされました', + desc: '高度なダイアログやクリエイティブなコンテンツ生成から詳細な指示まで、幅広いタスクに優れたパワフルなモデルです。', + callTimes: '通話回数', + usedUp: 'トライアルクォータが使い果たされました。独自のモデルプロバイダを追加してください。', + useYourModel: '現在、独自のモデルプロバイダを使用しています。', + close: '閉じる', + }, + anthropic: { + using: '埋め込み機能は使用中です', + enableTip: 'Anthropicモデルを有効にするには、まずOpenAIまたはAzure OpenAIサービスにバインドする必要があります。', + notEnabled: '有効にされていません', + keyFrom: 'AnthropicからAPIキーを取得してください', + }, + encrypted: { + front: 'API KEYは', + back: '技術を使用して暗号化および保存されます。', + }, + }, + modelProvider: { + notConfigured: 'システムモデルがまだ完全に設定されておらず、一部の機能が利用できない場合があります。', + systemModelSettings: 'システムモデル設定', + systemModelSettingsLink: 'システムモデルの設定が必要な理由は何ですか?', + selectModel: 'モデルを選択', + setupModelFirst: 'まずモデルをセットアップしてください', + systemReasoningModel: { + key: 'システム推論モデル', + tip: 'アプリの作成に使用されるデフォルトの推論モデルを設定します。また、対話名の生成や次の質問の提案などの機能もデフォルトの推論モデルを使用します。', + }, + embeddingModel: { + key: '埋め込みモデル', + tip: 'ナレッジのドキュメント埋め込み処理のデフォルトモデルを設定します。ナレッジの取得とインポートの両方に、この埋め込みモデルをベクトル化処理に使用します。切り替えると、インポートされたナレッジと質問の間のベクトル次元が一致せず、取得に失敗します。取得の失敗を避けるためには、このモデルを任意に切り替えないでください。', + required: '埋め込みモデルが必要です', + }, + speechToTextModel: { + key: '音声-to-テキストモデル', + tip: '会話での音声-to-テキスト入力に使用するデフォルトモデルを設定します。', + }, + ttsModel: { + key: 'テキスト-to-音声モデル', + tip: '会話でのテキスト-to-音声入力に使用するデフォルトモデルを設定します。', + }, + rerankModel: { + key: '再ランクモデル', + tip: '再ランクモデルは、ユーザークエリとの意味的一致に基づいて候補文書リストを再配置し、意味的ランキングの結果を向上させます。', + }, + quota: 'クォータ', + searchModel: '検索モデル', + noModelFound: '{{model}}に対するモデルが見つかりません', + models: 'モデル', + showMoreModelProvider: 'より多くのモデルプロバイダを表示', + selector: { + tip: 'このモデルは削除されました。別のモデルを追加するか、別のモデルを選択してください。', + emptyTip: '利用可能なモデルはありません', + emptySetting: '設定に移動して構成してください', + rerankTip: '再ランクモデルを設定してください', + }, + card: { + quota: 'クォータ', + onTrial: 'トライアル中', + paid: '有料', + quotaExhausted: 'クォータが使い果たされました', + callTimes: '通話回数', + tokens: 'トークン', + fee: '費用', + feeToken: 'トークン毎の費用', + basicSettings: '基本設定', + advancedSettings: '高度な設定', + comingSoon: '近日公開', + installPlugin: 'プラグインをインストール', + }, + }, + dataSource: { + externalAPI: '外部API', + webhooks: 'Webhook', + custom: 'カスタム', + }, + plugin: { + serpapi: { + apiKey: 'APIキー', + apiKeyPlaceholder: 'APIキーを入力してください', + keyFrom: 'SerpAPIアカウントページからSerpAPIキーを取得してください', + }, + }, + apiBasedExtension: { + title: 'API拡張機能は、Difyのアプリケーション全体での簡単な使用のための設定を簡素化し、集中的なAPI管理を提供します。', + link: '独自のAPI拡張機能を開発する方法について学ぶ。', + linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + add: 'API拡張機能を追加', + selector: { + title: 'API拡張機能', + placeholder: 'API拡張機能を選択してください', + manage: 'API拡張機能を管理', + }, + modal: { + title: 'API拡張機能を追加', + editTitle: 'API拡張機能を編集', + name: { + title: '名前', + placeholder: '名前を入力してください', + }, + apiEndpoint: { + title: 'APIエンドポイント', + placeholder: 'APIエンドポイントを入力してください', + }, + apiKey: { + title: 'APIキー', + placeholder: 'APIキーを入力してください', + lengthError: 'APIキーの長さは5文字未満にできません', + }, + }, + type: 'タイプ', + }, + about: { + changeLog: '変更ログ', + updateNow: '今すぐ更新', + nowAvailable: 'Dify {{version}} が利用可能です。', + latestAvailable: 'Dify {{version}} が最新バージョンです。', + }, + appMenus: { + overview: '概要', + promptEng: 'Orchestrate', + apiAccess: 'APIアクセス', + logAndAnn: 'ログ&アナウンス', + }, + environment: { + testing: 'テスト', + development: '開発', + }, + appModes: { + completionApp: 'テキスト生成', + chatApp: 'チャットアプリ', + }, + datasetMenus: { + documents: 'ドキュメント', + hitTesting: '検索テスト', + settings: '設定', + emptyTip: '関連付けられた知識がありません。アプリケーションやプラグインに移動して関連付けを完了してください。', + viewDoc: 'ドキュメントを表示', + relatedApp: '関連アプリ', + }, + voiceInput: { + speaking: '今話しています...', + converting: 'テキストに変換中...', + notAllow: 'マイクが許可されていません', + }, + modelName: { + 'gpt-3.5-turbo': 'GPT-3.5-Turbo', + 'gpt-3.5-turbo-16k': 'GPT-3.5-Turbo-16K', + 'gpt-4': 'GPT-4', + 'gpt-4-32k': 'GPT-4-32K', + 'text-davinci-003': 'Text-Davinci-003', + 'text-embedding-ada-002': 'Text-Embedding-Ada-002', + 'whisper-1': 'Whisper-1', + 'claude-instant-1': 'Claude-Instant', + 'claude-2': 'Claude-2', + }, + chat: { + renameConversation: '会話名を変更', + conversationName: '会話名', + conversationNamePlaceholder: '会話名を入力してください', + conversationNameCanNotEmpty: '会話名は必須です', + citation: { + title: '引用', + linkToDataset: '知識へのリンク', + characters: '文字数:', + hitCount: '検索回数:', + vectorHash: 'ベクトルハッシュ:', + hitScore: '検索スコア:', + }, + }, + promptEditor: { + placeholder: 'ここにプロンプトワードを入力してください。変数を挿入するには「{」を、プロンプトコンテンツブロックを挿入するには「/」を入力します。', + context: { + item: { + title: 'コンテキスト', + desc: 'コンテキストテンプレートを挿入', + }, + modal: { + title: '{{num}} 番目のコンテキスト', + add: 'コンテキストを追加', + footer: '以下のコンテキストセクションでコンテキストを管理できます。', + }, + }, + history: { + item: { + title: '会話履歴', + desc: '過去のメッセージテンプレートを挿入', + }, + modal: { + title: '例', + user: 'こんにちは', + assistant: 'こんにちは! 今日はどのようにお手伝いできますか?', + edit: '会話の役割名を編集', + }, + }, + variable: { + item: { + title: '変数&外部ツール', + desc: '変数&外部ツールを挿入', + }, + modal: { + add: '新しい変数', + addTool: '新しいツール', + }, + }, + query: { + item: { + title: 'クエリ', + desc: 'ユーザークエリテンプレートを挿入', + }, + }, + existed: 'プロンプトにすでに存在します', + }, + imageUploader: { + uploadFromComputer: 'コンピューターからアップロード', + uploadFromComputerReadError: '画像の読み込みに失敗しました。もう一度お試しください。', + uploadFromComputerUploadError: '画像のアップロードに失敗しました。もう一度アップロードしてください。', + uploadFromComputerLimit: 'アップロード画像のサイズは {{size}} MB を超えることはできません', + pasteImageLink: '画像リンクを貼り付ける', + pasteImageLinkInputPlaceholder: 'ここに画像リンクを貼り付けてください', + pasteImageLinkInvalid: '無効な画像リンク', + imageUpload: '画像アップロード', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/custom.ts b/web/i18n/ja-JP/custom.ts new file mode 100644 index 0000000000..a02773fc6f --- /dev/null +++ b/web/i18n/ja-JP/custom.ts @@ -0,0 +1,30 @@ +const translation = { + custom: 'カスタマイズ', + upgradeTip: { + prefix: 'プランをアップグレードして', + suffix: 'ブランドをカスタマイズしましょう。', + }, + webapp: { + title: 'WebAppブランドのカスタマイズ', + removeBrand: 'Powered by Difyを削除', + changeLogo: 'Powered byブランド画像を変更', + changeLogoTip: '最小サイズ40x40pxのSVGまたはPNG形式', + }, + app: { + title: 'アプリヘッダーブランドのカスタマイズ', + changeLogoTip: '最小サイズ80x80pxのSVGまたはPNG形式', + }, + upload: 'アップロード', + uploading: 'アップロード中', + uploadedFail: '画像のアップロードに失敗しました。再度アップロードしてください。', + change: '変更', + apply: '適用', + restore: 'デフォルトに戻す', + customize: { + contactUs: 'お問い合わせ', + prefix: 'アプリ内のブランドロゴをカスタマイズするには、', + suffix: 'エンタープライズ版にアップグレードしてください。', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/dataset-creation.ts b/web/i18n/ja-JP/dataset-creation.ts new file mode 100644 index 0000000000..b08d0224f9 --- /dev/null +++ b/web/i18n/ja-JP/dataset-creation.ts @@ -0,0 +1,130 @@ +const translation = { + steps: { + header: { + creation: 'ナレッジの作成', + update: 'データの追加', + }, + one: 'データソースの選択', + two: 'テキストの前処理とクリーニング', + three: '実行して完了', + }, + error: { + unavailable: 'このナレッジは利用できません', + }, + stepOne: { + filePreview: 'ファイルプレビュー', + pagePreview: 'ページプレビュー', + dataSourceType: { + file: 'テキストファイルからインポート', + notion: 'Notionから同期', + web: 'ウェブサイトから同期', + }, + uploader: { + title: 'テキストファイルをアップロード', + button: 'ファイルをドラッグ&ドロップするか', + browse: '参照', + tip: '{{supportTypes}}をサポートしています。1つあたりの最大サイズは{{size}}MBです。', + validation: { + typeError: 'サポートされていないファイルタイプです', + size: 'ファイルサイズが大きすぎます。最大サイズは{{size}}MBです', + count: '複数のファイルはサポートされていません', + filesNumber: 'バッチアップロードの制限({{filesNumber}}個)に達しました。', + }, + cancel: 'キャンセル', + change: '変更', + failed: 'アップロードに失敗しました', + }, + notionSyncTitle: 'Notionが接続されていません', + notionSyncTip: 'Notionと同期するには、まずNotionへの接続が必要です。', + connect: '接続する', + button: '次へ', + emptyDatasetCreation: '空のナレッジを作成します', + modal: { + title: '空のナレッジを作成', + tip: '空のナレッジにはドキュメントが含まれず、いつでもドキュメントをアップロードできます。', + input: 'ナレッジ名', + placeholder: '入力してください', + nameNotEmpty: '名前は空にできません', + nameLengthInvaild: '名前は1〜40文字である必要があります', + cancelButton: 'キャンセル', + confirmButton: '作成', + failed: '作成に失敗しました', + }, + }, + stepTwo: { + segmentation: 'チャンク設定', + auto: '自動', + autoDescription: 'チャンクと前処理ルールを自動的に設定します。初めてのユーザーはこれを選択することをおすすめします。', + custom: 'カスタム', + customDescription: 'チャンクのルール、チャンクの長さ、前処理ルールなどをカスタマイズします。', + separator: 'セグメント識別子', + separatorPlaceholder: '例えば改行(\\\\n)や特殊なセパレータ(例:「***」)', + maxLength: '最大チャンク長', + overlap: 'チャンクのオーバーラップ', + overlapTip: 'チャンクのオーバーラップを設定することで、それらの間の意味的な関連性を維持し、検索効果を向上させることができます。最大チャンクサイズの10%〜25%を設定することをおすすめします。', + overlapCheck: 'チャンクのオーバーラップは最大チャンク長を超えてはいけません', + rules: 'テキストの前処理ルール', + removeExtraSpaces: '連続するスペース、改行、タブを置換する', + removeUrlEmails: 'すべてのURLとメールアドレスを削除する', + removeStopwords: '「a」「an」「the」などのストップワードを削除する', + preview: '確認&プレビュー', + reset: 'リセット', + indexMode: 'インデックスモード', + qualified: '高品質', + recommend: 'おすすめ', + qualifiedTip: 'ユーザーのクエリに対してより高い精度を提供するために、デフォルトのシステム埋め込みインターフェースを呼び出して処理します。', + warning: 'モデルプロバイダのAPIキーを設定してください。', + click: '設定に移動', + economical: '経済的', + economicalTip: 'オフラインのベクトルエンジン、キーワードインデックスなどを使用して、トークンを消費せずに精度を低下させます。', + QATitle: '質問と回答形式でセグメント化', + QATip: 'このオプションを有効にすると、追加のトークンが消費されます', + QALanguage: '使用言語', + emstimateCost: '見積もり', + emstimateSegment: '推定チャンク数', + segmentCount: 'チャンク', + calculating: '計算中...', + fileSource: 'ドキュメントの前処理', + notionSource: 'ページの前処理', + other: 'その他', + fileUnit: 'ファイル', + notionUnit: 'ページ', + lastStep: '最後のステップ', + nextStep: '保存して処理', + save: '保存して処理', + cancel: 'キャンセル', + sideTipTitle: 'なぜチャンクと前処理が必要なのか', + sideTipP1: 'テキストデータを処理する際、チャンクとクリーニングは2つの重要な前処理ステップです。', + sideTipP2: 'セグメンテーションは長いテキストを段落に分割し、モデルがより理解しやすくします。これにより、モデルの結果の品質と関連性が向上します。', + sideTipP3: 'クリーニングは不要な文字や書式を削除し、ナレッジをよりクリーンで解析しやすいものにします。', + sideTipP4: '適切なチャンクとクリーニングはモデルのパフォーマンスを向上させ、より正確で価値のある結果を提供します。', + previewTitle: 'プレビュー', + previewTitleButton: 'プレビュー', + previewButton: 'Q&A形式に切り替える', + previewSwitchTipStart: '現在のチャンクプレビューはテキスト形式です。質問と回答形式のプレビューに切り替えると、', + previewSwitchTipEnd: ' 追加のトークンが消費されます', + characters: '文字', + indexSettedTip: 'インデックス方法を変更するには、', + retrivalSettedTip: 'インデックス方法を変更するには、', + datasetSettingLink: 'ナレッジ設定', + }, + stepThree: { + creationTitle: '🎉 ナレッジが作成されました', + creationContent: 'ナレッジの名前は自動的に設定されましたが、いつでも変更できます', + label: 'ナレッジ名', + additionTitle: '🎉 ドキュメントがアップロードされました', + additionP1: 'ドキュメントはナレッジにアップロードされました', + additionP2: '、ナレッジのドキュメントリストで見つけることができます。', + stop: '処理を停止', + resume: '処理を再開', + navTo: 'ドキュメントに移動', + sideTipTitle: '次は何ですか', + sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立したChatGPTインデックスプラグインとしてリリースすることもできます。', + modelTitle: '埋め込みを停止してもよろしいですか?', + modelContent: '後で処理を再開する必要がある場合は、中断した場所から続行します。', + modelButtonConfirm: '確認', + modelButtonCancel: 'キャンセル', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/dataset-documents.ts b/web/i18n/ja-JP/dataset-documents.ts new file mode 100644 index 0000000000..0152d10616 --- /dev/null +++ b/web/i18n/ja-JP/dataset-documents.ts @@ -0,0 +1,349 @@ +const translation = { + list: { + title: 'ドキュメント', + desc: 'ナレッジのすべてのファイルがここに表示され、ナレッジ全体がDifyの引用やチャットプラグインを介してリンクされるか、インデックス化されることができます。', + addFile: 'ファイルを追加', + addPages: 'ページを追加', + table: { + header: { + fileName: 'ファイル名', + words: '単語数', + hitCount: '検索回数', + uploadTime: 'アップロード時間', + status: 'ステータス', + action: 'アクション', + }, + }, + action: { + uploadFile: '新しいファイルをアップロード', + settings: 'セグメント設定', + addButton: 'チャンクを追加', + add: 'チャンクを追加', + batchAdd: '一括追加', + archive: 'アーカイブ', + unarchive: 'アーカイブ解除', + delete: '削除', + enableWarning: 'アーカイブされたファイルは有効にできません', + sync: '同期', + }, + index: { + enable: '有効にする', + disable: '無効にする', + all: 'すべて', + enableTip: 'ファイルをインデックス化できます', + disableTip: 'ファイルをインデックス化できません', + }, + status: { + queuing: 'キューイング中', + indexing: 'インデックス化中', + paused: '一時停止中', + error: 'エラー', + available: '利用可能', + enabled: '有効', + disabled: '無効', + archived: 'アーカイブ済み', + }, + empty: { + title: 'まだドキュメントがありません', + upload: { + tip: 'ファイルをアップロードしたり、ウェブサイトから同期したり、NotionやGitHubなどのウェブアプリから同期することができます。', + }, + sync: { + tip: 'Difyは定期的にNotionからファイルをダウンロードし、処理を完了します。', + }, + }, + delete: { + title: '本当に削除しますか?', + content: '後で処理を再開する必要がある場合は、中断した場所から続行します。', + }, + batchModal: { + title: '一括追加', + csvUploadTitle: 'CSVファイルをここにドラッグアンドドロップするか、', + browse: '参照', + tip: 'CSVファイルは次の構造に準拠する必要があります:', + question: '質問', + answer: '回答', + contentTitle: 'チャンクの内容', + content: '内容', + template: 'テンプレートをここからダウンロード', + cancel: 'キャンセル', + run: '一括実行', + runError: '一括実行に失敗しました', + processing: '一括処理中', + completed: 'インポート完了', + error: 'インポートエラー', + ok: 'OK', + }, + }, + metadata: { + title: 'メタデータ', + desc: 'ドキュメントのメタデータにラベルを付けることで、AIがタイムリーにアクセスできるようになり、ユーザーに参照元が公開されます。', + dateTimeFormat: 'YYYY年M月D日 hh:mm A', + docTypeSelectTitle: 'ドキュメントタイプを選択してください', + docTypeChangeTitle: 'ドキュメントタイプを変更', + docTypeSelectWarning: + 'ドキュメントタイプを変更すると、現在入力されているメタデータは保持されなくなります', + firstMetaAction: '始めましょう', + placeholder: { + add: '追加', + select: '選択', + }, + source: { + upload_file: 'ファイルをアップロード', + notion: 'Notionから同期', + github: 'GitHubから同期', + }, + type: { + book: '書籍', + webPage: 'ウェブページ', + paper: '論文', + socialMediaPost: 'ソーシャルメディアの投稿', + personalDocument: '個人のドキュメント', + businessDocument: 'ビジネスドキュメント', + IMChat: 'IMチャット', + wikipediaEntry: 'Wikipediaのエントリー', + notion: 'Notionから同期', + github: 'GitHubから同期', + technicalParameters: '技術的なパラメータ', + }, + field: { + processRule: { + processDoc: 'ドキュメントの処理', + segmentRule: 'チャンクのルール', + segmentLength: 'チャンクの長さ', + processClean: 'テキストの前処理', + }, + book: { + title: 'タイトル', + language: '言語', + author: '著者', + publisher: '出版社', + publicationDate: '出版日', + ISBN: 'ISBN', + category: 'カテゴリ', + }, + webPage: { + title: 'タイトル', + url: 'URL', + language: '言語', + authorPublisher: '著者/出版社', + publishDate: '公開日', + topicsKeywords: 'トピック/キーワード', + description: '説明', + }, + paper: { + title: 'タイトル', + language: '言語', + author: '著者', + publishDate: '公開日', + journalConferenceName: 'ジャーナル/会議名', + volumeIssuePage: '巻号ページ', + DOI: 'DOI', + topicsKeywords: 'トピック/キーワード', + abstract: '要約', + }, + socialMediaPost: { + platform: 'プラットフォーム', + authorUsername: '著者/ユーザー名', + publishDate: '公開日', + postURL: '投稿URL', + topicsTags: 'トピック/タグ', + }, + personalDocument: { + title: 'タイトル', + author: '著者', + creationDate: '作成日', + lastModifiedDate: '最終更新日', + documentType: 'ドキュメントタイプ', + tagsCategory: 'タグ/カテゴリ', + }, + businessDocument: { + title: 'タイトル', + author: '著者', + creationDate: '作成日', + lastModifiedDate: '最終更新日', + documentType: 'ドキュメントタイプ', + departmentTeam: '部署/チーム', + }, + IMChat: { + chatPlatform: 'チャットプラットフォーム', + chatPartiesGroupName: 'チャット参加者/グループ名', + participants: '参加者', + startDate: '開始日', + endDate: '終了日', + topicsKeywords: 'トピック/キーワード', + fileType: 'ファイルタイプ', + }, + wikipediaEntry: { + title: 'タイトル', + language: '言語', + webpageURL: 'ウェブページURL', + editorContributor: '編集者/寄稿者', + lastEditDate: '最終編集日', + summaryIntroduction: '概要/紹介', + }, + notion: { + title: 'タイトル', + language: '言語', + author: '著者', + createdTime: '作成日時', + lastModifiedTime: '最終更新日時', + url: 'URL', + tag: 'タグ', + description: '説明', + }, + github: { + repoName: 'リポジトリ名', + repoDesc: 'リポジトリの説明', + repoOwner: 'リポジトリの所有者', + fileName: 'ファイル名', + filePath: 'ファイルパス', + programmingLang: 'プログラミング言語', + url: 'URL', + license: 'ライセンス', + lastCommitTime: '最終コミット時刻', + lastCommitAuthor: '最終コミットの著者', + }, + originInfo: { + originalFilename: '元のファイル名', + originalFileSize: '元のファイルサイズ', + uploadDate: 'アップロード日', + lastUpdateDate: '最終更新日', + source: 'ソース', + }, + technicalParameters: { + segmentSpecification: 'チャンクの仕様', + segmentLength: 'チャンクの長さ', + avgParagraphLength: '平均段落長', + paragraphs: '段落', + hitCount: '検索回数', + embeddingTime: '埋め込み時間', + embeddedSpend: '埋め込み時間', + }, + }, + languageMap: { + zh: '中国語', + en: '英語', + es: 'スペイン語', + fr: 'フランス語', + de: 'ドイツ語', + ja: '日本語', + ko: '韓国語', + ru: 'ロシア語', + ar: 'アラビア語', + pt: 'ポルトガル語', + it: 'イタリア語', + nl: 'オランダ語', + pl: 'ポーランド語', + sv: 'スウェーデン語', + tr: 'トルコ語', + he: 'ヘブライ語', + hi: 'ヒンディー語', + da: 'デンマーク語', + fi: 'フィンランド語', + no: 'ノルウェー語', + hu: 'ハンガリー語', + el: 'ギリシャ語', + cs: 'チェコ語', + th: 'タイ語', + id: 'インドネシア語', + }, + categoryMap: { + book: { + fiction: 'フィクション', + biography: '伝記', + history: '歴史', + science: '科学', + technology: 'テクノロジー', + education: '教育', + philosophy: '哲学', + religion: '宗教', + socialSciences: '社会科学', + art: 'アート', + travel: '旅行', + health: '健康', + selfHelp: '自己啓発', + businessEconomics: 'ビジネス・経済', + cooking: '料理', + childrenYoungAdults: '子供・若者向け', + comicsGraphicNovels: 'コミック・グラフィックノベル', + poetry: '詩', + drama: 'ドラマ', + other: 'その他', + }, + personalDoc: { + notes: 'メモ', + blogDraft: 'ブログの下書き', + diary: '日記', + researchReport: '研究レポート', + bookExcerpt: '書籍の抜粋', + schedule: 'スケジュール', + list: 'リスト', + projectOverview: 'プロジェクトの概要', + photoCollection: '写真コレクション', + creativeWriting: '創作', + codeSnippet: 'コードスニペット', + designDraft: 'デザインの下書き', + personalResume: '履歴書', + other: 'その他', + }, + businessDoc: { + meetingMinutes: '会議議事録', + researchReport: '研究レポート', + proposal: '提案', + employeeHandbook: '従業員ハンドブック', + trainingMaterials: '研修資料', + requirementsDocument: '要件定義書', + designDocument: '設計書', + productSpecification: '製品仕様書', + financialReport: '財務報告書', + marketAnalysis: '市場分析', + projectPlan: 'プロジェクト計画', + teamStructure: 'チーム構成', + policiesProcedures: 'ポリシーと手順', + contractsAgreements: '契約と合意', + emailCorrespondence: 'メールのやり取り', + other: 'その他', + }, + }, + }, + embedding: { + processing: '埋め込み処理中...', + paused: '埋め込みが一時停止中', + completed: '埋め込みが完了しました', + error: '埋め込みエラー', + docName: 'ドキュメントの前処理', + mode: 'セグメンテーションルール', + segmentLength: 'チャンクの長さ', + textCleaning: 'テキストの前処理', + segments: '段落', + highQuality: '高品質モード', + economy: '経済モード', + estimate: '推定消費量', + stop: '処理を停止', + resume: '処理を再開', + automatic: '自動', + custom: 'カスタム', + previewTip: '埋め込みが完了した後、段落のプレビューが利用可能になります', + }, + segment: { + paragraphs: '段落', + keywords: 'キーワード', + addKeyWord: 'キーワードを追加', + keywordError: 'キーワードの最大長は20です', + characters: '文字', + hitCount: '検索回数', + vectorHash: 'ベクトルハッシュ: ', + questionPlaceholder: 'ここに質問を追加', + questionEmpty: '質問は空にできません', + answerPlaceholder: 'ここに回答を追加', + answerEmpty: '回答は空にできません', + contentPlaceholder: 'ここに内容を追加', + contentEmpty: '内容は空にできません', + newTextSegment: '新しいテキストセグメント', + newQaSegment: '新しいQ&Aセグメント', + delete: 'このチャンクを削除しますか?', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/dataset-hit-testing.ts b/web/i18n/ja-JP/dataset-hit-testing.ts new file mode 100644 index 0000000000..abfd65ddd7 --- /dev/null +++ b/web/i18n/ja-JP/dataset-hit-testing.ts @@ -0,0 +1,28 @@ +const translation = { + title: '検索テスト', + desc: '与えられたクエリテキストに基づいたナレッジのヒット効果をテストします。', + dateTimeFormat: 'MM/DD/YYYY hh:mm A', + recents: '最近の結果', + table: { + header: { + source: 'ソース', + text: 'テキスト', + time: '時間', + }, + }, + input: { + title: 'ソーステキスト', + placeholder: 'テキストを入力してください。短い記述文がおすすめです。', + countWarning: '最大200文字まで入力できます。', + indexWarning: '高品質のナレッジのみ。', + testing: 'テスト中', + }, + hit: { + title: '検索結果パラグラフ', + emptyTip: '検索テストの結果がここに表示されます。', + }, + noRecentTip: '最近のクエリ結果はありません。', + viewChart: 'ベクトルチャートを表示', +} + +export default translation diff --git a/web/i18n/ja-JP/dataset-settings.ts b/web/i18n/ja-JP/dataset-settings.ts new file mode 100644 index 0000000000..3ccf907e4d --- /dev/null +++ b/web/i18n/ja-JP/dataset-settings.ts @@ -0,0 +1,33 @@ +const translation = { + title: 'ナレッジの設定', + desc: 'ここではナレッジのプロパティと動作方法を変更できます。', + form: { + name: 'ナレッジ名', + namePlaceholder: 'ナレッジ名を入力してください', + nameError: '名前は空にできません', + desc: 'ナレッジの説明', + descInfo: 'ナレッジの内容を概説するための明確なテキストの説明を書いてください。この説明は、複数のナレッジから推論を選択する際の基準として使用されます。', + descPlaceholder: 'このナレッジに含まれる内容を説明してください。詳細な説明は、AIがナレッジの内容にタイムリーにアクセスできるようにします。空の場合、Difyはデフォルトのヒット戦略を使用します。', + descWrite: '良いナレッジの説明の書き方を学ぶ。', + permissions: '権限', + permissionsOnlyMe: '自分のみ', + permissionsAllMember: 'すべてのチームメンバー', + indexMethod: 'インデックス方法', + indexMethodHighQuality: '高品質', + indexMethodHighQualityTip: 'ユーザーのクエリ時により高い精度を提供するために、OpenAIの埋め込みインターフェースを呼び出して処理します。', + indexMethodEconomy: '経済的', + indexMethodEconomyTip: 'オフラインのベクトルエンジン、キーワードインデックスなどを使用して精度を低下させることなく、トークンを消費せずに処理します。', + embeddingModel: '埋め込みモデル', + embeddingModelTip: '埋め込みモデルを変更するには、', + embeddingModelTipLink: '設定', + retrievalSetting: { + title: '検索設定', + learnMore: '詳細を学ぶ', + description: ' 検索方法についての詳細', + longDescription: ' 検索方法についての詳細については、いつでもナレッジの設定で変更できます。', + }, + save: '保存', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/dataset.ts b/web/i18n/ja-JP/dataset.ts new file mode 100644 index 0000000000..1f3aea5033 --- /dev/null +++ b/web/i18n/ja-JP/dataset.ts @@ -0,0 +1,47 @@ +const translation = { + knowledge: '知識', + documentCount: ' ドキュメント', + wordCount: 'k 単語', + appCount: ' リンクされたアプリ', + createDataset: '知識を作成', + createDatasetIntro: '独自のテキストデータをインポートするか、LLMコンテキストの強化のためにWebhookを介してリアルタイムでデータを書き込むことができます。', + deleteDatasetConfirmTitle: 'この知識を削除しますか?', + deleteDatasetConfirmContent: + '知識を削除すると元に戻すことはできません。ユーザーはもはやあなたの知識にアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。', + datasetDeleted: '知識が削除されました', + datasetDeleteFailed: '知識の削除に失敗しました', + didYouKnow: 'ご存知ですか?', + intro1: '知識はDifyアプリケーションに統合することができます', + intro2: 'コンテキストとして', + intro3: '、', + intro4: 'または', + intro5: '作成することができます', + intro6: '単体のChatGPTインデックスプラグインとして公開するために', + unavailable: '利用不可', + unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります', + datasets: '知識', + datasetsApi: 'API', + retrieval: { + semantic_search: { + title: 'ベクトル検索', + description: 'クエリの埋め込みを生成し、そのベクトル表現に最も類似したテキストチャンクを検索します。', + }, + full_text_search: { + title: '全文検索', + description: 'ドキュメント内のすべての用語をインデックス化し、ユーザーが任意の用語を検索してそれに関連するテキストチャンクを取得できるようにします。', + }, + hybrid_search: { + title: 'ハイブリッド検索', + description: '全文検索とベクトル検索を同時に実行し、ユーザーのクエリに最適なマッチを選択するために再ランク付けを行います。再ランクモデルAPIの設定が必要です。', + recommend: 'おすすめ', + }, + invertedIndex: { + title: '逆インデックス', + description: '効率的な検索に使用される構造です。各用語が含まれるドキュメントまたはWebページを指すように、用語ごとに整理されています。', + }, + change: '変更', + changeRetrievalMethod: '検索方法の変更', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/explore.ts b/web/i18n/ja-JP/explore.ts new file mode 100644 index 0000000000..b893fe89dc --- /dev/null +++ b/web/i18n/ja-JP/explore.ts @@ -0,0 +1,41 @@ +const translation = { + title: '探索する', + sidebar: { + discovery: '探索', + chat: 'チャット', + workspace: 'ワークスペース', + action: { + pin: 'ピン留め', + unpin: 'ピン留め解除', + rename: '名前変更', + delete: '削除', + }, + delete: { + title: 'アプリを削除', + content: 'このアプリを削除してもよろしいですか?', + }, + }, + apps: { + title: 'Difyによるアプリの探索', + description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。', + allCategories: 'すべてのカテゴリ', + }, + appCard: { + addToWorkspace: 'ワークスペースに追加', + customize: 'カスタマイズ', + }, + appCustomize: { + title: '{{name}}からアプリを作成', + subTitle: 'アプリアイコンと名前', + nameRequired: 'アプリ名は必須です', + }, + category: { + Assistant: 'アシスタント', + Writing: '執筆', + Translate: '翻訳', + Programming: 'プログラミング', + HR: '人事', + }, +} + +export default translation diff --git a/web/i18n/ja-JP/layout.ts b/web/i18n/ja-JP/layout.ts new file mode 100644 index 0000000000..928649474b --- /dev/null +++ b/web/i18n/ja-JP/layout.ts @@ -0,0 +1,4 @@ +const translation = { +} + +export default translation diff --git a/web/i18n/ja-JP/login.ts b/web/i18n/ja-JP/login.ts new file mode 100644 index 0000000000..2ab862b8b6 --- /dev/null +++ b/web/i18n/ja-JP/login.ts @@ -0,0 +1,59 @@ +const translation = { + pageTitle: 'はじめましょう!👋', + welcome: 'Difyへようこそ。続行するにはログインしてください。', + email: 'メールアドレス', + emailPlaceholder: 'メールアドレスを入力してください', + password: 'パスワード', + passwordPlaceholder: 'パスワードを入力してください', + name: 'ユーザー名', + namePlaceholder: 'ユーザー名を入力してください', + forget: 'パスワードをお忘れですか?', + signBtn: 'サインイン', + installBtn: 'セットアップ', + setAdminAccount: '管理者アカウントの設定', + setAdminAccountDesc: 'アプリケーションの作成やLLMプロバイダの管理など、管理者アカウントの最大権限を設定します。', + createAndSignIn: '作成してサインイン', + oneMoreStep: 'あと一歩', + createSample: 'この情報を基に、サンプルアプリケーションを作成します', + invitationCode: '招待コード', + invitationCodePlaceholder: '招待コードを入力してください', + interfaceLanguage: 'インターフェース言語', + timezone: 'タイムゾーン', + go: 'Difyへ移動', + sendUsMail: '自己紹介をメールで送信し、招待リクエストを処理します。', + acceptPP: 'プライバシーポリシーを読み、同意します', + reset: 'パスワードをリセットするには、次のコマンドを実行してください', + withGitHub: 'GitHubで続行', + withGoogle: 'Googleで続行', + rightTitle: 'LLMのフルポテンシャルを解き放つ', + rightDesc: '魅力的で操作可能で改善可能なAIアプリケーションを簡単に構築します。', + tos: '利用規約', + pp: 'プライバシーポリシー', + tosDesc: 'サインアップすることで、以下に同意するものとします', + donthave: 'お持ちでない場合', + invalidInvitationCode: '無効な招待コード', + accountAlreadyInited: 'アカウントは既に初期化されています', + error: { + emailEmpty: 'メールアドレスは必須です', + emailInValid: '有効なメールアドレスを入力してください', + nameEmpty: '名前は必須です', + passwordEmpty: 'パスワードは必須です', + passwordInvalid: 'パスワードは文字と数字を含み、長さは8以上である必要があります', + }, + license: { + tip: 'Dify Community Editionを開始する前に、GitHubの', + link: 'オープンソースライセンス', + }, + join: '参加する', + joinTipStart: 'あなたを招待します', + joinTipEnd: 'チームに参加する', + invalid: 'リンクの有効期限が切れています', + explore: 'Difyを探索する', + activatedTipStart: 'あなたは', + activatedTipEnd: 'チームに参加しました', + activated: '今すぐサインイン', + adminInitPassword: '管理者初期化パスワード', + validate: '検証', +} + +export default translation diff --git a/web/i18n/ja-JP/register.ts b/web/i18n/ja-JP/register.ts new file mode 100644 index 0000000000..928649474b --- /dev/null +++ b/web/i18n/ja-JP/register.ts @@ -0,0 +1,4 @@ +const translation = { +} + +export default translation diff --git a/web/i18n/ja-JP/share-app.ts b/web/i18n/ja-JP/share-app.ts new file mode 100644 index 0000000000..c73d0bb3f6 --- /dev/null +++ b/web/i18n/ja-JP/share-app.ts @@ -0,0 +1,74 @@ +const translation = { + common: { + welcome: '利用していただきありがとうございます', + appUnavailable: 'アプリが利用できません', + appUnkonwError: 'アプリが利用できません', + }, + chat: { + newChat: '新しいチャット', + pinnedTitle: 'ピン留めされた', + unpinnedTitle: 'チャット', + newChatDefaultName: '新しい会話', + resetChat: '会話をリセット', + powerBy: 'Powered by', + prompt: 'プロンプト', + privatePromptConfigTitle: '会話の設定', + publicPromptConfigTitle: '初期プロンプト', + configStatusDes: '開始前に、会話の設定を変更できます', + configDisabled: + '前回のセッションの設定がこのセッションで使用されました。', + startChat: 'チャットを開始', + privacyPolicyLeft: + 'アプリ開発者が提供する', + privacyPolicyMiddle: + 'プライバシーポリシー', + privacyPolicyRight: + 'をお読みください。', + deleteConversation: { + title: '会話を削除する', + content: 'この会話を削除してもよろしいですか?', + }, + tryToSolve: '解決しようとしています', + temporarySystemIssue: '申し訳ありません、一時的なシステムの問題が発生しました。', + }, + generation: { + tabs: { + create: '一度だけ実行', + batch: '一括実行', + saved: '保存済み', + }, + savedNoData: { + title: 'まだ結果が保存されていません!', + description: 'コンテンツの生成を開始し、保存された結果をこちらで見つけてください。', + startCreateContent: 'コンテンツの作成を開始', + }, + title: 'AI Completion', + queryTitle: 'コンテンツのクエリ', + completionResult: 'Completion 結果', + queryPlaceholder: 'クエリコンテンツを書いてください...', + run: '実行', + copy: 'コピー', + resultTitle: 'AI Completion', + noData: 'AIはここで必要なものを提供します。', + csvUploadTitle: 'CSVファイルをここにドラッグアンドドロップするか、', + browse: '参照', + csvStructureTitle: 'CSVファイルは以下の構造に準拠する必要があります:', + downloadTemplate: 'こちらからテンプレートをダウンロード', + field: 'フィールド', + batchFailed: { + info: '{{num}} 回の実行が失敗しました', + retry: '再試行', + outputPlaceholder: '出力コンテンツなし', + }, + errorMsg: { + empty: 'アップロードされたファイルにコンテンツを入力してください。', + fileStructNotMatch: 'アップロードされたCSVファイルが構造と一致しません。', + emptyLine: '行 {{rowIndex}} が空です', + invalidLine: '行 {{rowIndex}}: {{varName}} の値は空にできません', + moreThanMaxLengthLine: '行 {{rowIndex}}: {{varName}} の値は {{maxLength}} 文字を超えることはできません', + atLeastOne: 'アップロードされたファイルには少なくとも1行の入力が必要です。', + }, + }, +} + +export default translation diff --git a/web/i18n/ja-JP/tools.ts b/web/i18n/ja-JP/tools.ts new file mode 100644 index 0000000000..3006e03ae5 --- /dev/null +++ b/web/i18n/ja-JP/tools.ts @@ -0,0 +1,115 @@ +const translation = { + title: 'ツール', + createCustomTool: 'カスタムツールを作成する', + type: { + all: 'すべて', + builtIn: '組み込み', + custom: 'カスタム', + }, + contribute: { + line1: '私は', + line2: 'Difyへのツールの貢献に興味があります。', + viewGuide: 'ガイドを見る', + }, + author: '著者', + auth: { + unauthorized: '認証する', + authorized: '認証済み', + setup: '使用するための認証を設定する', + setupModalTitle: '認証の設定', + setupModalTitleDescription: '資格情報を構成した後、ワークスペース内のすべてのメンバーがアプリケーションのオーケストレーション時にこのツールを使用できます。', + }, + includeToolNum: '{{num}}個のツールが含まれています', + addTool: 'ツールを追加する', + createTool: { + title: 'カスタムツールを作成する', + editAction: '設定', + editTitle: 'カスタムツールを編集する', + name: '名前', + toolNamePlaceHolder: 'ツール名を入力してください', + schema: 'スキーマ', + schemaPlaceHolder: 'ここにOpenAPIスキーマを入力してください', + viewSchemaSpec: 'OpenAPI-Swagger仕様を表示する', + importFromUrl: 'URLからインポートする', + importFromUrlPlaceHolder: 'https://...', + urlError: '有効なURLを入力してください', + examples: '例', + exampleOptions: { + json: '天気(JSON)', + yaml: 'ペットストア(YAML)', + blankTemplate: '空白テンプレート', + }, + availableTools: { + title: '利用可能なツール', + name: '名前', + description: '説明', + method: 'メソッド', + path: 'パス', + action: 'アクション', + test: 'テスト', + }, + authMethod: { + title: '認証方法', + type: '認証タイプ', + keyTooltip: 'HTTPヘッダーキー。アイデアがない場合は "Authorization" として残しておいてもかまいません。またはカスタム値に設定できます。', + types: { + none: 'なし', + api_key: 'APIキー', + apiKeyPlaceholder: 'APIキーのHTTPヘッダー名', + apiValuePlaceholder: 'APIキーを入力してください', + }, + key: 'キー', + value: '値', + }, + authHeaderPrefix: { + title: '認証タイプ', + types: { + basic: 'ベーシック', + bearer: 'ベアラー', + custom: 'カスタム', + }, + }, + privacyPolicy: 'プライバシーポリシー', + privacyPolicyPlaceholder: 'プライバシーポリシーを入力してください', + }, + test: { + title: 'テスト', + parametersValue: 'パラメーター&値', + parameters: 'パラメーター', + value: '値', + testResult: 'テスト結果', + testResultPlaceholder: 'ここにテスト結果が表示されます', + }, + thought: { + using: '使用中', + used: '使用済み', + requestTitle: 'リクエスト先', + responseTitle: 'レスポンス先', + }, + setBuiltInTools: { + info: '情報', + setting: '設定', + toolDescription: 'ツールの説明', + parameters: 'パラメーター', + string: '文字列', + number: '数', + required: '必須', + infoAndSetting: '情報と設定', + }, + noCustomTool: { + title: 'カスタムツールがありません!', + content: 'AIアプリを構築するためのカスタムツールをここで追加および管理します。', + createTool: 'ツールを作成する', + }, + noSearchRes: { + title: '申し訳ありません、結果がありません!', + content: '検索に一致するツールが見つかりませんでした。', + reset: '検索をリセット', + }, + builtInPromptTitle: 'プロンプト', + toolRemoved: 'ツールが削除されました', + notAuthorized: 'ツールが認可されていません', + howToGet: '取得方法', +} + +export default translation diff --git a/web/i18n/language.ts b/web/i18n/language.ts index 0123a7aa47..9b7f5a27f0 100644 --- a/web/i18n/language.ts +++ b/web/i18n/language.ts @@ -59,7 +59,7 @@ export const languages = [ value: 'ja-JP', name: '日本語(日本)', example: 'こんにちは、Dify!', - supported: false, + supported: true, }, { value: 'ko-KR', diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index 8c051d57d8..30f2a95676 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -32,6 +32,7 @@ const translation = { vectorSpace: '向量空间', vectorSpaceTooltip: '向量空间是 LLMs 理解您的数据所需的长期记忆系统。', vectorSpaceBillingTooltip: '向量存储是将知识库向量化处理后为让 LLMs 理解数据而使用的长期记忆存储,1MB 大约能满足1.2 million character 的向量化后数据存储(以 OpenAI Embedding 模型估算,不同模型计算方式有差异)。在向量化过程中,实际的压缩或尺寸减小取决于内容的复杂性和冗余性。', + documentsUploadQuota: '文档上传配额', documentProcessingPriority: '文档处理优先级', documentProcessingPriorityTip: '如需更高的文档处理优先级,请升级您的套餐', documentProcessingPriorityUpgrade: '以更快的速度、更高的精度处理更多的数据。', @@ -56,6 +57,7 @@ const translation = { dedicatedAPISupport: '专用 API 支持', customIntegration: '自定义集成和支持', ragAPIRequest: 'RAG API 请求', + bulkUpload: '批量上传文档', agentMode: '代理模式', workflow: '工作流', }, diff --git a/web/service/base.ts b/web/service/base.ts index fe23649929..25cc6570b8 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -308,7 +308,7 @@ const baseFetch = ( ]) as Promise } -export const upload = (options: any, isPublicAPI?: boolean, url?: string): Promise => { +export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => { const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX let token = '' if (isPublicAPI) { @@ -329,7 +329,7 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string): Promi } const defaultOptions = { method: 'POST', - url: url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`, + url: (url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) + (searchParams || ''), headers: { Authorization: `Bearer ${token}`, }, diff --git a/web/test/mocks/categories.ts b/web/test/mocks/categories.ts new file mode 100644 index 0000000000..e69cc69072 --- /dev/null +++ b/web/test/mocks/categories.ts @@ -0,0 +1,322 @@ +// TODO: maybe use faker.js to randomize the data +export const mockApps = { + recommended_apps: [ + { + app: { + id: 'b82da4c0-2887-48cc-a7d6-7edc0bdd6002', + name: 'AI 前端面试官', + mode: 'chat', + icon: '🤖', + icon_background: null, + }, + app_id: 'b82da4c0-2887-48cc-a7d6-7edc0bdd6002', + description: + '一个模拟的前端面试官,通过提问的方式对前端开发的技能水平进行检验。', + copyright: null, + privacy_policy: null, + category: 'HR', + position: 20, + is_listed: true, + install_count: 0, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: '1fa25f89-2883-41ac-877e-c372274020a4', + name: '扁平风插画生成', + mode: 'chat', + icon: '🖼️', + icon_background: '#D5F5F6', + }, + app_id: '1fa25f89-2883-41ac-877e-c372274020a4', + description: '输入相关元素,为你生成扁平插画风格的封面图片', + copyright: null, + privacy_policy: null, + category: '绘画', + position: 10, + is_listed: true, + install_count: 0, + installed: false, + editable: true, + is_agent: true, + }, + { + app: { + id: '94b509ad-4225-4924-8b50-5c25c2bd7e3c', + name: '文章翻译助理 ', + mode: 'completion', + icon: '🤖', + icon_background: null, + }, + app_id: '94b509ad-4225-4924-8b50-5c25c2bd7e3c', + description: + '一个多语言翻译器,提供多种语言翻译能力,输入你需要翻译的文本,选择目标语言即可。提示词来自宝玉。', + copyright: null, + privacy_policy: null, + category: 'Assistant', + position: 10, + is_listed: true, + install_count: 0, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: 'c8003ab3-9bb7-4693-9249-e603d48e58a6', + name: 'SQL 生成器', + mode: 'completion', + icon: '🤖', + icon_background: null, + }, + app_id: 'c8003ab3-9bb7-4693-9249-e603d48e58a6', + description: + '我将帮助你把自然语言转化成指定的数据库查询 SQL 语句,请在下方输入你需要查询的条件,并选择目标数据库类型。', + copyright: null, + privacy_policy: null, + category: 'Programming', + position: 12, + is_listed: true, + install_count: 3142, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: 'dad6a1e0-0fe9-47e1-91a9-e16de48f1276', + name: '代码解释器', + mode: 'chat', + icon: 'eye-in-speech-bubble', + icon_background: '#FFEAD5', + }, + app_id: 'dad6a1e0-0fe9-47e1-91a9-e16de48f1276', + description: '阐明代码的语法和语义。', + copyright: 'Copyright 2023 Dify', + privacy_policy: 'https://dify.ai', + category: 'Programming', + position: 2, + is_listed: true, + install_count: 2344, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: 'fae3e7ac-8ccc-4d43-8986-7c61d2bdde4f', + name: '赛博朋克插画生成', + mode: 'chat', + icon: '🖼️', + icon_background: '#FFEAD5', + }, + app_id: 'fae3e7ac-8ccc-4d43-8986-7c61d2bdde4f', + description: '输入相关元素,为你生成赛博朋克风格的插画', + copyright: null, + privacy_policy: null, + category: '绘画', + position: 10, + is_listed: true, + install_count: 0, + installed: false, + editable: true, + is_agent: true, + }, + { + app: { + id: '4e57bc83-ab95-4f8a-a955-70796b4804a0', + name: 'SEO 文章生成专家', + mode: 'completion', + icon: '🤖', + icon_background: '#FFEAD5', + }, + app_id: '4e57bc83-ab95-4f8a-a955-70796b4804a0', + description: + '我是一名SEO专家,可以根据您提供的标题、关键词、相关信息来批量生成SEO文章。', + copyright: null, + privacy_policy: null, + category: 'Assistant', + position: 10, + is_listed: true, + install_count: 0, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: '6786ce62-fa85-4ea7-a4d1-5dbe3e3ff59f', + name: '会议纪要', + mode: 'chat', + icon: 'clipboard', + icon_background: '#D1E0FF', + }, + app_id: '6786ce62-fa85-4ea7-a4d1-5dbe3e3ff59f', + description: '帮你重新组织和输出混乱复杂的会议纪要。', + copyright: 'Copyright 2023 Dify', + privacy_policy: 'https://dify.ai', + category: 'Writing', + position: 6, + is_listed: true, + install_count: 1542, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: '73dd96bb-49b7-4791-acbd-9ef2ef506900', + name: '美股投资分析助手', + mode: 'chat', + icon: '🤑', + icon_background: '#E4FBCC', + }, + app_id: '73dd96bb-49b7-4791-acbd-9ef2ef506900', + description: + '欢迎使用您的个性化美股投资分析助手,在这里我们深入的进行股票分析,为您提供全面的洞察。', + copyright: 'Dify.AI', + privacy_policy: null, + category: '智能助理', + position: 0, + is_listed: true, + install_count: 2, + installed: false, + editable: true, + is_agent: true, + }, + { + app: { + id: '93ca3c2c-3a47-4658-b230-d5a6cc61ff01', + name: 'SVG Logo 设计', + mode: 'chat', + icon: '🎨', + icon_background: '#E4FBCC', + }, + app_id: '93ca3c2c-3a47-4658-b230-d5a6cc61ff01', + description: + '您好,我是您的创意伙伴,将帮助您将想法生动地实现!我可以协助您利用DALL·E 3的能力创造出令人惊叹的设计。', + copyright: 'Dify.AI', + privacy_policy: null, + category: '智能助理', + position: 4, + is_listed: true, + install_count: 6, + installed: false, + editable: true, + is_agent: true, + }, + { + app: { + id: '59924f26-963f-4b4b-90cf-978bbfcddc49', + name: '中英文互译', + mode: 'chat', + icon: 'speaking_head_in_silhouette', + icon_background: '#FBE8FF', + }, + app_id: '59924f26-963f-4b4b-90cf-978bbfcddc49', + description: '翻译专家:提供中英文互译', + copyright: 'Copyright 2023 Dify', + privacy_policy: 'https://dify.ai', + category: 'Translate', + position: 4, + is_listed: true, + install_count: 1662, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: '89ad1e65-6711-4c80-b469-a71a434e2dbd', + name: '个人学习导师', + mode: 'chat', + icon: '🤖', + icon_background: '#FFEAD5', + }, + app_id: '89ad1e65-6711-4c80-b469-a71a434e2dbd', + description: '您的私人学习导师,帮您制定学习计划并辅导', + copyright: 'Copyright 2023 Dify', + privacy_policy: 'https://dify.ai', + category: 'Assistant', + position: 26, + is_listed: true, + install_count: 1441, + installed: true, + editable: true, + is_agent: false, + }, + { + app: { + id: 'ff551444-a3ff-4fd8-b297-f38581c98b4a', + name: '文献综述写作', + mode: 'completion', + icon: 'female-student', + icon_background: '#FBE8FF', + }, + app_id: 'ff551444-a3ff-4fd8-b297-f38581c98b4a', + description: '帮你撰写论文文献综述', + copyright: 'Copyright 2023 Dify', + privacy_policy: 'https://dify.ai', + category: 'Writing', + position: 7, + is_listed: true, + install_count: 1651, + installed: false, + editable: true, + is_agent: false, + }, + { + app: { + id: '79227a52-11f1-4cf9-8c49-0bd86f9be813', + name: 'Youtube 频道数据分析', + mode: 'chat', + icon: '🔢', + icon_background: '#E4FBCC', + }, + app_id: '79227a52-11f1-4cf9-8c49-0bd86f9be813', + description: + '你好,告诉我您想分析的 YouTube 频道,我将为您整理一份完整的数据分析报告。', + copyright: null, + privacy_policy: null, + category: '智能助理', + position: 0, + is_listed: true, + install_count: 2, + installed: false, + editable: true, + is_agent: true, + }, + { + app: { + id: '609f4a7f-36f7-4791-96a7-4ccbe6f8dfbb', + name: '旅行规划助手', + mode: 'chat', + icon: '✈️', + icon_background: '#E4FBCC', + }, + app_id: '609f4a7f-36f7-4791-96a7-4ccbe6f8dfbb', + description: + '欢迎使用您的个性化旅行服务顾问!🌍✈️ 准备好踏上一段充满冒险与放松的旅程了吗?让我们一起深入打造您难忘的旅行体验吧。', + copyright: null, + privacy_policy: null, + category: '智能助理', + position: 0, + is_listed: true, + install_count: 3, + installed: false, + editable: true, + is_agent: true, + }, + ], + categories: [ + '绘画', + 'HR', + 'Programming', + 'Translate', + 'Assistant', + 'Writing', + '智能助理', + ], +}