Compare commits

...

8 Commits

45 changed files with 391 additions and 583 deletions

View File

@ -5,6 +5,8 @@ label:
model_type: llm model_type: llm
features: features:
- agent-thought - agent-thought
- multi-tool-call
- stream-tool-call
model_properties: model_properties:
mode: chat mode: chat
context_size: 128000 context_size: 128000

View File

@ -5,6 +5,8 @@ label:
model_type: llm model_type: llm
features: features:
- agent-thought - agent-thought
- multi-tool-call
- stream-tool-call
model_properties: model_properties:
mode: chat mode: chat
context_size: 128000 context_size: 128000

View File

@ -28,7 +28,7 @@ class RetrievalService:
@classmethod @classmethod
def retrieve(cls, retrival_method: str, dataset_id: str, query: str, def retrieve(cls, retrival_method: str, dataset_id: str, query: str,
top_k: int, score_threshold: Optional[float] = .0, top_k: int, score_threshold: Optional[float] = .0,
reranking_model: Optional[dict] = None, reranking_mode: Optional[str] = None, reranking_model: Optional[dict] = None, reranking_mode: Optional[str] = 'reranking_model',
weights: Optional[dict] = None): weights: Optional[dict] = None):
dataset = db.session.query(Dataset).filter( dataset = db.session.query(Dataset).filter(
Dataset.id == dataset_id Dataset.id == dataset_id
@ -36,10 +36,6 @@ class RetrievalService:
if not dataset or dataset.available_document_count == 0 or dataset.available_segment_count == 0: if not dataset or dataset.available_document_count == 0 or dataset.available_segment_count == 0:
return [] return []
all_documents = [] all_documents = []
keyword_search_documents = []
embedding_search_documents = []
full_text_search_documents = []
hybrid_search_documents = []
threads = [] threads = []
exceptions = [] exceptions = []
# retrieval_model source with keyword # retrieval_model source with keyword

View File

@ -117,19 +117,63 @@ class WordExtractor(BaseExtractor):
return image_map return image_map
def _table_to_markdown(self, table): def _table_to_markdown(self, table, image_map):
markdown = "" markdown = []
# deal with table headers # calculate the total number of columns
header_row = table.rows[0] total_cols = max(len(row.cells) for row in table.rows)
headers = [cell.text for cell in header_row.cells]
markdown += "| " + " | ".join(headers) + " |\n"
markdown += "| " + " | ".join(["---"] * len(headers)) + " |\n"
# deal with table rows
for row in table.rows[1:]:
row_cells = [cell.text for cell in row.cells]
markdown += "| " + " | ".join(row_cells) + " |\n"
return markdown header_row = table.rows[0]
headers = self._parse_row(header_row, image_map, total_cols)
markdown.append("| " + " | ".join(headers) + " |")
markdown.append("| " + " | ".join(["---"] * total_cols) + " |")
for row in table.rows[1:]:
row_cells = self._parse_row(row, image_map, total_cols)
markdown.append("| " + " | ".join(row_cells) + " |")
return "\n".join(markdown)
def _parse_row(self, row, image_map, total_cols):
# Initialize a row, all of which are empty by default
row_cells = [""] * total_cols
col_index = 0
for cell in row.cells:
# make sure the col_index is not out of range
while col_index < total_cols and row_cells[col_index] != "":
col_index += 1
# if col_index is out of range the loop is jumped
if col_index >= total_cols:
break
cell_content = self._parse_cell(cell, image_map).strip()
cell_colspan = cell.grid_span if cell.grid_span else 1
for i in range(cell_colspan):
if col_index + i < total_cols:
row_cells[col_index + i] = cell_content if i == 0 else ""
col_index += cell_colspan
return row_cells
def _parse_cell(self, cell, image_map):
cell_content = []
for paragraph in cell.paragraphs:
parsed_paragraph = self._parse_cell_paragraph(paragraph, image_map)
if parsed_paragraph:
cell_content.append(parsed_paragraph)
unique_content = list(dict.fromkeys(cell_content))
return " ".join(unique_content)
def _parse_cell_paragraph(self, paragraph, image_map):
paragraph_content = []
for run in paragraph.runs:
if run.element.xpath('.//a:blip'):
for blip in run.element.xpath('.//a:blip'):
image_id = blip.get("{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed")
image_part = paragraph.part.rels[image_id].target_part
if image_part in image_map:
image_link = image_map[image_part]
paragraph_content.append(image_link)
else:
paragraph_content.append(run.text)
return "".join(paragraph_content).strip()
def _parse_paragraph(self, paragraph, image_map): def _parse_paragraph(self, paragraph, image_map):
paragraph_content = [] paragraph_content = []
@ -183,6 +227,6 @@ class WordExtractor(BaseExtractor):
content.append(parsed_paragraph) content.append(parsed_paragraph)
elif element.tag.endswith('tbl'): # table elif element.tag.endswith('tbl'): # table
table = tables.pop(0) table = tables.pop(0)
content.append(self._table_to_markdown(table)) content.append(self._table_to_markdown(table,image_map))
return '\n'.join(content) return '\n'.join(content)

View File

@ -278,6 +278,7 @@ class DatasetRetrieval:
query=query, query=query,
top_k=top_k, score_threshold=score_threshold, top_k=top_k, score_threshold=score_threshold,
reranking_model=reranking_model, reranking_model=reranking_model,
reranking_mode=retrieval_model_config.get('reranking_mode', 'reranking_model'),
weights=retrieval_model_config.get('weights', None), weights=retrieval_model_config.get('weights', None),
) )
self._on_query(query, [dataset_id], app_id, user_from, user_id) self._on_query(query, [dataset_id], app_id, user_from, user_id)
@ -431,10 +432,12 @@ class DatasetRetrieval:
dataset_id=dataset.id, dataset_id=dataset.id,
query=query, query=query,
top_k=top_k, top_k=top_k,
score_threshold=retrieval_model['score_threshold'] score_threshold=retrieval_model.get('score_threshold', .0)
if retrieval_model['score_threshold_enabled'] else None, if retrieval_model['score_threshold_enabled'] else None,
reranking_model=retrieval_model['reranking_model'] reranking_model=retrieval_model.get('reranking_model', None)
if retrieval_model['reranking_enable'] else None, if retrieval_model['reranking_enable'] else None,
reranking_mode=retrieval_model.get('reranking_mode')
if retrieval_model.get('reranking_mode') else 'reranking_model',
weights=retrieval_model.get('weights', None), weights=retrieval_model.get('weights', None),
) )

View File

@ -177,10 +177,12 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool):
dataset_id=dataset.id, dataset_id=dataset.id,
query=query, query=query,
top_k=self.top_k, top_k=self.top_k,
score_threshold=retrieval_model['score_threshold'] score_threshold=retrieval_model.get('score_threshold', .0)
if retrieval_model['score_threshold_enabled'] else None, if retrieval_model['score_threshold_enabled'] else None,
reranking_model=retrieval_model['reranking_model'] reranking_model=retrieval_model.get('reranking_model', None)
if retrieval_model['reranking_enable'] else None, if retrieval_model['reranking_enable'] else None,
reranking_mode=retrieval_model.get('reranking_mode')
if retrieval_model.get('reranking_mode') else 'reranking_model',
weights=retrieval_model.get('weights', None), weights=retrieval_model.get('weights', None),
) )

View File

