From c67f626b66fa6ebfbc9871b5b0911a34fecfcd3a Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Thu, 1 Jun 2023 23:19:36 +0800 Subject: [PATCH] Feat: Support re-segmentation (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: John Wang Co-authored-by: Jyong <718720800@qq.com> Co-authored-by: 金伟强 --- .../console/datasets/datasets_document.py | 11 +- api/services/dataset_service.py | 173 +++++++++++--- api/tasks/clean_document_task.py | 3 +- api/tasks/document_indexing_update_task.py | 85 +++++++ api/tasks/remove_document_from_index_task.py | 3 +- web/app/(commonLayout)/apps/AppCard.tsx | 2 +- web/app/(commonLayout)/apps/Apps.tsx | 11 +- .../documents/[documentId]/settings/page.tsx | 16 ++ .../[datasetId]/layout.tsx | 5 +- .../(commonLayout)/datasets/DatasetCard.tsx | 13 +- web/app/(commonLayout)/datasets/Datasets.tsx | 16 +- web/app/(commonLayout)/explore/apps/page.tsx | 4 +- .../explore/installed/[appId]/page.tsx | 7 +- web/app/(shareLayout)/chat/[token]/page.tsx | 1 - .../app/configuration/config-model/index.tsx | 22 +- .../dataset-config/select-dataset/index.tsx | 29 ++- .../chat-group/opening-statement/index.tsx | 72 +++--- .../prompt-value-panel/index.tsx | 90 ++++---- .../app/text-generate/item/index.tsx | 218 +++++++++--------- web/app/components/base/app-icon/index.tsx | 4 +- web/app/components/base/block-input/index.tsx | 65 +++--- .../components/base/emoji-picker/index.tsx | 43 ++-- .../datasets/create/step-two/index.tsx | 96 ++++++-- .../documents/detail/embedding/index.tsx | 51 +++- .../documents/detail/settings/index.tsx | 90 ++++++++ .../components/datasets/documents/list.tsx | 23 +- .../datasets/documents/style.module.css | 2 +- .../datasets/settings/form/index.tsx | 28 +-- .../develop/secret-key/input-copy.tsx | 2 +- web/app/components/explore/app-list/index.tsx | 48 ++-- web/app/components/explore/category.tsx | 29 +-- .../explore/create-app-modal/index.tsx | 81 ++++--- web/app/components/explore/index.tsx | 18 +- .../explore/installed-app/index.tsx | 25 +- .../explore/item-operation/index.tsx | 21 +- .../explore/sidebar/app-nav-item/index.tsx | 11 +- .../provider-page/azure-provider/index.tsx | 62 +++-- .../account-setting/provider-page/index.tsx | 14 +- .../provider-page/openai-provider/index.tsx | 45 ++-- .../provider-page/provider-input/Validate.tsx | 14 +- .../provider-page/provider-input/index.tsx | 17 +- .../provider-input/useValidateToken.ts | 33 +-- .../provider-page/provider-item/index.tsx | 55 +++-- web/app/components/header/index.tsx | 24 +- .../share/chat/sidebar/app-info/index.tsx | 9 +- .../components/share/chat/sidebar/index.tsx | 18 +- .../text-generation/config-scence/index.tsx | 40 ++-- web/config/index.ts | 48 ++-- web/context/dataset-detail.ts | 3 +- web/context/explore-context.ts | 2 +- web/i18n/lang/app.en.ts | 2 +- web/i18n/lang/app.zh.ts | 2 +- web/i18n/lang/dataset-creation.en.ts | 2 + web/i18n/lang/dataset-creation.zh.ts | 2 + web/i18n/lang/explore.en.ts | 14 +- web/i18n/lang/explore.zh.ts | 14 +- web/models/common.ts | 4 +- web/models/explore.ts | 40 ++-- web/service/explore.ts | 12 +- web/service/share.ts | 18 +- web/types/app.ts | 13 +- 61 files changed, 1166 insertions(+), 759 deletions(-) create mode 100644 api/tasks/document_indexing_update_task.py create mode 100644 web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx create mode 100644 web/app/components/datasets/documents/detail/settings/index.tsx diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 3b9efeaab4..6888f0ed30 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -208,9 +208,10 @@ class DatasetDocumentListApi(Resource): parser = reqparse.RequestParser() parser.add_argument('indexing_technique', type=str, choices=Dataset.INDEXING_TECHNIQUE_LIST, nullable=False, location='json') - parser.add_argument('data_source', type=dict, required=True, nullable=True, location='json') - parser.add_argument('process_rule', type=dict, required=True, nullable=True, location='json') + parser.add_argument('data_source', type=dict, required=False, location='json') + parser.add_argument('process_rule', type=dict, required=False, location='json') parser.add_argument('duplicate', type=bool, nullable=False, location='json') + parser.add_argument('original_document_id', type=str, required=False, location='json') args = parser.parse_args() if not dataset.indexing_technique and not args['indexing_technique']: @@ -347,10 +348,12 @@ class DocumentIndexingStatusApi(DocumentResource): completed_segments = DocumentSegment.query \ .filter(DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document_id)) \ + DocumentSegment.document_id == str(document_id), + DocumentSegment.status != 're_segment') \ .count() total_segments = DocumentSegment.query \ - .filter_by(document_id=str(document_id)) \ + .filter(DocumentSegment.document_id == str(document_id), + DocumentSegment.status != 're_segment') \ .count() document.completed_segments = completed_segments diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 36fe127cc5..9a03a63381 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -12,7 +12,7 @@ from events.dataset_event import dataset_was_deleted from events.document_event import document_was_deleted from extensions.ext_database import db from models.account import Account -from models.dataset import Dataset, Document, DatasetQuery, DatasetProcessRule, AppDatasetJoin +from models.dataset import Dataset, Document, DatasetQuery, DatasetProcessRule, AppDatasetJoin, DocumentSegment from models.model import UploadFile from services.errors.account import NoPermissionError from services.errors.dataset import DatasetNameDuplicateError @@ -20,6 +20,7 @@ from services.errors.document import DocumentIndexingError from services.errors.file import FileNotExistsError from tasks.deal_dataset_vector_index_task import deal_dataset_vector_index_task from tasks.document_indexing_task import document_indexing_task +from tasks.document_indexing_update_task import document_indexing_update_task class DatasetService: @@ -276,6 +277,14 @@ class DocumentService: return document + @staticmethod + def get_document_by_id(document_id: str) -> Optional[Document]: + document = db.session.query(Document).filter( + Document.id == document_id + ).first() + + return document + @staticmethod def get_document_file_detail(file_id: str): file_detail = db.session.query(UploadFile). \ @@ -355,8 +364,79 @@ class DocumentService: if dataset.indexing_technique == 'high_quality': IndexBuilder.get_default_service_context(dataset.tenant_id) + if 'original_document_id' in document_data and document_data["original_document_id"]: + document = DocumentService.update_document_with_dataset_id(dataset, document_data, account) + else: + # save process rule + if not dataset_process_rule: + process_rule = document_data["process_rule"] + if process_rule["mode"] == "custom": + dataset_process_rule = DatasetProcessRule( + dataset_id=dataset.id, + mode=process_rule["mode"], + rules=json.dumps(process_rule["rules"]), + created_by=account.id + ) + elif process_rule["mode"] == "automatic": + dataset_process_rule = DatasetProcessRule( + dataset_id=dataset.id, + mode=process_rule["mode"], + rules=json.dumps(DatasetProcessRule.AUTOMATIC_RULES), + created_by=account.id + ) + db.session.add(dataset_process_rule) + db.session.commit() + + file_name = '' + data_source_info = {} + if document_data["data_source"]["type"] == "upload_file": + file_id = document_data["data_source"]["info"] + file = db.session.query(UploadFile).filter( + UploadFile.tenant_id == dataset.tenant_id, + UploadFile.id == file_id + ).first() + + # raise error if file not found + if not file: + raise FileNotExistsError() + + file_name = file.name + data_source_info = { + "upload_file_id": file_id, + } + + # save document + position = DocumentService.get_documents_position(dataset.id) + document = Document( + tenant_id=dataset.tenant_id, + dataset_id=dataset.id, + position=position, + data_source_type=document_data["data_source"]["type"], + data_source_info=json.dumps(data_source_info), + dataset_process_rule_id=dataset_process_rule.id, + batch=time.strftime('%Y%m%d%H%M%S') + str(random.randint(100000, 999999)), + name=file_name, + created_from=created_from, + created_by=account.id, + # created_api_request_id = db.Column(UUID, nullable=True) + ) + + db.session.add(document) + db.session.commit() + + # trigger async task + document_indexing_task.delay(document.dataset_id, document.id) + return document + + @staticmethod + def update_document_with_dataset_id(dataset: Dataset, document_data: dict, + account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, + created_from: str = 'web'): + document = DocumentService.get_document(dataset.id, document_data["original_document_id"]) + if document.display_status != 'available': + raise ValueError("Document is not available") # save process rule - if not dataset_process_rule: + if 'process_rule' in document_data and document_data['process_rule']: process_rule = document_data["process_rule"] if process_rule["mode"] == "custom": dataset_process_rule = DatasetProcessRule( @@ -374,46 +454,48 @@ class DocumentService: ) db.session.add(dataset_process_rule) db.session.commit() + document.dataset_process_rule_id = dataset_process_rule.id + # update document data source + if 'data_source' in document_data and document_data['data_source']: + file_name = '' + data_source_info = {} + if document_data["data_source"]["type"] == "upload_file": + file_id = document_data["data_source"]["info"] + file = db.session.query(UploadFile).filter( + UploadFile.tenant_id == dataset.tenant_id, + UploadFile.id == file_id + ).first() - file_name = '' - data_source_info = {} - if document_data["data_source"]["type"] == "upload_file": - file_id = document_data["data_source"]["info"] - file = db.session.query(UploadFile).filter( - UploadFile.tenant_id == dataset.tenant_id, - UploadFile.id == file_id - ).first() - - # raise error if file not found - if not file: - raise FileNotExistsError() - - file_name = file.name - data_source_info = { - "upload_file_id": file_id, - } - - # save document - position = DocumentService.get_documents_position(dataset.id) - document = Document( - tenant_id=dataset.tenant_id, - dataset_id=dataset.id, - position=position, - data_source_type=document_data["data_source"]["type"], - data_source_info=json.dumps(data_source_info), - dataset_process_rule_id=dataset_process_rule.id, - batch=time.strftime('%Y%m%d%H%M%S') + str(random.randint(100000, 999999)), - name=file_name, - created_from=created_from, - created_by=account.id, - # created_api_request_id = db.Column(UUID, nullable=True) - ) + # raise error if file not found + if not file: + raise FileNotExistsError() + file_name = file.name + data_source_info = { + "upload_file_id": file_id, + } + document.data_source_type = document_data["data_source"]["type"] + document.data_source_info = json.dumps(data_source_info) + document.name = file_name + # update document to be waiting + document.indexing_status = 'waiting' + document.completed_at = None + document.processing_started_at = None + document.parsing_completed_at = None + document.cleaning_completed_at = None + document.splitting_completed_at = None + document.updated_at = datetime.datetime.utcnow() + document.created_from = created_from db.session.add(document) db.session.commit() - + # update document segment + update_params = { + DocumentSegment.status: 're_segment' + } + DocumentSegment.query.filter_by(document_id=document.id).update(update_params) + db.session.commit() # trigger async task - document_indexing_task.delay(document.dataset_id, document.id) + document_indexing_update_task.delay(document.dataset_id, document.id) return document @@ -443,6 +525,21 @@ class DocumentService: @classmethod def document_create_args_validate(cls, args: dict): + if 'original_document_id' not in args or not args['original_document_id']: + DocumentService.data_source_args_validate(args) + DocumentService.process_rule_args_validate(args) + else: + if ('data_source' not in args and not args['data_source'])\ + and ('process_rule' not in args and not args['process_rule']): + raise ValueError("Data source or Process rule is required") + else: + if 'data_source' in args and args['data_source']: + DocumentService.data_source_args_validate(args) + if 'process_rule' in args and args['process_rule']: + DocumentService.process_rule_args_validate(args) + + @classmethod + def data_source_args_validate(cls, args: dict): if 'data_source' not in args or not args['data_source']: raise ValueError("Data source is required") @@ -459,6 +556,8 @@ class DocumentService: if 'info' not in args['data_source'] or not args['data_source']['info']: raise ValueError("Data source info is required") + @classmethod + def process_rule_args_validate(cls, args: dict): if 'process_rule' not in args or not args['process_rule']: raise ValueError("Process rule is required") diff --git a/api/tasks/clean_document_task.py b/api/tasks/clean_document_task.py index 5ca7f2d5c2..63d66e4ea6 100644 --- a/api/tasks/clean_document_task.py +++ b/api/tasks/clean_document_task.py @@ -35,8 +35,7 @@ def clean_document_task(document_id: str, dataset_id: str): index_node_ids = [segment.index_node_id for segment in segments] # delete from vector index - if dataset.indexing_technique == "high_quality": - vector_index.del_nodes(index_node_ids) + vector_index.del_nodes(index_node_ids) # delete from keyword index if index_node_ids: diff --git a/api/tasks/document_indexing_update_task.py b/api/tasks/document_indexing_update_task.py new file mode 100644 index 0000000000..493c05505a --- /dev/null +++ b/api/tasks/document_indexing_update_task.py @@ -0,0 +1,85 @@ +import datetime +import logging +import time + +import click +from celery import shared_task +from werkzeug.exceptions import NotFound + +from core.index.keyword_table_index import KeywordTableIndex +from core.index.vector_index import VectorIndex +from core.indexing_runner import IndexingRunner, DocumentIsPausedException +from core.llm.error import ProviderTokenNotInitError +from extensions.ext_database import db +from models.dataset import Document, Dataset, DocumentSegment + + +@shared_task +def document_indexing_update_task(dataset_id: str, document_id: str): + """ + Async update document + :param dataset_id: + :param document_id: + + Usage: document_indexing_update_task.delay(dataset_id, document_id) + """ + logging.info(click.style('Start update document: {}'.format(document_id), fg='green')) + start_at = time.perf_counter() + + document = db.session.query(Document).filter( + Document.id == document_id, + Document.dataset_id == dataset_id + ).first() + + if not document: + raise NotFound('Document not found') + + document.indexing_status = 'parsing' + document.processing_started_at = datetime.datetime.utcnow() + db.session.commit() + + # delete all document segment and index + try: + dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first() + if not dataset: + raise Exception('Dataset not found') + + vector_index = VectorIndex(dataset=dataset) + keyword_table_index = KeywordTableIndex(dataset=dataset) + + segments = db.session.query(DocumentSegment).filter(DocumentSegment.document_id == document_id).all() + index_node_ids = [segment.index_node_id for segment in segments] + + # delete from vector index + vector_index.del_nodes(index_node_ids) + + # delete from keyword index + if index_node_ids: + keyword_table_index.del_nodes(index_node_ids) + + for segment in segments: + db.session.delete(segment) + + end_at = time.perf_counter() + logging.info( + click.style('Cleaned document when document update data source or process rule: {} latency: {}'.format(document_id, end_at - start_at), fg='green')) + except Exception: + logging.exception("Cleaned document when document update data source or process rule failed") + try: + indexing_runner = IndexingRunner() + indexing_runner.run(document) + end_at = time.perf_counter() + logging.info(click.style('update document: {} latency: {}'.format(document.id, end_at - start_at), fg='green')) + except DocumentIsPausedException: + logging.info(click.style('Document update paused, document id: {}'.format(document.id), fg='yellow')) + except ProviderTokenNotInitError as e: + document.indexing_status = 'error' + document.error = str(e.description) + document.stopped_at = datetime.datetime.utcnow() + db.session.commit() + except Exception as e: + logging.exception("consume update document failed") + document.indexing_status = 'error' + document.error = str(e) + document.stopped_at = datetime.datetime.utcnow() + db.session.commit() diff --git a/api/tasks/remove_document_from_index_task.py b/api/tasks/remove_document_from_index_task.py index 3dc6f9cd77..99a4bd3ec3 100644 --- a/api/tasks/remove_document_from_index_task.py +++ b/api/tasks/remove_document_from_index_task.py @@ -42,8 +42,7 @@ def remove_document_from_index_task(document_id: str): keyword_table_index = KeywordTableIndex(dataset=dataset) # delete from vector index - if dataset.indexing_technique == "high_quality": - vector_index.del_doc(document.id) + vector_index.del_doc(document.id) # delete from keyword index segments = db.session.query(DocumentSegment).filter(DocumentSegment.document_id == document.id).all() diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index f08ce3c7a9..2b432c0527 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -21,7 +21,7 @@ export type AppCardProps = { const AppCard = ({ app, - onDelete + onDelete, }: AppCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index f0edc3ac99..f5e9fae97a 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -3,13 +3,13 @@ import { useEffect, useRef } from 'react' import useSWRInfinite from 'swr/infinite' import { debounce } from 'lodash-es' +import { useTranslation } from 'react-i18next' import AppCard from './AppCard' import NewAppCard from './NewAppCard' -import { AppListResponse } from '@/models/app' +import type { AppListResponse } from '@/models/app' import { fetchAppList } from '@/service/apps' import { useSelector } from '@/context/app-context' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' -import { useTranslation } from 'react-i18next' const getKey = (pageIndex: number, previousPageData: AppListResponse) => { if (!pageIndex || previousPageData.has_more) @@ -25,8 +25,8 @@ const Apps = () => { const anchorRef = useRef(null) useEffect(() => { - document.title = `${t('app.title')} - Dify`; - if(localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') { + document.title = `${t('app.title')} - Dify` + if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') { localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) mutate() } @@ -41,9 +41,8 @@ const Apps = () => { if (!loadingStateRef.current) { const { scrollTop, clientHeight } = pageContainerRef.current! const anchorOffset = anchorRef.current!.offsetTop - if (anchorOffset - scrollTop - clientHeight < 100) { + if (anchorOffset - scrollTop - clientHeight < 100) setSize(size => size + 1) - } } }, 50) diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx new file mode 100644 index 0000000000..2194934ad1 --- /dev/null +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import Settings from '@/app/components/datasets/documents/detail/settings' + +export type IProps = { + params: { datasetId: string; documentId: string } +} + +const DocumentSettings = async ({ + params: { datasetId, documentId }, +}: IProps) => { + return ( + + ) +} + +export default DocumentSettings diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index ad198d329e..47111fbb92 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -164,7 +164,10 @@ const DatasetDetailLayout: FC = (props) => { extraInfo={} iconType='dataset' />} - +
{children}
diff --git a/web/app/(commonLayout)/datasets/DatasetCard.tsx b/web/app/(commonLayout)/datasets/DatasetCard.tsx index a27ac5955c..bf6db00f01 100644 --- a/web/app/(commonLayout)/datasets/DatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/DatasetCard.tsx @@ -1,20 +1,17 @@ 'use client' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import Link from 'next/link' -import useSWR from 'swr' import type { MouseEventHandler } from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import classNames from 'classnames' import style from '../list.module.css' -import type { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' import { ToastContext } from '@/app/components/base/toast' -import { deleteDataset, fetchDatasets } from '@/service/datasets' +import { deleteDataset } from '@/service/datasets' import AppIcon from '@/app/components/base/app-icon' -import AppsContext from '@/context/app-context' -import { DataSet } from '@/models/datasets' -import classNames from 'classnames' +import type { DataSet } from '@/models/datasets' export type DatasetCardProps = { dataset: DataSet @@ -23,7 +20,7 @@ export type DatasetCardProps = { const DatasetCard = ({ dataset, - onDelete + onDelete, }: DatasetCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) diff --git a/web/app/(commonLayout)/datasets/Datasets.tsx b/web/app/(commonLayout)/datasets/Datasets.tsx index 649ba64000..85457ae0f9 100644 --- a/web/app/(commonLayout)/datasets/Datasets.tsx +++ b/web/app/(commonLayout)/datasets/Datasets.tsx @@ -2,12 +2,12 @@ import { useEffect, useRef } from 'react' import useSWRInfinite from 'swr/infinite' -import { debounce } from 'lodash-es'; -import { DataSetListResponse } from '@/models/datasets'; +import { debounce } from 'lodash-es' import NewDatasetCard from './NewDatasetCard' -import DatasetCard from './DatasetCard'; -import { fetchDatasets } from '@/service/datasets'; -import { useSelector } from '@/context/app-context'; +import DatasetCard from './DatasetCard' +import type { DataSetListResponse } from '@/models/datasets' +import { fetchDatasets } from '@/service/datasets' +import { useSelector } from '@/context/app-context' const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { if (!pageIndex || previousPageData.has_more) @@ -30,9 +30,8 @@ const Datasets = () => { if (!loadingStateRef.current) { const { scrollTop, clientHeight } = pageContainerRef.current! const anchorOffset = anchorRef.current!.offsetTop - if (anchorOffset - scrollTop - clientHeight < 100) { + if (anchorOffset - scrollTop - clientHeight < 100) setSize(size => size + 1) - } } }, 50) @@ -43,7 +42,7 @@ const Datasets = () => { return ( @@ -51,4 +50,3 @@ const Datasets = () => { } export default Datasets - diff --git a/web/app/(commonLayout)/explore/apps/page.tsx b/web/app/(commonLayout)/explore/apps/page.tsx index 8066728562..b2430605e7 100644 --- a/web/app/(commonLayout)/explore/apps/page.tsx +++ b/web/app/(commonLayout)/explore/apps/page.tsx @@ -1,7 +1,7 @@ -import AppList from "@/app/components/explore/app-list" import React from 'react' +import AppList from '@/app/components/explore/app-list' -const Apps = ({ }) => { +const Apps = () => { return } diff --git a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx index 8a9000108c..c22645cab3 100644 --- a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx +++ b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx @@ -1,13 +1,14 @@ -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import Main from '@/app/components/explore/installed-app' -export interface IInstalledAppProps { +export type IInstalledAppProps = { params: { appId: string } } -const InstalledApp: FC = ({ params: {appId} }) => { +const InstalledApp: FC = ({ params: { appId } }) => { return (
) diff --git a/web/app/(shareLayout)/chat/[token]/page.tsx b/web/app/(shareLayout)/chat/[token]/page.tsx index abbee6a6f8..fbb9d5adee 100644 --- a/web/app/(shareLayout)/chat/[token]/page.tsx +++ b/web/app/(shareLayout)/chat/[token]/page.tsx @@ -5,7 +5,6 @@ import type { IMainProps } from '@/app/components/share/chat' import Main from '@/app/components/share/chat' const Chat: FC = () => { - return (
) diff --git a/web/app/components/app/configuration/config-model/index.tsx b/web/app/components/app/configuration/config-model/index.tsx index b0a63f70f9..7a9601e39b 100644 --- a/web/app/components/app/configuration/config-model/index.tsx +++ b/web/app/components/app/configuration/config-model/index.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useState } from 'react' +import React, { useEffect } from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useBoolean, useClickAway } from 'ahooks' +import { ChevronDownIcon, Cog8ToothIcon, InformationCircleIcon } from '@heroicons/react/24/outline' import ParamItem from './param-item' import Radio from '@/app/components/base/radio' import Panel from '@/app/components/base/panel' import type { CompletionParams } from '@/models/debug' -import { Cog8ToothIcon, InformationCircleIcon, ChevronDownIcon } from '@heroicons/react/24/outline' import { AppType } from '@/types/app' import { TONE_LIST } from '@/config' import Toast from '@/app/components/base/toast' @@ -51,7 +51,7 @@ const ConifgModel: FC = ({ }) => { const { t } = useTranslation() const isChatApp = mode === AppType.chat - const availableModels = options.filter((item) => item.type === mode) + const availableModels = options.filter(item => item.type === mode) const [isShowConfig, { setFalse: hideConfig, toggle: toogleShowConfig }] = useBoolean(false) const configContentRef = React.useRef(null) useClickAway(() => { @@ -116,14 +116,14 @@ const ConifgModel: FC = ({ onShowUseGPT4Confirm() return } - if(id !== 'gpt-4' && completionParams.max_tokens > 4000) { + if (id !== 'gpt-4' && completionParams.max_tokens > 4000) { Toast.notify({ type: 'warning', - message: t('common.model.params.setToCurrentModelMaxTokenTip') + message: t('common.model.params.setToCurrentModelMaxTokenTip'), }) onCompletionParamsChange({ ...completionParams, - max_tokens: 4000 + max_tokens: 4000, }) } setModelId(id) @@ -153,7 +153,7 @@ const ConifgModel: FC = ({ setToneId(id) onCompletionParamsChange({ ...tone.config, - max_tokens: completionParams.max_tokens + max_tokens: completionParams.max_tokens, } as CompletionParams) } } @@ -178,7 +178,7 @@ const ConifgModel: FC = ({ return (
!disabled && toogleShowConfig()} > @@ -206,14 +206,14 @@ const ConifgModel: FC = ({
{t('appDebug.modelConfig.model')}
{/* model selector */} -
-
!selectModelDisabled && toogleOption()} className={cn(selectModelDisabled ? 'cursor-not-allowed' : 'cursor-pointer', "flex items-center h-9 px-3 space-x-2 rounded-lg bg-gray-50 ")}> +
+
!selectModelDisabled && toogleOption()} className={cn(selectModelDisabled ? 'cursor-not-allowed' : 'cursor-pointer', 'flex items-center h-9 px-3 space-x-2 rounded-lg bg-gray-50 ')}>
{selectedModel?.name}
{!selectModelDisabled && }
{isShowOption && ( -
+
{availableModels.map(item => (
diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 2ff3ef1649..a576f3d22d 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -1,19 +1,19 @@ 'use client' -import React, { FC, useEffect } from 'react' +import type { FC } from 'react' +import React, { useEffect } from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' -import Modal from '@/app/components/base/modal' -import { DataSet } from '@/models/datasets' +import Link from 'next/link' import TypeIcon from '../type-icon' +import s from './style.module.css' +import Modal from '@/app/components/base/modal' +import type { DataSet } from '@/models/datasets' import Button from '@/app/components/base/button' import { fetchDatasets } from '@/service/datasets' import Loading from '@/app/components/base/loading' import { formatNumber } from '@/utils/format' -import Link from 'next/link' -import s from './style.module.css' - -export interface ISelectDataSetProps { +export type ISelectDataSetProps = { isShow: boolean onClose: () => void selectedIds: string[] @@ -37,20 +37,19 @@ const SelectDataSet: FC = ({ const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1 } }) setDataSets(data) setLoaded(true) - setSelected(data.filter((item) => selectedIds.includes(item.id))) + setSelected(data.filter(item => selectedIds.includes(item.id))) })() }, []) const toggleSelect = (dataSet: DataSet) => { - const isSelected = selected.some((item) => item.id === dataSet.id) + const isSelected = selected.some(item => item.id === dataSet.id) if (isSelected) { - setSelected(selected.filter((item) => item.id !== dataSet.id)) + setSelected(selected.filter(item => item.id !== dataSet.id)) } else { - if (canSelectMulti) { + if (canSelectMulti) setSelected([...selected, dataSet]) - } else { + else setSelected([dataSet]) - } } } @@ -74,7 +73,7 @@ const SelectDataSet: FC = ({
{t('appDebug.feature.dataSet.noDataSet')} @@ -85,7 +84,7 @@ const SelectDataSet: FC = ({ {datasets && datasets?.length > 0 && ( <>
- {datasets.map((item) => ( + {datasets.map(item => (
i.id === item.id) && s.selected, 'flex justify-between items-center h-10 px-2 rounded-lg bg-white border border-gray-200 cursor-pointer')} diff --git a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx b/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx index 391881841b..977a11cc76 100644 --- a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx @@ -1,11 +1,13 @@ +/* eslint-disable multiline-ternary */ 'use client' -import React, { FC, useEffect, useRef, useState } from 'react' +import type { FC } from 'react' +import React, { useEffect, useRef, useState } from 'react' import cn from 'classnames' import { useContext } from 'use-context-selector' -import ConfigContext from '@/context/debug-configuration' import produce from 'immer' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' +import ConfigContext from '@/context/debug-configuration' import Panel from '@/app/components/app/configuration/base/feature-panel' import Button from '@/app/components/base/button' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' @@ -14,7 +16,7 @@ import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/conf import { getNewVar } from '@/utils/var' import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' -export interface IOpeningStatementProps { +export type IOpeningStatementProps = { promptTemplate: string value: string onChange: (value: string) => void @@ -25,7 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g const OpeningStatement: FC = ({ value = '', - onChange + onChange, }) => { const { t } = useTranslation() const { @@ -60,8 +62,6 @@ const OpeningStatement: FC = ({ .replace(/>/g, '>') .replace(regex, varHighlightHTML({ name: '$1' })) // `{{$1}}` .replace(/\n/g, '
') - - const handleEdit = () => { setFocus() @@ -76,15 +76,15 @@ const OpeningStatement: FC = ({ const handleConfirm = () => { const keys = getInputKeys(tempValue) - const promptKeys = promptVariables.map((item) => item.key) + const promptKeys = promptVariables.map(item => item.key) let notIncludeKeys: string[] = [] if (promptKeys.length === 0) { - if (keys.length > 0) { + if (keys.length > 0) notIncludeKeys = keys - } - } else { - notIncludeKeys = keys.filter((key) => !promptKeys.includes(key)) + } + else { + notIncludeKeys = keys.filter(key => !promptKeys.includes(key)) } if (notIncludeKeys.length > 0) { @@ -104,7 +104,7 @@ const OpeningStatement: FC = ({ const autoAddVar = () => { const newModelConfig = produce(modelConfig, (draft) => { - draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map((key) => getNewVar(key))] + draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))] }) onChange(tempValue) setModelConfig(newModelConfig) @@ -130,26 +130,30 @@ const OpeningStatement: FC = ({ isFocus={isFocus} >
- {(hasValue || (!hasValue && isFocus)) ? ( - <> - {isFocus ? ( - - ) : ( -
- )} + {(hasValue || (!hasValue && isFocus)) + ? ( + <> + {isFocus + ? ( + + ) + : ( +
+ )} - {/* Operation Bar */} - {isFocus && ( + {/* Operation Bar */} + {isFocus + && (
{t('appDebug.openingStatement.varTip')}
@@ -160,9 +164,9 @@ const OpeningStatement: FC = ({
)} - ) : ( -
{t('appDebug.openingStatement.noDataPlaceHolder')}
- )} + ) : ( +
{t('appDebug.openingStatement.noDataPlaceHolder')}
+ )} {isShowConfirmAddVar && ( = ({
{ - (promptTemplate && promptTemplate?.trim()) ? ( -
/g, '>'), promptVariables, inputs)), - }} - > -
- ) : ( -
{t('appDebug.inputs.noPrompt')}
- ) + (promptTemplate && promptTemplate?.trim()) + ? ( +
/g, '>'), promptVariables, inputs)), + }} + > +
+ ) + : ( +
{t('appDebug.inputs.noPrompt')}
+ ) }
@@ -105,37 +107,41 @@ const PromptValuePanel: FC = ({ )}
{ - promptVariables.length > 0 ? ( -
- {promptVariables.map(({ key, name, type, options, max_length, required }) => ( -
-
{name || key}
- {type === 'select' ? ( - { handleInputValueChange(key, e.target.value) }} - maxLength={max_length || DEFAULT_VALUE_MAX_LEN} - /> - )} + promptVariables.length > 0 + ? ( +
+ {promptVariables.map(({ key, name, type, options, max_length, required }) => ( +
+
{name || key}
+ {type === 'select' + ? ( + { handleInputValueChange(key, e.target.value) }} + maxLength={max_length || DEFAULT_VALUE_MAX_LEN} + /> + )} -
- ))} -
- ) : ( -
{t('appDebug.inputs.noVar')}
- ) +
+ ))} +
+ ) + : ( +
{t('appDebug.inputs.noVar')}
+ ) }
diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index 700df81d23..f9e1c2e813 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -1,18 +1,19 @@ 'use client' -import React, { FC, useState } from 'react' +import type { FC } from 'react' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' -import { Markdown } from '@/app/components/base/markdown' -import Loading from '@/app/components/base/loading' import copy from 'copy-to-clipboard' -import Toast from '@/app/components/base/toast' -import { Feedbacktype } from '@/app/components/app/chat' import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' import { useBoolean } from 'ahooks' +import { Markdown } from '@/app/components/base/markdown' +import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' +import type { Feedbacktype } from '@/app/components/app/chat' import { fetchMoreLikeThis, updateFeedback } from '@/service/share' const MAX_DEPTH = 3 -export interface IGenerationItemProps { +export type IGenerationItemProps = { className?: string content: string messageId?: string | null @@ -24,13 +25,13 @@ export interface IGenerationItemProps { onFeedback?: (feedback: Feedbacktype) => void onSave?: (messageId: string) => void isMobile?: boolean - isInstalledApp: boolean, - installedAppId?: string, + isInstalledApp: boolean + installedAppId?: string } export const SimpleBtn = ({ className, onClick, children }: { className?: string - onClick?: () => void, + onClick?: () => void children: React.ReactNode }) => (
= ({ const [childMessageId, setChildMessageId] = useState(null) const hasChild = !!childMessageId const [childFeedback, setChildFeedback] = useState({ - rating: null + rating: null, }) const handleFeedback = async (childFeedback: Feedbacktype) => { @@ -126,115 +127,120 @@ const GenerationItem: FC = ({ } const mainStyle = (() => { - const res: any = !isTop ? { - background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff' - } : {} + const res: any = !isTop + ? { + background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff', + } + : {} - if (hasChild) { + if (hasChild) res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)' - } + return res })() return (
- {isLoading ? ( -
- ) : ( -
- - {messageId && ( -
-
- { - copy(content) - Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) - }}> - {copyIcon} - {!isMobile &&
{t('common.operation.copy')}
} -
- {isInWebApp && ( - <> - { onSave?.(messageId as string) }} - > - {saveIcon} - {!isMobile &&
{t('common.operation.save')}
} -
- {(moreLikeThis && depth < MAX_DEPTH) && ( + {isLoading + ? ( +
+ ) + : ( +
+ + {messageId && ( +
+
+ { + copy(content) + Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) + }}> + {copyIcon} + {!isMobile &&
{t('common.operation.copy')}
} +
+ {isInWebApp && ( + <> { onSave?.(messageId as string) }} > - {moreLikeThisIcon} - {!isMobile &&
{t('appDebug.feature.moreLikeThis.title')}
} -
)} -
- {!feedback?.rating && ( - - <> -
{ - onFeedback?.({ - rating: 'like' - }) - }} - className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> - -
-
{ - onFeedback?.({ - rating: 'dislike' - }) - }} - className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> - -
- + {saveIcon} + {!isMobile &&
{t('common.operation.save')}
}
- )} - {feedback?.rating === 'like' && ( -
{ - onFeedback?.({ - rating: null - }) - }} - className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'> - -
- )} - {feedback?.rating === 'dislike' && ( -
{ - onFeedback?.({ - rating: null - }) - }} - className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'> - -
- )} - - )} + {(moreLikeThis && depth < MAX_DEPTH) && ( + + {moreLikeThisIcon} + {!isMobile &&
{t('appDebug.feature.moreLikeThis.title')}
} +
)} +
+ {!feedback?.rating && ( + + <> +
{ + onFeedback?.({ + rating: 'like', + }) + }} + className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> + +
+
{ + onFeedback?.({ + rating: 'dislike', + }) + }} + className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> + +
+ +
+ )} + {feedback?.rating === 'like' && ( +
{ + onFeedback?.({ + rating: null, + }) + }} + className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'> + +
+ )} + {feedback?.rating === 'dislike' && ( +
{ + onFeedback?.({ + rating: null, + }) + }} + className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'> + +
+ )} + + )} +
+
{content?.length} {t('common.unit.char')}
-
{content?.length} {t('common.unit.char')}
-
- )} - -
- )} + )} +
+ )} {((childMessageId || isQuerying) && depth < 3) && (
diff --git a/web/app/components/base/app-icon/index.tsx b/web/app/components/base/app-icon/index.tsx index 7454734b1e..71c726606d 100644 --- a/web/app/components/base/app-icon/index.tsx +++ b/web/app/components/base/app-icon/index.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' import classNames from 'classnames' -import style from './style.module.css' import data from '@emoji-mart/data' import { init } from 'emoji-mart' +import style from './style.module.css' init({ data }) @@ -39,7 +39,7 @@ const AppIcon: FC = ({ }} onClick={onClick} > - {innerIcon ? innerIcon : icon && icon !== '' ? : } + {innerIcon || ((icon && icon !== '') ? : )} ) } diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index 796d1c06a5..9b56dd7b87 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -3,11 +3,11 @@ import type { ChangeEvent, FC } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' import classNames from 'classnames' -import { checkKeys } from '@/utils/var' import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' import Toast from '../toast' import { varHighlightHTML } from '../../app/configuration/base/var-highlight' +import Button from '@/app/components/base/button' +import { checkKeys } from '@/utils/var' // regex to match the {{}} and replace it with a span const regex = /\{\{([^}]+)\}\}/g @@ -55,9 +55,9 @@ const BlockInput: FC = ({ useEffect(() => { if (isEditing && contentEditableRef.current) { // TODO: Focus at the click positon - if (currentValue) { + if (currentValue) contentEditableRef.current.setSelectionRange(currentValue.length, currentValue.length) - } + contentEditableRef.current.focus() } }, [isEditing]) @@ -72,7 +72,6 @@ const BlockInput: FC = ({ .replace(/>/g, '>') .replace(regex, varHighlightHTML({ name: '$1' })) // `{{$1}}` .replace(/\n/g, '
') - // Not use useCallback. That will cause out callback get old data. const handleSubmit = () => { @@ -83,7 +82,7 @@ const BlockInput: FC = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }) + message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), }) return } @@ -125,9 +124,9 @@ const BlockInput: FC = ({ value={currentValue} onBlur={() => { blur() - if (!isContentChanged) { + if (!isContentChanged) setIsEditing(false) - } + // click confirm also make blur. Then outter value is change. So below code has problem. // setTimeout(() => { // handleCancel() @@ -143,31 +142,33 @@ const BlockInput: FC = ({ {textAreaContent} {/* footer */}
- {isContentChanged ? ( -
-
{currentValue.length}
-
- - -
+ {isContentChanged + ? ( +
+
{currentValue.length}
+
+ + +
-
- ) : ( -

- {t('appDebug.promptTip')} -

- )} +
+ ) + : ( +

+ {t('appDebug.promptTip')} +

+ )}
diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index c0e71549c3..94db4debb6 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -1,26 +1,28 @@ +/* eslint-disable multiline-ternary */ 'use client' -import React from 'react' -import { useState, FC, ChangeEvent } from 'react' +import type { ChangeEvent, FC } from 'react' +import React, { useState } from 'react' import data from '@emoji-mart/data' -import { init, SearchIndex } from 'emoji-mart' +import { SearchIndex, init } from 'emoji-mart' import cn from 'classnames' +import { + MagnifyingGlassIcon, +} from '@heroicons/react/24/outline' +import { useTranslation } from 'react-i18next' +import s from './style.module.css' import Divider from '@/app/components/base/divider' import Button from '@/app/components/base/button' -import s from './style.module.css' -import { - MagnifyingGlassIcon -} from '@heroicons/react/24/outline' import Modal from '@/app/components/base/modal' -import { useTranslation } from 'react-i18next' declare global { namespace JSX { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface IntrinsicElements { 'em-emoji': React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - >; + React.HTMLAttributes, + HTMLElement + > } } } @@ -57,7 +59,7 @@ const backgroundColors = [ '#ECE9FE', '#FFE4E8', ] -interface IEmojiPickerProps { +type IEmojiPickerProps = { isModal?: boolean onSelect?: (emoji: string, background: string) => void onClose?: () => void @@ -66,7 +68,7 @@ interface IEmojiPickerProps { const EmojiPicker: FC = ({ isModal = true, onSelect, - onClose + onClose, }) => { const { t } = useTranslation() @@ -97,8 +99,8 @@ const EmojiPicker: FC = ({ onChange={async (e: ChangeEvent) => { if (e.target.value === '') { setIsSearching(false) - return - } else { + } + else { setIsSearching(true) const emojis = await search(e.target.value) setSearchedEmojis(emojis) @@ -111,7 +113,7 @@ const EmojiPicker: FC = ({
{isSearching && <> -
+

Search

{searchedEmojis.map((emoji: string, index: number) => { @@ -131,7 +133,6 @@ const EmojiPicker: FC = ({
} - {categories.map((category: any, index: number) => { return

{category.id}

@@ -156,7 +157,7 @@ const EmojiPicker: FC = ({
{/* Color Select */} -
+

Choose Style

{backgroundColors.map((color) => { @@ -165,9 +166,9 @@ const EmojiPicker: FC = ({ className={ cn( 'cursor-pointer', - `hover:ring-1 ring-offset-1`, + 'hover:ring-1 ring-offset-1', 'inline-flex w-10 h-10 rounded-lg items-center justify-center', - color === selectedBackground ? `ring-1 ring-gray-300` : '', + color === selectedBackground ? 'ring-1 ring-gray-300' : '', )} onClick={() => { setSelectedBackground(color) @@ -191,7 +192,7 @@ const EmojiPicker: FC = ({ {t('app.emoji.cancel')}
-
- -
- -
+ {!isSetting ? ( +
+ +
+ +
+ ) : ( +
+ + +
+ )}
diff --git a/web/app/components/datasets/documents/detail/embedding/index.tsx b/web/app/components/datasets/documents/detail/embedding/index.tsx index 720400f5c6..e0e2051833 100644 --- a/web/app/components/datasets/documents/detail/embedding/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/index.tsx @@ -19,7 +19,7 @@ import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets' import type { CommonResponse } from '@/models/common' import { asyncRunSafe } from '@/utils' import { formatNumber } from '@/utils/format' -import { fetchIndexingEstimate, fetchIndexingStatus, fetchProcessRule, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets' +import { fetchIndexingEstimate, fetchProcessRule, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets' import DatasetDetailContext from '@/context/dataset-detail' import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal' @@ -118,14 +118,45 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d const localDocumentId = docId ?? documentId const localIndexingTechnique = indexingType ?? indexingTechnique - const { data: indexingStatusDetail, error: indexingStatusErr, mutate: statusMutate } = useSWR({ - action: 'fetchIndexingStatus', - datasetId: localDatasetId, - documentId: localDocumentId, - }, apiParams => fetchIndexingStatus(omit(apiParams, 'action')), { - refreshInterval: 5000, - revalidateOnFocus: false, - }) + // const { data: indexingStatusDetailFromApi, error: indexingStatusErr, mutate: statusMutate } = useSWR({ + // action: 'fetchIndexingStatus', + // datasetId: localDatasetId, + // documentId: localDocumentId, + // }, apiParams => fetchIndexingStatus(omit(apiParams, 'action')), { + // refreshInterval: 2500, + // revalidateOnFocus: false, + // }) + + const [indexingStatusDetail, setIndexingStatusDetail, getIndexingStatusDetail] = useGetState(null) + const fetchIndexingStatus = async () => { + const status = await doFetchIndexingStatus({ datasetId: localDatasetId, documentId: localDocumentId }) + setIndexingStatusDetail(status) + } + + const [runId, setRunId, getRunId] = useGetState(null) + const startQueryStatus = () => { + const runId = setInterval(() => { + const indexingStatusDetail = getIndexingStatusDetail() + if (indexingStatusDetail?.indexing_status === 'completed') { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + stopQueryStatus() + return + } + fetchIndexingStatus() + }, 2500) + setRunId(runId) + } + const stopQueryStatus = () => { + clearInterval(getRunId()) + } + + useEffect(() => { + fetchIndexingStatus() + startQueryStatus() + return () => { + stopQueryStatus() + } + }, []) const { data: indexingEstimateDetail, error: indexingEstimateErr } = useSWR({ action: 'fetchIndexingEstimate', @@ -168,7 +199,7 @@ const EmbeddingDetail: FC = ({ detail, stopPosition = 'top', datasetId: d const [e] = await asyncRunSafe(opApi({ datasetId: localDatasetId, documentId: localDocumentId }) as Promise) if (!e) { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - statusMutate() + setIndexingStatusDetail(null) } else { notify({ type: 'error', message: t('common.actionMsg.modificationFailed') }) diff --git a/web/app/components/datasets/documents/detail/settings/index.tsx b/web/app/components/datasets/documents/detail/settings/index.tsx new file mode 100644 index 0000000000..cdd0b1ddd8 --- /dev/null +++ b/web/app/components/datasets/documents/detail/settings/index.tsx @@ -0,0 +1,90 @@ +'use client' +import React, { useState, useCallback, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import { useContext } from 'use-context-selector' +import { useRouter } from 'next/navigation' +import DatasetDetailContext from '@/context/dataset-detail' +import type { FullDocumentDetail } from '@/models/datasets' +import { fetchTenantInfo } from '@/service/common' +import { fetchDocumentDetail, MetadataType } from '@/service/datasets' + +import Loading from '@/app/components/base/loading' +import StepTwo from '@/app/components/datasets/create/step-two' +import AccountSetting from '@/app/components/header/account-setting' +import AppUnavailable from '@/app/components/base/app-unavailable' + +type DocumentSettingsProps = { + datasetId: string; + documentId: string; +} + +const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { + const { t } = useTranslation() + const router = useRouter() + const [hasSetAPIKEY, setHasSetAPIKEY] = useState(true) + const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean() + const [hasError, setHasError] = useState(false) + const { indexingTechnique, dataset } = useContext(DatasetDetailContext) + + const saveHandler = () => router.push(`/datasets/${datasetId}/documents/${documentId}`) + + const cancelHandler = () => router.back() + + const checkAPIKey = async () => { + const data = await fetchTenantInfo({ url: '/info' }) + const hasSetKey = data.providers.some(({ is_valid }) => is_valid) + setHasSetAPIKEY(hasSetKey) + } + + useEffect(() => { + checkAPIKey() + }, []) + + const [documentDetail, setDocumentDetail] = useState(null) + useEffect(() => { + (async () => { + try { + const detail = await fetchDocumentDetail({ + datasetId, + documentId, + params: { metadata: 'without' as MetadataType } + }) + setDocumentDetail(detail) + } catch (e) { + setHasError(true) + } + })() + }, [datasetId, documentId]) + + if (hasError) { + return + } + + return ( +
+
+ {!documentDetail && } + {dataset && documentDetail && ( + + )} +
+ {isShowSetAPIKey && { + await checkAPIKey() + hideSetAPIkey() + }} />} +
+ ) +} + +export default DocumentSettings diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index b77e305a07..8cb7df253f 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -95,6 +95,7 @@ export const OperationAction: FC<{ const [showModal, setShowModal] = useState(false) const { notify } = useContext(ToastContext) const { t } = useTranslation() + const router = useRouter() const isListScene = scene === 'list' @@ -166,15 +167,19 @@ export const OperationAction: FC<{
} - {/*
- - {t('datasetDocuments.list.action.settings')} -
-
router.push(`/datasets/${datasetId}/documents/create`)}> - - {t('datasetDocuments.list.action.uploadFile')} -
- */} + {!archived && ( + <> +
router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}> + + {t('datasetDocuments.list.action.settings')} +
+ {/*
router.push(`/datasets/${datasetId}/documents/create`)}> + + {t('datasetDocuments.list.action.uploadFile')} +
*/} + + + )} {!archived &&
onOperate('archive')}> {t('datasetDocuments.list.action.archive')} diff --git a/web/app/components/datasets/documents/style.module.css b/web/app/components/datasets/documents/style.module.css index 76327d83f6..d412b382bc 100644 --- a/web/app/components/datasets/documents/style.module.css +++ b/web/app/components/datasets/documents/style.module.css @@ -72,7 +72,7 @@ .txtIcon { background-image: url(./assets/txt.svg); } -.mdIcon { +.markdownIcon { background-image: url(./assets/md.svg); } .statusItemDetail { diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index c40c900bb7..6b627e11b9 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -1,15 +1,15 @@ 'use client' -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import useSWR from 'swr' import { useContext } from 'use-context-selector' import { BookOpenIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' -import { ToastContext } from '@/app/components/base/toast' import PermissionsRadio from '../permissions-radio' import IndexMethodRadio from '../index-method-radio' +import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' -import { updateDatasetSetting, fetchDataDetail } from '@/service/datasets' -import { DataSet } from '@/models/datasets' +import { fetchDataDetail, updateDatasetSetting } from '@/service/datasets' +import type { DataSet } from '@/models/datasets' const rowClass = ` flex justify-between py-4 @@ -20,8 +20,7 @@ const labelClass = ` const inputClass = ` w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none ` - -const useInitialValue = (depend: T, dispatch: Dispatch>) => { +const useInitialValue = (depend: any, dispatch: any) => { useEffect(() => { dispatch(depend) }, [depend]) @@ -32,7 +31,7 @@ type Props = { } const Form = ({ - datasetId + datasetId, }: Props) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) @@ -44,7 +43,8 @@ const Form = ({ const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique) const handleSave = async () => { - if (loading) return + if (loading) + return if (!name?.trim()) { notify({ type: 'error', message: t('datasetSettings.form.nameError') }) return @@ -57,14 +57,16 @@ const Form = ({ name, description, permission, - indexing_technique: indexMethod - } + indexing_technique: indexMethod, + }, }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) await mutateDatasets() - } catch (e) { + } + catch (e) { notify({ type: 'error', message: t('common.actionMsg.modificationFailed') }) - } finally { + } + finally { setLoading(false) } } @@ -142,4 +144,4 @@ const Form = ({ ) } -export default Form \ No newline at end of file +export default Form diff --git a/web/app/components/develop/secret-key/input-copy.tsx b/web/app/components/develop/secret-key/input-copy.tsx index 8ad1b78da6..74a3ff65d4 100644 --- a/web/app/components/develop/secret-key/input-copy.tsx +++ b/web/app/components/develop/secret-key/input-copy.tsx @@ -1,9 +1,9 @@ 'use client' import React, { useEffect, useState } from 'react' import copy from 'copy-to-clipboard' -import Tooltip from '@/app/components/base/tooltip' import { t } from 'i18next' import s from './style.module.css' +import Tooltip from '@/app/components/base/tooltip' type IInputCopyProps = { value?: string diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 5470cebdec..e5e57ffb2f 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -1,22 +1,22 @@ 'use client' -import React, { FC, useEffect } from 'react' +import type { FC } from 'react' +import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' +import Toast from '../../base/toast' +import s from './style.module.css' import ExploreContext from '@/context/explore-context' -import { App } from '@/models/explore' +import type { App } from '@/models/explore' import Category from '@/app/components/explore/category' import AppCard from '@/app/components/explore/app-card' -import { fetchAppList, installApp, fetchAppDetail } from '@/service/explore' +import { fetchAppDetail, fetchAppList, installApp } from '@/service/explore' import { createApp } from '@/service/apps' import CreateAppModal from '@/app/components/explore/create-app-modal' import Loading from '@/app/components/base/loading' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' -import s from './style.module.css' -import Toast from '../../base/toast' - -const Apps: FC = ({ }) => { +const Apps: FC = () => { const { t } = useTranslation() const router = useRouter() const { setControlUpdateInstalledApps, hasEditPermission } = useContext(ExploreContext) @@ -25,13 +25,14 @@ const Apps: FC = ({ }) => { const [isLoaded, setIsLoaded] = React.useState(false) const currList = (() => { - if(currCategory === '') return allList + if (currCategory === '') + return allList return allList.filter(item => item.category === currCategory) })() const [categories, setCategories] = React.useState([]) useEffect(() => { (async () => { - const {categories, recommended_apps}:any = await fetchAppList() + const { categories, recommended_apps }: any = await fetchAppList() setCategories(categories) setAllList(recommended_apps) setIsLoaded(true) @@ -49,9 +50,9 @@ const Apps: FC = ({ }) => { const [currApp, setCurrApp] = React.useState(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) - const onCreate = async ({name, icon, icon_background}: any) => { + const onCreate = async ({ name, icon, icon_background }: any) => { const { app_model_config: model_config } = await fetchAppDetail(currApp?.app.id as string) - + try { const app = await createApp({ name, @@ -67,12 +68,13 @@ const Apps: FC = ({ }) => { }) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') router.push(`/app/${app.id}/overview`) - } catch (e) { + } + catch (e) { Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } } - if(!isLoaded) { + if (!isLoaded) { return (
@@ -92,16 +94,16 @@ const Apps: FC = ({ }) => { value={currCategory} onChange={setCurrCategory} /> -
{isShowCreateModal && ( - setIsShowCreateModal(false)} - /> - )} + setIsShowCreateModal(false)} + /> + )}
) } diff --git a/web/app/components/explore/category.tsx b/web/app/components/explore/category.tsx index 3c77b2aa2c..dce0c8919b 100644 --- a/web/app/components/explore/category.tsx +++ b/web/app/components/explore/category.tsx @@ -1,12 +1,13 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' -import exploreI18n from '@/i18n/lang/explore.en' import cn from 'classnames' +import exploreI18n from '@/i18n/lang/explore.en' const categoryI18n = exploreI18n.category -export interface ICategoryProps { +export type ICategoryProps = { className?: string list: string[] value: string @@ -17,23 +18,23 @@ const Category: FC = ({ className, list, value, - onChange + onChange, }) => { const { t } = useTranslation() - const itemClassName = (isSelected: boolean) => cn(isSelected ? 'bg-white text-primary-600 border-gray-200 font-semibold' : 'border-transparent font-medium','flex items-center h-7 px-3 border cursor-pointer rounded-lg') - const itemStyle = (isSelected: boolean) => isSelected ? {boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)'} : {} + const itemClassName = (isSelected: boolean) => cn(isSelected ? 'bg-white text-primary-600 border-gray-200 font-semibold' : 'border-transparent font-medium', 'flex items-center h-7 px-3 border cursor-pointer rounded-lg') + const itemStyle = (isSelected: boolean) => isSelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {} return (
-
onChange('')} - > - {t('explore.apps.allCategories')} -
+
onChange('')} + > + {t('explore.apps.allCategories')} +
{list.map(name => ( -
void, - onHide: () => void, + appName: string + show: boolean + onConfirm: (info: any) => void + onHide: () => void } const CreateAppModal = ({ @@ -31,7 +30,7 @@ const CreateAppModal = ({ const [emoji, setEmoji] = useState({ icon: '🤖', icon_background: '#FFEAD5' }) const submit = () => { - if(!name.trim()) { + if (!name.trim()) { Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') }) return } @@ -44,42 +43,42 @@ const CreateAppModal = ({ return ( <> - - -
{t('explore.appCustomize.title', {name: appName})}
-
-
{t('explore.appCustomize.subTitle')}
-
- { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} /> - setName(e.target.value)} - className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' - /> + + +
{t('explore.appCustomize.title', { name: appName })}
+
+
{t('explore.appCustomize.subTitle')}
+
+ { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} /> + setName(e.target.value)} + className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' + /> +
-
-
- - -
- - {showEmojiPicker && { - console.log(icon, icon_background) - setEmoji({ icon, icon_background }) - setShowEmojiPicker(false) - }} - onClose={() => { - setEmoji({ icon: '🤖', icon_background: '#FFEAD5' }) - setShowEmojiPicker(false) - }} - />} +
+ + +
+ + {showEmojiPicker && { + console.log(icon, icon_background) + setEmoji({ icon, icon_background }) + setShowEmojiPicker(false) + }} + onClose={() => { + setEmoji({ icon: '🤖', icon_background: '#FFEAD5' }) + setShowEmojiPicker(false) + }} + />} - + ) } diff --git a/web/app/components/explore/index.tsx b/web/app/components/explore/index.tsx index 03486ececf..8a007cd65a 100644 --- a/web/app/components/explore/index.tsx +++ b/web/app/components/explore/index.tsx @@ -1,18 +1,19 @@ 'use client' -import React, { FC, useEffect, useState } from 'react' +import type { FC } from 'react' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import ExploreContext from '@/context/explore-context' import Sidebar from '@/app/components/explore/sidebar' import { useAppContext } from '@/context/app-context' import { fetchMembers } from '@/service/common' -import { InstalledApp } from '@/models/explore' -import { useTranslation } from 'react-i18next' +import type { InstalledApp } from '@/models/explore' -export interface IExploreProps { +export type IExploreProps = { children: React.ReactNode } const Explore: FC = ({ - children + children, }) => { const { t } = useTranslation() const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0) @@ -23,8 +24,9 @@ const Explore: FC = ({ useEffect(() => { document.title = `${t('explore.title')} - Dify`; (async () => { - const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {}}) - if(!accounts) return + const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) + if (!accounts) + return const currUser = accounts.find(account => account.id === userProfile.id) setHasEditPermission(currUser?.role !== 'normal') })() @@ -39,7 +41,7 @@ const Explore: FC = ({ setControlUpdateInstalledApps, hasEditPermission, installedApps, - setInstalledApps + setInstalledApps, } } > diff --git a/web/app/components/explore/installed-app/index.tsx b/web/app/components/explore/installed-app/index.tsx index 097a281def..36460cc223 100644 --- a/web/app/components/explore/installed-app/index.tsx +++ b/web/app/components/explore/installed-app/index.tsx @@ -1,12 +1,13 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useContext } from 'use-context-selector' import ExploreContext from '@/context/explore-context' import ChatApp from '@/app/components/share/chat' import TextGenerationApp from '@/app/components/share/text-generation' import Loading from '@/app/components/base/loading' -export interface IInstalledAppProps { +export type IInstalledAppProps = { id: string } @@ -14,23 +15,25 @@ const InstalledApp: FC = ({ id, }) => { const { installedApps } = useContext(ExploreContext) - const installedApp = installedApps.find(item => item.id === id) - - if(!installedApp) { + const installedApp = installedApps.find(item => item.id === id) + + if (!installedApp) { return (
) } - + return (
- {installedApp?.app.mode === 'chat' ? ( - - ): ( - - )} + {installedApp?.app.mode === 'chat' + ? ( + + ) + : ( + + )}
) } diff --git a/web/app/components/explore/item-operation/index.tsx b/web/app/components/explore/item-operation/index.tsx index 8675f9dce1..45f197a7f3 100644 --- a/web/app/components/explore/item-operation/index.tsx +++ b/web/app/components/explore/item-operation/index.tsx @@ -1,11 +1,12 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' -import Popover from '@/app/components/base/popover' import { TrashIcon } from '@heroicons/react/24/outline' import s from './style.module.css' +import Popover from '@/app/components/base/popover' const PinIcon = ( @@ -13,7 +14,7 @@ const PinIcon = ( ) -export interface IItemOperationProps { +export type IItemOperationProps = { className?: string isPinned: boolean isShowDelete: boolean @@ -26,7 +27,7 @@ const ItemOperation: FC = ({ isPinned, isShowDelete, togglePin, - onDelete + onDelete, }) => { const { t } = useTranslation() @@ -42,18 +43,18 @@ const ItemOperation: FC = ({
{isShowDelete && (
- - {t('explore.sidebar.action.delete')} -
+ + {t('explore.sidebar.action.delete')} +
)} - +
} trigger='click' position='br' btnElement={
} - btnClassName={(open) => cn(className, s.btn, 'h-6 w-6 rounded-md border-none p-1', open && '!bg-gray-100 !shadow-none')} - className={`!w-[120px] h-fit !z-20`} + btnClassName={open => cn(className, s.btn, 'h-6 w-6 rounded-md border-none p-1', open && '!bg-gray-100 !shadow-none')} + className={'!w-[120px] h-fit !z-20'} /> ) } diff --git a/web/app/components/explore/sidebar/app-nav-item/index.tsx b/web/app/components/explore/sidebar/app-nav-item/index.tsx index 9ed5213667..7dd2542015 100644 --- a/web/app/components/explore/sidebar/app-nav-item/index.tsx +++ b/web/app/components/explore/sidebar/app-nav-item/index.tsx @@ -1,12 +1,11 @@ 'use client' import cn from 'classnames' import { useRouter } from 'next/navigation' +import s from './style.module.css' import ItemOperation from '@/app/components/explore/item-operation' import AppIcon from '@/app/components/base/app-icon' -import s from './style.module.css' - -export interface IAppNavItemProps { +export type IAppNavItemProps = { name: string id: string icon: string @@ -31,7 +30,7 @@ export default function AppNavItem({ }: IAppNavItemProps) { const router = useRouter() const url = `/explore/installed/${id}` - + return (
{ + onClick={() => { router.push(url) // use Link causes popup item always trigger jump. Can not be solved by e.stopPropagation(). }} > @@ -53,7 +52,7 @@ export default function AppNavItem({ borderColor: '0.5px solid rgba(0, 0, 0, 0.05)' }} /> */} - +
{name}
{ diff --git a/web/app/components/header/account-setting/provider-page/azure-provider/index.tsx b/web/app/components/header/account-setting/provider-page/azure-provider/index.tsx index a32acaa745..97fce91f39 100644 --- a/web/app/components/header/account-setting/provider-page/azure-provider/index.tsx +++ b/web/app/components/header/account-setting/provider-page/azure-provider/index.tsx @@ -1,92 +1,90 @@ -import type { Provider, ProviderAzureToken } from '@/models/common' -import { ProviderName } from '@/models/common' import { useTranslation } from 'react-i18next' import Link from 'next/link' import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' -import { useState, useEffect } from 'react' +import { useEffect, useState } from 'react' import ProviderInput from '../provider-input' -import useValidateToken, { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken' -import { - ValidatedErrorIcon, +import type { ValidatedStatusState } from '../provider-input/useValidateToken' +import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken' +import { + ValidatedErrorIcon, + ValidatedErrorOnAzureOpenaiTip, ValidatedSuccessIcon, ValidatingTip, - ValidatedErrorOnAzureOpenaiTip } from '../provider-input/Validate' +import { ProviderName } from '@/models/common' +import type { Provider, ProviderAzureToken } from '@/models/common' -interface IAzureProviderProps { +type IAzureProviderProps = { provider: Provider onValidatedStatus: (status?: ValidatedStatusState) => void onTokenChange: (token: ProviderAzureToken) => void } const AzureProvider = ({ - provider, + provider, onTokenChange, - onValidatedStatus + onValidatedStatus, }: IAzureProviderProps) => { const { t } = useTranslation() - const [token, setToken] = useState(provider.provider_name === ProviderName.AZURE_OPENAI ? {...provider.token}: {}) - const [ validating, validatedStatus, setValidatedStatus, validate ] = useValidateToken(provider.provider_name) + const [token, setToken] = useState(provider.provider_name === ProviderName.AZURE_OPENAI ? { ...provider.token } : {}) + const [validating, validatedStatus, setValidatedStatus, validate] = useValidateToken(provider.provider_name) const handleFocus = (type: keyof ProviderAzureToken) => { if (token[type] === (provider?.token as ProviderAzureToken)[type]) { token[type] = '' - setToken({...token}) - onTokenChange({...token}) + setToken({ ...token }) + onTokenChange({ ...token }) setValidatedStatus({}) } } const handleChange = (type: keyof ProviderAzureToken, v: string, validate: any) => { token[type] = v - setToken({...token}) - onTokenChange({...token}) - validate({...token}, { + setToken({ ...token }) + onTokenChange({ ...token }) + validate({ ...token }, { beforeValidating: () => { if (!token.openai_api_base || !token.openai_api_key) { setValidatedStatus({}) return false } return true - } + }, }) } const getValidatedIcon = () => { - if (validatedStatus.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) { + if (validatedStatus.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) return - } - if (validatedStatus.status === ValidatedStatus.Success) { + + if (validatedStatus.status === ValidatedStatus.Success) return - } } const getValidatedTip = () => { - if (validating) { + if (validating) return - } - if (validatedStatus.status === ValidatedStatus.Error) { + + if (validatedStatus.status === ValidatedStatus.Error) return - } } useEffect(() => { - if (typeof onValidatedStatus === 'function') { + if (typeof onValidatedStatus === 'function') onValidatedStatus(validatedStatus) - } }, [validatedStatus]) return (
- handleChange('openai_api_base', v, validate)} + onChange={v => handleChange('openai_api_base', v, validate)} onFocus={() => handleFocus('openai_api_base')} validatedIcon={getValidatedIcon()} /> - handleChange('openai_api_key', v, validate)} + onChange={v => handleChange('openai_api_key', v, validate)} onFocus={() => handleFocus('openai_api_key')} validatedIcon={getValidatedIcon()} validatedTip={getValidatedTip()} diff --git a/web/app/components/header/account-setting/provider-page/index.tsx b/web/app/components/header/account-setting/provider-page/index.tsx index 67112b8142..938dca5cc4 100644 --- a/web/app/components/header/account-setting/provider-page/index.tsx +++ b/web/app/components/header/account-setting/provider-page/index.tsx @@ -1,15 +1,15 @@ import { useState } from 'react' import useSWR from 'swr' -import { fetchProviders } from '@/service/common' -import ProviderItem from './provider-item' -import OpenaiHostedProvider from './openai-hosted-provider' -import type { ProviderHosted } from '@/models/common' import { LockClosedIcon } from '@heroicons/react/24/solid' import { useTranslation } from 'react-i18next' import Link from 'next/link' +import ProviderItem from './provider-item' +import OpenaiHostedProvider from './openai-hosted-provider' +import type { ProviderHosted } from '@/models/common' +import { fetchProviders } from '@/service/common' import { IS_CE_EDITION } from '@/config' -const providersMap: {[k: string]: any} = { +const providersMap: { [k: string]: any } = { 'openai-custom': { icon: 'openai', name: 'OpenAI', @@ -17,7 +17,7 @@ const providersMap: {[k: string]: any} = { 'azure_openai-custom': { icon: 'azure', name: 'Azure OpenAI Service', - } + }, } // const providersList = [ @@ -56,7 +56,7 @@ const ProviderPage = () => { const { t } = useTranslation() const [activeProviderId, setActiveProviderId] = useState('') const { data, mutate } = useSWR({ url: '/workspaces/current/providers' }, fetchProviders) - const providers = data?.filter(provider => providersMap[`${provider.provider_name}-${provider.provider_type}`])?.map(provider => { + const providers = data?.filter(provider => providersMap[`${provider.provider_name}-${provider.provider_type}`])?.map((provider) => { const providerKey = `${provider.provider_name}-${provider.provider_type}` return { provider, diff --git a/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx b/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx index d33042fcfd..1b1a4f7b0b 100644 --- a/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx +++ b/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx @@ -1,19 +1,19 @@ -import type { Provider } from '@/models/common' -import { useState, useEffect } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import ProviderInput from '../provider-input' import Link from 'next/link' import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' -import useValidateToken, { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken' -import { - ValidatedErrorIcon, +import ProviderInput from '../provider-input' +import type { ValidatedStatusState } from '../provider-input/useValidateToken' +import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken' +import { + ValidatedErrorIcon, + ValidatedErrorOnOpenaiTip, ValidatedSuccessIcon, ValidatingTip, - ValidatedExceedOnOpenaiTip, - ValidatedErrorOnOpenaiTip } from '../provider-input/Validate' +import type { Provider } from '@/models/common' -interface IOpenaiProviderProps { +type IOpenaiProviderProps = { provider: Provider onValidatedStatus: (status?: ValidatedStatusState) => void onTokenChange: (token: string) => void @@ -22,11 +22,11 @@ interface IOpenaiProviderProps { const OpenaiProvider = ({ provider, onValidatedStatus, - onTokenChange + onTokenChange, }: IOpenaiProviderProps) => { const { t } = useTranslation() const [token, setToken] = useState(provider.token as string || '') - const [ validating, validatedStatus, setValidatedStatus, validate ] = useValidateToken(provider.provider_name) + const [validating, validatedStatus, setValidatedStatus, validate] = useValidateToken(provider.provider_name) const handleFocus = () => { if (token === provider.token) { setToken('') @@ -44,35 +44,32 @@ const OpenaiProvider = ({ return false } return true - } + }, }) } useEffect(() => { - if (typeof onValidatedStatus === 'function') { + if (typeof onValidatedStatus === 'function') onValidatedStatus(validatedStatus) - } }, [validatedStatus]) const getValidatedIcon = () => { - if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) { + if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) return - } - if (validatedStatus.status === ValidatedStatus.Success) { + + if (validatedStatus.status === ValidatedStatus.Success) return - } } const getValidatedTip = () => { - if (validating) { + if (validating) return - } - if (validatedStatus?.status === ValidatedStatus.Error) { + + if (validatedStatus?.status === ValidatedStatus.Error) return - } } return (
- { export const ValidatingTip = () => { const { t } = useTranslation() return ( -
+
{t('common.provider.validating')}
) @@ -26,11 +26,11 @@ export const ValidatedExceedOnOpenaiTip = () => { const { locale } = useContext(I18n) return ( -
+
{t('common.provider.apiKeyExceedBill')}  - {locale === 'en' ? 'this link' : '这篇文档'} @@ -42,7 +42,7 @@ export const ValidatedErrorOnOpenaiTip = ({ errorMessage }: { errorMessage: stri const { t } = useTranslation() return ( -
+
{t('common.provider.validatedError')}{errorMessage}
) @@ -52,8 +52,8 @@ export const ValidatedErrorOnAzureOpenaiTip = ({ errorMessage }: { errorMessage: const { t } = useTranslation() return ( -
+
{t('common.provider.validatedError')}{errorMessage}
) -} \ No newline at end of file +} diff --git a/web/app/components/header/account-setting/provider-page/provider-input/index.tsx b/web/app/components/header/account-setting/provider-page/provider-input/index.tsx index 84ab9901c1..8265454063 100644 --- a/web/app/components/header/account-setting/provider-page/provider-input/index.tsx +++ b/web/app/components/header/account-setting/provider-page/provider-input/index.tsx @@ -1,7 +1,7 @@ -import { ChangeEvent } from 'react' -import { ReactElement } from 'react-markdown/lib/react-markdown' +import type { ChangeEvent } from 'react' +import type { ReactElement } from 'react-markdown/lib/react-markdown' -interface IProviderInputProps { +type IProviderInputProps = { value?: string name: string placeholder: string @@ -20,9 +20,8 @@ const ProviderInput = ({ onChange, onFocus, validatedIcon, - validatedTip + validatedTip, }: IProviderInputProps) => { - const handleChange = (e: ChangeEvent) => { const inputValue = e.target.value onChange(inputValue) @@ -35,12 +34,12 @@ const ProviderInput = ({ flex items-center px-3 bg-white rounded-lg shadow-[0_1px_2px_rgba(16,24,40,0.05)] '> - > export type ValidateFn = DebouncedFunc<(token: any, config: ValidateFnConfig) => void> type ValidateTokenReturn = [ - boolean, - ValidatedStatusState, + boolean, + ValidatedStatusState, SetValidatedStatus, - ValidateFn + ValidateFn, ] export type ValidateFnConfig = { beforeValidating: (token: any) => boolean @@ -29,19 +30,21 @@ const useValidateToken = (providerName: string): ValidateTokenReturn => { const [validating, setValidating] = useState(false) const [validatedStatus, setValidatedStatus] = useState({}) const validate = useCallback(debounce(async (token: string, config: ValidateFnConfig) => { - if (!config.beforeValidating(token)) { + if (!config.beforeValidating(token)) return false - } + setValidating(true) try { const res = await validateProviderKey({ url: `/workspaces/current/providers/${providerName}/token-validate`, body: { token } }) setValidatedStatus( - res.result === 'success' - ? { status: ValidatedStatus.Success } + res.result === 'success' + ? { status: ValidatedStatus.Success } : { status: ValidatedStatus.Error, message: res.error }) - } catch (e: any) { + } + catch (e: any) { setValidatedStatus({ status: ValidatedStatus.Error, message: e.message }) - } finally { + } + finally { setValidating(false) } }, 500), []) @@ -50,8 +53,8 @@ const useValidateToken = (providerName: string): ValidateTokenReturn => { validating, validatedStatus, setValidatedStatus, - validate + validate, ] } -export default useValidateToken \ No newline at end of file +export default useValidateToken diff --git a/web/app/components/header/account-setting/provider-page/provider-item/index.tsx b/web/app/components/header/account-setting/provider-page/provider-item/index.tsx index cd1ad038da..9c16836aa9 100644 --- a/web/app/components/header/account-setting/provider-page/provider-item/index.tsx +++ b/web/app/components/header/account-setting/provider-page/provider-item/index.tsx @@ -1,18 +1,19 @@ import { useState } from 'react' import cn from 'classnames' -import s from './index.module.css' import { useContext } from 'use-context-selector' -import Indicator from '../../../indicator' import { useTranslation } from 'react-i18next' -import type { Provider, ProviderAzureToken } from '@/models/common' -import { ProviderName } from '@/models/common' +import Indicator from '../../../indicator' import OpenaiProvider from '../openai-provider' import AzureProvider from '../azure-provider' -import { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken' +import type { ValidatedStatusState } from '../provider-input/useValidateToken' +import { ValidatedStatus } from '../provider-input/useValidateToken' +import s from './index.module.css' +import type { Provider, ProviderAzureToken } from '@/models/common' +import { ProviderName } from '@/models/common' import { updateProviderAIKey } from '@/service/common' import { ToastContext } from '@/app/components/base/toast' -interface IProviderItemProps { +type IProviderItemProps = { icon: string name: string provider: Provider @@ -26,17 +27,17 @@ const ProviderItem = ({ name, provider, onActive, - onSave + onSave, }: IProviderItemProps) => { const { t } = useTranslation() const [validatedStatus, setValidatedStatus] = useState() const [loading, setLoading] = useState(false) const { notify } = useContext(ToastContext) const [token, setToken] = useState( - provider.provider_name === 'azure_openai' + provider.provider_name === 'azure_openai' ? { openai_api_base: '', openai_api_key: '' } - : '' - ) + : '', + ) const id = `${provider.provider_name}-${provider.provider_type}` const isOpen = id === activeId const comingSoon = false @@ -44,26 +45,30 @@ const ProviderItem = ({ const providerTokenHasSetted = () => { if (provider.provider_name === ProviderName.AZURE_OPENAI) { - return provider.token && provider.token.openai_api_base && provider.token.openai_api_key ? { - openai_api_base: provider.token.openai_api_base, - openai_api_key: provider.token.openai_api_key - }: undefined + return (provider.token && provider.token.openai_api_base && provider.token.openai_api_key) + ? { + openai_api_base: provider.token.openai_api_base, + openai_api_key: provider.token.openai_api_key, + } + : undefined } - if (provider.provider_name === ProviderName.OPENAI) { + if (provider.provider_name === ProviderName.OPENAI) return provider.token - } } const handleUpdateToken = async () => { - if (loading) return + if (loading) + return if (validatedStatus?.status === ValidatedStatus.Success) { try { setLoading(true) await updateProviderAIKey({ url: `/workspaces/current/providers/${provider.provider_name}/token`, body: { token } }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) onActive('') - } catch (e) { + } + catch (e) { notify({ type: 'error', message: t('common.provider.saveFailed') }) - } finally { + } + finally { setLoading(false) onSave() } @@ -126,18 +131,18 @@ const ProviderItem = ({
{ provider.provider_name === ProviderName.OPENAI && isOpen && ( - setValidatedStatus(v)} + setValidatedStatus(v)} onTokenChange={v => setToken(v)} /> ) } { provider.provider_name === ProviderName.AZURE_OPENAI && isOpen && ( - setValidatedStatus(v)} + setValidatedStatus(v)} onTokenChange={v => setToken(v)} /> ) diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index 9149d5f69e..bad3431f3f 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { useSelectedLayoutSegment, useRouter } from 'next/navigation' +import { useRouter, useSelectedLayoutSegment } from 'next/navigation' import classNames from 'classnames' import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline' import { CommandLineIcon, Squares2X2Icon } from '@heroicons/react/24/solid' @@ -15,9 +15,9 @@ import NewAppDialog from '@/app/(commonLayout)/apps/NewAppDialog' import { WorkspaceProvider } from '@/context/workspace-context' import { useDatasetsContext } from '@/context/datasets-context' -const BuildAppsIcon = ({isSelected}: {isSelected: boolean}) => ( +const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => ( - + ) @@ -51,22 +51,22 @@ const Header: FC = ({ appItems, curApp, userProfile, onLogout, lan
-
+
{/* Add it when has many stars */}
-
+
{t('common.menus.status')}
@@ -74,7 +74,7 @@ const Header: FC = ({ appItems, curApp, userProfile, onLogout, lan {t('common.menus.explore')} @@ -84,13 +84,13 @@ const Header: FC = ({ appItems, curApp, userProfile, onLogout, lan text={t('common.menus.apps')} activeSegment={['apps', 'app']} link='/apps' - curNav={curApp && { id: curApp.id, name: curApp.name ,icon: curApp.icon, icon_background: curApp.icon_background}} + curNav={curApp && { id: curApp.id, name: curApp.name, icon: curApp.icon, icon_background: curApp.icon_background }} navs={appItems.map(item => ({ id: item.id, name: item.name, link: `/app/${item.id}/overview`, icon: item.icon, - icon_background: item.icon_background + icon_background: item.icon_background, }))} createText={t('common.menus.newApp')} onCreate={() => setShowNewAppDialog(true)} @@ -98,7 +98,7 @@ const Header: FC = ({ appItems, curApp, userProfile, onLogout, lan {t('common.menus.plugins')} @@ -114,7 +114,7 @@ const Header: FC = ({ appItems, curApp, userProfile, onLogout, lan name: dataset.name, link: `/datasets/${dataset.id}/documents`, icon: dataset.icon, - icon_background: dataset.icon_background + icon_background: dataset.icon_background, }))} createText={t('common.menus.newDataset')} onCreate={() => router.push('/datasets/create')} diff --git a/web/app/components/share/chat/sidebar/app-info/index.tsx b/web/app/components/share/chat/sidebar/app-info/index.tsx index d3987faa90..a7f5052721 100644 --- a/web/app/components/share/chat/sidebar/app-info/index.tsx +++ b/web/app/components/share/chat/sidebar/app-info/index.tsx @@ -1,10 +1,11 @@ 'use client' -import React, { FC } from 'react' -import cn from 'classnames' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' import { appDefaultIconBackground } from '@/config/index' import AppIcon from '@/app/components/base/app-icon' -export interface IAppInfoProps { +export type IAppInfoProps = { className?: string icon: string icon_background?: string @@ -15,7 +16,7 @@ const AppInfo: FC = ({ className, icon, icon_background, - name + name, }) => { return (
diff --git a/web/app/components/share/chat/sidebar/index.tsx b/web/app/components/share/chat/sidebar/index.tsx index 9f9c609c77..34a5b5d7a9 100644 --- a/web/app/components/share/chat/sidebar/index.tsx +++ b/web/app/components/share/chat/sidebar/index.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useRef } from 'react' +import React, { useRef } from 'react' import type { FC } from 'react' import { useTranslation } from 'react-i18next' import { ChatBubbleOvalLeftEllipsisIcon, - PencilSquareIcon + PencilSquareIcon, } from '@heroicons/react/24/outline' -import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon, } from '@heroicons/react/24/solid' +import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid' +import { useInfiniteScroll } from 'ahooks' import Button from '../../../base/button' import AppInfo from '@/app/components/share/chat/sidebar/app-info' // import Card from './card' import type { ConversationItem, SiteInfo } from '@/models/share' -import { useInfiniteScroll } from 'ahooks' import { fetchConversations } from '@/service/share' function classNames(...classes: any[]) { @@ -25,7 +25,7 @@ export type ISidebarProps = { isInstalledApp: boolean installedAppId?: string siteInfo: SiteInfo - onMoreLoaded: (res: {data: ConversationItem[], has_more: boolean}) => void + onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void isNoMore: boolean } @@ -45,19 +45,19 @@ const Sidebar: FC = ({ useInfiniteScroll( async () => { - if(!isNoMore) { + if (!isNoMore) { const lastId = list[list.length - 1].id const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId) onMoreLoaded({ data: conversations, has_more }) } - return {list: []} + return { list: [] } }, { target: listRef, isNoMore: () => { return isNoMore }, - reloadDeps: [isNoMore] + reloadDeps: [isNoMore], }, ) @@ -66,7 +66,7 @@ const Sidebar: FC = ({ className={ classNames( isInstalledApp ? 'tablet:h-[calc(100vh_-_74px)]' : 'tablet:h-[calc(100vh_-_3rem)]', - "shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen" + 'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen', ) } > diff --git a/web/app/components/share/text-generation/config-scence/index.tsx b/web/app/components/share/text-generation/config-scence/index.tsx index d95e0263c1..395f8dc3dd 100644 --- a/web/app/components/share/text-generation/config-scence/index.tsx +++ b/web/app/components/share/text-generation/config-scence/index.tsx @@ -38,25 +38,27 @@ const ConfigSence: FC = ({
- {item.type === 'select' ? ( - { onInputsChange({ ...inputs, [item.key]: e.target.value }) }} - maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} - /> - )} + {item.type === 'select' + ? ( + { onInputsChange({ ...inputs, [item.key]: e.target.value }) }} + maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} + /> + )}
))} diff --git a/web/config/index.ts b/web/config/index.ts index e48bac3253..f75e64f052 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -1,35 +1,38 @@ -const isDevelopment = process.env.NODE_ENV === 'development'; +/* eslint-disable import/no-mutable-exports */ +const isDevelopment = process.env.NODE_ENV === 'development' -export let apiPrefix = ''; -let publicApiPrefix = ''; +export let apiPrefix = '' +export let publicApiPrefix = '' // NEXT_PUBLIC_API_PREFIX=/console/api NEXT_PUBLIC_PUBLIC_API_PREFIX=/api npm run start if (process.env.NEXT_PUBLIC_API_PREFIX && process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX) { - apiPrefix = process.env.NEXT_PUBLIC_API_PREFIX; - publicApiPrefix = process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX; -} else if ( - globalThis.document?.body?.getAttribute('data-api-prefix') && - globalThis.document?.body?.getAttribute('data-pubic-api-prefix') + apiPrefix = process.env.NEXT_PUBLIC_API_PREFIX + publicApiPrefix = process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX +} +else if ( + globalThis.document?.body?.getAttribute('data-api-prefix') + && globalThis.document?.body?.getAttribute('data-pubic-api-prefix') ) { // Not bulild can not get env from process.env.NEXT_PUBLIC_ in browser https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser apiPrefix = globalThis.document.body.getAttribute('data-api-prefix') as string publicApiPrefix = globalThis.document.body.getAttribute('data-pubic-api-prefix') as string -} else { +} +else { if (isDevelopment) { - apiPrefix = 'https://cloud.dify.dev/console/api'; - publicApiPrefix = 'https://dev.udify.app/api'; - } else { + apiPrefix = 'https://cloud.dify.dev/console/api' + publicApiPrefix = 'https://dev.udify.app/api' + } + else { // const domainParts = globalThis.location?.host?.split('.'); // in production env, the host is dify.app . In other env, the host is [dev].dify.app // const env = domainParts.length === 2 ? 'ai' : domainParts?.[0]; - apiPrefix = '/console/api'; - publicApiPrefix = `/api`; // avoid browser private mode api cross origin + apiPrefix = '/console/api' + publicApiPrefix = '/api' // avoid browser private mode api cross origin } } - -export const API_PREFIX: string = apiPrefix; -export const PUBLIC_API_PREFIX: string = publicApiPrefix; +export const API_PREFIX: string = apiPrefix +export const PUBLIC_API_PREFIX: string = publicApiPrefix const EDITION = process.env.NEXT_PUBLIC_EDITION || globalThis.document?.body?.getAttribute('data-public-edition') export const IS_CE_EDITION = EDITION === 'SELF_HOSTED' @@ -75,15 +78,15 @@ export const LOCALE_COOKIE_NAME = 'locale' export const DEFAULT_VALUE_MAX_LEN = 48 -export const zhRegex = /^[\u4e00-\u9fa5]$/m +export const zhRegex = /^[\u4E00-\u9FA5]$/m export const emojiRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/m export const emailRegex = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/m const MAX_ZN_VAR_NAME_LENGHT = 8 const MAX_EN_VAR_VALUE_LENGHT = 16 export const getMaxVarNameLength = (value: string) => { - if (zhRegex.test(value)) { + if (zhRegex.test(value)) return MAX_ZN_VAR_NAME_LENGHT - } + return MAX_EN_VAR_VALUE_LENGHT } @@ -94,12 +97,9 @@ export const VAR_ITEM_TEMPLATE = { name: '', type: 'string', max_length: DEFAULT_VALUE_MAX_LEN, - required: true + required: true, } export const appDefaultIconBackground = '#D5F5F6' export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList' - - - diff --git a/web/context/dataset-detail.ts b/web/context/dataset-detail.ts index b507fbcc4c..362b13535a 100644 --- a/web/context/dataset-detail.ts +++ b/web/context/dataset-detail.ts @@ -1,5 +1,6 @@ import { createContext } from 'use-context-selector' +import type { DataSet } from '@/models/datasets' -const DatasetDetailContext = createContext<{ indexingTechnique?: string; }>({}) +const DatasetDetailContext = createContext<{ indexingTechnique?: string; dataset?: DataSet }>({}) export default DatasetDetailContext diff --git a/web/context/explore-context.ts b/web/context/explore-context.ts index 211586d7ec..796b6a5bfb 100644 --- a/web/context/explore-context.ts +++ b/web/context/explore-context.ts @@ -1,5 +1,5 @@ import { createContext } from 'use-context-selector' -import { InstalledApp } from '@/models/explore' +import type { InstalledApp } from '@/models/explore' type IExplore = { controlUpdateInstalledApps: number diff --git a/web/i18n/lang/app.en.ts b/web/i18n/lang/app.en.ts index cfa99606dd..3f12e0e9d3 100644 --- a/web/i18n/lang/app.en.ts +++ b/web/i18n/lang/app.en.ts @@ -39,7 +39,7 @@ const translation = { emoji: { ok: 'OK', cancel: 'Cancel', - } + }, } export default translation diff --git a/web/i18n/lang/app.zh.ts b/web/i18n/lang/app.zh.ts index 10eb8aab20..b07110b308 100644 --- a/web/i18n/lang/app.zh.ts +++ b/web/i18n/lang/app.zh.ts @@ -38,7 +38,7 @@ const translation = { emoji: { ok: '确认', cancel: '取消', - } + }, } export default translation diff --git a/web/i18n/lang/dataset-creation.en.ts b/web/i18n/lang/dataset-creation.en.ts index 2a0ecf574f..f937fad5b6 100644 --- a/web/i18n/lang/dataset-creation.en.ts +++ b/web/i18n/lang/dataset-creation.en.ts @@ -76,6 +76,8 @@ const translation = { fileName: 'Preprocess document', lastStep: 'Last step', nextStep: 'Save & Process', + save: 'Save & Process', + cancel: 'Cancel', sideTipTitle: 'Why segment and preprocess?', sideTipP1: 'When processing text data, segmentation and cleaning are two important preprocessing steps.', sideTipP2: 'Segmentation splits long text into paragraphs so models can understand better. This improves the quality and relevance of model results.', diff --git a/web/i18n/lang/dataset-creation.zh.ts b/web/i18n/lang/dataset-creation.zh.ts index 4fbaea9661..1ab2d642cb 100644 --- a/web/i18n/lang/dataset-creation.zh.ts +++ b/web/i18n/lang/dataset-creation.zh.ts @@ -76,6 +76,8 @@ const translation = { fileName: '预处理文档', lastStep: '上一步', nextStep: '保存并处理', + save: '保存并处理', + cancel: '取消', sideTipTitle: '为什么要分段和预处理?', sideTipP1: '在处理文本数据时,分段和清洗是两个重要的预处理步骤。', sideTipP2: '分段的目的是将长文本拆分成较小的段落,以便模型更有效地处理和理解。这有助于提高模型生成的结果的质量和相关性。', diff --git a/web/i18n/lang/explore.en.ts b/web/i18n/lang/explore.en.ts index 572516a599..f618bb430b 100644 --- a/web/i18n/lang/explore.en.ts +++ b/web/i18n/lang/explore.en.ts @@ -11,7 +11,7 @@ const translation = { delete: { title: 'Delete app', content: 'Are you sure you want to delete this app?', - } + }, }, apps: { title: 'Explore Apps by Dify', @@ -28,12 +28,12 @@ const translation = { nameRequired: 'App name is required', }, category: { - 'Assistant': 'Assistant', - 'Writing': 'Writing', - 'Translate': 'Translate', - 'Programming': 'Programming', - 'HR': 'HR', - } + Assistant: 'Assistant', + Writing: 'Writing', + Translate: 'Translate', + Programming: 'Programming', + HR: 'HR', + }, } export default translation diff --git a/web/i18n/lang/explore.zh.ts b/web/i18n/lang/explore.zh.ts index 9311fcb9ff..90d21c8e35 100644 --- a/web/i18n/lang/explore.zh.ts +++ b/web/i18n/lang/explore.zh.ts @@ -11,7 +11,7 @@ const translation = { delete: { title: '删除程序', content: '您确定要删除此程序吗?', - } + }, }, apps: { title: '探索 Dify 的应用', @@ -28,12 +28,12 @@ const translation = { nameRequired: '应用程序名称不能为空', }, category: { - 'Assistant': '助手', - 'Writing': '写作', - 'Translate': '翻译', - 'Programming': '编程', - 'HR': '人力资源', - } + Assistant: '助手', + Writing: '写作', + Translate: '翻译', + Programming: '编程', + HR: '人力资源', + }, } export default translation diff --git a/web/models/common.ts b/web/models/common.ts index df19701a8d..1b71ea8e7a 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -56,7 +56,7 @@ export type Member = Pick { return get('/explore/apps') } -export const fetchAppDetail = (id: string) : Promise => { +export const fetchAppDetail = (id: string): Promise => { return get(`/explore/apps/${id}`) } @@ -15,8 +15,8 @@ export const fetchInstalledAppList = () => { export const installApp = (id: string) => { return post('/installed-apps', { body: { - app_id: id - } + app_id: id, + }, }) } @@ -27,7 +27,7 @@ export const uninstallApp = (id: string) => { export const updatePinStatus = (id: string, isPinned: boolean) => { return patch(`/installed-apps/${id}`, { body: { - is_pinned: isPinned - } + is_pinned: isPinned, + }, }) } diff --git a/web/service/share.ts b/web/service/share.ts index 5a0812f6ba..316e9d47db 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -1,7 +1,7 @@ import type { IOnCompleted, IOnData, IOnError } from './base' -import { - get as consoleGet, post as consolePost, del as consoleDel, - getPublic as get, postPublic as post, ssePost, delPublic as del +import { + del as consoleDel, get as consoleGet, post as consolePost, + delPublic as del, getPublic as get, postPublic as post, ssePost, } from './base' import type { Feedbacktype } from '@/app/components/app/chat' @@ -23,7 +23,7 @@ function getUrl(url: string, isInstalledApp: boolean, installedAppId: string) { export const sendChatMessage = async (body: Record, { onData, onCompleted, onError, getAbortController }: { onData: IOnData onCompleted: IOnCompleted - onError: IOnError, + onError: IOnError getAbortController?: (abortController: AbortController) => void }, isInstalledApp: boolean, installedAppId = '') => { return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), { @@ -51,11 +51,11 @@ export const fetchAppInfo = async () => { return get('/site') } -export const fetchConversations = async (isInstalledApp: boolean, installedAppId='', last_id?: string) => { - return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: {...{ limit: 20 }, ...(last_id ? { last_id } : {}) } }) +export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string) => { + return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { ...{ limit: 20 }, ...(last_id ? { last_id } : {}) } }) } -export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId='') => { +export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId = '') => { return getAction('get', isInstalledApp)(getUrl('messages', isInstalledApp, installedAppId), { params: { conversation_id: conversationId, limit: 20, last_id: '' } }) } @@ -77,7 +77,7 @@ export const fetchMoreLikeThis = async (messageId: string, isInstalledApp: boole return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), { params: { response_mode: 'blocking', - } + }, }) } @@ -86,7 +86,7 @@ export const saveMessage = (messageId: string, isInstalledApp: boolean, installe } export const fetchSavedMessage = async (isInstalledApp: boolean, installedAppId = '') => { - return (getAction('get', isInstalledApp))(getUrl(`/saved-messages`, isInstalledApp, installedAppId)) + return (getAction('get', isInstalledApp))(getUrl('/saved-messages', isInstalledApp, installedAppId)) } export const removeMessage = (messageId: string, isInstalledApp: boolean, installedAppId = '') => { diff --git a/web/types/app.ts b/web/types/app.ts index 8c9d992d31..8989981dd9 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -38,16 +38,16 @@ export type PromptVariable = { } export type TextTypeFormItem = { - label: string, - variable: string, + label: string + variable: string required: boolean max_length: number } export type SelectTypeFormItem = { - label: string, - variable: string, - required: boolean, + label: string + variable: string + required: boolean options: string[] } /** @@ -59,7 +59,6 @@ export type UserInputFormItem = { 'select': SelectTypeFormItem } - export type ToolItem = { dataset: { enabled: boolean @@ -195,7 +194,7 @@ export type App = { icon: string /** Icon Background */ icon_background: string - + /** Mode */ mode: AppMode /** Enable web app */