mirror of
https://github.com/langgenius/dify.git
synced 2025-12-23 07:47:29 +00:00
Compare commits
10 Commits
feat/agent
...
feat/workf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
894a039a3e | ||
|
|
5b8e211c9a | ||
|
|
f549d53b68 | ||
|
|
a085ad4719 | ||
|
|
f230a9232e | ||
|
|
e84bf35e2a | ||
|
|
20f090537f | ||
|
|
dbe7a7c4fd | ||
|
|
b7a4e3903e | ||
|
|
bb305e52bc |
@@ -7,4 +7,4 @@ api = ExternalApi(bp)
|
||||
|
||||
from . import index
|
||||
from .app import app, audio, completion, conversation, file, message, workflow
|
||||
from .dataset import dataset, document, hit_testing, segment
|
||||
from .dataset import dataset, document, hit_testing, segment, upload_file
|
||||
|
||||
54
api/controllers/service_api/dataset/upload_file.py
Normal file
54
api/controllers/service_api/dataset/upload_file.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.service_api import api
|
||||
from controllers.service_api.wraps import (
|
||||
DatasetApiResource,
|
||||
)
|
||||
from core.file import helpers as file_helpers
|
||||
from extensions.ext_database import db
|
||||
from models.dataset import Dataset
|
||||
from models.model import UploadFile
|
||||
from services.dataset_service import DocumentService
|
||||
|
||||
|
||||
class UploadFileApi(DatasetApiResource):
|
||||
def get(self, tenant_id, dataset_id, document_id):
|
||||
"""Get upload file."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
# check document
|
||||
document_id = str(document_id)
|
||||
document = DocumentService.get_document(dataset.id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
# check upload file
|
||||
if document.data_source_type != "upload_file":
|
||||
raise ValueError(f"Document data source type ({document.data_source_type}) is not upload_file.")
|
||||
data_source_info = document.data_source_info_dict
|
||||
if data_source_info and "upload_file_id" in data_source_info:
|
||||
file_id = data_source_info["upload_file_id"]
|
||||
upload_file = db.session.query(UploadFile).filter(UploadFile.id == file_id).first()
|
||||
if not upload_file:
|
||||
raise NotFound("UploadFile not found.")
|
||||
else:
|
||||
raise ValueError("Upload file id not found in document data source info.")
|
||||
|
||||
url = file_helpers.get_signed_file_url(upload_file_id=upload_file.id)
|
||||
return {
|
||||
"id": upload_file.id,
|
||||
"name": upload_file.name,
|
||||
"size": upload_file.size,
|
||||
"extension": upload_file.extension,
|
||||
"url": url,
|
||||
"download_url": f"{url}&as_attachment=true",
|
||||
"mime_type": upload_file.mime_type,
|
||||
"created_by": upload_file.created_by,
|
||||
"created_at": upload_file.created_at.timestamp(),
|
||||
}, 200
|
||||
|
||||
|
||||
api.add_resource(UploadFileApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/upload-file")
|
||||
@@ -530,7 +530,6 @@ class IndexingRunner:
|
||||
# chunk nodes by chunk size
|
||||
indexing_start_at = time.perf_counter()
|
||||
tokens = 0
|
||||
chunk_size = 10
|
||||
if dataset_document.doc_form != IndexType.PARENT_CHILD_INDEX:
|
||||
# create keyword index
|
||||
create_keyword_thread = threading.Thread(
|
||||
@@ -539,11 +538,22 @@ class IndexingRunner:
|
||||
)
|
||||
create_keyword_thread.start()
|
||||
|
||||
max_workers = 10
|
||||
if dataset.indexing_technique == "high_quality":
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = []
|
||||
for i in range(0, len(documents), chunk_size):
|
||||
chunk_documents = documents[i : i + chunk_size]
|
||||
|
||||
# Distribute documents into multiple groups based on the hash values of page_content
|
||||
# This is done to prevent multiple threads from processing the same document,
|
||||
# Thereby avoiding potential database insertion deadlocks
|
||||
document_groups: list[list[Document]] = [[] for _ in range(max_workers)]
|
||||
for document in documents:
|
||||
hash = helper.generate_text_hash(document.page_content)
|
||||
group_index = int(hash, 16) % max_workers
|
||||
document_groups[group_index].append(document)
|
||||
for chunk_documents in document_groups:
|
||||
if len(chunk_documents) == 0:
|
||||
continue
|
||||
futures.append(
|
||||
executor.submit(
|
||||
self._process_chunk,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import logging
|
||||
from threading import Lock
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_tokenizer: Any = None
|
||||
_lock = Lock()
|
||||
|
||||
@@ -43,5 +46,6 @@ class GPT2Tokenizer:
|
||||
base_path = abspath(__file__)
|
||||
gpt2_tokenizer_path = join(dirname(base_path), "gpt2")
|
||||
_tokenizer = TransformerGPT2Tokenizer.from_pretrained(gpt2_tokenizer_path)
|
||||
logger.info("Fallback to Transformers' GPT-2 tokenizer from tiktoken")
|
||||
|
||||
return _tokenizer
|
||||
|
||||
@@ -112,7 +112,7 @@ class ApiBasedToolSchemaParser:
|
||||
llm_description=property.get("description", ""),
|
||||
default=property.get("default", None),
|
||||
placeholder=I18nObject(
|
||||
en_US=parameter.get("description", ""), zh_Hans=parameter.get("description", "")
|
||||
en_US=property.get("description", ""), zh_Hans=property.get("description", "")
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1106,6 +1106,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||
method='GET'
|
||||
title='Get Upload File'
|
||||
name='#get_upload_file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Knowledge ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
Document ID
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="GET"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/upload-file"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/upload-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/upload-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"id": "file_id",
|
||||
"name": "file_name",
|
||||
"size": 1024,
|
||||
"extension": "txt",
|
||||
"url": "preview_url",
|
||||
"download_url": "download_url",
|
||||
"mime_type": "text/plain",
|
||||
"created_by": "user_id",
|
||||
"created_at": 1728734540,
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/retrieve'
|
||||
method='POST'
|
||||
|
||||
@@ -1107,6 +1107,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||
method='GET'
|
||||
title='获取上传文件'
|
||||
name='#get_upload_file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
文档 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="GET"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/upload-file"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/upload-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/upload-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"id": "file_id",
|
||||
"name": "file_name",
|
||||
"size": 1024,
|
||||
"extension": "txt",
|
||||
"url": "preview_url",
|
||||
"download_url": "download_url",
|
||||
"mime_type": "text/plain",
|
||||
"created_by": "user_id",
|
||||
"created_at": 1728734540,
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/retrieve'
|
||||
method='POST'
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type IRemoveIconProps = {
|
||||
className?: string
|
||||
isHoverStatus?: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const RemoveIcon = ({
|
||||
className,
|
||||
isHoverStatus,
|
||||
onClick,
|
||||
...props
|
||||
}: IRemoveIconProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const computedIsHovered = isHoverStatus || isHovered
|
||||
return (
|
||||
<div
|
||||
className={cn(className, computedIsHovered && 'bg-[#FEE4E2]', 'flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-[#FEE4E2]')}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={cn('flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-state-destructive-hover text-text-tertiary hover:text-text-destructive', className)}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 6H14M6 8H18M16.6667 8L16.1991 15.0129C16.129 16.065 16.0939 16.5911 15.8667 16.99C15.6666 17.3412 15.3648 17.6235 15.0011 17.7998C14.588 18 14.0607 18 13.0062 18H10.9938C9.93927 18 9.41202 18 8.99889 17.7998C8.63517 17.6235 8.33339 17.3412 8.13332 16.99C7.90607 16.5911 7.871 16.065 7.80086 15.0129L7.33333 8M10.6667 11V14.3333M13.3333 11V14.3333" stroke={computedIsHovered ? '#D92D20' : '#667085'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10 6H14M6 8H18M16.6667 8L16.1991 15.0129C16.129 16.065 16.0939 16.5911 15.8667 16.99C15.6666 17.3412 15.3648 17.6235 15.0011 17.7998C14.588 18 14.0607 18 13.0062 18H10.9938C9.93927 18 9.41202 18 8.99889 17.7998C8.63517 17.6235 8.33339 17.3412 8.13332 16.99C7.90607 16.5911 7.871 16.065 7.80086 15.0129L7.33333 8M10.6667 11V14.3333M13.3333 11V14.3333" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusIcon } from '@heroicons/react/24/outline'
|
||||
import { ReactSortable } from 'react-sortablejs'
|
||||
import RemoveIcon from '../../base/icons/remove-icon'
|
||||
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type Options = string[]
|
||||
export type IConfigSelectProps = {
|
||||
@@ -19,6 +20,8 @@ const ConfigSelect: FC<IConfigSelectProps> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [delBtnHoverIndex, setDelBtnHoverIndex] = useState(-1)
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1)
|
||||
|
||||
const optionList = options.map((content, index) => {
|
||||
return ({
|
||||
@@ -36,48 +39,62 @@ const ConfigSelect: FC<IConfigSelectProps> = ({
|
||||
list={optionList}
|
||||
setList={list => onChange(list.map(item => item.name))}
|
||||
handle='.handle'
|
||||
ghostClass="opacity-50"
|
||||
ghostClass="opacity-30"
|
||||
animation={150}
|
||||
>
|
||||
{options.map((o, index) => (
|
||||
<div className={`${s.inputWrap} relative`} key={index}>
|
||||
<div className='handle flex items-center justify-center w-4 h-4 cursor-grab'>
|
||||
<svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="#98A2B3" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
{options.map((o, index) => {
|
||||
const delBtnHover = delBtnHoverIndex === index
|
||||
const inputFocused = focusedIndex === index
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
`${s.inputWrap} relative border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg`,
|
||||
inputFocused && 'border-components-input-border-active bg-components-input-bg-active',
|
||||
delBtnHover && 'bg-state-destructive-hover',
|
||||
)}
|
||||
key={index}
|
||||
type="input"
|
||||
value={o || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
onChange(options.map((item, i) => {
|
||||
if (index === i)
|
||||
return value
|
||||
>
|
||||
<div className='handle flex items-center justify-center w-3.5 h-3.5 cursor-grab text-text-quaternary'>
|
||||
<svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
key={index}
|
||||
type="input"
|
||||
value={o || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
onChange(options.map((item, i) => {
|
||||
if (index === i)
|
||||
return value
|
||||
|
||||
return item
|
||||
}))
|
||||
}}
|
||||
className={'w-full pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer'}
|
||||
/>
|
||||
<RemoveIcon
|
||||
className={`${s.deleteBtn} absolute top-1/2 translate-y-[-50%] right-1.5 items-center justify-center w-6 h-6 rounded-md cursor-pointer hover:bg-[#FEE4E2]`}
|
||||
onClick={() => {
|
||||
onChange(options.filter((_, i) => index !== i))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
return item
|
||||
}))
|
||||
}}
|
||||
onFocus={() => { setFocusedIndex(index) }}
|
||||
onBlur={() => { setFocusedIndex(-1) }}
|
||||
className={'w-full pl-1 pr-8 system-sm-medium text-text-secondary border-0 grow h-8 bg-transparent group focus:outline-none cursor-pointer caret-[#295EFF]'}
|
||||
/>
|
||||
<RemoveIcon
|
||||
className={`${s.deleteBtn} absolute top-1/2 translate-y-[-50%] right-1 items-center justify-center w-6 h-6 rounded-lg cursor-pointer`}
|
||||
onClick={() => {
|
||||
onChange(options.filter((_, i) => index !== i))
|
||||
}}
|
||||
onMouseEnter={() => setDelBtnHoverIndex(index)}
|
||||
onMouseLeave={() => setDelBtnHoverIndex(-1)}
|
||||
/>
|
||||
</div>)
|
||||
})}
|
||||
</ReactSortable>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
onClick={() => { onChange([...options, '']) }}
|
||||
className='flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100'>
|
||||
<PlusIcon width={16} height={16}></PlusIcon>
|
||||
<div className='text-gray-500 text-[13px]'>{t('appDebug.variableConfig.addOption')}</div>
|
||||
className='flex items-center h-8 px-2 gap-1 rounded-lg cursor-pointer bg-components-button-tertiary-bg'>
|
||||
<PlusIcon className='text-components-button-tertiary-text' width={16} height={16} />
|
||||
<div className='text-components-button-tertiary-text system-sm-medium'>{t('appDebug.variableConfig.addOption')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #EAECF0;
|
||||
padding-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ const SelectTypeItem: FC<ISelectTypeItemProps> = ({
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className='shrink-0'>
|
||||
<InputVarTypeIcon type={type} className='w-5 h-5' />
|
||||
<InputVarTypeIcon type={type} className='w-5 h-5 text-text-secondary' />
|
||||
</div>
|
||||
<span>{typeName}</span>
|
||||
<span className='text-text-secondary'>{typeName}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { EChartsOption } from 'echarts'
|
||||
import useSWR from 'swr'
|
||||
import dayjs from 'dayjs'
|
||||
import { get } from 'lodash-es'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import Basic from '@/app/components/app-sidebar/basic'
|
||||
@@ -60,10 +61,8 @@ const CHART_TYPE_CONFIG: Record<string, IChartConfigType> = {
|
||||
},
|
||||
}
|
||||
|
||||
const sum = (arr: number[]): number => {
|
||||
return arr.reduce((acr, cur) => {
|
||||
return acr + cur
|
||||
})
|
||||
const sum = (arr: Decimal.Value[]): number => {
|
||||
return Decimal.sum(...arr).toNumber()
|
||||
}
|
||||
|
||||
const defaultPeriod = {
|
||||
|
||||
@@ -306,8 +306,14 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
}
|
||||
<div className={`flex ${contentClassName}`}>
|
||||
<div className='grow w-0'>
|
||||
{siteInfo && siteInfo.show_workflow_steps && workflowProcessData && (
|
||||
<WorkflowProcessItem data={workflowProcessData} expand={workflowProcessData.expand} hideProcessDetail={hideProcessDetail} />
|
||||
{siteInfo && workflowProcessData && (
|
||||
<WorkflowProcessItem
|
||||
data={workflowProcessData}
|
||||
expand={workflowProcessData.expand}
|
||||
hideProcessDetail={hideProcessDetail}
|
||||
hideInfo={hideProcessDetail}
|
||||
readonly={!siteInfo.show_workflow_steps}
|
||||
/>
|
||||
)}
|
||||
{workflowProcessData && !isError && (
|
||||
<ResultTab data={workflowProcessData} content={content} currentTab={currentTab} onCurrentTabChange={setCurrentTab} />
|
||||
|
||||
@@ -13,7 +13,7 @@ import AgentContent from './agent-content'
|
||||
import BasicContent from './basic-content'
|
||||
import SuggestedQuestions from './suggested-questions'
|
||||
import More from './more'
|
||||
import WorkflowProcess from './workflow-process'
|
||||
import WorkflowProcessItem from './workflow-process'
|
||||
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
|
||||
import Citation from '@/app/components/base/chat/chat/citation'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
@@ -133,7 +133,7 @@ const Answer: FC<AnswerProps> = ({
|
||||
{/** Render the normal steps */}
|
||||
{
|
||||
workflowProcess && !hideProcessDetail && (
|
||||
<WorkflowProcess
|
||||
<WorkflowProcessItem
|
||||
data={workflowProcess}
|
||||
item={item}
|
||||
hideProcessDetail={hideProcessDetail}
|
||||
@@ -142,11 +142,12 @@ const Answer: FC<AnswerProps> = ({
|
||||
}
|
||||
{/** Hide workflow steps by it's settings in siteInfo */}
|
||||
{
|
||||
workflowProcess && hideProcessDetail && appData && appData.site.show_workflow_steps && (
|
||||
<WorkflowProcess
|
||||
workflowProcess && hideProcessDetail && appData && (
|
||||
<WorkflowProcessItem
|
||||
data={workflowProcess}
|
||||
item={item}
|
||||
hideProcessDetail={hideProcessDetail}
|
||||
readonly={!appData.site.show_workflow_steps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type WorkflowProcessProps = {
|
||||
expand?: boolean
|
||||
hideInfo?: boolean
|
||||
hideProcessDetail?: boolean
|
||||
readonly?: boolean
|
||||
}
|
||||
const WorkflowProcessItem = ({
|
||||
data,
|
||||
@@ -30,6 +31,7 @@ const WorkflowProcessItem = ({
|
||||
expand = false,
|
||||
hideInfo = false,
|
||||
hideProcessDetail = false,
|
||||
readonly = false,
|
||||
}: WorkflowProcessProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [collapse, setCollapse] = useState(!expand)
|
||||
@@ -81,8 +83,8 @@ const WorkflowProcessItem = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn('flex items-center cursor-pointer', !collapse && 'px-1.5')}
|
||||
onClick={() => setCollapse(!collapse)}
|
||||
className={cn('flex items-center cursor-pointer', !collapse && 'px-1.5', readonly && 'cursor-default')}
|
||||
onClick={() => !readonly && setCollapse(!collapse)}
|
||||
>
|
||||
{
|
||||
running && (
|
||||
@@ -102,10 +104,10 @@ const WorkflowProcessItem = ({
|
||||
<div className={cn('system-xs-medium text-text-secondary', !collapse && 'grow')}>
|
||||
{t('workflow.common.workflowProcess')}
|
||||
</div>
|
||||
<RiArrowRightSLine className={`'ml-1 w-4 h-4 text-text-tertiary' ${collapse ? '' : 'rotate-90'}`} />
|
||||
{!readonly && <RiArrowRightSLine className={`'ml-1 w-4 h-4 text-text-tertiary' ${collapse ? '' : 'rotate-90'}`} />}
|
||||
</div>
|
||||
{
|
||||
!collapse && (
|
||||
!collapse && !readonly && (
|
||||
<div className='mt-1.5'>
|
||||
{
|
||||
<TracingPanel
|
||||
|
||||
@@ -82,7 +82,8 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className={`
|
||||
flex items-center justify-center border-[0.5px] border-white/2 text-white
|
||||
flex items-center justify-center border-[0.5px] border-divider-subtle
|
||||
text-text-primary-on-surface shadow-md shadow-shadow-shadow-5
|
||||
${ICON_CONTAINER_CLASSNAME_SIZE_MAP[size]}
|
||||
${ICON_CONTAINER_BG_COLOR_MAP[type]}
|
||||
${toolIcon && '!shadow-none'}
|
||||
|
||||
@@ -33,9 +33,10 @@ export const TitleInput = memo(({
|
||||
value={localValue}
|
||||
onChange={e => setLocalValue(e.target.value)}
|
||||
className={`
|
||||
grow mr-2 px-1 h-6 text-base text-gray-900 font-semibold rounded-lg border border-transparent appearance-none outline-none
|
||||
hover:bg-gray-50
|
||||
focus:border-gray-300 focus:shadow-xs focus:bg-white caret-[#295EFF]
|
||||
grow mr-2 px-1 h-6 system-xl-semibold text-text-primary rounded-lg border border-transparent appearance-none outline-none
|
||||
placeholder:text-text-placeholder
|
||||
bg-transparent hover:bg-state-base-hover
|
||||
focus:border-components-input-border-active focus:shadow-xs shadow-shadow-shadow-3 focus:bg-components-input-active caret-[#295EFF]
|
||||
min-w-0
|
||||
`}
|
||||
placeholder={t('workflow.common.addTitle') || ''}
|
||||
@@ -66,8 +67,8 @@ export const DescriptionInput = memo(({
|
||||
<div
|
||||
className={`
|
||||
group flex px-2 py-[5px] max-h-[60px] rounded-lg overflow-y-auto
|
||||
border border-transparent hover:bg-gray-50 leading-0
|
||||
${focus && '!border-gray-300 shadow-xs !bg-gray-50'}
|
||||
border border-transparent bg-transparent hover:bg-state-base-hover leading-0
|
||||
${focus && '!border-components-input-border-active shadow-xs shadow-shadow-shadow-3 !bg-components-input-bg-active'}
|
||||
`}
|
||||
>
|
||||
<Textarea
|
||||
@@ -77,9 +78,9 @@ export const DescriptionInput = memo(({
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
className={`
|
||||
w-full text-xs text-gray-900 leading-[18px] bg-transparent
|
||||
w-full text-xs text-text-primary leading-[18px] bg-transparent
|
||||
appearance-none outline-none resize-none
|
||||
placeholder:text-gray-400 caret-[#295EFF]
|
||||
placeholder:text-text-placeholder caret-[#295EFF]
|
||||
`}
|
||||
placeholder={t('workflow.common.addDescription') || ''}
|
||||
autoSize
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useBoolean, useHover } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
} from '@remixicon/react'
|
||||
import InputVarTypeIcon from '../../_base/components/input-var-type-icon'
|
||||
import type { InputVar, MoreInfo } from '@/app/components/workflow/types'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal'
|
||||
|
||||
@@ -46,12 +46,12 @@ const VarItem: FC<Props> = ({
|
||||
hideEditVarModal()
|
||||
}, [onChange, hideEditVarModal])
|
||||
return (
|
||||
<div ref={ref} className='flex items-center h-8 justify-between px-2.5 bg-white rounded-lg border border-gray-200 shadow-xs cursor-pointer hover:shadow-md'>
|
||||
<div ref={ref} className='flex items-center h-8 justify-between px-2.5 bg-components-panel-on-panel-item-bg rounded-lg border border-components-panel-border-subtle shadow-xs cursor-pointer hover:shadow-md shadow-shadow-shadow-3'>
|
||||
<div className='flex items-center space-x-1 grow w-0'>
|
||||
<Variable02 className='w-3.5 h-3.5 text-primary-500' />
|
||||
<div title={payload.variable} className='shrink-0 max-w-[130px] truncate text-[13px] font-medium text-gray-700'>{payload.variable}</div>
|
||||
{payload.label && (<><div className='shrink-0 text-xs font-medium text-gray-400'>·</div>
|
||||
<div title={payload.label as string} className='max-w-[130px] truncate text-[13px] font-medium text-gray-500'>{payload.label as string}</div>
|
||||
<Variable02 className='w-3.5 h-3.5 text-text-accent' />
|
||||
<div title={payload.variable} className='shrink-0 max-w-[130px] truncate system-sm-medium text-text-secondary'>{payload.variable}</div>
|
||||
{payload.label && (<><div className='shrink-0 system-xs-regular text-text-quaternary'>·</div>
|
||||
<div title={payload.label as string} className='max-w-[130px] truncate system-xs-medium text-text-tertiary'>{payload.label as string}</div>
|
||||
</>)}
|
||||
{showLegacyBadge && (
|
||||
<Badge
|
||||
@@ -66,18 +66,18 @@ const VarItem: FC<Props> = ({
|
||||
? (
|
||||
<>
|
||||
{payload.required && (
|
||||
<div className='mr-2 text-xs font-normal text-gray-500'>{t('workflow.nodes.start.required')}</div>
|
||||
<Badge className='mr-2' uppercase>{t('workflow.nodes.start.required')}</Badge>
|
||||
)}
|
||||
<InputVarTypeIcon type={payload.type} className='w-3.5 h-3.5 text-gray-500' />
|
||||
<InputVarTypeIcon type={payload.type} className='w-3 h-3 text-text-tertiary' />
|
||||
</>
|
||||
)
|
||||
: (!readonly && (
|
||||
<>
|
||||
<div onClick={showEditVarModal} className='mr-1 p-1 rounded-md cursor-pointer hover:bg-black/5'>
|
||||
<Edit03 className='w-4 h-4 text-gray-500' />
|
||||
<div onClick={showEditVarModal} className='mr-1 p-1 cursor-pointer'>
|
||||
<RiEditLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div onClick={onRemove} className='p-1 rounded-md cursor-pointer hover:bg-black/5'>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-gray-500' />
|
||||
<div onClick={onRemove} className='p-1 cursor-pointer'>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
||||
@@ -46,7 +46,7 @@ const VarList: FC<Props> = ({
|
||||
|
||||
if (list.length === 0) {
|
||||
return (
|
||||
<div className='flex rounded-md bg-gray-50 items-center h-[42px] justify-center leading-[18px] text-xs font-normal text-gray-500'>
|
||||
<div className='flex rounded-md bg-background-section items-center h-10 justify-center system-xs-regular text-text-tertiary'>
|
||||
{t('workflow.nodes.start.noVarTip')}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -20,15 +20,15 @@ const Node: FC<NodeProps<StartNodeType>> = ({
|
||||
<div className='mb-1 px-3 py-1'>
|
||||
<div className='space-y-0.5'>
|
||||
{variables.map(variable => (
|
||||
<div key={variable.variable} className='flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal text-gray-700'>
|
||||
<div key={variable.variable} className='flex items-center h-6 justify-between bg-workflow-block-parma-bg rounded-md px-1 space-x-1'>
|
||||
<div className='w-0 grow flex items-center space-x-1'>
|
||||
<Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />
|
||||
<span className='w-0 grow truncate text-xs font-normal text-gray-700'>{variable.variable}</span>
|
||||
<Variable02 className='shrink-0 w-3.5 h-3.5 text-text-accent' />
|
||||
<span className='w-0 grow truncate system-xs-regular text-text-secondary'>{variable.variable}</span>
|
||||
</div>
|
||||
|
||||
<div className='ml-1 flex items-center space-x-1'>
|
||||
{variable.required && <span className='text-xs font-normal text-gray-500 uppercase'>{t(`${i18nPrefix}.required`)}</span>}
|
||||
<InputVarTypeIcon type={variable.type} className='w-3 h-3 text-gray-500' />
|
||||
{variable.required && <span className='system-2xs-regular-uppercase text-text-tertiary'>{t(`${i18nPrefix}.required`)}</span>}
|
||||
<InputVarTypeIcon type={variable.type} className='w-3 h-3 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -64,7 +64,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.query',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
@@ -78,7 +78,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.files',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
Array[File]
|
||||
</div>
|
||||
}
|
||||
@@ -92,7 +92,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.dialogue_count',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
Number
|
||||
</div>
|
||||
}
|
||||
@@ -103,7 +103,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.conversation_id',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
@@ -117,7 +117,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.user_id',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
@@ -128,7 +128,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.app_id',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
@@ -139,7 +139,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.workflow_id',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
@@ -150,7 +150,7 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
|
||||
variable: 'sys.workflow_run_id',
|
||||
} as any}
|
||||
rightContent={
|
||||
<div className='text-xs font-normal text-gray-500'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
String
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"decimal.js": "^10.4.3",
|
||||
"echarts": "^5.5.1",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"elkjs": "^0.9.3",
|
||||
|
||||
@@ -21,16 +21,23 @@ function waitUntilTokenRefreshed() {
|
||||
})
|
||||
}
|
||||
|
||||
const isRefreshingSignAvailable = function (delta: number) {
|
||||
const nowTime = new Date().getTime()
|
||||
const lastTime = globalThis.localStorage.getItem('last_refresh_time') || '0'
|
||||
return nowTime - parseInt(lastTime) <= delta
|
||||
}
|
||||
|
||||
// only one request can send
|
||||
async function getNewAccessToken(): Promise<void> {
|
||||
async function getNewAccessToken(timeout: number): Promise<void> {
|
||||
try {
|
||||
const isRefreshingSign = globalThis.localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
|
||||
if ((isRefreshingSign && isRefreshingSign === '1' && isRefreshingSignAvailable(timeout)) || isRefreshing) {
|
||||
await waitUntilTokenRefreshed()
|
||||
}
|
||||
else {
|
||||
isRefreshing = true
|
||||
globalThis.localStorage.setItem(LOCAL_STORAGE_KEY, '1')
|
||||
globalThis.localStorage.setItem('last_refresh_time', new Date().getTime().toString())
|
||||
globalThis.addEventListener('beforeunload', releaseRefreshLock)
|
||||
const refresh_token = globalThis.localStorage.getItem('refresh_token')
|
||||
|
||||
@@ -72,6 +79,7 @@ function releaseRefreshLock() {
|
||||
if (isRefreshing) {
|
||||
isRefreshing = false
|
||||
globalThis.localStorage.removeItem(LOCAL_STORAGE_KEY)
|
||||
globalThis.localStorage.removeItem('last_refresh_time')
|
||||
globalThis.removeEventListener('beforeunload', releaseRefreshLock)
|
||||
}
|
||||
}
|
||||
@@ -80,5 +88,5 @@ export async function refreshAccessTokenOrRelogin(timeout: number) {
|
||||
return Promise.race([new Promise<void>((resolve, reject) => setTimeout(() => {
|
||||
releaseRefreshLock()
|
||||
reject(new Error('request timeout'))
|
||||
}, timeout)), getNewAccessToken()])
|
||||
}, timeout)), getNewAccessToken(timeout)])
|
||||
}
|
||||
|
||||
@@ -5568,6 +5568,11 @@ decimal.js@^10.4.2:
|
||||
resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz"
|
||||
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
|
||||
|
||||
decimal.js@^10.4.3:
|
||||
version "10.4.3"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
|
||||
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
|
||||
|
||||
decode-named-character-reference@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user