@ -14,6 +14,7 @@ default_retrieval_model = {
'reranking_provider_name': '', 'reranking_provider_name': '',
'reranking_model_name': '' 'reranking_model_name': ''
}, },
'reranking_mode': 'reranking_model',
'top_k': 2, 'top_k': 2,
'score_threshold_enabled': False 'score_threshold_enabled': False
} }
@ -71,14 +72,15 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool):
else: else:
if self.top_k > 0: if self.top_k > 0:
# retrieval source # retrieval source
documents = RetrievalService.retrieve(retrival_method=retrieval_model['search_method'], documents = RetrievalService.retrieve(retrival_method=retrieval_model.get('search_method', 'semantic_search'),
dataset_id=dataset.id, dataset_id=dataset.id,
query=query, query=query,
top_k=self.top_k, top_k=self.top_k,
score_threshold=retrieval_model['score_threshold'] score_threshold=retrieval_model.get('score_threshold', .0)
if retrieval_model['score_threshold_enabled'] else None, if retrieval_model['score_threshold_enabled'] else None,
reranking_model=retrieval_model['reranking_model'] reranking_model=retrieval_model.get('reranking_model', None),
if retrieval_model['reranking_enable'] else None, reranking_mode=retrieval_model.get('reranking_mode')
if retrieval_model.get('reranking_mode') else 'reranking_model',
weights=retrieval_model.get('weights', None), weights=retrieval_model.get('weights', None),
) )
else: else:

View File

@ -94,8 +94,11 @@ class CodeNode(BaseNode):
:return: :return:
""" """
if not isinstance(value, str): if not isinstance(value, str):
raise ValueError(f"Output variable `{variable}` must be a string") if isinstance(value, type(None)):
return None
else:
raise ValueError(f"Output variable `{variable}` must be a string")
if len(value) > MAX_STRING_LENGTH: if len(value) > MAX_STRING_LENGTH:
raise ValueError(f'The length of output variable `{variable}` must be less than {MAX_STRING_LENGTH} characters') raise ValueError(f'The length of output variable `{variable}` must be less than {MAX_STRING_LENGTH} characters')
@ -109,7 +112,10 @@ class CodeNode(BaseNode):
:return: :return:
""" """
if not isinstance(value, int | float): if not isinstance(value, int | float):
raise ValueError(f"Output variable `{variable}` must be a number") if isinstance(value, type(None)):
return None
else:
raise ValueError(f"Output variable `{variable}` must be a number")
if value > MAX_NUMBER or value < MIN_NUMBER: if value > MAX_NUMBER or value < MIN_NUMBER:
raise ValueError(f'Output variable `{variable}` is out of range, it must be between {MIN_NUMBER} and {MAX_NUMBER}.') raise ValueError(f'Output variable `{variable}` is out of range, it must be between {MIN_NUMBER} and {MAX_NUMBER}.')
@ -157,28 +163,31 @@ class CodeNode(BaseNode):
elif isinstance(output_value, list): elif isinstance(output_value, list):
first_element = output_value[0] if len(output_value) > 0 else None first_element = output_value[0] if len(output_value) > 0 else None
if first_element is not None: if first_element is not None:
if isinstance(first_element, int | float) and all(isinstance(value, int | float) for value in output_value): if isinstance(first_element, int | float) and all(value is None or isinstance(value, int | float) for value in output_value):
for i, value in enumerate(output_value): for i, value in enumerate(output_value):
self._check_number( self._check_number(
value=value, value=value,
variable=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]' variable=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]'
) )
elif isinstance(first_element, str) and all(isinstance(value, str) for value in output_value): elif isinstance(first_element, str) and all(value is None or isinstance(value, str) for value in output_value):
for i, value in enumerate(output_value): for i, value in enumerate(output_value):
self._check_string( self._check_string(
value=value, value=value,
variable=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]' variable=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]'
) )
elif isinstance(first_element, dict) and all(isinstance(value, dict) for value in output_value): elif isinstance(first_element, dict) and all(value is None or isinstance(value, dict) for value in output_value):
for i, value in enumerate(output_value): for i, value in enumerate(output_value):
self._transform_result( if value is not None:
result=value, self._transform_result(
output_schema=None, result=value,
prefix=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]', output_schema=None,
depth=depth + 1 prefix=f'{prefix}.{output_name}[{i}]' if prefix else f'{output_name}[{i}]',
) depth=depth + 1
)
else: else:
raise ValueError(f'Output {prefix}.{output_name} is not a valid array. make sure all elements are of the same type.') raise ValueError(f'Output {prefix}.{output_name} is not a valid array. make sure all elements are of the same type.')
elif isinstance(output_value, type(None)):
pass
else: else:
raise ValueError(f'Output {prefix}.{output_name} is not a valid type.') raise ValueError(f'Output {prefix}.{output_name} is not a valid type.')
@ -193,16 +202,19 @@ class CodeNode(BaseNode):
if output_config.type == 'object': if output_config.type == 'object':
# check if output is object # check if output is object
if not isinstance(result.get(output_name), dict): if not isinstance(result.get(output_name), dict):
raise ValueError( if isinstance(result.get(output_name), type(None)):
f'Output {prefix}{dot}{output_name} is not an object, got {type(result.get(output_name))} instead.' transformed_result[output_name] = None
else:
raise ValueError(
f'Output {prefix}{dot}{output_name} is not an object, got {type(result.get(output_name))} instead.'
)
else:
transformed_result[output_name] = self._transform_result(
result=result[output_name],
output_schema=output_config.children,
prefix=f'{prefix}.{output_name}',
depth=depth + 1
) )
transformed_result[output_name] = self._transform_result(
result=result[output_name],
output_schema=output_config.children,
prefix=f'{prefix}.{output_name}',
depth=depth + 1
)
elif output_config.type == 'number': elif output_config.type == 'number':
# check if number available # check if number available
transformed_result[output_name] = self._check_number( transformed_result[output_name] = self._check_number(
@ -218,68 +230,80 @@ class CodeNode(BaseNode):
elif output_config.type == 'array[number]': elif output_config.type == 'array[number]':
# check if array of number available # check if array of number available
if not isinstance(result[output_name], list): if not isinstance(result[output_name], list):
raise ValueError( if isinstance(result[output_name], type(None)):
f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.' transformed_result[output_name] = None
) else:
raise ValueError(
f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.'
)
else:
if len(result[output_name]) > MAX_NUMBER_ARRAY_LENGTH:
raise ValueError(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_NUMBER_ARRAY_LENGTH} elements.'
)
if len(result[output_name]) > MAX_NUMBER_ARRAY_LENGTH: transformed_result[output_name] = [
raise ValueError( self._check_number(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_NUMBER_ARRAY_LENGTH} elements.' value=value,
) variable=f'{prefix}{dot}{output_name}[{i}]'
)
transformed_result[output_name] = [ for i, value in enumerate(result[output_name])
self._check_number( ]
value=value,
variable=f'{prefix}{dot}{output_name}[{i}]'
)
for i, value in enumerate(result[output_name])
]
elif output_config.type == 'array[string]': elif output_config.type == 'array[string]':
# check if array of string available # check if array of string available
if not isinstance(result[output_name], list): if not isinstance(result[output_name], list):
raise ValueError( if isinstance(result[output_name], type(None)):
f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.' transformed_result[output_name] = None
) else:
raise ValueError(
f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.'
)
else:
if len(result[output_name]) > MAX_STRING_ARRAY_LENGTH:
raise ValueError(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_STRING_ARRAY_LENGTH} elements.'
)
if len(result[output_name]) > MAX_STRING_ARRAY_LENGTH: transformed_result[output_name] = [
raise ValueError( self._check_string(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_STRING_ARRAY_LENGTH} elements.' value=value,
) variable=f'{prefix}{dot}{output_name}[{i}]'
)
transformed_result[output_name] = [ for i, value in enumerate(result[output_name])
self._check_string( ]
value=value,
variable=f'{prefix}{dot}{output_name}[{i}]'
)
for i, value in enumerate(result[output_name])
]
elif output_config.type == 'array[object]': elif output_config.type == 'array[object]':
# check if array of object available # check if array of object available
if not isinstance(result[output_name], list): if not isinstance(result[output_name], list):
raise ValueError( if isinstance(result[output_name], type(None)):
f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.' transformed_result[output_name] = None
) else:
if len(result[output_name]) > MAX_OBJECT_ARRAY_LENGTH:
raise ValueError(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_OBJECT_ARRAY_LENGTH} elements.'
)
for i, value in enumerate(result[output_name]):
if not isinstance(value, dict):
raise ValueError( raise ValueError(
f'Output {prefix}{dot}{output_name}[{i}] is not an object, got {type(value)} instead at index {i}.' f'Output {prefix}{dot}{output_name} is not an array, got {type(result.get(output_name))} instead.'
) )
else:
if len(result[output_name]) > MAX_OBJECT_ARRAY_LENGTH:
raise ValueError(
f'The length of output variable `{prefix}{dot}{output_name}` must be less than {MAX_OBJECT_ARRAY_LENGTH} elements.'
)
for i, value in enumerate(result[output_name]):
if not isinstance(value, dict):
if isinstance(value, type(None)):
pass
else:
raise ValueError(
f'Output {prefix}{dot}{output_name}[{i}] is not an object, got {type(value)} instead at index {i}.'
)
transformed_result[output_name] = [ transformed_result[output_name] = [
self._transform_result( None if value is None else self._transform_result(
result=value, result=value,
output_schema=output_config.children, output_schema=output_config.children,
prefix=f'{prefix}{dot}{output_name}[{i}]', prefix=f'{prefix}{dot}{output_name}[{i}]',
depth=depth + 1 depth=depth + 1
) )
for i, value in enumerate(result[output_name]) for i, value in enumerate(result[output_name])
] ]
else: else:
raise ValueError(f'Output type {output_config.type} is not supported.') raise ValueError(f'Output type {output_config.type} is not supported.')

