mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-07-07 12:11:45 +08:00
Feat: Support re-segmentation (#114)
Co-authored-by: John Wang <takatost@gmail.com> Co-authored-by: Jyong <718720800@qq.com> Co-authored-by: 金伟强 <iamjoel007@gmail.com>
This commit is contained in:
parent
f65a3ad1cc
commit
c67f626b66
@ -208,9 +208,10 @@ class DatasetDocumentListApi(Resource):
|
|||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument('indexing_technique', type=str, choices=Dataset.INDEXING_TECHNIQUE_LIST, nullable=False,
|
parser.add_argument('indexing_technique', type=str, choices=Dataset.INDEXING_TECHNIQUE_LIST, nullable=False,
|
||||||
location='json')
|
location='json')
|
||||||
parser.add_argument('data_source', 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=True, nullable=True, 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('duplicate', type=bool, nullable=False, location='json')
|
||||||
|
parser.add_argument('original_document_id', type=str, required=False, location='json')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not dataset.indexing_technique and not args['indexing_technique']:
|
if not dataset.indexing_technique and not args['indexing_technique']:
|
||||||
@ -347,10 +348,12 @@ class DocumentIndexingStatusApi(DocumentResource):
|
|||||||
|
|
||||||
completed_segments = DocumentSegment.query \
|
completed_segments = DocumentSegment.query \
|
||||||
.filter(DocumentSegment.completed_at.isnot(None),
|
.filter(DocumentSegment.completed_at.isnot(None),
|
||||||
DocumentSegment.document_id == str(document_id)) \
|
DocumentSegment.document_id == str(document_id),
|
||||||
|
DocumentSegment.status != 're_segment') \
|
||||||
.count()
|
.count()
|
||||||
total_segments = DocumentSegment.query \
|
total_segments = DocumentSegment.query \
|
||||||
.filter_by(document_id=str(document_id)) \
|
.filter(DocumentSegment.document_id == str(document_id),
|
||||||
|
DocumentSegment.status != 're_segment') \
|
||||||
.count()
|
.count()
|
||||||
|
|
||||||
document.completed_segments = completed_segments
|
document.completed_segments = completed_segments
|
||||||
|
@ -12,7 +12,7 @@ from events.dataset_event import dataset_was_deleted
|
|||||||
from events.document_event import document_was_deleted
|
from events.document_event import document_was_deleted
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from models.account import Account
|
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 models.model import UploadFile
|
||||||
from services.errors.account import NoPermissionError
|
from services.errors.account import NoPermissionError
|
||||||
from services.errors.dataset import DatasetNameDuplicateError
|
from services.errors.dataset import DatasetNameDuplicateError
|
||||||
@ -20,6 +20,7 @@ from services.errors.document import DocumentIndexingError
|
|||||||
from services.errors.file import FileNotExistsError
|
from services.errors.file import FileNotExistsError
|
||||||
from tasks.deal_dataset_vector_index_task import deal_dataset_vector_index_task
|
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_task import document_indexing_task
|
||||||
|
from tasks.document_indexing_update_task import document_indexing_update_task
|
||||||
|
|
||||||
|
|
||||||
class DatasetService:
|
class DatasetService:
|
||||||
@ -276,6 +277,14 @@ class DocumentService:
|
|||||||
|
|
||||||
return document
|
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
|
@staticmethod
|
||||||
def get_document_file_detail(file_id: str):
|
def get_document_file_detail(file_id: str):
|
||||||
file_detail = db.session.query(UploadFile). \
|
file_detail = db.session.query(UploadFile). \
|
||||||
@ -355,6 +364,9 @@ class DocumentService:
|
|||||||
if dataset.indexing_technique == 'high_quality':
|
if dataset.indexing_technique == 'high_quality':
|
||||||
IndexBuilder.get_default_service_context(dataset.tenant_id)
|
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
|
# save process rule
|
||||||
if not dataset_process_rule:
|
if not dataset_process_rule:
|
||||||
process_rule = document_data["process_rule"]
|
process_rule = document_data["process_rule"]
|
||||||
@ -414,6 +426,76 @@ class DocumentService:
|
|||||||
|
|
||||||
# trigger async task
|
# trigger async task
|
||||||
document_indexing_task.delay(document.dataset_id, document.id)
|
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 '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(
|
||||||
|
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()
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 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_update_task.delay(document.dataset_id, document.id)
|
||||||
|
|
||||||
return document
|
return document
|
||||||
|
|
||||||
@ -443,6 +525,21 @@ class DocumentService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def document_create_args_validate(cls, args: dict):
|
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']:
|
if 'data_source' not in args or not args['data_source']:
|
||||||
raise ValueError("Data source is required")
|
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']:
|
if 'info' not in args['data_source'] or not args['data_source']['info']:
|
||||||
raise ValueError("Data source info is required")
|
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']:
|
if 'process_rule' not in args or not args['process_rule']:
|
||||||
raise ValueError("Process rule is required")
|
raise ValueError("Process rule is required")
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ def clean_document_task(document_id: str, dataset_id: str):
|
|||||||
index_node_ids = [segment.index_node_id for segment in segments]
|
index_node_ids = [segment.index_node_id for segment in segments]
|
||||||
|
|
||||||
# delete from vector index
|
# 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
|
# delete from keyword index
|
||||||
|
85
api/tasks/document_indexing_update_task.py
Normal file
85
api/tasks/document_indexing_update_task.py
Normal file
@ -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()
|
@ -42,7 +42,6 @@ def remove_document_from_index_task(document_id: str):
|
|||||||
keyword_table_index = KeywordTableIndex(dataset=dataset)
|
keyword_table_index = KeywordTableIndex(dataset=dataset)
|
||||||
|
|
||||||
# delete from vector index
|
# 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
|
# delete from keyword index
|
||||||
|
@ -21,7 +21,7 @@ export type AppCardProps = {
|
|||||||
|
|
||||||
const AppCard = ({
|
const AppCard = ({
|
||||||
app,
|
app,
|
||||||
onDelete
|
onDelete,
|
||||||
}: AppCardProps) => {
|
}: AppCardProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
import useSWRInfinite from 'swr/infinite'
|
||||||
import { debounce } from 'lodash-es'
|
import { debounce } from 'lodash-es'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppCard from './AppCard'
|
import AppCard from './AppCard'
|
||||||
import NewAppCard from './NewAppCard'
|
import NewAppCard from './NewAppCard'
|
||||||
import { AppListResponse } from '@/models/app'
|
import type { AppListResponse } from '@/models/app'
|
||||||
import { fetchAppList } from '@/service/apps'
|
import { fetchAppList } from '@/service/apps'
|
||||||
import { useSelector } from '@/context/app-context'
|
import { useSelector } from '@/context/app-context'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
|
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
|
||||||
if (!pageIndex || previousPageData.has_more)
|
if (!pageIndex || previousPageData.has_more)
|
||||||
@ -25,7 +25,7 @@ const Apps = () => {
|
|||||||
const anchorRef = useRef<HTMLAnchorElement>(null)
|
const anchorRef = useRef<HTMLAnchorElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${t('app.title')} - Dify`;
|
document.title = `${t('app.title')} - Dify`
|
||||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||||
mutate()
|
mutate()
|
||||||
@ -41,10 +41,9 @@ const Apps = () => {
|
|||||||
if (!loadingStateRef.current) {
|
if (!loadingStateRef.current) {
|
||||||
const { scrollTop, clientHeight } = pageContainerRef.current!
|
const { scrollTop, clientHeight } = pageContainerRef.current!
|
||||||
const anchorOffset = anchorRef.current!.offsetTop
|
const anchorOffset = anchorRef.current!.offsetTop
|
||||||
if (anchorOffset - scrollTop - clientHeight < 100) {
|
if (anchorOffset - scrollTop - clientHeight < 100)
|
||||||
setSize(size => size + 1)
|
setSize(size => size + 1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, 50)
|
}, 50)
|
||||||
|
|
||||||
pageContainerRef.current?.addEventListener('scroll', onScroll)
|
pageContainerRef.current?.addEventListener('scroll', onScroll)
|
||||||
|
@ -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 (
|
||||||
|
<Settings datasetId={datasetId} documentId={documentId} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocumentSettings
|
@ -164,7 +164,10 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
extraInfo={<ExtraInfo />}
|
extraInfo={<ExtraInfo />}
|
||||||
iconType='dataset'
|
iconType='dataset'
|
||||||
/>}
|
/>}
|
||||||
<DatasetDetailContext.Provider value={{ indexingTechnique: datasetRes?.indexing_technique }}>
|
<DatasetDetailContext.Provider value={{
|
||||||
|
indexingTechnique: datasetRes?.indexing_technique,
|
||||||
|
dataset: datasetRes,
|
||||||
|
}}>
|
||||||
<div className="bg-white grow">{children}</div>
|
<div className="bg-white grow">{children}</div>
|
||||||
</DatasetDetailContext.Provider>
|
</DatasetDetailContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useContext, useContextSelector } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import useSWR from 'swr'
|
|
||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import classNames from 'classnames'
|
||||||
import style from '../list.module.css'
|
import style from '../list.module.css'
|
||||||
import type { App } from '@/types/app'
|
|
||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
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 AppIcon from '@/app/components/base/app-icon'
|
||||||
import AppsContext from '@/context/app-context'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import { DataSet } from '@/models/datasets'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
export type DatasetCardProps = {
|
export type DatasetCardProps = {
|
||||||
dataset: DataSet
|
dataset: DataSet
|
||||||
@ -23,7 +20,7 @@ export type DatasetCardProps = {
|
|||||||
|
|
||||||
const DatasetCard = ({
|
const DatasetCard = ({
|
||||||
dataset,
|
dataset,
|
||||||
onDelete
|
onDelete,
|
||||||
}: DatasetCardProps) => {
|
}: DatasetCardProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
import useSWRInfinite from 'swr/infinite'
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es'
|
||||||
import { DataSetListResponse } from '@/models/datasets';
|
|
||||||
import NewDatasetCard from './NewDatasetCard'
|
import NewDatasetCard from './NewDatasetCard'
|
||||||
import DatasetCard from './DatasetCard';
|
import DatasetCard from './DatasetCard'
|
||||||
import { fetchDatasets } from '@/service/datasets';
|
import type { DataSetListResponse } from '@/models/datasets'
|
||||||
import { useSelector } from '@/context/app-context';
|
import { fetchDatasets } from '@/service/datasets'
|
||||||
|
import { useSelector } from '@/context/app-context'
|
||||||
|
|
||||||
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
|
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
|
||||||
if (!pageIndex || previousPageData.has_more)
|
if (!pageIndex || previousPageData.has_more)
|
||||||
@ -30,10 +30,9 @@ const Datasets = () => {
|
|||||||
if (!loadingStateRef.current) {
|
if (!loadingStateRef.current) {
|
||||||
const { scrollTop, clientHeight } = pageContainerRef.current!
|
const { scrollTop, clientHeight } = pageContainerRef.current!
|
||||||
const anchorOffset = anchorRef.current!.offsetTop
|
const anchorOffset = anchorRef.current!.offsetTop
|
||||||
if (anchorOffset - scrollTop - clientHeight < 100) {
|
if (anchorOffset - scrollTop - clientHeight < 100)
|
||||||
setSize(size => size + 1)
|
setSize(size => size + 1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, 50)
|
}, 50)
|
||||||
|
|
||||||
pageContainerRef.current?.addEventListener('scroll', onScroll)
|
pageContainerRef.current?.addEventListener('scroll', onScroll)
|
||||||
@ -43,7 +42,7 @@ const Datasets = () => {
|
|||||||
return (
|
return (
|
||||||
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'>
|
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'>
|
||||||
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
||||||
<DatasetCard key={dataset.id} dataset={dataset} onDelete={mutate} />)
|
<DatasetCard key={dataset.id} dataset={dataset} onDelete={mutate} />),
|
||||||
))}
|
))}
|
||||||
<NewDatasetCard ref={anchorRef} />
|
<NewDatasetCard ref={anchorRef} />
|
||||||
</nav>
|
</nav>
|
||||||
@ -51,4 +50,3 @@ const Datasets = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Datasets
|
export default Datasets
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import AppList from "@/app/components/explore/app-list"
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import AppList from '@/app/components/explore/app-list'
|
||||||
|
|
||||||
const Apps = ({ }) => {
|
const Apps = () => {
|
||||||
return <AppList />
|
return <AppList />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
import Main from '@/app/components/explore/installed-app'
|
import Main from '@/app/components/explore/installed-app'
|
||||||
|
|
||||||
export interface IInstalledAppProps {
|
export type IInstalledAppProps = {
|
||||||
params: {
|
params: {
|
||||||
appId: string
|
appId: string
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import type { IMainProps } from '@/app/components/share/chat'
|
|||||||
import Main from '@/app/components/share/chat'
|
import Main from '@/app/components/share/chat'
|
||||||
|
|
||||||
const Chat: FC<IMainProps> = () => {
|
const Chat: FC<IMainProps> = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main />
|
<Main />
|
||||||
)
|
)
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean, useClickAway } from 'ahooks'
|
import { useBoolean, useClickAway } from 'ahooks'
|
||||||
|
import { ChevronDownIcon, Cog8ToothIcon, InformationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
import ParamItem from './param-item'
|
import ParamItem from './param-item'
|
||||||
import Radio from '@/app/components/base/radio'
|
import Radio from '@/app/components/base/radio'
|
||||||
import Panel from '@/app/components/base/panel'
|
import Panel from '@/app/components/base/panel'
|
||||||
import type { CompletionParams } from '@/models/debug'
|
import type { CompletionParams } from '@/models/debug'
|
||||||
import { Cog8ToothIcon, InformationCircleIcon, ChevronDownIcon } from '@heroicons/react/24/outline'
|
|
||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
import { TONE_LIST } from '@/config'
|
import { TONE_LIST } from '@/config'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
@ -51,7 +51,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isChatApp = mode === AppType.chat
|
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 [isShowConfig, { setFalse: hideConfig, toggle: toogleShowConfig }] = useBoolean(false)
|
||||||
const configContentRef = React.useRef(null)
|
const configContentRef = React.useRef(null)
|
||||||
useClickAway(() => {
|
useClickAway(() => {
|
||||||
@ -119,11 +119,11 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
|||||||
if (id !== 'gpt-4' && completionParams.max_tokens > 4000) {
|
if (id !== 'gpt-4' && completionParams.max_tokens > 4000) {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: t('common.model.params.setToCurrentModelMaxTokenTip')
|
message: t('common.model.params.setToCurrentModelMaxTokenTip'),
|
||||||
})
|
})
|
||||||
onCompletionParamsChange({
|
onCompletionParamsChange({
|
||||||
...completionParams,
|
...completionParams,
|
||||||
max_tokens: 4000
|
max_tokens: 4000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setModelId(id)
|
setModelId(id)
|
||||||
@ -153,7 +153,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
|||||||
setToneId(id)
|
setToneId(id)
|
||||||
onCompletionParamsChange({
|
onCompletionParamsChange({
|
||||||
...tone.config,
|
...tone.config,
|
||||||
max_tokens: completionParams.max_tokens
|
max_tokens: completionParams.max_tokens,
|
||||||
} as CompletionParams)
|
} as CompletionParams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className='relative' ref={configContentRef}>
|
<div className='relative' ref={configContentRef}>
|
||||||
<div
|
<div
|
||||||
className={cn(`flex items-center border h-8 px-2.5 space-x-2 rounded-lg`, disabled ? diabledStyle : ableStyle)}
|
className={cn('flex items-center border h-8 px-2.5 space-x-2 rounded-lg', disabled ? diabledStyle : ableStyle)}
|
||||||
onClick={() => !disabled && toogleShowConfig()}
|
onClick={() => !disabled && toogleShowConfig()}
|
||||||
>
|
>
|
||||||
<ModelIcon />
|
<ModelIcon />
|
||||||
@ -207,13 +207,13 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
|||||||
<div>{t('appDebug.modelConfig.model')}</div>
|
<div>{t('appDebug.modelConfig.model')}</div>
|
||||||
{/* model selector */}
|
{/* model selector */}
|
||||||
<div className="relative" style={{ zIndex: 30 }}>
|
<div className="relative" style={{ zIndex: 30 }}>
|
||||||
<div ref={triggerRef} onClick={() => !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 ")}>
|
<div ref={triggerRef} onClick={() => !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 ')}>
|
||||||
<ModelIcon />
|
<ModelIcon />
|
||||||
<div className="text-sm gray-900">{selectedModel?.name}</div>
|
<div className="text-sm gray-900">{selectedModel?.name}</div>
|
||||||
{!selectModelDisabled && <ChevronDownIcon className={cn(isShowOption && 'rotate-180', 'w-[14px] h-[14px] text-gray-500')} />}
|
{!selectModelDisabled && <ChevronDownIcon className={cn(isShowOption && 'rotate-180', 'w-[14px] h-[14px] text-gray-500')} />}
|
||||||
</div>
|
</div>
|
||||||
{isShowOption && (
|
{isShowOption && (
|
||||||
<div className={cn(isChatApp ? 'w-[159px]' : 'w-[179px]', "absolute right-0 bg-gray-50 rounded-lg shadow")}>
|
<div className={cn(isChatApp ? 'w-[159px]' : 'w-[179px]', 'absolute right-0 bg-gray-50 rounded-lg shadow')}>
|
||||||
{availableModels.map(item => (
|
{availableModels.map(item => (
|
||||||
<div key={item.id} onClick={handleSelectModel(item.id)} className="flex items-center h-9 px-3 rounded-lg cursor-pointer hover:bg-gray-100">
|
<div key={item.id} onClick={handleSelectModel(item.id)} className="flex items-center h-9 px-3 rounded-lg cursor-pointer hover:bg-gray-100">
|
||||||
<ModelIcon className='mr-2' />
|
<ModelIcon className='mr-2' />
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC, useEffect } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Link from 'next/link'
|
||||||
import { DataSet } from '@/models/datasets'
|
|
||||||
import TypeIcon from '../type-icon'
|
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 Button from '@/app/components/base/button'
|
||||||
import { fetchDatasets } from '@/service/datasets'
|
import { fetchDatasets } from '@/service/datasets'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
import Link from 'next/link'
|
|
||||||
|
|
||||||
import s from './style.module.css'
|
export type ISelectDataSetProps = {
|
||||||
|
|
||||||
export interface ISelectDataSetProps {
|
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
selectedIds: string[]
|
selectedIds: string[]
|
||||||
@ -37,22 +37,21 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
|||||||
const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1 } })
|
const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1 } })
|
||||||
setDataSets(data)
|
setDataSets(data)
|
||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
setSelected(data.filter((item) => selectedIds.includes(item.id)))
|
setSelected(data.filter(item => selectedIds.includes(item.id)))
|
||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
const toggleSelect = (dataSet: DataSet) => {
|
const toggleSelect = (dataSet: DataSet) => {
|
||||||
const isSelected = selected.some((item) => item.id === dataSet.id)
|
const isSelected = selected.some(item => item.id === dataSet.id)
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setSelected(selected.filter((item) => item.id !== dataSet.id))
|
setSelected(selected.filter(item => item.id !== dataSet.id))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (canSelectMulti) {
|
if (canSelectMulti)
|
||||||
setSelected([...selected, dataSet])
|
setSelected([...selected, dataSet])
|
||||||
} else {
|
else
|
||||||
setSelected([dataSet])
|
setSelected([dataSet])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = () => {
|
const handleSelect = () => {
|
||||||
onSelect(selected)
|
onSelect(selected)
|
||||||
@ -74,7 +73,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
|||||||
<div className='flex items-center justify-center mt-6 rounded-lg space-x-1 h-[128px] text-[13px] border'
|
<div className='flex items-center justify-center mt-6 rounded-lg space-x-1 h-[128px] text-[13px] border'
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(0, 0, 0, 0.02)',
|
background: 'rgba(0, 0, 0, 0.02)',
|
||||||
borderColor: 'rgba(0, 0, 0, 0.02'
|
borderColor: 'rgba(0, 0, 0, 0.02',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className='text-gray-500'>{t('appDebug.feature.dataSet.noDataSet')}</span>
|
<span className='text-gray-500'>{t('appDebug.feature.dataSet.noDataSet')}</span>
|
||||||
@ -85,7 +84,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
|||||||
{datasets && datasets?.length > 0 && (
|
{datasets && datasets?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className='mt-7 space-y-1 max-h-[286px] overflow-y-auto'>
|
<div className='mt-7 space-y-1 max-h-[286px] overflow-y-auto'>
|
||||||
{datasets.map((item) => (
|
{datasets.map(item => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={cn(s.item, selected.some(i => 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')}
|
className={cn(s.item, selected.some(i => 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')}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
/* eslint-disable multiline-ternary */
|
||||||
'use client'
|
'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 cn from 'classnames'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import ConfigContext from '@/context/debug-configuration'
|
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
|
import ConfigContext from '@/context/debug-configuration'
|
||||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
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 { getNewVar } from '@/utils/var'
|
||||||
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
|
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
|
||||||
|
|
||||||
export interface IOpeningStatementProps {
|
export type IOpeningStatementProps = {
|
||||||
promptTemplate: string
|
promptTemplate: string
|
||||||
value: string
|
value: string
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
@ -25,7 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g
|
|||||||
|
|
||||||
const OpeningStatement: FC<IOpeningStatementProps> = ({
|
const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||||
value = '',
|
value = '',
|
||||||
onChange
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
@ -61,8 +63,6 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||||||
.replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>`
|
.replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>`
|
||||||
.replace(/\n/g, '<br />')
|
.replace(/\n/g, '<br />')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
setFocus()
|
setFocus()
|
||||||
}
|
}
|
||||||
@ -76,15 +76,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
const keys = getInputKeys(tempValue)
|
const keys = getInputKeys(tempValue)
|
||||||
const promptKeys = promptVariables.map((item) => item.key)
|
const promptKeys = promptVariables.map(item => item.key)
|
||||||
let notIncludeKeys: string[] = []
|
let notIncludeKeys: string[] = []
|
||||||
|
|
||||||
if (promptKeys.length === 0) {
|
if (promptKeys.length === 0) {
|
||||||
if (keys.length > 0) {
|
if (keys.length > 0)
|
||||||
notIncludeKeys = keys
|
notIncludeKeys = keys
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
notIncludeKeys = keys.filter((key) => !promptKeys.includes(key))
|
notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notIncludeKeys.length > 0) {
|
if (notIncludeKeys.length > 0) {
|
||||||
@ -104,7 +104,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||||||
|
|
||||||
const autoAddVar = () => {
|
const autoAddVar = () => {
|
||||||
const newModelConfig = produce(modelConfig, (draft) => {
|
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)
|
onChange(tempValue)
|
||||||
setModelConfig(newModelConfig)
|
setModelConfig(newModelConfig)
|
||||||
@ -130,9 +130,11 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||||||
isFocus={isFocus}
|
isFocus={isFocus}
|
||||||
>
|
>
|
||||||
<div className='text-gray-700 text-sm'>
|
<div className='text-gray-700 text-sm'>
|
||||||
{(hasValue || (!hasValue && isFocus)) ? (
|
{(hasValue || (!hasValue && isFocus))
|
||||||
|
? (
|
||||||
<>
|
<>
|
||||||
{isFocus ? (
|
{isFocus
|
||||||
|
? (
|
||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={tempValue}
|
value={tempValue}
|
||||||
@ -142,14 +144,16 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||||||
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<div dangerouslySetInnerHTML={{
|
<div dangerouslySetInnerHTML={{
|
||||||
__html: coloredContent
|
__html: coloredContent,
|
||||||
}}></div>
|
}}></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Operation Bar */}
|
{/* Operation Bar */}
|
||||||
{isFocus && (
|
{isFocus
|
||||||
|
&& (
|
||||||
<div className='mt-2 flex items-center justify-between'>
|
<div className='mt-2 flex items-center justify-between'>
|
||||||
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
|
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
|
||||||
|
|
||||||
|
@ -6,12 +6,12 @@ import { useContext } from 'use-context-selector'
|
|||||||
import {
|
import {
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
} from '@heroicons/react/24/solid'
|
} from '@heroicons/react/24/solid'
|
||||||
|
import VarIcon from '../base/icons/var-icon'
|
||||||
import ConfigContext from '@/context/debug-configuration'
|
import ConfigContext from '@/context/debug-configuration'
|
||||||
import type { PromptVariable } from '@/models/debug'
|
import type { PromptVariable } from '@/models/debug'
|
||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
import Select from '@/app/components/base/select'
|
import Select from '@/app/components/base/select'
|
||||||
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
||||||
import VarIcon from '../base/icons/var-icon'
|
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
export type IPromptValuePanelProps = {
|
export type IPromptValuePanelProps = {
|
||||||
@ -71,7 +71,8 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='mt-2 leading-normal'>
|
<div className='mt-2 leading-normal'>
|
||||||
{
|
{
|
||||||
(promptTemplate && promptTemplate?.trim()) ? (
|
(promptTemplate && promptTemplate?.trim())
|
||||||
|
? (
|
||||||
<div
|
<div
|
||||||
className="max-h-48 overflow-y-auto text-sm text-gray-700 break-all"
|
className="max-h-48 overflow-y-auto text-sm text-gray-700 break-all"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
@ -79,7 +80,8 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noPrompt')}</div>
|
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noPrompt')}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -105,12 +107,14 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
promptVariables.length > 0 ? (
|
promptVariables.length > 0
|
||||||
|
? (
|
||||||
<div className="space-y-3 ">
|
<div className="space-y-3 ">
|
||||||
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
|
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
|
||||||
<div key={key} className="flex items-center justify-between">
|
<div key={key} className="flex items-center justify-between">
|
||||||
<div className="mr-1 shrink-0 w-[120px] text-sm text-gray-900">{name || key}</div>
|
<div className="mr-1 shrink-0 w-[120px] text-sm text-gray-900">{name || key}</div>
|
||||||
{type === 'select' ? (
|
{type === 'select'
|
||||||
|
? (
|
||||||
<Select
|
<Select
|
||||||
className='w-full'
|
className='w-full'
|
||||||
defaultValue={inputs[key] as string}
|
defaultValue={inputs[key] as string}
|
||||||
@ -119,7 +123,8 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
|||||||
allowSearch={false}
|
allowSearch={false}
|
||||||
bgClassName='bg-gray-50'
|
bgClassName='bg-gray-50'
|
||||||
/>
|
/>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<input
|
<input
|
||||||
className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||||
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
||||||
@ -133,7 +138,8 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
|
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC, useState } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
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 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 { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
|
||||||
import { useBoolean } from 'ahooks'
|
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'
|
import { fetchMoreLikeThis, updateFeedback } from '@/service/share'
|
||||||
|
|
||||||
const MAX_DEPTH = 3
|
const MAX_DEPTH = 3
|
||||||
export interface IGenerationItemProps {
|
export type IGenerationItemProps = {
|
||||||
className?: string
|
className?: string
|
||||||
content: string
|
content: string
|
||||||
messageId?: string | null
|
messageId?: string | null
|
||||||
@ -24,13 +25,13 @@ export interface IGenerationItemProps {
|
|||||||
onFeedback?: (feedback: Feedbacktype) => void
|
onFeedback?: (feedback: Feedbacktype) => void
|
||||||
onSave?: (messageId: string) => void
|
onSave?: (messageId: string) => void
|
||||||
isMobile?: boolean
|
isMobile?: boolean
|
||||||
isInstalledApp: boolean,
|
isInstalledApp: boolean
|
||||||
installedAppId?: string,
|
installedAppId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SimpleBtn = ({ className, onClick, children }: {
|
export const SimpleBtn = ({ className, onClick, children }: {
|
||||||
className?: string
|
className?: string
|
||||||
onClick?: () => void,
|
onClick?: () => void
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
@ -88,7 +89,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
const [childMessageId, setChildMessageId] = useState<string | null>(null)
|
const [childMessageId, setChildMessageId] = useState<string | null>(null)
|
||||||
const hasChild = !!childMessageId
|
const hasChild = !!childMessageId
|
||||||
const [childFeedback, setChildFeedback] = useState<Feedbacktype>({
|
const [childFeedback, setChildFeedback] = useState<Feedbacktype>({
|
||||||
rating: null
|
rating: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleFeedback = async (childFeedback: Feedbacktype) => {
|
const handleFeedback = async (childFeedback: Feedbacktype) => {
|
||||||
@ -126,24 +127,30 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mainStyle = (() => {
|
const mainStyle = (() => {
|
||||||
const res: any = !isTop ? {
|
const res: any = !isTop
|
||||||
background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff'
|
? {
|
||||||
} : {}
|
background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff',
|
||||||
|
|
||||||
if (hasChild) {
|
|
||||||
res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)'
|
|
||||||
}
|
}
|
||||||
|
: {}
|
||||||
|
|
||||||
|
if (hasChild)
|
||||||
|
res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)'
|
||||||
|
|
||||||
return res
|
return res
|
||||||
})()
|
})()
|
||||||
return (
|
return (
|
||||||
<div className={cn(className, isTop ? 'rounded-xl border border-gray-200 bg-white' : 'rounded-br-xl !mt-0')}
|
<div className={cn(className, isTop ? 'rounded-xl border border-gray-200 bg-white' : 'rounded-br-xl !mt-0')}
|
||||||
style={isTop ? {
|
style={isTop
|
||||||
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)'
|
? {
|
||||||
} : {}}
|
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
|
||||||
|
}
|
||||||
|
: {}}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading
|
||||||
|
? (
|
||||||
<div className='flex items-center h-10'><Loading type='area' /></div>
|
<div className='flex items-center h-10'><Loading type='area' /></div>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<div
|
<div
|
||||||
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
|
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
|
||||||
style={mainStyle}
|
style={mainStyle}
|
||||||
@ -185,7 +192,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFeedback?.({
|
onFeedback?.({
|
||||||
rating: 'like'
|
rating: 'like',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||||
@ -194,7 +201,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFeedback?.({
|
onFeedback?.({
|
||||||
rating: 'dislike'
|
rating: 'dislike',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||||
@ -207,7 +214,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFeedback?.({
|
onFeedback?.({
|
||||||
rating: null
|
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'>
|
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'>
|
||||||
@ -218,7 +225,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFeedback?.({
|
onFeedback?.({
|
||||||
rating: null
|
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'>
|
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'>
|
||||||
@ -235,7 +242,6 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{((childMessageId || isQuerying) && depth < 3) && (
|
{((childMessageId || isQuerying) && depth < 3) && (
|
||||||
<div className='pl-4'>
|
<div className='pl-4'>
|
||||||
<GenerationItem {...childProps} />
|
<GenerationItem {...childProps} />
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import style from './style.module.css'
|
|
||||||
|
|
||||||
import data from '@emoji-mart/data'
|
import data from '@emoji-mart/data'
|
||||||
import { init } from 'emoji-mart'
|
import { init } from 'emoji-mart'
|
||||||
|
import style from './style.module.css'
|
||||||
|
|
||||||
init({ data })
|
init({ data })
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ const AppIcon: FC<AppIconProps> = ({
|
|||||||
}}
|
}}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{innerIcon ? innerIcon : icon && icon !== '' ? <em-emoji id={icon} /> : <em-emoji id='🤖' />}
|
{innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />)}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
import type { ChangeEvent, FC } from 'react'
|
import type { ChangeEvent, FC } from 'react'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { checkKeys } from '@/utils/var'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '@/app/components/base/button'
|
|
||||||
import Toast from '../toast'
|
import Toast from '../toast'
|
||||||
import { varHighlightHTML } from '../../app/configuration/base/var-highlight'
|
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
|
// regex to match the {{}} and replace it with a span
|
||||||
const regex = /\{\{([^}]+)\}\}/g
|
const regex = /\{\{([^}]+)\}\}/g
|
||||||
@ -55,9 +55,9 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditing && contentEditableRef.current) {
|
if (isEditing && contentEditableRef.current) {
|
||||||
// TODO: Focus at the click positon
|
// TODO: Focus at the click positon
|
||||||
if (currentValue) {
|
if (currentValue)
|
||||||
contentEditableRef.current.setSelectionRange(currentValue.length, currentValue.length)
|
contentEditableRef.current.setSelectionRange(currentValue.length, currentValue.length)
|
||||||
}
|
|
||||||
contentEditableRef.current.focus()
|
contentEditableRef.current.focus()
|
||||||
}
|
}
|
||||||
}, [isEditing])
|
}, [isEditing])
|
||||||
@ -73,7 +73,6 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
.replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>`
|
.replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>`
|
||||||
.replace(/\n/g, '<br />')
|
.replace(/\n/g, '<br />')
|
||||||
|
|
||||||
|
|
||||||
// Not use useCallback. That will cause out callback get old data.
|
// Not use useCallback. That will cause out callback get old data.
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (onConfirm) {
|
if (onConfirm) {
|
||||||
@ -83,7 +82,7 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey })
|
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -125,9 +124,9 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
value={currentValue}
|
value={currentValue}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
blur()
|
blur()
|
||||||
if (!isContentChanged) {
|
if (!isContentChanged)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
}
|
|
||||||
// click confirm also make blur. Then outter value is change. So below code has problem.
|
// click confirm also make blur. Then outter value is change. So below code has problem.
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// handleCancel()
|
// handleCancel()
|
||||||
@ -143,7 +142,8 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
{textAreaContent}
|
{textAreaContent}
|
||||||
{/* footer */}
|
{/* footer */}
|
||||||
<div className='flex item-center h-14 px-4'>
|
<div className='flex item-center h-14 px-4'>
|
||||||
{isContentChanged ? (
|
{isContentChanged
|
||||||
|
? (
|
||||||
<div className='flex items-center justify-between w-full'>
|
<div className='flex items-center justify-between w-full'>
|
||||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue.length}</div>
|
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue.length}</div>
|
||||||
<div className='flex space-x-2'>
|
<div className='flex space-x-2'>
|
||||||
@ -163,7 +163,8 @@ const BlockInput: FC<IBlockInputProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<p className="leading-5 text-xs text-gray-500">
|
<p className="leading-5 text-xs text-gray-500">
|
||||||
{t('appDebug.promptTip')}
|
{t('appDebug.promptTip')}
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
|
/* eslint-disable multiline-ternary */
|
||||||
'use client'
|
'use client'
|
||||||
import React from 'react'
|
import type { ChangeEvent, FC } from 'react'
|
||||||
import { useState, FC, ChangeEvent } from 'react'
|
import React, { useState } from 'react'
|
||||||
import data from '@emoji-mart/data'
|
import data from '@emoji-mart/data'
|
||||||
import { init, SearchIndex } from 'emoji-mart'
|
import { SearchIndex, init } from 'emoji-mart'
|
||||||
import cn from 'classnames'
|
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 Divider from '@/app/components/base/divider'
|
||||||
import Button from '@/app/components/base/button'
|
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 Modal from '@/app/components/base/modal'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
interface IntrinsicElements {
|
interface IntrinsicElements {
|
||||||
'em-emoji': React.DetailedHTMLProps<
|
'em-emoji': React.DetailedHTMLProps<
|
||||||
React.HTMLAttributes<HTMLElement>,
|
React.HTMLAttributes<HTMLElement>,
|
||||||
HTMLElement
|
HTMLElement
|
||||||
>;
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +59,7 @@ const backgroundColors = [
|
|||||||
'#ECE9FE',
|
'#ECE9FE',
|
||||||
'#FFE4E8',
|
'#FFE4E8',
|
||||||
]
|
]
|
||||||
interface IEmojiPickerProps {
|
type IEmojiPickerProps = {
|
||||||
isModal?: boolean
|
isModal?: boolean
|
||||||
onSelect?: (emoji: string, background: string) => void
|
onSelect?: (emoji: string, background: string) => void
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
@ -66,7 +68,7 @@ interface IEmojiPickerProps {
|
|||||||
const EmojiPicker: FC<IEmojiPickerProps> = ({
|
const EmojiPicker: FC<IEmojiPickerProps> = ({
|
||||||
isModal = true,
|
isModal = true,
|
||||||
onSelect,
|
onSelect,
|
||||||
onClose
|
onClose,
|
||||||
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -97,8 +99,8 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
|
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.value === '') {
|
if (e.target.value === '') {
|
||||||
setIsSearching(false)
|
setIsSearching(false)
|
||||||
return
|
}
|
||||||
} else {
|
else {
|
||||||
setIsSearching(true)
|
setIsSearching(true)
|
||||||
const emojis = await search(e.target.value)
|
const emojis = await search(e.target.value)
|
||||||
setSearchedEmojis(emojis)
|
setSearchedEmojis(emojis)
|
||||||
@ -111,7 +113,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
|
|
||||||
<div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
|
<div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
|
||||||
{isSearching && <>
|
{isSearching && <>
|
||||||
<div key={`category-search`} className='flex flex-col'>
|
<div key={'category-search'} className='flex flex-col'>
|
||||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p>
|
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p>
|
||||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||||
{searchedEmojis.map((emoji: string, index: number) => {
|
{searchedEmojis.map((emoji: string, index: number) => {
|
||||||
@ -131,7 +133,6 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
|
|
||||||
{categories.map((category: any, index: number) => {
|
{categories.map((category: any, index: number) => {
|
||||||
return <div key={`category-${index}`} className='flex flex-col'>
|
return <div key={`category-${index}`} className='flex flex-col'>
|
||||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p>
|
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p>
|
||||||
@ -156,7 +157,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Color Select */}
|
{/* Color Select */}
|
||||||
<div className={cn('p-3 ', selectedEmoji == '' ? 'opacity-25' : '')}>
|
<div className={cn('p-3 ', selectedEmoji === '' ? 'opacity-25' : '')}>
|
||||||
<p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p>
|
<p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p>
|
||||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||||
{backgroundColors.map((color) => {
|
{backgroundColors.map((color) => {
|
||||||
@ -165,9 +166,9 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
className={
|
className={
|
||||||
cn(
|
cn(
|
||||||
'cursor-pointer',
|
'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',
|
'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={() => {
|
onClick={() => {
|
||||||
setSelectedBackground(color)
|
setSelectedBackground(color)
|
||||||
@ -191,7 +192,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
{t('app.emoji.cancel')}
|
{t('app.emoji.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedEmoji == ''}
|
disabled={selectedEmoji === ''}
|
||||||
type="primary"
|
type="primary"
|
||||||
className='w-full'
|
className='w-full'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
createDocument,
|
createDocument,
|
||||||
fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
|
fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
|
||||||
} from '@/service/datasets'
|
} from '@/service/datasets'
|
||||||
import type { CreateDocumentReq, createDocumentResponse } from '@/models/datasets'
|
import type { CreateDocumentReq, createDocumentResponse, FullDocumentDetail } from '@/models/datasets'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import PreviewItem from './preview-item'
|
import PreviewItem from './preview-item'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -22,14 +22,18 @@ import Toast from '@/app/components/base/toast'
|
|||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
|
|
||||||
type StepTwoProps = {
|
type StepTwoProps = {
|
||||||
|
isSetting?: boolean,
|
||||||
|
documentDetail?: FullDocumentDetail
|
||||||
hasSetAPIKEY: boolean,
|
hasSetAPIKEY: boolean,
|
||||||
onSetting: () => void,
|
onSetting: () => void,
|
||||||
datasetId?: string,
|
datasetId?: string,
|
||||||
indexingType?: string,
|
indexingType?: string,
|
||||||
file?: File,
|
file?: File,
|
||||||
onStepChange: (delta: number) => void,
|
onStepChange?: (delta: number) => void,
|
||||||
updateIndexingTypeCache: (type: string) => void,
|
updateIndexingTypeCache?: (type: string) => void,
|
||||||
updateResultCache: (res: createDocumentResponse) => void
|
updateResultCache?: (res: createDocumentResponse) => void
|
||||||
|
onSave?: () => void
|
||||||
|
onCancel?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SegmentType {
|
enum SegmentType {
|
||||||
@ -42,6 +46,8 @@ enum IndexingType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const StepTwo = ({
|
const StepTwo = ({
|
||||||
|
isSetting,
|
||||||
|
documentDetail,
|
||||||
hasSetAPIKEY,
|
hasSetAPIKEY,
|
||||||
onSetting,
|
onSetting,
|
||||||
datasetId,
|
datasetId,
|
||||||
@ -50,6 +56,8 @@ const StepTwo = ({
|
|||||||
onStepChange,
|
onStepChange,
|
||||||
updateIndexingTypeCache,
|
updateIndexingTypeCache,
|
||||||
updateResultCache,
|
updateResultCache,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
}: StepTwoProps) => {
|
}: StepTwoProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
@ -171,7 +179,14 @@ const StepTwo = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getCreationParams = () => {
|
const getCreationParams = () => {
|
||||||
const params = {
|
let params
|
||||||
|
if (isSetting) {
|
||||||
|
params = {
|
||||||
|
original_document_id: documentDetail?.id,
|
||||||
|
process_rule: getProcessRule(),
|
||||||
|
} as CreateDocumentReq
|
||||||
|
} else {
|
||||||
|
params = {
|
||||||
data_source: {
|
data_source: {
|
||||||
type: 'upload_file',
|
type: 'upload_file',
|
||||||
info: file?.id,
|
info: file?.id,
|
||||||
@ -180,6 +195,7 @@ const StepTwo = ({
|
|||||||
indexing_technique: getIndexing_technique(),
|
indexing_technique: getIndexing_technique(),
|
||||||
process_rule: getProcessRule(),
|
process_rule: getProcessRule(),
|
||||||
} as CreateDocumentReq
|
} as CreateDocumentReq
|
||||||
|
}
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +212,25 @@ const StepTwo = ({
|
|||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRulesFromDetail = () => {
|
||||||
|
if (documentDetail) {
|
||||||
|
const rules = documentDetail.dataset_process_rule.rules
|
||||||
|
const separator = rules.segmentation.separator
|
||||||
|
const max = rules.segmentation.max_tokens
|
||||||
|
setSegmentIdentifier(separator === '\n' ? '\\n' : separator || '\\n')
|
||||||
|
setMax(max)
|
||||||
|
setRules(rules.pre_processing_rules)
|
||||||
|
setDefaultConfig(rules)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultMode = () => {
|
||||||
|
if (documentDetail) {
|
||||||
|
setSegmentationType(documentDetail.dataset_process_rule.mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createHandle = async () => {
|
const createHandle = async () => {
|
||||||
try {
|
try {
|
||||||
let res;
|
let res;
|
||||||
@ -204,19 +239,20 @@ const StepTwo = ({
|
|||||||
res = await createFirstDocument({
|
res = await createFirstDocument({
|
||||||
body: params
|
body: params
|
||||||
})
|
})
|
||||||
updateIndexingTypeCache(indexType)
|
updateIndexingTypeCache && updateIndexingTypeCache(indexType)
|
||||||
updateResultCache(res)
|
updateResultCache && updateResultCache(res)
|
||||||
} else {
|
} else {
|
||||||
res = await createDocument({
|
res = await createDocument({
|
||||||
datasetId,
|
datasetId,
|
||||||
body: params
|
body: params
|
||||||
})
|
})
|
||||||
updateIndexingTypeCache(indexType)
|
updateIndexingTypeCache && updateIndexingTypeCache(indexType)
|
||||||
updateResultCache({
|
updateResultCache && updateResultCache({
|
||||||
document: res,
|
document: res,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onStepChange(+1)
|
onStepChange && onStepChange(+1)
|
||||||
|
isSetting && onSave && onSave()
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
@ -228,7 +264,12 @@ const StepTwo = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// fetch rules
|
// fetch rules
|
||||||
|
if (!isSetting) {
|
||||||
getRules()
|
getRules()
|
||||||
|
} else {
|
||||||
|
getRulesFromDetail()
|
||||||
|
getDefaultMode()
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -444,11 +485,18 @@ const StepTwo = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!isSetting ? (
|
||||||
<div className='flex items-center mt-8 py-2'>
|
<div className='flex items-center mt-8 py-2'>
|
||||||
<Button onClick={() => onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
|
<Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
|
||||||
<div className={s.divider} />
|
<div className={s.divider} />
|
||||||
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
|
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='flex items-center mt-8 py-2'>
|
||||||
|
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button>
|
||||||
|
<Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@ import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets'
|
|||||||
import type { CommonResponse } from '@/models/common'
|
import type { CommonResponse } from '@/models/common'
|
||||||
import { asyncRunSafe } from '@/utils'
|
import { asyncRunSafe } from '@/utils'
|
||||||
import { formatNumber } from '@/utils/format'
|
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 DatasetDetailContext from '@/context/dataset-detail'
|
||||||
import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal'
|
import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal'
|
||||||
|
|
||||||
@ -118,14 +118,45 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
|
|||||||
const localDocumentId = docId ?? documentId
|
const localDocumentId = docId ?? documentId
|
||||||
const localIndexingTechnique = indexingType ?? indexingTechnique
|
const localIndexingTechnique = indexingType ?? indexingTechnique
|
||||||
|
|
||||||
const { data: indexingStatusDetail, error: indexingStatusErr, mutate: statusMutate } = useSWR({
|
// const { data: indexingStatusDetailFromApi, error: indexingStatusErr, mutate: statusMutate } = useSWR({
|
||||||
action: 'fetchIndexingStatus',
|
// action: 'fetchIndexingStatus',
|
||||||
datasetId: localDatasetId,
|
// datasetId: localDatasetId,
|
||||||
documentId: localDocumentId,
|
// documentId: localDocumentId,
|
||||||
}, apiParams => fetchIndexingStatus(omit(apiParams, 'action')), {
|
// }, apiParams => fetchIndexingStatus(omit(apiParams, 'action')), {
|
||||||
refreshInterval: 5000,
|
// refreshInterval: 2500,
|
||||||
revalidateOnFocus: false,
|
// revalidateOnFocus: false,
|
||||||
})
|
// })
|
||||||
|
|
||||||
|
const [indexingStatusDetail, setIndexingStatusDetail, getIndexingStatusDetail] = useGetState<any>(null)
|
||||||
|
const fetchIndexingStatus = async () => {
|
||||||
|
const status = await doFetchIndexingStatus({ datasetId: localDatasetId, documentId: localDocumentId })
|
||||||
|
setIndexingStatusDetail(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [runId, setRunId, getRunId] = useGetState<any>(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({
|
const { data: indexingEstimateDetail, error: indexingEstimateErr } = useSWR({
|
||||||
action: 'fetchIndexingEstimate',
|
action: 'fetchIndexingEstimate',
|
||||||
@ -168,7 +199,7 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
|
|||||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId: localDatasetId, documentId: localDocumentId }) as Promise<CommonResponse>)
|
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId: localDatasetId, documentId: localDocumentId }) as Promise<CommonResponse>)
|
||||||
if (!e) {
|
if (!e) {
|
||||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||||
statusMutate()
|
setIndexingStatusDetail(null)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
notify({ type: 'error', message: t('common.actionMsg.modificationFailed') })
|
notify({ type: 'error', message: t('common.actionMsg.modificationFailed') })
|
||||||
|
@ -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<FullDocumentDetail | null>(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 <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
|
||||||
|
<div className="grow bg-white">
|
||||||
|
{!documentDetail && <Loading type='app' />}
|
||||||
|
{dataset && documentDetail && (
|
||||||
|
<StepTwo
|
||||||
|
hasSetAPIKEY={hasSetAPIKEY}
|
||||||
|
onSetting={showSetAPIKey}
|
||||||
|
datasetId={datasetId}
|
||||||
|
indexingType={indexingTechnique || ''}
|
||||||
|
isSetting
|
||||||
|
documentDetail={documentDetail}
|
||||||
|
file={documentDetail.data_source_info.upload_file}
|
||||||
|
onSave={saveHandler}
|
||||||
|
onCancel={cancelHandler}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isShowSetAPIKey && <AccountSetting activeTab="provider" onCancel={async () => {
|
||||||
|
await checkAPIKey()
|
||||||
|
hideSetAPIkey()
|
||||||
|
}} />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocumentSettings
|
@ -95,6 +95,7 @@ export const OperationAction: FC<{
|
|||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const isListScene = scene === 'list'
|
const isListScene = scene === 'list'
|
||||||
|
|
||||||
@ -166,15 +167,19 @@ export const OperationAction: FC<{
|
|||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
</>}
|
</>}
|
||||||
{/* <div className={s.actionItem}>
|
{!archived && (
|
||||||
|
<>
|
||||||
|
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
|
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/create`)}>
|
{/* <div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/create`)}>
|
||||||
<FilePlusIcon />
|
<FilePlusIcon />
|
||||||
<span className={s.actionName}>{t('datasetDocuments.list.action.uploadFile')}</span>
|
<span className={s.actionName}>{t('datasetDocuments.list.action.uploadFile')}</span>
|
||||||
</div>
|
</div> */}
|
||||||
<Divider className='my-1' /> */}
|
<Divider className='my-1' />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
|
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
|
||||||
<ArchiveIcon />
|
<ArchiveIcon />
|
||||||
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
|
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
.txtIcon {
|
.txtIcon {
|
||||||
background-image: url(./assets/txt.svg);
|
background-image: url(./assets/txt.svg);
|
||||||
}
|
}
|
||||||
.mdIcon {
|
.markdownIcon {
|
||||||
background-image: url(./assets/md.svg);
|
background-image: url(./assets/md.svg);
|
||||||
}
|
}
|
||||||
.statusItemDetail {
|
.statusItemDetail {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { BookOpenIcon } from '@heroicons/react/24/outline'
|
import { BookOpenIcon } from '@heroicons/react/24/outline'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
|
||||||
import PermissionsRadio from '../permissions-radio'
|
import PermissionsRadio from '../permissions-radio'
|
||||||
import IndexMethodRadio from '../index-method-radio'
|
import IndexMethodRadio from '../index-method-radio'
|
||||||
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { updateDatasetSetting, fetchDataDetail } from '@/service/datasets'
|
import { fetchDataDetail, updateDatasetSetting } from '@/service/datasets'
|
||||||
import { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
|
|
||||||
const rowClass = `
|
const rowClass = `
|
||||||
flex justify-between py-4
|
flex justify-between py-4
|
||||||
@ -20,8 +20,7 @@ const labelClass = `
|
|||||||
const inputClass = `
|
const inputClass = `
|
||||||
w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
|
w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
|
||||||
`
|
`
|
||||||
|
const useInitialValue = (depend: any, dispatch: any) => {
|
||||||
const useInitialValue = <T,>(depend: T, dispatch: Dispatch<SetStateAction<T>>) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(depend)
|
dispatch(depend)
|
||||||
}, [depend])
|
}, [depend])
|
||||||
@ -32,7 +31,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Form = ({
|
const Form = ({
|
||||||
datasetId
|
datasetId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
@ -44,7 +43,8 @@ const Form = ({
|
|||||||
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
|
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (loading) return
|
if (loading)
|
||||||
|
return
|
||||||
if (!name?.trim()) {
|
if (!name?.trim()) {
|
||||||
notify({ type: 'error', message: t('datasetSettings.form.nameError') })
|
notify({ type: 'error', message: t('datasetSettings.form.nameError') })
|
||||||
return
|
return
|
||||||
@ -57,14 +57,16 @@ const Form = ({
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
permission,
|
permission,
|
||||||
indexing_technique: indexMethod
|
indexing_technique: indexMethod,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||||
await mutateDatasets()
|
await mutateDatasets()
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
notify({ type: 'error', message: t('common.actionMsg.modificationFailed') })
|
notify({ type: 'error', message: t('common.actionMsg.modificationFailed') })
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
|
||||||
type IInputCopyProps = {
|
type IInputCopyProps = {
|
||||||
value?: string
|
value?: string
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC, useEffect } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
|
import Toast from '../../base/toast'
|
||||||
|
import s from './style.module.css'
|
||||||
import ExploreContext from '@/context/explore-context'
|
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 Category from '@/app/components/explore/category'
|
||||||
import AppCard from '@/app/components/explore/app-card'
|
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 { createApp } from '@/service/apps'
|
||||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
|
|
||||||
import s from './style.module.css'
|
const Apps: FC = () => {
|
||||||
import Toast from '../../base/toast'
|
|
||||||
|
|
||||||
const Apps: FC = ({ }) => {
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { setControlUpdateInstalledApps, hasEditPermission } = useContext(ExploreContext)
|
const { setControlUpdateInstalledApps, hasEditPermission } = useContext(ExploreContext)
|
||||||
@ -25,7 +25,8 @@ const Apps: FC = ({ }) => {
|
|||||||
const [isLoaded, setIsLoaded] = React.useState(false)
|
const [isLoaded, setIsLoaded] = React.useState(false)
|
||||||
|
|
||||||
const currList = (() => {
|
const currList = (() => {
|
||||||
if(currCategory === '') return allList
|
if (currCategory === '')
|
||||||
|
return allList
|
||||||
return allList.filter(item => item.category === currCategory)
|
return allList.filter(item => item.category === currCategory)
|
||||||
})()
|
})()
|
||||||
const [categories, setCategories] = React.useState([])
|
const [categories, setCategories] = React.useState([])
|
||||||
@ -67,7 +68,8 @@ const Apps: FC = ({ }) => {
|
|||||||
})
|
})
|
||||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||||
router.push(`/app/${app.id}/overview`)
|
router.push(`/app/${app.id}/overview`)
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ const Apps: FC = ({ }) => {
|
|||||||
<div
|
<div
|
||||||
className='flex mt-6 flex-col overflow-auto bg-gray-100 shrink-0 grow'
|
className='flex mt-6 flex-col overflow-auto bg-gray-100 shrink-0 grow'
|
||||||
style={{
|
style={{
|
||||||
maxHeight: 'calc(100vh - 243px)'
|
maxHeight: 'calc(100vh - 243px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<nav
|
<nav
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import exploreI18n from '@/i18n/lang/explore.en'
|
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
|
import exploreI18n from '@/i18n/lang/explore.en'
|
||||||
|
|
||||||
const categoryI18n = exploreI18n.category
|
const categoryI18n = exploreI18n.category
|
||||||
|
|
||||||
export interface ICategoryProps {
|
export type ICategoryProps = {
|
||||||
className?: string
|
className?: string
|
||||||
list: string[]
|
list: string[]
|
||||||
value: string
|
value: string
|
||||||
@ -17,7 +18,7 @@ const Category: FC<ICategoryProps> = ({
|
|||||||
className,
|
className,
|
||||||
list,
|
list,
|
||||||
value,
|
value,
|
||||||
onChange
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -26,8 +27,8 @@ const Category: FC<ICategoryProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={cn(className, 'flex space-x-1 text-[13px]')}>
|
<div className={cn(className, 'flex space-x-1 text-[13px]')}>
|
||||||
<div
|
<div
|
||||||
className={itemClassName('' === value)}
|
className={itemClassName(value === '')}
|
||||||
style={itemStyle('' === value)}
|
style={itemStyle(value === '')}
|
||||||
onClick={() => onChange('')}
|
onClick={() => onChange('')}
|
||||||
>
|
>
|
||||||
{t('explore.apps.allCategories')}
|
{t('explore.apps.allCategories')}
|
||||||
|
@ -2,19 +2,18 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import s from './style.module.css'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import EmojiPicker from '@/app/components/base/emoji-picker'
|
import EmojiPicker from '@/app/components/base/emoji-picker'
|
||||||
|
|
||||||
import s from './style.module.css'
|
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
appName: string,
|
appName: string
|
||||||
show: boolean,
|
show: boolean
|
||||||
onConfirm: (info: any) => void,
|
onConfirm: (info: any) => void
|
||||||
onHide: () => void,
|
onHide: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateAppModal = ({
|
const CreateAppModal = ({
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
'use client'
|
'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 ExploreContext from '@/context/explore-context'
|
||||||
import Sidebar from '@/app/components/explore/sidebar'
|
import Sidebar from '@/app/components/explore/sidebar'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { fetchMembers } from '@/service/common'
|
import { fetchMembers } from '@/service/common'
|
||||||
import { InstalledApp } from '@/models/explore'
|
import type { InstalledApp } from '@/models/explore'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export interface IExploreProps {
|
export type IExploreProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const Explore: FC<IExploreProps> = ({
|
const Explore: FC<IExploreProps> = ({
|
||||||
children
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
|
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
|
||||||
@ -24,7 +25,8 @@ const Explore: FC<IExploreProps> = ({
|
|||||||
document.title = `${t('explore.title')} - Dify`;
|
document.title = `${t('explore.title')} - Dify`;
|
||||||
(async () => {
|
(async () => {
|
||||||
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
||||||
if(!accounts) return
|
if (!accounts)
|
||||||
|
return
|
||||||
const currUser = accounts.find(account => account.id === userProfile.id)
|
const currUser = accounts.find(account => account.id === userProfile.id)
|
||||||
setHasEditPermission(currUser?.role !== 'normal')
|
setHasEditPermission(currUser?.role !== 'normal')
|
||||||
})()
|
})()
|
||||||
@ -39,7 +41,7 @@ const Explore: FC<IExploreProps> = ({
|
|||||||
setControlUpdateInstalledApps,
|
setControlUpdateInstalledApps,
|
||||||
hasEditPermission,
|
hasEditPermission,
|
||||||
installedApps,
|
installedApps,
|
||||||
setInstalledApps
|
setInstalledApps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import ExploreContext from '@/context/explore-context'
|
import ExploreContext from '@/context/explore-context'
|
||||||
import ChatApp from '@/app/components/share/chat'
|
import ChatApp from '@/app/components/share/chat'
|
||||||
import TextGenerationApp from '@/app/components/share/text-generation'
|
import TextGenerationApp from '@/app/components/share/text-generation'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
|
|
||||||
export interface IInstalledAppProps {
|
export type IInstalledAppProps = {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,9 +27,11 @@ const InstalledApp: FC<IInstalledAppProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full p-2'>
|
<div className='h-full p-2'>
|
||||||
{installedApp?.app.mode === 'chat' ? (
|
{installedApp?.app.mode === 'chat'
|
||||||
|
? (
|
||||||
<ChatApp isInstalledApp installedAppInfo={installedApp}/>
|
<ChatApp isInstalledApp installedAppInfo={installedApp}/>
|
||||||
): (
|
)
|
||||||
|
: (
|
||||||
<TextGenerationApp isInstalledApp installedAppInfo={installedApp}/>
|
<TextGenerationApp isInstalledApp installedAppInfo={installedApp}/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Popover from '@/app/components/base/popover'
|
|
||||||
import { TrashIcon } from '@heroicons/react/24/outline'
|
import { TrashIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
|
import Popover from '@/app/components/base/popover'
|
||||||
|
|
||||||
const PinIcon = (
|
const PinIcon = (
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -13,7 +14,7 @@ const PinIcon = (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface IItemOperationProps {
|
export type IItemOperationProps = {
|
||||||
className?: string
|
className?: string
|
||||||
isPinned: boolean
|
isPinned: boolean
|
||||||
isShowDelete: boolean
|
isShowDelete: boolean
|
||||||
@ -26,7 +27,7 @@ const ItemOperation: FC<IItemOperationProps> = ({
|
|||||||
isPinned,
|
isPinned,
|
||||||
isShowDelete,
|
isShowDelete,
|
||||||
togglePin,
|
togglePin,
|
||||||
onDelete
|
onDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -52,8 +53,8 @@ const ItemOperation: FC<IItemOperationProps> = ({
|
|||||||
trigger='click'
|
trigger='click'
|
||||||
position='br'
|
position='br'
|
||||||
btnElement={<div />}
|
btnElement={<div />}
|
||||||
btnClassName={(open) => cn(className, s.btn, 'h-6 w-6 rounded-md border-none p-1', open && '!bg-gray-100 !shadow-none')}
|
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`}
|
className={'!w-[120px] h-fit !z-20'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import s from './style.module.css'
|
||||||
import ItemOperation from '@/app/components/explore/item-operation'
|
import ItemOperation from '@/app/components/explore/item-operation'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
|
||||||
import s from './style.module.css'
|
export type IAppNavItemProps = {
|
||||||
|
|
||||||
export interface IAppNavItemProps {
|
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
icon: string
|
icon: string
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import type { Provider, ProviderAzureToken } from '@/models/common'
|
|
||||||
import { ProviderName } from '@/models/common'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import ProviderInput from '../provider-input'
|
import ProviderInput from '../provider-input'
|
||||||
import useValidateToken, { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken'
|
import type { ValidatedStatusState } from '../provider-input/useValidateToken'
|
||||||
|
import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken'
|
||||||
import {
|
import {
|
||||||
ValidatedErrorIcon,
|
ValidatedErrorIcon,
|
||||||
|
ValidatedErrorOnAzureOpenaiTip,
|
||||||
ValidatedSuccessIcon,
|
ValidatedSuccessIcon,
|
||||||
ValidatingTip,
|
ValidatingTip,
|
||||||
ValidatedErrorOnAzureOpenaiTip
|
|
||||||
} from '../provider-input/Validate'
|
} from '../provider-input/Validate'
|
||||||
|
import { ProviderName } from '@/models/common'
|
||||||
|
import type { Provider, ProviderAzureToken } from '@/models/common'
|
||||||
|
|
||||||
interface IAzureProviderProps {
|
type IAzureProviderProps = {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
onValidatedStatus: (status?: ValidatedStatusState) => void
|
onValidatedStatus: (status?: ValidatedStatusState) => void
|
||||||
onTokenChange: (token: ProviderAzureToken) => void
|
onTokenChange: (token: ProviderAzureToken) => void
|
||||||
@ -21,7 +22,7 @@ interface IAzureProviderProps {
|
|||||||
const AzureProvider = ({
|
const AzureProvider = ({
|
||||||
provider,
|
provider,
|
||||||
onTokenChange,
|
onTokenChange,
|
||||||
onValidatedStatus
|
onValidatedStatus,
|
||||||
}: IAzureProviderProps) => {
|
}: IAzureProviderProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [token, setToken] = useState<ProviderAzureToken>(provider.provider_name === ProviderName.AZURE_OPENAI ? { ...provider.token } : {})
|
const [token, setToken] = useState<ProviderAzureToken>(provider.provider_name === ProviderName.AZURE_OPENAI ? { ...provider.token } : {})
|
||||||
@ -45,29 +46,26 @@ const AzureProvider = ({
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const getValidatedIcon = () => {
|
const getValidatedIcon = () => {
|
||||||
if (validatedStatus.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) {
|
if (validatedStatus.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed)
|
||||||
return <ValidatedErrorIcon />
|
return <ValidatedErrorIcon />
|
||||||
}
|
|
||||||
if (validatedStatus.status === ValidatedStatus.Success) {
|
if (validatedStatus.status === ValidatedStatus.Success)
|
||||||
return <ValidatedSuccessIcon />
|
return <ValidatedSuccessIcon />
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const getValidatedTip = () => {
|
const getValidatedTip = () => {
|
||||||
if (validating) {
|
if (validating)
|
||||||
return <ValidatingTip />
|
return <ValidatingTip />
|
||||||
}
|
|
||||||
if (validatedStatus.status === ValidatedStatus.Error) {
|
if (validatedStatus.status === ValidatedStatus.Error)
|
||||||
return <ValidatedErrorOnAzureOpenaiTip errorMessage={validatedStatus.message ?? ''} />
|
return <ValidatedErrorOnAzureOpenaiTip errorMessage={validatedStatus.message ?? ''} />
|
||||||
}
|
}
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof onValidatedStatus === 'function') {
|
if (typeof onValidatedStatus === 'function')
|
||||||
onValidatedStatus(validatedStatus)
|
onValidatedStatus(validatedStatus)
|
||||||
}
|
|
||||||
}, [validatedStatus])
|
}, [validatedStatus])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -77,7 +75,7 @@ const AzureProvider = ({
|
|||||||
name={t('common.provider.azure.apiBase')}
|
name={t('common.provider.azure.apiBase')}
|
||||||
placeholder={t('common.provider.azure.apiBasePlaceholder')}
|
placeholder={t('common.provider.azure.apiBasePlaceholder')}
|
||||||
value={token.openai_api_base}
|
value={token.openai_api_base}
|
||||||
onChange={(v) => handleChange('openai_api_base', v, validate)}
|
onChange={v => handleChange('openai_api_base', v, validate)}
|
||||||
onFocus={() => handleFocus('openai_api_base')}
|
onFocus={() => handleFocus('openai_api_base')}
|
||||||
validatedIcon={getValidatedIcon()}
|
validatedIcon={getValidatedIcon()}
|
||||||
/>
|
/>
|
||||||
@ -86,7 +84,7 @@ const AzureProvider = ({
|
|||||||
name={t('common.provider.azure.apiKey')}
|
name={t('common.provider.azure.apiKey')}
|
||||||
placeholder={t('common.provider.azure.apiKeyPlaceholder')}
|
placeholder={t('common.provider.azure.apiKeyPlaceholder')}
|
||||||
value={token.openai_api_key}
|
value={token.openai_api_key}
|
||||||
onChange={(v) => handleChange('openai_api_key', v, validate)}
|
onChange={v => handleChange('openai_api_key', v, validate)}
|
||||||
onFocus={() => handleFocus('openai_api_key')}
|
onFocus={() => handleFocus('openai_api_key')}
|
||||||
validatedIcon={getValidatedIcon()}
|
validatedIcon={getValidatedIcon()}
|
||||||
validatedTip={getValidatedTip()}
|
validatedTip={getValidatedTip()}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import useSWR from 'swr'
|
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 { LockClosedIcon } from '@heroicons/react/24/solid'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Link from 'next/link'
|
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'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
|
|
||||||
const providersMap: { [k: string]: any } = {
|
const providersMap: { [k: string]: any } = {
|
||||||
@ -17,7 +17,7 @@ const providersMap: {[k: string]: any} = {
|
|||||||
'azure_openai-custom': {
|
'azure_openai-custom': {
|
||||||
icon: 'azure',
|
icon: 'azure',
|
||||||
name: 'Azure OpenAI Service',
|
name: 'Azure OpenAI Service',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// const providersList = [
|
// const providersList = [
|
||||||
@ -56,7 +56,7 @@ const ProviderPage = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [activeProviderId, setActiveProviderId] = useState('')
|
const [activeProviderId, setActiveProviderId] = useState('')
|
||||||
const { data, mutate } = useSWR({ url: '/workspaces/current/providers' }, fetchProviders)
|
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}`
|
const providerKey = `${provider.provider_name}-${provider.provider_type}`
|
||||||
return {
|
return {
|
||||||
provider,
|
provider,
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import type { Provider } from '@/models/common'
|
import { useEffect, useState } from 'react'
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ProviderInput from '../provider-input'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
|
||||||
import useValidateToken, { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken'
|
import ProviderInput from '../provider-input'
|
||||||
|
import type { ValidatedStatusState } from '../provider-input/useValidateToken'
|
||||||
|
import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken'
|
||||||
import {
|
import {
|
||||||
ValidatedErrorIcon,
|
ValidatedErrorIcon,
|
||||||
|
ValidatedErrorOnOpenaiTip,
|
||||||
ValidatedSuccessIcon,
|
ValidatedSuccessIcon,
|
||||||
ValidatingTip,
|
ValidatingTip,
|
||||||
ValidatedExceedOnOpenaiTip,
|
|
||||||
ValidatedErrorOnOpenaiTip
|
|
||||||
} from '../provider-input/Validate'
|
} from '../provider-input/Validate'
|
||||||
|
import type { Provider } from '@/models/common'
|
||||||
|
|
||||||
interface IOpenaiProviderProps {
|
type IOpenaiProviderProps = {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
onValidatedStatus: (status?: ValidatedStatusState) => void
|
onValidatedStatus: (status?: ValidatedStatusState) => void
|
||||||
onTokenChange: (token: string) => void
|
onTokenChange: (token: string) => void
|
||||||
@ -22,7 +22,7 @@ interface IOpenaiProviderProps {
|
|||||||
const OpenaiProvider = ({
|
const OpenaiProvider = ({
|
||||||
provider,
|
provider,
|
||||||
onValidatedStatus,
|
onValidatedStatus,
|
||||||
onTokenChange
|
onTokenChange,
|
||||||
}: IOpenaiProviderProps) => {
|
}: IOpenaiProviderProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [token, setToken] = useState(provider.token as string || '')
|
const [token, setToken] = useState(provider.token as string || '')
|
||||||
@ -44,31 +44,28 @@ const OpenaiProvider = ({
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof onValidatedStatus === 'function') {
|
if (typeof onValidatedStatus === 'function')
|
||||||
onValidatedStatus(validatedStatus)
|
onValidatedStatus(validatedStatus)
|
||||||
}
|
|
||||||
}, [validatedStatus])
|
}, [validatedStatus])
|
||||||
|
|
||||||
const getValidatedIcon = () => {
|
const getValidatedIcon = () => {
|
||||||
if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) {
|
if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed)
|
||||||
return <ValidatedErrorIcon />
|
return <ValidatedErrorIcon />
|
||||||
}
|
|
||||||
if (validatedStatus.status === ValidatedStatus.Success) {
|
if (validatedStatus.status === ValidatedStatus.Success)
|
||||||
return <ValidatedSuccessIcon />
|
return <ValidatedSuccessIcon />
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const getValidatedTip = () => {
|
const getValidatedTip = () => {
|
||||||
if (validating) {
|
if (validating)
|
||||||
return <ValidatingTip />
|
return <ValidatingTip />
|
||||||
}
|
|
||||||
if (validatedStatus?.status === ValidatedStatus.Error) {
|
if (validatedStatus?.status === ValidatedStatus.Error)
|
||||||
return <ValidatedErrorOnOpenaiTip errorMessage={validatedStatus.message ?? ''} />
|
return <ValidatedErrorOnOpenaiTip errorMessage={validatedStatus.message ?? ''} />
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='px-4 pt-3 pb-4'>
|
<div className='px-4 pt-3 pb-4'>
|
||||||
|
@ -15,7 +15,7 @@ export const ValidatedSuccessIcon = () => {
|
|||||||
export const ValidatingTip = () => {
|
export const ValidatingTip = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className={`mt-2 text-primary-600 text-xs font-normal`}>
|
<div className={'mt-2 text-primary-600 text-xs font-normal'}>
|
||||||
{t('common.provider.validating')}
|
{t('common.provider.validating')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -26,7 +26,7 @@ export const ValidatedExceedOnOpenaiTip = () => {
|
|||||||
const { locale } = useContext(I18n)
|
const { locale } = useContext(I18n)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
<div className={'mt-2 text-[#D92D20] text-xs font-normal'}>
|
||||||
{t('common.provider.apiKeyExceedBill')}
|
{t('common.provider.apiKeyExceedBill')}
|
||||||
<Link
|
<Link
|
||||||
className='underline'
|
className='underline'
|
||||||
@ -42,7 +42,7 @@ export const ValidatedErrorOnOpenaiTip = ({ errorMessage }: { errorMessage: stri
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
<div className={'mt-2 text-[#D92D20] text-xs font-normal'}>
|
||||||
{t('common.provider.validatedError')}{errorMessage}
|
{t('common.provider.validatedError')}{errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -52,7 +52,7 @@ export const ValidatedErrorOnAzureOpenaiTip = ({ errorMessage }: { errorMessage:
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`mt-2 text-[#D92D20] text-xs font-normal`}>
|
<div className={'mt-2 text-[#D92D20] text-xs font-normal'}>
|
||||||
{t('common.provider.validatedError')}{errorMessage}
|
{t('common.provider.validatedError')}{errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ChangeEvent } from 'react'
|
import type { ChangeEvent } from 'react'
|
||||||
import { ReactElement } from 'react-markdown/lib/react-markdown'
|
import type { ReactElement } from 'react-markdown/lib/react-markdown'
|
||||||
|
|
||||||
interface IProviderInputProps {
|
type IProviderInputProps = {
|
||||||
value?: string
|
value?: string
|
||||||
name: string
|
name: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
@ -20,9 +20,8 @@ const ProviderInput = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
validatedIcon,
|
validatedIcon,
|
||||||
validatedTip
|
validatedTip,
|
||||||
}: IProviderInputProps) => {
|
}: IProviderInputProps) => {
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const inputValue = e.target.value
|
const inputValue = e.target.value
|
||||||
onChange(inputValue)
|
onChange(inputValue)
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { useState, useCallback, SetStateAction, Dispatch } from 'react'
|
import type { Dispatch, SetStateAction } from 'react'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
import debounce from 'lodash-es/debounce'
|
import debounce from 'lodash-es/debounce'
|
||||||
import { DebouncedFunc } from 'lodash-es'
|
import type { DebouncedFunc } from 'lodash-es'
|
||||||
import { validateProviderKey } from '@/service/common'
|
import { validateProviderKey } from '@/service/common'
|
||||||
|
|
||||||
export enum ValidatedStatus {
|
export enum ValidatedStatus {
|
||||||
Success = 'success',
|
Success = 'success',
|
||||||
Error = 'error',
|
Error = 'error',
|
||||||
Exceed = 'exceed'
|
Exceed = 'exceed',
|
||||||
}
|
}
|
||||||
export type ValidatedStatusState = {
|
export type ValidatedStatusState = {
|
||||||
status?: ValidatedStatus,
|
status?: ValidatedStatus
|
||||||
message?: string
|
message?: string
|
||||||
}
|
}
|
||||||
// export type ValidatedStatusState = ValidatedStatus | undefined | ValidatedError
|
// export type ValidatedStatusState = ValidatedStatus | undefined | ValidatedError
|
||||||
@ -19,7 +20,7 @@ type ValidateTokenReturn = [
|
|||||||
boolean,
|
boolean,
|
||||||
ValidatedStatusState,
|
ValidatedStatusState,
|
||||||
SetValidatedStatus,
|
SetValidatedStatus,
|
||||||
ValidateFn
|
ValidateFn,
|
||||||
]
|
]
|
||||||
export type ValidateFnConfig = {
|
export type ValidateFnConfig = {
|
||||||
beforeValidating: (token: any) => boolean
|
beforeValidating: (token: any) => boolean
|
||||||
@ -29,9 +30,9 @@ const useValidateToken = (providerName: string): ValidateTokenReturn => {
|
|||||||
const [validating, setValidating] = useState(false)
|
const [validating, setValidating] = useState(false)
|
||||||
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>({})
|
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>({})
|
||||||
const validate = useCallback(debounce(async (token: string, config: ValidateFnConfig) => {
|
const validate = useCallback(debounce(async (token: string, config: ValidateFnConfig) => {
|
||||||
if (!config.beforeValidating(token)) {
|
if (!config.beforeValidating(token))
|
||||||
return false
|
return false
|
||||||
}
|
|
||||||
setValidating(true)
|
setValidating(true)
|
||||||
try {
|
try {
|
||||||
const res = await validateProviderKey({ url: `/workspaces/current/providers/${providerName}/token-validate`, body: { token } })
|
const res = await validateProviderKey({ url: `/workspaces/current/providers/${providerName}/token-validate`, body: { token } })
|
||||||
@ -39,9 +40,11 @@ const useValidateToken = (providerName: string): ValidateTokenReturn => {
|
|||||||
res.result === 'success'
|
res.result === 'success'
|
||||||
? { status: ValidatedStatus.Success }
|
? { status: ValidatedStatus.Success }
|
||||||
: { status: ValidatedStatus.Error, message: res.error })
|
: { status: ValidatedStatus.Error, message: res.error })
|
||||||
} catch (e: any) {
|
}
|
||||||
|
catch (e: any) {
|
||||||
setValidatedStatus({ status: ValidatedStatus.Error, message: e.message })
|
setValidatedStatus({ status: ValidatedStatus.Error, message: e.message })
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
setValidating(false)
|
setValidating(false)
|
||||||
}
|
}
|
||||||
}, 500), [])
|
}, 500), [])
|
||||||
@ -50,7 +53,7 @@ const useValidateToken = (providerName: string): ValidateTokenReturn => {
|
|||||||
validating,
|
validating,
|
||||||
validatedStatus,
|
validatedStatus,
|
||||||
setValidatedStatus,
|
setValidatedStatus,
|
||||||
validate
|
validate,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import s from './index.module.css'
|
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import Indicator from '../../../indicator'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { Provider, ProviderAzureToken } from '@/models/common'
|
import Indicator from '../../../indicator'
|
||||||
import { ProviderName } from '@/models/common'
|
|
||||||
import OpenaiProvider from '../openai-provider'
|
import OpenaiProvider from '../openai-provider'
|
||||||
import AzureProvider from '../azure-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 { updateProviderAIKey } from '@/service/common'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
interface IProviderItemProps {
|
type IProviderItemProps = {
|
||||||
icon: string
|
icon: string
|
||||||
name: string
|
name: string
|
||||||
provider: Provider
|
provider: Provider
|
||||||
@ -26,7 +27,7 @@ const ProviderItem = ({
|
|||||||
name,
|
name,
|
||||||
provider,
|
provider,
|
||||||
onActive,
|
onActive,
|
||||||
onSave
|
onSave,
|
||||||
}: IProviderItemProps) => {
|
}: IProviderItemProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
|
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
|
||||||
@ -35,7 +36,7 @@ const ProviderItem = ({
|
|||||||
const [token, setToken] = useState<ProviderAzureToken | string>(
|
const [token, setToken] = useState<ProviderAzureToken | string>(
|
||||||
provider.provider_name === 'azure_openai'
|
provider.provider_name === 'azure_openai'
|
||||||
? { openai_api_base: '', openai_api_key: '' }
|
? { openai_api_base: '', openai_api_key: '' }
|
||||||
: ''
|
: '',
|
||||||
)
|
)
|
||||||
const id = `${provider.provider_name}-${provider.provider_type}`
|
const id = `${provider.provider_name}-${provider.provider_type}`
|
||||||
const isOpen = id === activeId
|
const isOpen = id === activeId
|
||||||
@ -44,26 +45,30 @@ const ProviderItem = ({
|
|||||||
|
|
||||||
const providerTokenHasSetted = () => {
|
const providerTokenHasSetted = () => {
|
||||||
if (provider.provider_name === ProviderName.AZURE_OPENAI) {
|
if (provider.provider_name === ProviderName.AZURE_OPENAI) {
|
||||||
return provider.token && provider.token.openai_api_base && provider.token.openai_api_key ? {
|
return (provider.token && provider.token.openai_api_base && provider.token.openai_api_key)
|
||||||
|
? {
|
||||||
openai_api_base: provider.token.openai_api_base,
|
openai_api_base: provider.token.openai_api_base,
|
||||||
openai_api_key: provider.token.openai_api_key
|
openai_api_key: provider.token.openai_api_key,
|
||||||
}: undefined
|
|
||||||
}
|
}
|
||||||
if (provider.provider_name === ProviderName.OPENAI) {
|
: undefined
|
||||||
|
}
|
||||||
|
if (provider.provider_name === ProviderName.OPENAI)
|
||||||
return provider.token
|
return provider.token
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const handleUpdateToken = async () => {
|
const handleUpdateToken = async () => {
|
||||||
if (loading) return
|
if (loading)
|
||||||
|
return
|
||||||
if (validatedStatus?.status === ValidatedStatus.Success) {
|
if (validatedStatus?.status === ValidatedStatus.Success) {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
await updateProviderAIKey({ url: `/workspaces/current/providers/${provider.provider_name}/token`, body: { token } })
|
await updateProviderAIKey({ url: `/workspaces/current/providers/${provider.provider_name}/token`, body: { token } })
|
||||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||||
onActive('')
|
onActive('')
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
notify({ type: 'error', message: t('common.provider.saveFailed') })
|
notify({ type: 'error', message: t('common.provider.saveFailed') })
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
onSave()
|
onSave()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelectedLayoutSegment, useRouter } from 'next/navigation'
|
import { useRouter, useSelectedLayoutSegment } from 'next/navigation'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline'
|
import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline'
|
||||||
import { CommandLineIcon, Squares2X2Icon } from '@heroicons/react/24/solid'
|
import { CommandLineIcon, Squares2X2Icon } from '@heroicons/react/24/solid'
|
||||||
@ -51,22 +51,22 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
|
|||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
|
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
|
||||||
s.header,
|
s.header,
|
||||||
isBordered ? 'border-b border-gray-200' : ''
|
isBordered ? 'border-b border-gray-200' : '',
|
||||||
)}>
|
)}>
|
||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
s[`header-${langeniusVersionInfo.current_env}`],
|
s[`header-${langeniusVersionInfo.current_env}`],
|
||||||
'flex flex-1 items-center justify-between px-4'
|
'flex flex-1 items-center justify-between px-4',
|
||||||
)}>
|
)}>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<Link href="/apps" className='flex items-center mr-3'>
|
<Link href="/apps" className='flex items-center mr-3'>
|
||||||
<div className={s['logo']} />
|
<div className={s.logo} />
|
||||||
</Link>
|
</Link>
|
||||||
{/* Add it when has many stars */}
|
{/* Add it when has many stars */}
|
||||||
<div className='
|
<div className='
|
||||||
flex items-center h-[26px] px-2 bg-white
|
flex items-center h-[26px] px-2 bg-white
|
||||||
border border-solid border-[#E5E7EB] rounded-l-[6px] rounded-r-[6px]
|
border border-solid border-[#E5E7EB] rounded-l-[6px] rounded-r-[6px]
|
||||||
'>
|
'>
|
||||||
<div className={s['alpha']} />
|
<div className={s.alpha} />
|
||||||
<div className='ml-1 text-xs font-semibold text-gray-700'>{t('common.menus.status')}</div>
|
<div className='ml-1 text-xs font-semibold text-gray-700'>{t('common.menus.status')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +74,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
|
|||||||
<Link href="/explore/apps" className={classNames(
|
<Link href="/explore/apps" className={classNames(
|
||||||
navClassName, 'group',
|
navClassName, 'group',
|
||||||
isExplore && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
|
isExplore && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
|
||||||
isExplore ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200 hover:text-gray-700'
|
isExplore ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200 hover:text-gray-700',
|
||||||
)}>
|
)}>
|
||||||
<Squares2X2Icon className='mr-1 w-[18px] h-[18px]' />
|
<Squares2X2Icon className='mr-1 w-[18px] h-[18px]' />
|
||||||
{t('common.menus.explore')}
|
{t('common.menus.explore')}
|
||||||
@ -90,7 +90,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
link: `/app/${item.id}/overview`,
|
link: `/app/${item.id}/overview`,
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
icon_background: item.icon_background
|
icon_background: item.icon_background,
|
||||||
}))}
|
}))}
|
||||||
createText={t('common.menus.newApp')}
|
createText={t('common.menus.newApp')}
|
||||||
onCreate={() => setShowNewAppDialog(true)}
|
onCreate={() => setShowNewAppDialog(true)}
|
||||||
@ -98,7 +98,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
|
|||||||
<Link href="/plugins-coming-soon" className={classNames(
|
<Link href="/plugins-coming-soon" className={classNames(
|
||||||
navClassName, 'group',
|
navClassName, 'group',
|
||||||
isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
|
isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
|
||||||
isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200 hover:text-gray-700'
|
isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200 hover:text-gray-700',
|
||||||
)}>
|
)}>
|
||||||
<PuzzlePieceIcon className='mr-1 w-[18px] h-[18px]' />
|
<PuzzlePieceIcon className='mr-1 w-[18px] h-[18px]' />
|
||||||
{t('common.menus.plugins')}
|
{t('common.menus.plugins')}
|
||||||
@ -114,7 +114,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
|
|||||||
name: dataset.name,
|
name: dataset.name,
|
||||||
link: `/datasets/${dataset.id}/documents`,
|
link: `/datasets/${dataset.id}/documents`,
|
||||||
icon: dataset.icon,
|
icon: dataset.icon,
|
||||||
icon_background: dataset.icon_background
|
icon_background: dataset.icon_background,
|
||||||
}))}
|
}))}
|
||||||
createText={t('common.menus.newDataset')}
|
createText={t('common.menus.newDataset')}
|
||||||
onCreate={() => router.push('/datasets/create')}
|
onCreate={() => router.push('/datasets/create')}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { appDefaultIconBackground } from '@/config/index'
|
import { appDefaultIconBackground } from '@/config/index'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
|
||||||
export interface IAppInfoProps {
|
export type IAppInfoProps = {
|
||||||
className?: string
|
className?: string
|
||||||
icon: string
|
icon: string
|
||||||
icon_background?: string
|
icon_background?: string
|
||||||
@ -15,7 +16,7 @@ const AppInfo: FC<IAppInfoProps> = ({
|
|||||||
className,
|
className,
|
||||||
icon,
|
icon,
|
||||||
icon_background,
|
icon_background,
|
||||||
name
|
name,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn(className, 'flex items-center space-x-3')}>
|
<div className={cn(className, 'flex items-center space-x-3')}>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useRef } from 'react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
PencilSquareIcon
|
PencilSquareIcon,
|
||||||
} from '@heroicons/react/24/outline'
|
} 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 Button from '../../../base/button'
|
||||||
import AppInfo from '@/app/components/share/chat/sidebar/app-info'
|
import AppInfo from '@/app/components/share/chat/sidebar/app-info'
|
||||||
// import Card from './card'
|
// import Card from './card'
|
||||||
import type { ConversationItem, SiteInfo } from '@/models/share'
|
import type { ConversationItem, SiteInfo } from '@/models/share'
|
||||||
import { useInfiniteScroll } from 'ahooks'
|
|
||||||
import { fetchConversations } from '@/service/share'
|
import { fetchConversations } from '@/service/share'
|
||||||
|
|
||||||
function classNames(...classes: any[]) {
|
function classNames(...classes: any[]) {
|
||||||
@ -25,7 +25,7 @@ export type ISidebarProps = {
|
|||||||
isInstalledApp: boolean
|
isInstalledApp: boolean
|
||||||
installedAppId?: string
|
installedAppId?: string
|
||||||
siteInfo: SiteInfo
|
siteInfo: SiteInfo
|
||||||
onMoreLoaded: (res: {data: ConversationItem[], has_more: boolean}) => void
|
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||||
isNoMore: boolean
|
isNoMore: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
isNoMore: () => {
|
isNoMore: () => {
|
||||||
return isNoMore
|
return isNoMore
|
||||||
},
|
},
|
||||||
reloadDeps: [isNoMore]
|
reloadDeps: [isNoMore],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
className={
|
className={
|
||||||
classNames(
|
classNames(
|
||||||
isInstalledApp ? 'tablet:h-[calc(100vh_-_74px)]' : 'tablet:h-[calc(100vh_-_3rem)]',
|
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',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -38,7 +38,8 @@ const ConfigSence: FC<IConfigSenceProps> = ({
|
|||||||
<div className='w-full mt-4' key={item.key}>
|
<div className='w-full mt-4' key={item.key}>
|
||||||
<label className='text-gray-900 text-sm font-medium'>{item.name}</label>
|
<label className='text-gray-900 text-sm font-medium'>{item.name}</label>
|
||||||
<div className='mt-2'>
|
<div className='mt-2'>
|
||||||
{item.type === 'select' ? (
|
{item.type === 'select'
|
||||||
|
? (
|
||||||
<Select
|
<Select
|
||||||
className='w-full'
|
className='w-full'
|
||||||
defaultValue={inputs[item.key]}
|
defaultValue={inputs[item.key]}
|
||||||
@ -47,7 +48,8 @@ const ConfigSence: FC<IConfigSenceProps> = ({
|
|||||||
allowSearch={false}
|
allowSearch={false}
|
||||||
bgClassName='bg-gray-50'
|
bgClassName='bg-gray-50'
|
||||||
/>
|
/>
|
||||||
) : (
|
)
|
||||||
|
: (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
|
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
|
||||||
|
@ -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 = '';
|
export let apiPrefix = ''
|
||||||
let publicApiPrefix = '';
|
export let publicApiPrefix = ''
|
||||||
|
|
||||||
// NEXT_PUBLIC_API_PREFIX=/console/api NEXT_PUBLIC_PUBLIC_API_PREFIX=/api npm run start
|
// 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) {
|
if (process.env.NEXT_PUBLIC_API_PREFIX && process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX) {
|
||||||
apiPrefix = process.env.NEXT_PUBLIC_API_PREFIX;
|
apiPrefix = process.env.NEXT_PUBLIC_API_PREFIX
|
||||||
publicApiPrefix = process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX;
|
publicApiPrefix = process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX
|
||||||
} else if (
|
}
|
||||||
globalThis.document?.body?.getAttribute('data-api-prefix') &&
|
else if (
|
||||||
globalThis.document?.body?.getAttribute('data-pubic-api-prefix')
|
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
|
// 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
|
apiPrefix = globalThis.document.body.getAttribute('data-api-prefix') as string
|
||||||
publicApiPrefix = globalThis.document.body.getAttribute('data-pubic-api-prefix') as string
|
publicApiPrefix = globalThis.document.body.getAttribute('data-pubic-api-prefix') as string
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
apiPrefix = 'https://cloud.dify.dev/console/api';
|
apiPrefix = 'https://cloud.dify.dev/console/api'
|
||||||
publicApiPrefix = 'https://dev.udify.app/api';
|
publicApiPrefix = 'https://dev.udify.app/api'
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// const domainParts = globalThis.location?.host?.split('.');
|
// const domainParts = globalThis.location?.host?.split('.');
|
||||||
// in production env, the host is dify.app . In other env, the host is [dev].dify.app
|
// 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];
|
// const env = domainParts.length === 2 ? 'ai' : domainParts?.[0];
|
||||||
apiPrefix = '/console/api';
|
apiPrefix = '/console/api'
|
||||||
publicApiPrefix = `/api`; // avoid browser private mode api cross origin
|
publicApiPrefix = '/api' // avoid browser private mode api cross origin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const API_PREFIX: string = apiPrefix
|
||||||
export const API_PREFIX: string = apiPrefix;
|
export const PUBLIC_API_PREFIX: string = publicApiPrefix
|
||||||
export const PUBLIC_API_PREFIX: string = publicApiPrefix;
|
|
||||||
|
|
||||||
const EDITION = process.env.NEXT_PUBLIC_EDITION || globalThis.document?.body?.getAttribute('data-public-edition')
|
const EDITION = process.env.NEXT_PUBLIC_EDITION || globalThis.document?.body?.getAttribute('data-public-edition')
|
||||||
export const IS_CE_EDITION = EDITION === 'SELF_HOSTED'
|
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 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 emojiRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/m
|
||||||
export const emailRegex = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/m
|
export const emailRegex = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/m
|
||||||
const MAX_ZN_VAR_NAME_LENGHT = 8
|
const MAX_ZN_VAR_NAME_LENGHT = 8
|
||||||
const MAX_EN_VAR_VALUE_LENGHT = 16
|
const MAX_EN_VAR_VALUE_LENGHT = 16
|
||||||
export const getMaxVarNameLength = (value: string) => {
|
export const getMaxVarNameLength = (value: string) => {
|
||||||
if (zhRegex.test(value)) {
|
if (zhRegex.test(value))
|
||||||
return MAX_ZN_VAR_NAME_LENGHT
|
return MAX_ZN_VAR_NAME_LENGHT
|
||||||
}
|
|
||||||
return MAX_EN_VAR_VALUE_LENGHT
|
return MAX_EN_VAR_VALUE_LENGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +97,9 @@ export const VAR_ITEM_TEMPLATE = {
|
|||||||
name: '',
|
name: '',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
max_length: DEFAULT_VALUE_MAX_LEN,
|
max_length: DEFAULT_VALUE_MAX_LEN,
|
||||||
required: true
|
required: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appDefaultIconBackground = '#D5F5F6'
|
export const appDefaultIconBackground = '#D5F5F6'
|
||||||
|
|
||||||
export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList'
|
export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createContext } from 'use-context-selector'
|
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
|
export default DatasetDetailContext
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'use-context-selector'
|
import { createContext } from 'use-context-selector'
|
||||||
import { InstalledApp } from '@/models/explore'
|
import type { InstalledApp } from '@/models/explore'
|
||||||
|
|
||||||
type IExplore = {
|
type IExplore = {
|
||||||
controlUpdateInstalledApps: number
|
controlUpdateInstalledApps: number
|
||||||
|
@ -39,7 +39,7 @@ const translation = {
|
|||||||
emoji: {
|
emoji: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -38,7 +38,7 @@ const translation = {
|
|||||||
emoji: {
|
emoji: {
|
||||||
ok: '确认',
|
ok: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -76,6 +76,8 @@ const translation = {
|
|||||||
fileName: 'Preprocess document',
|
fileName: 'Preprocess document',
|
||||||
lastStep: 'Last step',
|
lastStep: 'Last step',
|
||||||
nextStep: 'Save & Process',
|
nextStep: 'Save & Process',
|
||||||
|
save: 'Save & Process',
|
||||||
|
cancel: 'Cancel',
|
||||||
sideTipTitle: 'Why segment and preprocess?',
|
sideTipTitle: 'Why segment and preprocess?',
|
||||||
sideTipP1: 'When processing text data, segmentation and cleaning are two important preprocessing steps.',
|
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.',
|
sideTipP2: 'Segmentation splits long text into paragraphs so models can understand better. This improves the quality and relevance of model results.',
|
||||||
|
@ -76,6 +76,8 @@ const translation = {
|
|||||||
fileName: '预处理文档',
|
fileName: '预处理文档',
|
||||||
lastStep: '上一步',
|
lastStep: '上一步',
|
||||||
nextStep: '保存并处理',
|
nextStep: '保存并处理',
|
||||||
|
save: '保存并处理',
|
||||||
|
cancel: '取消',
|
||||||
sideTipTitle: '为什么要分段和预处理?',
|
sideTipTitle: '为什么要分段和预处理?',
|
||||||
sideTipP1: '在处理文本数据时,分段和清洗是两个重要的预处理步骤。',
|
sideTipP1: '在处理文本数据时,分段和清洗是两个重要的预处理步骤。',
|
||||||
sideTipP2: '分段的目的是将长文本拆分成较小的段落,以便模型更有效地处理和理解。这有助于提高模型生成的结果的质量和相关性。',
|
sideTipP2: '分段的目的是将长文本拆分成较小的段落,以便模型更有效地处理和理解。这有助于提高模型生成的结果的质量和相关性。',
|
||||||
|
@ -11,7 +11,7 @@ const translation = {
|
|||||||
delete: {
|
delete: {
|
||||||
title: 'Delete app',
|
title: 'Delete app',
|
||||||
content: 'Are you sure you want to delete this app?',
|
content: 'Are you sure you want to delete this app?',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: 'Explore Apps by Dify',
|
title: 'Explore Apps by Dify',
|
||||||
@ -28,12 +28,12 @@ const translation = {
|
|||||||
nameRequired: 'App name is required',
|
nameRequired: 'App name is required',
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
'Assistant': 'Assistant',
|
Assistant: 'Assistant',
|
||||||
'Writing': 'Writing',
|
Writing: 'Writing',
|
||||||
'Translate': 'Translate',
|
Translate: 'Translate',
|
||||||
'Programming': 'Programming',
|
Programming: 'Programming',
|
||||||
'HR': 'HR',
|
HR: 'HR',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -11,7 +11,7 @@ const translation = {
|
|||||||
delete: {
|
delete: {
|
||||||
title: '删除程序',
|
title: '删除程序',
|
||||||
content: '您确定要删除此程序吗?',
|
content: '您确定要删除此程序吗?',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: '探索 Dify 的应用',
|
title: '探索 Dify 的应用',
|
||||||
@ -28,12 +28,12 @@ const translation = {
|
|||||||
nameRequired: '应用程序名称不能为空',
|
nameRequired: '应用程序名称不能为空',
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
'Assistant': '助手',
|
Assistant: '助手',
|
||||||
'Writing': '写作',
|
Writing: '写作',
|
||||||
'Translate': '翻译',
|
Translate: '翻译',
|
||||||
'Programming': '编程',
|
Programming: '编程',
|
||||||
'HR': '人力资源',
|
HR: '人力资源',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -56,7 +56,7 @@ export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_l
|
|||||||
|
|
||||||
export enum ProviderName {
|
export enum ProviderName {
|
||||||
OPENAI = 'openai',
|
OPENAI = 'openai',
|
||||||
AZURE_OPENAI = 'azure_openai'
|
AZURE_OPENAI = 'azure_openai',
|
||||||
}
|
}
|
||||||
export type ProviderAzureToken = {
|
export type ProviderAzureToken = {
|
||||||
openai_api_base?: string
|
openai_api_base?: string
|
||||||
@ -91,7 +91,7 @@ export type AccountIntegrate = {
|
|||||||
link: string
|
link: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspace {
|
export type IWorkspace = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
plan: string
|
plan: string
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
import { AppMode } from "./app";
|
import type { AppMode } from './app'
|
||||||
|
|
||||||
export type AppBasicInfo = {
|
export type AppBasicInfo = {
|
||||||
id: string;
|
id: string
|
||||||
name: string;
|
name: string
|
||||||
mode: AppMode;
|
mode: AppMode
|
||||||
icon: string;
|
icon: string
|
||||||
icon_background: string;
|
icon_background: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type App = {
|
export type App = {
|
||||||
app: AppBasicInfo;
|
app: AppBasicInfo
|
||||||
app_id: string;
|
app_id: string
|
||||||
description: string;
|
description: string
|
||||||
copyright: string;
|
copyright: string
|
||||||
privacy_policy: string;
|
privacy_policy: string
|
||||||
category: string;
|
category: string
|
||||||
position: number;
|
position: number
|
||||||
is_listed: boolean;
|
is_listed: boolean
|
||||||
install_count: number;
|
install_count: number
|
||||||
installed: boolean;
|
installed: boolean
|
||||||
editable: boolean;
|
editable: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstalledApp = {
|
export type InstalledApp = {
|
||||||
app: AppBasicInfo;
|
app: AppBasicInfo
|
||||||
id: string;
|
id: string
|
||||||
uninstallable: boolean
|
uninstallable: boolean
|
||||||
is_pinned: boolean
|
is_pinned: boolean
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { get, post, del, patch } from './base'
|
import { del, get, patch, post } from './base'
|
||||||
|
|
||||||
export const fetchAppList = () => {
|
export const fetchAppList = () => {
|
||||||
return get('/explore/apps')
|
return get('/explore/apps')
|
||||||
@ -15,8 +15,8 @@ export const fetchInstalledAppList = () => {
|
|||||||
export const installApp = (id: string) => {
|
export const installApp = (id: string) => {
|
||||||
return post('/installed-apps', {
|
return post('/installed-apps', {
|
||||||
body: {
|
body: {
|
||||||
app_id: id
|
app_id: id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export const uninstallApp = (id: string) => {
|
|||||||
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
||||||
return patch(`/installed-apps/${id}`, {
|
return patch(`/installed-apps/${id}`, {
|
||||||
body: {
|
body: {
|
||||||
is_pinned: isPinned
|
is_pinned: isPinned,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { IOnCompleted, IOnData, IOnError } from './base'
|
import type { IOnCompleted, IOnData, IOnError } from './base'
|
||||||
import {
|
import {
|
||||||
get as consoleGet, post as consolePost, del as consoleDel,
|
del as consoleDel, get as consoleGet, post as consolePost,
|
||||||
getPublic as get, postPublic as post, ssePost, delPublic as del
|
delPublic as del, getPublic as get, postPublic as post, ssePost,
|
||||||
} from './base'
|
} from './base'
|
||||||
import type { Feedbacktype } from '@/app/components/app/chat'
|
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<string, any>, { onData, onCompleted, onError, getAbortController }: {
|
export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, getAbortController }: {
|
||||||
onData: IOnData
|
onData: IOnData
|
||||||
onCompleted: IOnCompleted
|
onCompleted: IOnCompleted
|
||||||
onError: IOnError,
|
onError: IOnError
|
||||||
getAbortController?: (abortController: AbortController) => void
|
getAbortController?: (abortController: AbortController) => void
|
||||||
}, isInstalledApp: boolean, installedAppId = '') => {
|
}, isInstalledApp: boolean, installedAppId = '') => {
|
||||||
return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), {
|
return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), {
|
||||||
@ -77,7 +77,7 @@ export const fetchMoreLikeThis = async (messageId: string, isInstalledApp: boole
|
|||||||
return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), {
|
return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), {
|
||||||
params: {
|
params: {
|
||||||
response_mode: 'blocking',
|
response_mode: 'blocking',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ export const saveMessage = (messageId: string, isInstalledApp: boolean, installe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchSavedMessage = async (isInstalledApp: boolean, installedAppId = '') => {
|
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 = '') => {
|
export const removeMessage = (messageId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||||
|
@ -38,16 +38,16 @@ export type PromptVariable = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TextTypeFormItem = {
|
export type TextTypeFormItem = {
|
||||||
label: string,
|
label: string
|
||||||
variable: string,
|
variable: string
|
||||||
required: boolean
|
required: boolean
|
||||||
max_length: number
|
max_length: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectTypeFormItem = {
|
export type SelectTypeFormItem = {
|
||||||
label: string,
|
label: string
|
||||||
variable: string,
|
variable: string
|
||||||
required: boolean,
|
required: boolean
|
||||||
options: string[]
|
options: string[]
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -59,7 +59,6 @@ export type UserInputFormItem = {
|
|||||||
'select': SelectTypeFormItem
|
'select': SelectTypeFormItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ToolItem = {
|
export type ToolItem = {
|
||||||
dataset: {
|
dataset: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
Loading…
x
Reference in New Issue
Block a user