View File

@ -42,11 +42,11 @@ class HitTestingService:
dataset_id=dataset.id, dataset_id=dataset.id,
query=cls.escape_query_for_search(query), query=cls.escape_query_for_search(query),
top_k=retrieval_model.get('top_k', 2), top_k=retrieval_model.get('top_k', 2),
score_threshold=retrieval_model['score_threshold'] score_threshold=retrieval_model.get('score_threshold', .0)
if retrieval_model['score_threshold_enabled'] else None, if retrieval_model['score_threshold_enabled'] else None,
reranking_model=retrieval_model['reranking_model'] reranking_model=retrieval_model.get('reranking_model', None),
if retrieval_model['reranking_enable'] else None, reranking_mode=retrieval_model.get('reranking_mode')
reranking_mode=retrieval_model.get('reranking_mode', None), if retrieval_model.get('reranking_mode') else 'reranking_model',
weights=retrieval_model.get('weights', None), weights=retrieval_model.get('weights', None),
) )

View File

@ -14,7 +14,7 @@ import {
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
import ConfirmUi from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps' import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
@ -276,9 +276,8 @@ const ProviderConfigModal: FC<Props> = ({
</PortalToFollowElem> </PortalToFollowElem>
) )
: ( : (
<ConfirmUi <Confirm
isShow isShow
onClose={hideRemoveConfirm}
type='warning' type='warning'
title={t(`${I18N_PREFIX}.removeConfirmTitle`, { key: t(`app.tracing.${type}.title`) })!} title={t(`${I18N_PREFIX}.removeConfirmTitle`, { key: t(`app.tracing.${type}.title`) })!}
content={t(`${I18N_PREFIX}.removeConfirmContent`)} content={t(`${I18N_PREFIX}.removeConfirmContent`)}

View File

@ -392,7 +392,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
title={t('app.deleteAppConfirmTitle')} title={t('app.deleteAppConfirmTitle')}
content={t('app.deleteAppConfirmContent')} content={t('app.deleteAppConfirmContent')}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />

View File

@ -219,7 +219,6 @@ const DatasetCard = ({
title={t('dataset.deleteDatasetConfirmTitle')} title={t('dataset.deleteDatasetConfirmTitle')}
content={confirmMessage} content={confirmMessage}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />

View File

@ -426,7 +426,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
title={t('app.deleteAppConfirmTitle')} title={t('app.deleteAppConfirmTitle')}
content={t('app.deleteAppConfirmContent')} content={t('app.deleteAppConfirmContent')}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import EditItem, { EditItemType } from './edit-item' import EditItem, { EditItemType } from './edit-item'
import Drawer from '@/app/components/base/drawer-plus' import Drawer from '@/app/components/base/drawer-plus'
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication' import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
import DeleteConfirmModal from '@/app/components/base/modal/delete-confirm-modal' import Confirm from '@/app/components/base/confirm'
import { addAnnotation, editAnnotation } from '@/service/annotation' import { addAnnotation, editAnnotation } from '@/service/annotation'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
@ -85,19 +85,31 @@ const EditAnnotationModal: FC<Props> = ({
maxWidthClassName='!max-w-[480px]' maxWidthClassName='!max-w-[480px]'
title={t('appAnnotation.editModal.title') as string} title={t('appAnnotation.editModal.title') as string}
body={( body={(
<div className='p-6 pb-4 space-y-6'> <div>
<EditItem <div className='p-6 pb-4 space-y-6'>
type={EditItemType.Query} <EditItem
content={query} type={EditItemType.Query}
readonly={(isAdd && isAnnotationFull) || onlyEditResponse} content={query}
onSave={editedContent => handleSave(EditItemType.Query, editedContent)} readonly={(isAdd && isAnnotationFull) || onlyEditResponse}
/> onSave={editedContent => handleSave(EditItemType.Query, editedContent)}
<EditItem />
type={EditItemType.Answer} <EditItem
content={answer} type={EditItemType.Answer}
readonly={isAdd && isAnnotationFull} content={answer}
onSave={editedContent => handleSave(EditItemType.Answer, editedContent)} readonly={isAdd && isAnnotationFull}
/> onSave={editedContent => handleSave(EditItemType.Answer, editedContent)}
/>
<Confirm
isShow={showModal}
onCancel={() => setShowModal(false)}
onConfirm={() => {
onRemove()
setShowModal(false)
onHide()
}}
title={t('appDebug.feature.annotation.removeConfirm')}
/>
</div>
</div> </div>
)} )}
foot={ foot={
@ -127,16 +139,6 @@ const EditAnnotationModal: FC<Props> = ({
</div> </div>
} }
/> />
<DeleteConfirmModal
isShow={showModal}
onHide={() => setShowModal(false)}
onRemove={() => {
onRemove()
setShowModal(false)
onHide()
}}
text={t('appDebug.feature.annotation.removeConfirm') as string}
/>
</div> </div>
) )

View File

@ -2,7 +2,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import DeleteConfirmModal from '@/app/components/base/modal/delete-confirm-modal' import Confirm from '@/app/components/base/confirm'
type Props = { type Props = {
isShow: boolean isShow: boolean
@ -18,11 +18,11 @@ const RemoveAnnotationConfirmModal: FC<Props> = ({
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<DeleteConfirmModal <Confirm
isShow={isShow} isShow={isShow}
onHide={onHide} onCancel={onHide}
onRemove={onRemove} onConfirm={onRemove}
text={t('appDebug.feature.annotation.removeConfirm') as string} title={t('appDebug.feature.annotation.removeConfirm')}
/> />
) )
} }

View File

@ -11,7 +11,7 @@ import HitHistoryNoData from './hit-history-no-data'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Drawer from '@/app/components/base/drawer-plus' import Drawer from '@/app/components/base/drawer-plus'
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication' import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
import DeleteConfirmModal from '@/app/components/base/modal/delete-confirm-modal' import Confirm from '@/app/components/base/confirm'
import TabSlider from '@/app/components/base/tab-slider-plain' import TabSlider from '@/app/components/base/tab-slider-plain'
import { fetchHitHistoryList } from '@/service/annotation' import { fetchHitHistoryList } from '@/service/annotation'
import { APP_PAGE_LIMIT } from '@/config' import { APP_PAGE_LIMIT } from '@/config'
@ -201,8 +201,20 @@ const ViewAnnotationModal: FC<Props> = ({
/> />
} }
body={( body={(
<div className='p-6 pb-4 space-y-6'> <div>
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab} <div className='p-6 pb-4 space-y-6'>
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
</div>
<Confirm
isShow={showModal}
onCancel={() => setShowModal(false)}
onConfirm={async () => {
await onRemove()
setShowModal(false)
onHide()
}}
title={t('appDebug.feature.annotation.removeConfirm')}
/>
</div> </div>
)} )}
foot={id foot={id
@ -220,16 +232,6 @@ const ViewAnnotationModal: FC<Props> = ({
) )
: undefined} : undefined}
/> />
<DeleteConfirmModal
isShow={showModal}
onHide={() => setShowModal(false)}
onRemove={async () => {
await onRemove()
setShowModal(false)
onHide()
}}
text={t('appDebug.feature.annotation.removeConfirm') as string}
/>
</div> </div>
) )

View File

@ -24,7 +24,7 @@ import { checkKeys, getNewVar } from '@/utils/var'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
import ConfirmModal from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import type { ExternalDataTool } from '@/models/common' import type { ExternalDataTool } from '@/models/common'
@ -389,11 +389,10 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
)} )}
{isShowDeleteContextVarModal && ( {isShowDeleteContextVarModal && (
<ConfirmModal <Confirm
isShow={isShowDeleteContextVarModal} isShow={isShowDeleteContextVarModal}
title={t('appDebug.feature.dataSet.queryVariable.deleteContextVarTitle', { varName: promptVariables[removeIndex as number]?.name })} title={t('appDebug.feature.dataSet.queryVariable.deleteContextVarTitle', { varName: promptVariables[removeIndex as number]?.name })}
desc={t('appDebug.feature.dataSet.queryVariable.deleteContextVarTip') as string} content={t('appDebug.feature.dataSet.queryVariable.deleteContextVarTip')}
confirmBtnClassName='bg-[#B42318] hover:bg-[#B42318]'
onConfirm={() => { onConfirm={() => {
didRemoveVar(removeIndex as number) didRemoveVar(removeIndex as number)
hideDeleteContextVarModal() hideDeleteContextVarModal()

View File

@ -282,7 +282,6 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
title={t('appDebug.generate.overwriteTitle')} title={t('appDebug.generate.overwriteTitle')}
content={t('appDebug.generate.overwriteMessage')} content={t('appDebug.generate.overwriteMessage')}
isShow={showConfirmOverwrite} isShow={showConfirmOverwrite}
onClose={() => setShowConfirmOverwrite(false)}
onConfirm={() => { onConfirm={() => {
setShowConfirmOverwrite(false) setShowConfirmOverwrite(false)
onFinished(res!) onFinished(res!)

View File

@ -880,7 +880,6 @@ const Configuration: FC = () => {
title={t('appDebug.resetConfig.title')} title={t('appDebug.resetConfig.title')}
content={t('appDebug.resetConfig.message')} content={t('appDebug.resetConfig.message')}
isShow={restoreConfirmOpen} isShow={restoreConfirmOpen}
onClose={() => setRestoreConfirmOpen(false)}
onConfirm={resetAppConfig} onConfirm={resetAppConfig}
onCancel={() => setRestoreConfirmOpen(false)} onCancel={() => setRestoreConfirmOpen(false)}
/> />
@ -890,7 +889,6 @@ const Configuration: FC = () => {
title={t('appDebug.trailUseGPT4Info.title')} title={t('appDebug.trailUseGPT4Info.title')}
content={t('appDebug.trailUseGPT4Info.description')} content={t('appDebug.trailUseGPT4Info.description')}
isShow={showUseGPT4Confirm} isShow={showUseGPT4Confirm}
onClose={() => setShowUseGPT4Confirm(false)}
onConfirm={() => { onConfirm={() => {
setShowAccountSettingModal({ payload: 'provider' }) setShowAccountSettingModal({ payload: 'provider' })
setShowUseGPT4Confirm(false) setShowUseGPT4Confirm(false)

View File

@ -185,7 +185,6 @@ function AppCard({
title={t('appOverview.overview.appInfo.regenerate')} title={t('appOverview.overview.appInfo.regenerate')}
content={t('appOverview.overview.appInfo.regenerateNotice')} content={t('appOverview.overview.appInfo.regenerateNotice')}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={() => { onConfirm={() => {
onGenCode() onGenCode()
setShowConfirmDelete(false) setShowConfirmDelete(false)

View File

@ -147,10 +147,6 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
setShowConfirmDelete(false) setShowConfirmDelete(false)
setRemoveOriginal(false) setRemoveOriginal(false)
}} }}
onClose={() => {
setShowConfirmDelete(false)
setRemoveOriginal(false)
}}
/> />
)} )}
</> </>

View File

@ -121,7 +121,6 @@ const Sidebar = () => {
title={t('share.chat.deleteConversation.title')} title={t('share.chat.deleteConversation.title')}
content={t('share.chat.deleteConversation.content') || ''} content={t('share.chat.deleteConversation.content') || ''}
isShow isShow
onClose={handleCancelConfirm}
onCancel={handleCancelConfirm} onCancel={handleCancelConfirm}
onConfirm={handleDelete} onConfirm={handleDelete}
/> />

View File

@ -83,7 +83,7 @@ export const useChat = (
const { t } = useTranslation() const { t } = useTranslation()
const { formatTime } = useTimestamp() const { formatTime } = useTimestamp()
const { notify } = useToastContext() const { notify } = useToastContext()
const connversationId = useRef('') const conversationId = useRef('')
const hasStopResponded = useRef(false) const hasStopResponded = useRef(false)
const [isResponding, setIsResponding] = useState(false) const [isResponding, setIsResponding] = useState(false)
const isRespondingRef = useRef(false) const isRespondingRef = useRef(false)
@ -152,7 +152,7 @@ export const useChat = (
}, [stopChat, handleResponding]) }, [stopChat, handleResponding])
const handleRestart = useCallback(() => { const handleRestart = useCallback(() => {
connversationId.current = '' conversationId.current = ''
taskIdRef.current = '' taskIdRef.current = ''
handleStop() handleStop()
const newChatList = config?.opening_statement const newChatList = config?.opening_statement
@ -248,7 +248,7 @@ export const useChat = (
const bodyParams = { const bodyParams = {
response_mode: 'streaming', response_mode: 'streaming',
conversation_id: connversationId.current, conversation_id: conversationId.current,
...data, ...data,
} }
if (bodyParams?.files?.length) { if (bodyParams?.files?.length) {
@ -302,7 +302,7 @@ export const useChat = (
} }
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
connversationId.current = newConversationId conversationId.current = newConversationId
taskIdRef.current = taskId taskIdRef.current = taskId
if (messageId) if (messageId)
@ -322,11 +322,11 @@ export const useChat = (
return return
if (onConversationComplete) if (onConversationComplete)
onConversationComplete(connversationId.current) onConversationComplete(conversationId.current)
if (connversationId.current && !hasStopResponded.current && onGetConvesationMessages) { if (conversationId.current && !hasStopResponded.current && onGetConvesationMessages) {
const { data }: any = await onGetConvesationMessages( const { data }: any = await onGetConvesationMessages(
connversationId.current, conversationId.current,
newAbortController => conversationMessagesAbortControllerRef.current = newAbortController, newAbortController => conversationMessagesAbortControllerRef.current = newAbortController,
) )
const newResponseItem = data.find((item: any) => item.id === responseItem.id) const newResponseItem = data.find((item: any) => item.id === responseItem.id)
@ -361,7 +361,7 @@ export const useChat = (
latency: newResponseItem.provider_response_latency.toFixed(2), latency: newResponseItem.provider_response_latency.toFixed(2),
}, },
// for agent log // for agent log
conversationId: connversationId.current, conversationId: conversationId.current,
input: { input: {
inputs: newResponseItem.inputs, inputs: newResponseItem.inputs,
query: newResponseItem.query, query: newResponseItem.query,
@ -640,7 +640,7 @@ export const useChat = (
return { return {
chatList, chatList,
setChatList, setChatList,
conversationId: connversationId.current, conversationId: conversationId.current,
isResponding, isResponding,
setIsResponding, setIsResponding,
handleSend, handleSend,

View File

@ -1,52 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../button'
export type IConfirmUIProps = {
type: 'info' | 'warning'
title: string
content: string
confirmText?: string
onConfirm: () => void
cancelText?: string
onCancel: () => void
}
const ConfirmUI: FC<IConfirmUIProps> = ({
type,
title,
content,
confirmText,
cancelText,
onConfirm,
onCancel,
}) => {
const { t } = useTranslation()
return (
<div className="w-[420px] max-w-full rounded-lg p-7 bg-white">
<div className='flex items-center'>
{type === 'info' && (<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.3333 21.3333H16V16H14.6667M16 10.6667H16.0133M28 16C28 17.5759 27.6896 19.1363 27.0866 20.5922C26.4835 22.0481 25.5996 23.371 24.4853 24.4853C23.371 25.5996 22.0481 26.4835 20.5922 27.0866C19.1363 27.6896 17.5759 28 16 28C14.4241 28 12.8637 27.6896 11.4078 27.0866C9.95189 26.4835 8.62902 25.5996 7.51472 24.4853C6.40042 23.371 5.5165 22.0481 4.91345 20.5922C4.31039 19.1363 4 17.5759 4 16C4 12.8174 5.26428 9.76516 7.51472 7.51472C9.76516 5.26428 12.8174 4 16 4C19.1826 4 22.2348 5.26428 24.4853 7.51472C26.7357 9.76516 28 12.8174 28 16Z" stroke="#9CA3AF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>)}
{type === 'warning' && (<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 10.6667V16M16 21.3333H16.0133M28 16C28 17.5759 27.6896 19.1363 27.0866 20.5922C26.4835 22.0481 25.5996 23.371 24.4853 24.4853C23.371 25.5996 22.0481 26.4835 20.5922 27.0866C19.1363 27.6896 17.5759 28 16 28C14.4241 28 12.8637 27.6896 11.4078 27.0866C9.95189 26.4835 8.62902 25.5996 7.51472 24.4853C6.40042 23.371 5.5165 22.0481 4.91345 20.5922C4.31039 19.1363 4 17.5759 4 16C4 12.8174 5.26428 9.76516 7.51472 7.51472C9.76516 5.26428 12.8174 4 16 4C19.1826 4 22.2348 5.26428 24.4853 7.51472C26.7357 9.76516 28 12.8174 28 16Z" stroke="#FACA15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)}
<div className='ml-4 text-lg text-gray-900'>{title}</div>
</div>
<div className='mt-1 ml-12'>
<div className='text-sm leading-normal text-gray-500'>{content}</div>
</div>
<div className='flex gap-3 mt-4 ml-12'>
<Button variant='primary' onClick={onConfirm}>{confirmText || t('common.operation.confirm')}</Button>
<Button onClick={onCancel}>{cancelText || t('common.operation.cancel')}</Button>
</div>
</div>
)
}
export default React.memo(ConfirmUI)

View File

@ -1,7 +0,0 @@
.wrapper-danger {
background: linear-gradient(180deg, rgba(217, 45, 32, 0.05) 0%, rgba(217, 45, 32, 0.00) 24.02%), #F9FAFB;
}
.wrapper-success {
background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0.00) 22.44%), #F9FAFB;
}

View File

@ -1,97 +0,0 @@
import type { FC, ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiCloseLine,
RiErrorWarningFill,
} from '@remixicon/react'
import s from './common.module.css'
import cn from '@/utils/classnames'
import Modal from '@/app/components/base/modal'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import Button from '@/app/components/base/button'
export type ConfirmCommonProps = {
type?: string
isShow: boolean
onCancel: () => void
title: string
desc?: string
onConfirm?: () => void
showOperate?: boolean
showOperateCancel?: boolean
confirmBtnClassName?: string
confirmText?: string
confirmWrapperClassName?: string
confirmDisabled?: boolean
}
const ConfirmCommon: FC<ConfirmCommonProps> = ({
type = 'danger',
isShow,
onCancel,
title,
desc,
onConfirm,
showOperate = true,
showOperateCancel = true,
confirmBtnClassName,
confirmText,
confirmWrapperClassName,
confirmDisabled,
}) => {
const { t } = useTranslation()
const CONFIRM_MAP: Record<string, { icon: ReactElement; confirmText: string }> = {
danger: {
icon: <RiErrorWarningFill className='w-6 h-6 text-[#D92D20]' />,
confirmText: t('common.operation.remove'),
},
success: {
icon: <CheckCircle className='w-6 h-6 text-[#039855]' />,
confirmText: t('common.operation.ok'),
},
}
return (
<Modal isShow={isShow} onClose={() => { }} className='!w-[480px] !max-w-[480px] !p-0 !rounded-2xl' wrapperClassName={confirmWrapperClassName}>
<div className={cn(s[`wrapper-${type}`], 'relative p-8')}>
<div className='flex items-center justify-center absolute top-4 right-4 w-8 h-8 cursor-pointer' onClick={onCancel}>
<RiCloseLine className='w-4 h-4 text-gray-500' />
</div>
<div className='flex items-center justify-center mb-3 w-12 h-12 bg-white shadow-xl rounded-xl'>
{CONFIRM_MAP[type].icon}
</div>
<div className='text-xl font-semibold text-gray-900'>{title}</div>
{
desc && <div className='mt-1 text-sm text-gray-500'>{desc}</div>
}
{
showOperate && (
<div className='flex items-center justify-end mt-10'>
{
showOperateCancel && (
<Button
className='mr-2'
onClick={onCancel}
>
{t('common.operation.cancel')}
</Button>
)
}
<Button
variant='primary'
className={confirmBtnClassName || ''}
onClick={onConfirm}
disabled={confirmDisabled}
>
{confirmText || CONFIRM_MAP[type].confirmText}
</Button>
</div>
)
}
</div>
</Modal>
)
}
export default ConfirmCommon

View File

@ -1,26 +1,27 @@
import { Dialog, Transition } from '@headlessui/react' import React, { useEffect, useRef, useState } from 'react'
import { Fragment } from 'react' import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ConfirmUI from '../confirm-ui' import Button from '../button'
// https://headlessui.com/react/dialog export type IConfirm = {
type IConfirm = {
className?: string className?: string
isShow: boolean isShow: boolean
onClose: () => void
type?: 'info' | 'warning' type?: 'info' | 'warning'
title: string title: string
content: string content?: React.ReactNode
confirmText?: string confirmText?: string | null
onConfirm: () => void onConfirm: () => void
cancelText?: string cancelText?: string
onCancel: () => void onCancel: () => void
isLoading?: boolean
isDisabled?: boolean
showConfirm?: boolean
showCancel?: boolean
maskClosable?: boolean
} }
export default function Confirm({ function Confirm({
isShow, isShow,
onClose,
type = 'warning', type = 'warning',
title, title,
content, content,
@ -28,52 +29,76 @@ export default function Confirm({
cancelText, cancelText,
onConfirm, onConfirm,
onCancel, onCancel,
showConfirm = true,
showCancel = true,
isLoading = false,
isDisabled = false,
maskClosable = true,
}: IConfirm) { }: IConfirm) {
const { t } = useTranslation() const { t } = useTranslation()
const dialogRef = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(isShow)
const confirmTxt = confirmText || `${t('common.operation.confirm')}` const confirmTxt = confirmText || `${t('common.operation.confirm')}`
const cancelTxt = cancelText || `${t('common.operation.cancel')}` const cancelTxt = cancelText || `${t('common.operation.cancel')}`
return (
<Transition appear show={isShow} as={Fragment}>
<Dialog as="div" className="relative z-[100]" onClose={onClose} onClick={e => e.preventDefault()}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto"> useEffect(() => {
<div className="flex items-center justify-center min-h-full p-4 text-center"> const handleKeyDown = (event: KeyboardEvent) => {
<Transition.Child if (event.key === 'Escape')
as={Fragment} onCancel()
enter="ease-out duration-300" }
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className={'w-full max-w-md transform overflow-hidden rounded-2xl bg-white text-left align-middle shadow-xl transition-all'}>
<ConfirmUI
type={type}
title={title}
content={content}
confirmText={confirmTxt}
cancelText={cancelTxt}
onConfirm={onConfirm}
onCancel={onCancel}
/>
</Dialog.Panel>
</Transition.Child>
document.addEventListener('keydown', handleKeyDown)
return () => {
document.removeEventListener('keydown', handleKeyDown)
}
}, [onCancel])
const handleClickOutside = (event: MouseEvent) => {
if (maskClosable && dialogRef.current && !dialogRef.current.contains(event.target as Node))
onCancel()
}
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [maskClosable])
useEffect(() => {
if (isShow) {
setIsVisible(true)
}
else {
const timer = setTimeout(() => setIsVisible(false), 200)
return () => clearTimeout(timer)
}
}, [isShow])
if (!isVisible)
return null
return createPortal(
<div className={'fixed inset-0 flex items-center justify-center z-[10000000] bg-background-overlay'}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}>
<div ref={dialogRef} className={'relative w-full max-w-[480px] overflow-hidden'}>
<div className='flex flex-col items-start max-w-full rounded-2xl border-[0.5px] border-solid border-components-panel-border shadows-shadow-lg bg-components-panel-bg'>
<div className='flex pt-6 pl-6 pr-6 pb-4 flex-col items-start gap-2 self-stretch'>
<div className='title-2xl-semi-bold text-text-primary'>{title}</div>
<div className='system-md-regular text-text-tertiary'>{content}</div>
</div>
<div className='flex p-6 gap-2 justify-end items-start self-stretch'>
{showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}
{showConfirm && <Button variant={'primary'} destructive={type !== 'info'} loading={isLoading} disabled={isDisabled} onClick={onConfirm}>{confirmTxt}</Button>}
</div> </div>
</div> </div>
</Dialog> </div>
</Transition> </div>, document.body,
) )
} }
export default React.memo(Confirm)

View File

@ -1,66 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import {
RiErrorWarningFill,
} from '@remixicon/react'
import s from './style.module.css'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
type Props = {
isShow: boolean
onHide: () => void
onRemove: () => void
text?: string
children?: JSX.Element
}
const DeleteConfirmModal: FC<Props> = ({
isShow,
onHide,
onRemove,
children,
text,
}) => {
const { t } = useTranslation()
if (!isShow)
return null
return (
<Modal
isShow={isShow}
onClose={onHide}
className={s.delModal}
closable
>
<div onClick={(e) => {
e.stopPropagation()
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}}>
<div className={s.warningWrapper}>
<RiErrorWarningFill className='w-6 h-6 text-red-600' />
</div>
{text
? (
<div className='text-xl font-semibold text-gray-900 mb-3'>{text}</div>
)
: children}
<div className='flex gap-2 justify-end'>
<Button onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button
variant='warning'
onClick={onRemove}
className='border-red-700'
>
{t('common.operation.sure')}
</Button>
</div>
</div>
</Modal>
)
}
export default React.memo(DeleteConfirmModal)

View File

@ -1,16 +0,0 @@
.delModal {
background: linear-gradient(180deg,
rgba(217, 45, 32, 0.05) 0%,
rgba(217, 45, 32, 0) 24.02%),
#f9fafb;
box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08),
0px 8px 8px -4px rgba(16, 24, 40, 0.03);
@apply rounded-2xl p-8;
}
.warningWrapper {
box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08),
0px 8px 8px -4px rgba(16, 24, 40, 0.03);
background: rgba(255, 255, 255, 0.9);
@apply h-12 w-12 border-[0.5px] border-gray-100 rounded-xl mb-3 flex items-center justify-center;
}

View File

@ -4,15 +4,13 @@ import { ArrowUpRightIcon } from '@heroicons/react/24/outline'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
RiDeleteBinLine, RiDeleteBinLine,
RiErrorWarningFill,
} from '@remixicon/react' } from '@remixicon/react'
import { StatusItem } from '../../list' import { StatusItem } from '../../list'
import { DocumentTitle } from '../index' import { DocumentTitle } from '../index'
import s from './style.module.css' import s from './style.module.css'
import { SegmentIndexTag } from './index' import { SegmentIndexTag } from './index'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Modal from '@/app/components/base/modal' import Confirm from '@/app/components/base/confirm'
import Button from '@/app/components/base/button'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
@ -217,26 +215,15 @@ const SegmentCard: FC<ISegmentCardProps> = ({
</div> </div>
</> </>
)} )}
{showModal && <Modal isShow={showModal} onClose={() => setShowModal(false)} className={s.delModal} closable> {showModal
<div> && <Confirm
<div className={s.warningWrapper}> isShow={showModal}
<RiErrorWarningFill className='w-6 h-6 text-red-600' /> title={t('datasetDocuments.segment.delete')}
</div> confirmText={t('common.operation.sure')}
<div className='text-xl font-semibold text-gray-900 mb-1'>{t('datasetDocuments.segment.delete')}</div> onConfirm={async () => { await onDelete?.(id) }}
<div className='flex gap-2 justify-end'> onCancel={() => setShowModal(false)}
<Button onClick={() => setShowModal(false)}>{t('common.operation.cancel')}</Button> />
<Button }
variant='warning'
onClick={async () => {
await onDelete?.(id)
}}
className='border-red-700'
>
{t('common.operation.sure')}
</Button>
</div>
</div>
</Modal>}
</div> </div>
) )
} }

View File

@ -4,7 +4,6 @@ import type { FC, SVGProps } from 'react'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { useBoolean, useDebounceFn } from 'ahooks' import { useBoolean, useDebounceFn } from 'ahooks'
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline' import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
import { pick } from 'lodash-es' import { pick } from 'lodash-es'
import { import {
RiMoreFill, RiMoreFill,
@ -23,8 +22,7 @@ import cn from '@/utils/classnames'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import Popover from '@/app/components/base/popover' import Popover from '@/app/components/base/popover'
import Modal from '@/app/components/base/modal' import Confirm from '@/app/components/base/confirm'
import Button from '@/app/components/base/button'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import type { IndicatorProps } from '@/app/components/header/indicator' import type { IndicatorProps } from '@/app/components/header/indicator'
@ -294,25 +292,16 @@ export const OperationAction: FC<{
className={`flex justify-end !w-[200px] h-fit !z-20 ${className}`} className={`flex justify-end !w-[200px] h-fit !z-20 ${className}`}
/> />
)} )}
{showModal && <Modal isShow={showModal} onClose={() => setShowModal(false)} className={s.delModal} closable> {showModal
<div> && <Confirm
<div className={s.warningWrapper}> isShow={showModal}
<ExclamationCircleIcon className={s.warningIcon} /> title={t('datasetDocuments.list.delete.title')}
</div> content={t('datasetDocuments.list.delete.content')}
<div className='text-xl font-semibold text-gray-900 mb-1'>{t('datasetDocuments.list.delete.title')}</div> confirmText={t('common.operation.sure')}
<div className='text-sm text-gray-500 mb-10'>{t('datasetDocuments.list.delete.content')}</div> onConfirm={() => onOperate('delete')}
<div className='flex gap-2 justify-end'> onCancel={() => setShowModal(false)}
<Button onClick={() => setShowModal(false)}>{t('common.operation.cancel')}</Button> />
<Button }
variant='warning'
onClick={() => onOperate('delete')}
className='border-red-700'
>
{t('common.operation.sure')}
</Button>
</div>
</div>
</Modal>}
{isShowRenameModal && currDocument && ( {isShowRenameModal && currDocument && (
<RenameModal <RenameModal

View File

@ -154,10 +154,6 @@ const SecretKeyModal = ({
title={`${t('appApi.actionMsg.deleteConfirmTitle')}`} title={`${t('appApi.actionMsg.deleteConfirmTitle')}`}
content={`${t('appApi.actionMsg.deleteConfirmTips')}`} content={`${t('appApi.actionMsg.deleteConfirmTips')}`}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => {
setDelKeyId('')
setShowConfirmDelete(false)
}}
onConfirm={onDel} onConfirm={onDel}
onCancel={() => { onCancel={() => {
setDelKeyId('') setDelKeyId('')

View File

@ -137,7 +137,6 @@ const SideBar: FC<IExploreSideBarProps> = ({
title={t('explore.sidebar.delete.title')} title={t('explore.sidebar.delete.title')}
content={t('explore.sidebar.delete.content')} content={t('explore.sidebar.delete.content')}
isShow={showConfirm} isShow={showConfirm}
onClose={() => setShowConfirm(false)}
onConfirm={handleDelete} onConfirm={handleDelete}
onCancel={() => setShowConfirm(false)} onCancel={() => setShowConfirm(false)}
/> />

View File

@ -1,16 +1,14 @@
'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
RiCloseLine,
RiErrorWarningFill,
} from '@remixicon/react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import Collapse from '../collapse' import Collapse from '../collapse'
import type { IItem } from '../collapse' import type { IItem } from '../collapse'
import s from './index.module.css' import s from './index.module.css'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Confirm from '@/app/components/base/confirm'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { updateUserProfile } from '@/service/common' import { updateUserProfile } from '@/service/common'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
@ -245,30 +243,24 @@ export default function AccountPage() {
</Modal> </Modal>
)} )}
{showDeleteAccountModal && ( {showDeleteAccountModal && (
<Modal <Confirm
className={classNames('p-8 max-w-[480px] w-[480px]', s.bg)} isShow
isShow={showDeleteAccountModal} onCancel={() => setShowDeleteAccountModal(false)}
onClose={() => { }} onConfirm={() => setShowDeleteAccountModal(false)}
> showCancel={false}
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={() => setShowDeleteAccountModal(false)}> type='warning'
<RiCloseLine className='w-4 h-4 text-gray-500' /> title={t('common.account.delete')}
</div> content={<>
<div className='w-12 h-12 p-3 bg-white rounded-xl border-[0.5px] border-gray-100 shadow-xl'> <div className='my-1 text-[#D92D20] text-sm leading-5'>
<RiErrorWarningFill className='w-6 h-6 text-[#D92D20]' /> {t('common.account.deleteTip')}
</div> </div>
<div className='relative mt-3 text-xl font-semibold leading-[30px] text-gray-900'>{t('common.account.delete')}</div> <div className='mt-3 text-sm leading-5'>
<div className='my-1 text-[#D92D20] text-sm leading-5'> <span>{t('common.account.deleteConfirmTip')}</span>
{t('common.account.deleteTip')} <a className='text-primary-600 cursor' href={`mailto:support@dify.ai?subject=Delete Account Request&body=Delete Account: ${userProfile.email}`} target='_blank'>support@dify.ai</a>
</div> </div>
<div className='mt-3 text-sm leading-5'> </>}
<span>{t('common.account.deleteConfirmTip')}</span> confirmText={t('common.operation.ok') as string}
<a className='text-primary-600 cursor' href={`mailto:support@dify.ai?subject=Delete Account Request&body=Delete Account: ${userProfile.email}`} target='_blank'>support@dify.ai</a> />
</div>
<div className='my-2 px-3 py-2 rounded-lg bg-gray-100 text-sm font-medium leading-5 text-gray-800'>{`Delete Account: ${userProfile.email}`}</div>
<div className='pt-6 flex justify-end items-center'>
<Button className='w-24' onClick={() => setShowDeleteAccountModal(false)}>{t('common.operation.ok')}</Button>
</div>
</Modal>
)} )}
</> </>
) )

View File

@ -8,7 +8,7 @@ import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
import type { ApiBasedExtension } from '@/models/common' import type { ApiBasedExtension } from '@/models/common'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import { deleteApiBasedExtension } from '@/service/common' import { deleteApiBasedExtension } from '@/service/common'
import ConfirmCommon from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
type ItemProps = { type ItemProps = {
data: ApiBasedExtension data: ApiBasedExtension
@ -57,18 +57,14 @@ const Item: FC<ItemProps> = ({
</div> </div>
</div> </div>
{ {
showDeleteConfirm && ( showDeleteConfirm
<ConfirmCommon && <Confirm
type='danger'
isShow={showDeleteConfirm} isShow={showDeleteConfirm}
onCancel={() => setShowDeleteConfirm(false)} onCancel={() => setShowDeleteConfirm(false)}
title={`${t('common.operation.delete')}${data.name}”?`} title={`${t('common.operation.delete')}${data.name}”?`}
onConfirm={handleDeleteApiBasedExtension} onConfirm={handleDeleteApiBasedExtension}
confirmWrapperClassName='!z-30'
confirmText={t('common.operation.delete') || ''} confirmText={t('common.operation.delete') || ''}
confirmBtnClassName='!bg-[#D92D20]'
/> />
)
} }
</div> </div>
) )

View File

@ -48,7 +48,7 @@ import {
PortalToFollowElemContent, PortalToFollowElemContent,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import ConfirmCommon from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
type ModelModalProps = { type ModelModalProps = {
@ -385,12 +385,11 @@ const ModelModal: FC<ModelModalProps> = ({
</div> </div>
{ {
showConfirm && ( showConfirm && (
<ConfirmCommon <Confirm
title={t('common.modelProvider.confirmDelete')} title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm} isShow={showConfirm}
onCancel={() => setShowConfirm(false)} onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove} onConfirm={handleRemove}
confirmWrapperClassName='z-[70]'
/> />
) )
} }

View File

@ -40,7 +40,7 @@ import {
PortalToFollowElemContent, PortalToFollowElemContent,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import ConfirmCommon from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
type ModelModalProps = { type ModelModalProps = {
provider: ModelProvider provider: ModelProvider
@ -330,12 +330,11 @@ const ModelLoadBalancingEntryModal: FC<ModelModalProps> = ({
</div> </div>
{ {
showConfirm && ( showConfirm && (
<ConfirmCommon <Confirm
title={t('common.modelProvider.confirmDelete')} title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm} isShow={showConfirm}
onCancel={() => setShowConfirm(false)} onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove} onConfirm={handleRemove}
confirmWrapperClassName='z-[70]'
/> />
) )
} }

View File

@ -366,7 +366,6 @@ const ProviderDetail = ({
title={t('tools.createTool.deleteToolConfirmTitle')} title={t('tools.createTool.deleteToolConfirmTitle')}
content={t('tools.createTool.deleteToolConfirmContent')} content={t('tools.createTool.deleteToolConfirmContent')}
isShow={showConfirmDelete} isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={handleConfirmDelete} onConfirm={handleConfirmDelete}
onCancel={() => setShowConfirmDelete(false)} onCancel={() => setShowConfirmDelete(false)}
/> />

View File

@ -12,7 +12,7 @@ const RestoringTitle = () => {
return ( return (
<div className='flex items-center h-[18px] text-xs text-gray-500'> <div className='flex items-center h-[18px] text-xs text-gray-500'>
<ClockRefresh className='mr-1 w-3 h-3 text-gray-500' /> <ClockRefresh className='mr-1 w-3 h-3 text-gray-500' />
{t('workflow.common.latestPublished')} {t('workflow.common.latestPublished')}<span> </span>
{formatTimeFromNow(publishedAt)} {formatTimeFromNow(publishedAt)}
</div> </div>
) )

View File

@ -87,7 +87,7 @@ import { FeaturesProvider } from '@/app/components/base/features'
import type { Features as FeaturesData } from '@/app/components/base/features/types' import type { Features as FeaturesData } from '@/app/components/base/features/types'
import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import Confirm from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
const nodeTypes = { const nodeTypes = {
[CUSTOM_NODE]: CustomNode, [CUSTOM_NODE]: CustomNode,
@ -330,8 +330,7 @@ const Workflow: FC<WorkflowProps> = memo(({
onCancel={() => setShowConfirm(undefined)} onCancel={() => setShowConfirm(undefined)}
onConfirm={showConfirm.onConfirm} onConfirm={showConfirm.onConfirm}
title={showConfirm.title} title={showConfirm.title}
desc={showConfirm.desc} content={showConfirm.desc}
confirmWrapperClassName='!z-[11]'
/> />
) )
} }

View File

@ -25,7 +25,6 @@ const RemoveVarConfirm: FC<Props> = ({
content={t(`${i18nPrefix}.content`)} content={t(`${i18nPrefix}.content`)}
onConfirm={onConfirm} onConfirm={onConfirm}
onCancel={onCancel} onCancel={onCancel}
onClose={onCancel}
/> />
) )
} }

View File

@ -125,12 +125,14 @@ const formatItem = (
const { const {
outputs, outputs,
} = data as CodeNodeType } = data as CodeNodeType
res.vars = Object.keys(outputs).map((key) => { res.vars = outputs
return { ? Object.keys(outputs).map((key) => {
variable: key, return {
type: outputs[key].type, variable: key,
} type: outputs[key].type,
}) }
})
: []
break break
} }

View File

@ -35,7 +35,7 @@ export const useChat = (
const { notify } = useToastContext() const { notify } = useToastContext()
const { handleRun } = useWorkflowRun() const { handleRun } = useWorkflowRun()
const hasStopResponded = useRef(false) const hasStopResponded = useRef(false)
const connversationId = useRef('') const conversationId = useRef('')
const taskIdRef = useRef('') const taskIdRef = useRef('')
const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || []) const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
const chatListRef = useRef<ChatItem[]>(prevChatList || []) const chatListRef = useRef<ChatItem[]>(prevChatList || [])
@ -100,7 +100,7 @@ export const useChat = (
}, [handleResponding, stopChat]) }, [handleResponding, stopChat])
const handleRestart = useCallback(() => { const handleRestart = useCallback(() => {
connversationId.current = '' conversationId.current = ''
taskIdRef.current = '' taskIdRef.current = ''
handleStop() handleStop()
const newChatList = config?.opening_statement const newChatList = config?.opening_statement
@ -185,7 +185,7 @@ export const useChat = (
handleResponding(true) handleResponding(true)
const bodyParams = { const bodyParams = {
conversation_id: connversationId.current, conversation_id: conversationId.current,
...params, ...params,
} }
if (bodyParams?.files?.length) { if (bodyParams?.files?.length) {
@ -214,7 +214,7 @@ export const useChat = (
} }
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
connversationId.current = newConversationId conversationId.current = newConversationId
taskIdRef.current = taskId taskIdRef.current = taskId
if (messageId) if (messageId)
@ -403,7 +403,7 @@ export const useChat = (
}, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled]) }, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled])
return { return {
conversationId: connversationId.current, conversationId: conversationId.current,
chatList, chatList,
handleSend, handleSend,
handleStop, handleStop,

View File

@ -10,10 +10,10 @@ import {
fetchDataSourceNotionBinding, fetchDataSourceNotionBinding,
fetchFreeQuotaVerify, fetchFreeQuotaVerify,
} from '@/service/common' } from '@/service/common'
import type { ConfirmCommonProps } from '@/app/components/base/confirm/common' import type { IConfirm } from '@/app/components/base/confirm'
import Confirm from '@/app/components/base/confirm/common' import Confirm from '@/app/components/base/confirm'
export type ConfirmType = Pick<ConfirmCommonProps, 'type' | 'title' | 'desc'> export type ConfirmType = Pick<IConfirm, 'type' | 'title' | 'content'>
export const useAnthropicCheckPay = () => { export const useAnthropicCheckPay = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -25,7 +25,7 @@ export const useAnthropicCheckPay = () => {
useEffect(() => { useEffect(() => {
if (providerName === 'anthropic' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) { if (providerName === 'anthropic' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
setConfirm({ setConfirm({
type: paymentResult === 'succeeded' ? 'success' : 'danger', type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'), title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'),
}) })
} }
@ -44,7 +44,7 @@ export const useBillingPay = () => {
useEffect(() => { useEffect(() => {
if (paymentType === 'billing' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) { if (paymentType === 'billing' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
setConfirm({ setConfirm({
type: paymentResult === 'succeeded' ? 'success' : 'danger', type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'), title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'),
}) })
} }
@ -96,7 +96,7 @@ export const useCheckFreeQuota = () => {
useEffect(() => { useEffect(() => {
if (error) if (error)
router.replace('/', { forceOptimisticNavigation: false }) router.replace('/')
}, [error, router]) }, [error, router])
useEffect(() => { useEffect(() => {
@ -106,7 +106,7 @@ export const useCheckFreeQuota = () => {
return (data && provider) return (data && provider)
? { ? {
type: data.flag ? 'success' : 'danger', type: data.flag ? 'info' : 'warning',
title: data.flag ? QUOTA_RECEIVE_STATUS[provider as string].success[locale] : QUOTA_RECEIVE_STATUS[provider].fail[locale], title: data.flag ? QUOTA_RECEIVE_STATUS[provider as string].success[locale] : QUOTA_RECEIVE_STATUS[provider].fail[locale],
desc: !data.flag ? data.reason : undefined, desc: !data.flag ? data.reason : undefined,
} }
@ -130,13 +130,13 @@ export const useCheckNotion = () => {
useEffect(() => { useEffect(() => {
if (data) if (data)
router.replace('/', { forceOptimisticNavigation: false }) router.replace('/')
}, [data, router]) }, [data, router])
useEffect(() => { useEffect(() => {
if (type === 'notion') { if (type === 'notion') {
if (notionError) { if (notionError) {
setConfirm({ setConfirm({
type: 'danger', type: 'warning',
title: notionError, title: notionError,
}) })
} }
@ -160,7 +160,7 @@ export const CheckModal = () => {
const handleCancelShowPayStatusModal = useCallback(() => { const handleCancelShowPayStatusModal = useCallback(() => {
setShowPayStatusModal(false) setShowPayStatusModal(false)
router.replace('/', { forceOptimisticNavigation: false }) router.replace('/')
}, [router]) }, [router])
const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo || notionConfirmInfo || billingConfirmInfo const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo || notionConfirmInfo || billingConfirmInfo
@ -173,11 +173,11 @@ export const CheckModal = () => {
isShow isShow
onCancel={handleCancelShowPayStatusModal} onCancel={handleCancelShowPayStatusModal}
onConfirm={handleCancelShowPayStatusModal} onConfirm={handleCancelShowPayStatusModal}
type={confirmInfo.type} showCancel={false}
type={confirmInfo.type === 'info' ? 'info' : 'warning' }
title={confirmInfo.title} title={confirmInfo.title}
desc={confirmInfo.desc} content={(confirmInfo as { desc: string }).desc || ''}
showOperateCancel={false} confirmText={(confirmInfo.type === 'info' && t('common.operation.ok')) || ''}
confirmText={(confirmInfo.type === 'danger' && t('common.operation.ok')) || ''}
/> />
) )
} }

View File

@ -348,7 +348,7 @@ const translation = {
getFreeTokens: 'Get free Tokens', getFreeTokens: 'Get free Tokens',
priorityUsing: 'Prioritize using', priorityUsing: 'Prioritize using',
deprecated: 'Deprecated', deprecated: 'Deprecated',
confirmDelete: 'confirm deletion?', confirmDelete: 'Confirm deletion?',
quotaTip: 'Remaining available free tokens', quotaTip: 'Remaining available free tokens',
loadPresets: 'Load Presents', loadPresets: 'Load Presents',
parameters: 'PARAMETERS', parameters: 'PARAMETERS',