mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 17:08:03 +08:00
Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins
This commit is contained in:
@ -9,8 +9,8 @@ const DatasetFooter = () => {
|
||||
<footer className='px-12 py-6 grow-0 shrink-0'>
|
||||
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('dataset.didYouKnow')}</h3>
|
||||
<p className='mt-1 text-sm font-normal leading-tight text-gray-700'>
|
||||
{t('dataset.intro1')}<a className='inline-flex items-center gap-1 link' target='_blank' rel='noopener noreferrer' href='/'>{t('dataset.intro2')}</a>{t('dataset.intro3')}<br />
|
||||
{t('dataset.intro4')}<a className='inline-flex items-center gap-1 link' target='_blank' rel='noopener noreferrer' href='/'>{t('dataset.intro5')}</a>{t('dataset.intro6')}
|
||||
{t('dataset.intro1')}<span className='inline-flex items-center gap-1 text-blue-600'>{t('dataset.intro2')}</span>{t('dataset.intro3')}<br />
|
||||
{t('dataset.intro4')}<span className='inline-flex items-center gap-1 text-blue-600'>{t('dataset.intro5')}</span>{t('dataset.intro6')}
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { type FC, useEffect } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import TemplateEn from './template/template.en.mdx'
|
||||
import TemplateZh from './template/template.zh.mdx'
|
||||
@ -14,6 +14,13 @@ const Doc: FC<DocProps> = ({
|
||||
apiBaseUrl,
|
||||
}) => {
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
useEffect(() => {
|
||||
const hash = location.hash
|
||||
if (hash)
|
||||
document.querySelector(hash)?.scrollIntoView()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
|
||||
{
|
||||
|
||||
@ -20,17 +20,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/document/create_by_text'
|
||||
url='/datasets/{dataset_id}/document/create-by-text'
|
||||
method='POST'
|
||||
title='Create a document from text'
|
||||
name='#create_by_text'
|
||||
title='Create a Document from Text'
|
||||
name='#create-by-text'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
This api is based on an existing Knowledge and creates a new document through text based on this Knowledge.
|
||||
This API is based on an existing knowledge and creates a new document through text based on this knowledge.
|
||||
|
||||
### Params
|
||||
<Properties>
|
||||
@ -50,7 +50,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Property name='indexing_technique' type='string' key='indexing_technique'>
|
||||
Index mode
|
||||
- <code>high_quality</code> High quality: embedding using embedding model, built as vector database index
|
||||
- <code>economy</code> Economy: Build using inverted index of Keyword Table Index
|
||||
- <code>economy</code> Economy: Build using inverted index of keyword table index
|
||||
</Property>
|
||||
<Property name='process_rule' type='object' key='process_rule'>
|
||||
Processing rules
|
||||
@ -62,7 +62,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>remove_extra_spaces</code> Replace consecutive spaces, newlines, tabs
|
||||
- <code>remove_urls_emails</code> Delete URL, email address
|
||||
- <code>enabled</code> (bool) Whether to select this rule or not. If no document ID is passed in, it represents the default value.
|
||||
- <code>segmentation</code> (object) segmentation rules
|
||||
- <code>segmentation</code> (object) Segmentation rules
|
||||
- <code>separator</code> Custom segment identifier, currently only allows one delimiter to be set. Default is \n
|
||||
- <code>max_tokens</code> Maximum length (token) defaults to 1000
|
||||
</Property>
|
||||
@ -72,11 +72,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/document/create_by_text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "text","text": "text","indexing_technique": "high_quality","process_rule": {"mode": "automatic"}}'`}
|
||||
label="/datasets/{dataset_id}/document/create-by-text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "text","text": "text","indexing_technique": "high_quality","process_rule": {"mode": "automatic"}}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_text' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-text' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -123,17 +123,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/document/create_by_file'
|
||||
url='/datasets/{dataset_id}/document/create-by-file'
|
||||
method='POST'
|
||||
title='Create documents from files'
|
||||
name='#create_by_file'
|
||||
title='Create a Document from a File'
|
||||
name='#create-by-file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
This api is based on an existing Knowledge and creates a new document through a file based on this Knowledge.
|
||||
This API is based on an existing knowledge and creates a new document through a file based on this knowledge.
|
||||
|
||||
### Params
|
||||
<Properties>
|
||||
@ -145,17 +145,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='data' type='multipart/form-data json string' key='data'>
|
||||
- original_document_id Source document ID (optional)
|
||||
- <code>original_document_id</code> Source document ID (optional)
|
||||
- Used to re-upload the document or modify the document cleaning and segmentation configuration. The missing information is copied from the source document
|
||||
- The source document cannot be an archived document
|
||||
- When original_document_id is passed in, the update operation is performed on behalf of the document. process_rule is a fillable item. If not filled in, the segmentation method of the source document will be used by default
|
||||
- When original_document_id is not passed in, the new operation is performed on behalf of the document, and process_rule is required
|
||||
|
||||
- indexing_technique Index mode
|
||||
- <code>indexing_technique</code> Index mode
|
||||
- <code>high_quality</code> High quality: embedding using embedding model, built as vector database index
|
||||
- <code>economy</code> Economy: Build using inverted index of Keyword Table Index
|
||||
- <code>economy</code> Economy: Build using inverted index of keyword table index
|
||||
|
||||
- process_rule Processing rules
|
||||
- <code>process_rule</code> Processing rules
|
||||
- <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom
|
||||
- <code>rules</code> (object) Custom rules (in automatic mode, this field is empty)
|
||||
- <code>pre_processing_rules</code> (array[object]) Preprocessing rules
|
||||
@ -164,7 +164,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>remove_extra_spaces</code> Replace consecutive spaces, newlines, tabs
|
||||
- <code>remove_urls_emails</code> Delete URL, email address
|
||||
- <code>enabled</code> (bool) Whether to select this rule or not. If no document ID is passed in, it represents the default value.
|
||||
- <code>segmentation</code> (object) segmentation rules
|
||||
- <code>segmentation</code> (object) Segmentation rules
|
||||
- <code>separator</code> Custom segment identifier, currently only allows one delimiter to be set. Default is \n
|
||||
- <code>max_tokens</code> Maximum length (token) defaults to 1000
|
||||
</Property>
|
||||
@ -177,11 +177,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/document/create_by_file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
label="/datasets/{dataset_id}/document/create-by-file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_file' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'data="{\"name\":\"Dify\",\"indexing_technique\":\"high_quality\",\"process_rule\":{\"rules\":{\"pre_processing_rules\":[{\"id\":\"remove_extra_spaces\",\"enabled\":true},{\"id\":\"remove_urls_emails\",\"enabled\":true}],\"segmentation\":{\"separator\":\"###\",\"max_tokens\":500}},\"mode\":\"custom\"}}";type=text/plain' \
|
||||
--form 'file=@"/path/to/file"'
|
||||
@ -221,12 +221,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets'
|
||||
method='POST'
|
||||
title='Create an empty Knowledge'
|
||||
title='Create an Empty Knowledge Base'
|
||||
name='#create_empty_dataset'
|
||||
/>
|
||||
<Row>
|
||||
@ -240,9 +240,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
Knowledge description (optional)
|
||||
</Property>
|
||||
<Property name='indexing_technique' type='string' key='indexing_technique'>
|
||||
Index Technique (optional)
|
||||
- <code>high_quality</code> high_quality
|
||||
- <code>economy</code> economy
|
||||
Index technique (optional)
|
||||
- <code>high_quality</code> High quality
|
||||
- <code>economy</code> Economy
|
||||
</Property>
|
||||
<Property name='permission' type='string' key='permission'>
|
||||
Permission
|
||||
@ -252,21 +252,21 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Property>
|
||||
<Property name='provider' type='string' key='provider'>
|
||||
Provider (optional, default: vendor)
|
||||
- <code>vendor</code> vendor
|
||||
- <code>external</code> external knowledge
|
||||
- <code>vendor</code> Vendor
|
||||
- <code>external</code> External knowledge
|
||||
</Property>
|
||||
<Property name='external_knowledge_api_id' type='str' key='external_knowledge_api_id'>
|
||||
External Knowledge api id (optional)
|
||||
External knowledge API ID (optional)
|
||||
</Property>
|
||||
<Property name='external_knowledge_id' type='str' key='external_knowledge_id'>
|
||||
External Knowledge id (optional)
|
||||
External knowledge ID (optional)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name", "permission": "only_me"}'`}
|
||||
>
|
||||
@ -306,12 +306,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets'
|
||||
method='GET'
|
||||
title='Knowledge list'
|
||||
title='Get Knowledge Base List'
|
||||
name='#dataset_list'
|
||||
/>
|
||||
<Row>
|
||||
@ -327,9 +327,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets?page=1&limit=20' \\\n--header 'Authorization: Bearer {api_key}'`}
|
||||
>
|
||||
@ -369,12 +369,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}'
|
||||
method='DELETE'
|
||||
title='Delete knowledge'
|
||||
title='Delete a Knowledge Base'
|
||||
name='#delete_dataset'
|
||||
/>
|
||||
<Row>
|
||||
@ -406,17 +406,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update_by_text'
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
||||
method='POST'
|
||||
title='Update document via text'
|
||||
name='#update_by_text'
|
||||
title='Update a Document with Text'
|
||||
name='#update-by-text'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
This api is based on an existing Knowledge and updates the document through text based on this Knowledge.
|
||||
This API is based on an existing knowledge and updates the document through text based on this knowledge.
|
||||
|
||||
### Params
|
||||
<Properties>
|
||||
@ -446,7 +446,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>remove_extra_spaces</code> Replace consecutive spaces, newlines, tabs
|
||||
- <code>remove_urls_emails</code> Delete URL, email address
|
||||
- <code>enabled</code> (bool) Whether to select this rule or not. If no document ID is passed in, it represents the default value.
|
||||
- <code>segmentation</code> (object) segmentation rules
|
||||
- <code>segmentation</code> (object) Segmentation rules
|
||||
- <code>separator</code> Custom segment identifier, currently only allows one delimiter to be set. Default is \n
|
||||
- <code>max_tokens</code> Maximum length (token) defaults to 1000
|
||||
</Property>
|
||||
@ -456,11 +456,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update_by_text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name","text": "text"}'`}
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update-by-text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name","text": "text"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_text' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-text' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -503,17 +503,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update_by_file'
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
||||
method='POST'
|
||||
title='Update a document from a file'
|
||||
name='#update_by_file'
|
||||
title='Update a Document with a File'
|
||||
name='#update-by-file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
This api is based on an existing Knowledge, and updates documents through files based on this Knowledge
|
||||
This API is based on an existing knowledge, and updates documents through files based on this knowledge
|
||||
|
||||
### Params
|
||||
<Properties>
|
||||
@ -543,7 +543,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>remove_extra_spaces</code> Replace consecutive spaces, newlines, tabs
|
||||
- <code>remove_urls_emails</code> Delete URL, email address
|
||||
- <code>enabled</code> (bool) Whether to select this rule or not. If no document ID is passed in, it represents the default value.
|
||||
- <code>segmentation</code> (object) segmentation rules
|
||||
- <code>segmentation</code> (object) Segmentation rules
|
||||
- <code>separator</code> Custom segment identifier, currently only allows one delimiter to be set. Default is \n
|
||||
- <code>max_tokens</code> Maximum length (token) defaults to 1000
|
||||
</Property>
|
||||
@ -553,11 +553,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update_by_file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"name":"Dify","indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update-by-file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"name":"Dify","indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_file' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'data="{\"name\":\"Dify\",\"indexing_technique\":\"high_quality\",\"process_rule\":{\"rules\":{\"pre_processing_rules\":[{\"id\":\"remove_extra_spaces\",\"enabled\":true},{\"id\":\"remove_urls_emails\",\"enabled\":true}],\"segmentation\":{\"separator\":\"###\",\"max_tokens\":500}},\"mode\":\"custom\"}}";type=text/plain' \
|
||||
--form 'file=@"/path/to/file"'
|
||||
@ -597,12 +597,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
||||
method='GET'
|
||||
title='Get document embedding status (progress)'
|
||||
title='Get Document Embedding Status (Progress)'
|
||||
name='#indexing_status'
|
||||
/>
|
||||
<Row>
|
||||
@ -652,12 +652,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||
method='DELETE'
|
||||
title='Delete document'
|
||||
title='Delete a Document'
|
||||
name='#delete_document'
|
||||
/>
|
||||
<Row>
|
||||
@ -694,12 +694,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents'
|
||||
method='GET'
|
||||
title='Knowledge document list'
|
||||
title='Get the Document List of a Knowledge Base'
|
||||
name='#dataset_document_list'
|
||||
/>
|
||||
<Row>
|
||||
@ -714,13 +714,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Query
|
||||
<Properties>
|
||||
<Property name='keyword' type='string' key='keyword'>
|
||||
Search keywords, currently only search document names(optional)
|
||||
Search keywords, currently only search document names (optional)
|
||||
</Property>
|
||||
<Property name='page' type='string' key='page'>
|
||||
Page number(optional)
|
||||
Page number (optional)
|
||||
</Property>
|
||||
<Property name='limit' type='string' key='limit'>
|
||||
Number of items returned, default 20, range 1-100(optional)
|
||||
Number of items returned, default 20, range 1-100 (optional)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -769,12 +769,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||
method='POST'
|
||||
title='Add segment'
|
||||
title='Add Chunks to a Document'
|
||||
name='#create_new_segment'
|
||||
/>
|
||||
<Row>
|
||||
@ -792,9 +792,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='segments' type='object list' key='segments'>
|
||||
- <code>content</code> (text) Text content/question content, required
|
||||
- <code>answer</code> (text) Answer content, if the mode of the Knowledge is qa mode, pass the value(optional)
|
||||
- <code>keywords</code> (list) Keywords(optional)
|
||||
- <code>content</code> (text) Text content / question content, required
|
||||
- <code>answer</code> (text) Answer content, if the mode of the knowledge is Q&A mode, pass the value (optional)
|
||||
- <code>keywords</code> (list) Keywords (optional)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -855,12 +855,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||
method='GET'
|
||||
title='get documents segments'
|
||||
title='Get Chunks from a Document'
|
||||
name='#get_segment'
|
||||
/>
|
||||
<Row>
|
||||
@ -878,10 +878,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Query
|
||||
<Properties>
|
||||
<Property name='keyword' type='string' key='keyword'>
|
||||
keyword,choosable
|
||||
Keyword (optional)
|
||||
</Property>
|
||||
<Property name='status' type='string' key='status'>
|
||||
Search status,completed
|
||||
Search status, completed
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -933,12 +933,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||
method='DELETE'
|
||||
title='delete document segment'
|
||||
title='Delete a Chunk in a Document'
|
||||
name='#delete_segment'
|
||||
/>
|
||||
<Row>
|
||||
@ -979,12 +979,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||
method='POST'
|
||||
title='update document segment'
|
||||
title='Update a Chunk in a Document '
|
||||
name='#update_segment'
|
||||
/>
|
||||
<Row>
|
||||
@ -1005,10 +1005,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='segment' type='object' key='segment'>
|
||||
- <code>content</code> (text) text content/question content,required
|
||||
- <code>answer</code> (text) Answer content, not required, passed if the Knowledge is in qa mode
|
||||
- <code>keywords</code> (list) keyword, not required
|
||||
- <code>enabled</code> (bool) false/true, not required
|
||||
- <code>content</code> (text) Text content / question content, required
|
||||
- <code>answer</code> (text) Answer content, passed if the knowledge is in Q&A mode (optional)
|
||||
- <code>keywords</code> (list) Keyword (optional)
|
||||
- <code>enabled</code> (bool) False / true (optional)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -1067,41 +1067,41 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/hit_testing'
|
||||
url='/datasets/{dataset_id}/retrieve'
|
||||
method='POST'
|
||||
title='Dataset hit testing'
|
||||
name='#dataset_hit_testing'
|
||||
title='Retrieve Chunks from a Knowledge Base'
|
||||
name='#dataset_retrieval'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Dataset ID
|
||||
Knowledge ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='query' type='string' key='query'>
|
||||
retrieval keywordc
|
||||
Query keyword
|
||||
</Property>
|
||||
<Property name='retrieval_model' type='object' key='retrieval_model'>
|
||||
retrieval keyword(Optional, if not filled, it will be recalled according to the default method)
|
||||
Retrieval model (optional, if not filled, it will be recalled according to the default method)
|
||||
- <code>search_method</code> (text) Search method: One of the following four keywords is required
|
||||
- <code>keyword_search</code> Keyword search
|
||||
- <code>semantic_search</code> Semantic search
|
||||
- <code>full_text_search</code> Full-text search
|
||||
- <code>hybrid_search</code> Hybrid search
|
||||
- <code>reranking_enable</code> (bool) Whether to enable reranking, optional, required if the search mode is semantic_search or hybrid_search
|
||||
- <code>reranking_mode</code> (object) Rerank model configuration, optional, required if reranking is enabled
|
||||
- <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional)
|
||||
- <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled
|
||||
- <code>reranking_provider_name</code> (string) Rerank model provider
|
||||
- <code>reranking_model_name</code> (string) Rerank model name
|
||||
- <code>weights</code> (double) Semantic search weight setting in hybrid search mode
|
||||
- <code>top_k</code> (integer) Number of results to return, optional
|
||||
- <code>top_k</code> (integer) Number of results to return (optional)
|
||||
- <code>score_threshold_enabled</code> (bool) Whether to enable score threshold
|
||||
- <code>score_threshold</code> (double) Score threshold
|
||||
</Property>
|
||||
@ -1114,26 +1114,26 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/hit_testing"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
label="/datasets/{dataset_id}/retrieve"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/retrieve' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/retrieve' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -1212,7 +1212,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
|
||||
@ -20,13 +20,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/document/create_by_text'
|
||||
url='/datasets/{dataset_id}/document/create-by-text'
|
||||
method='POST'
|
||||
title='通过文本创建文档'
|
||||
name='#create_by_text'
|
||||
name='#create-by-text'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -50,7 +50,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Property name='indexing_technique' type='string' key='indexing_technique'>
|
||||
索引方式
|
||||
- <code>high_quality</code> 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引
|
||||
- <code>economy</code> 经济:使用 Keyword Table Index 的倒排索引进行构建
|
||||
- <code>economy</code> 经济:使用 keyword table index 的倒排索引进行构建
|
||||
</Property>
|
||||
<Property name='process_rule' type='object' key='process_rule'>
|
||||
处理规则
|
||||
@ -64,7 +64,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>enabled</code> (bool) 是否选中该规则,不传入文档 ID 时代表默认值
|
||||
- <code>segmentation</code> (object) 分段规则
|
||||
- <code>separator</code> 自定义分段标识符,目前仅允许设置一个分隔符。默认为 \n
|
||||
- <code>max_tokens</code> 最大长度 (token) 默认为 1000
|
||||
- <code>max_tokens</code> 最大长度(token)默认为 1000
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -72,11 +72,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/document/create_by_text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "text","text": "text","indexing_technique": "high_quality","process_rule": {"mode": "automatic"}}'`}
|
||||
label="/datasets/{dataset_id}/document/create-by-text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "text","text": "text","indexing_technique": "high_quality","process_rule": {"mode": "automatic"}}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_text' \
|
||||
curl --location --request --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-text' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -123,13 +123,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/document/create_by_file'
|
||||
url='/datasets/{dataset_id}/document/create-by-file'
|
||||
method='POST'
|
||||
title='通过文件创建文档 '
|
||||
name='#create_by_file'
|
||||
name='#create-by-file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -145,17 +145,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='data' type='multipart/form-data json string' key='data'>
|
||||
- original_document_id 源文档 ID (选填)
|
||||
- <code>original_document_id</code> 源文档 ID(选填)
|
||||
- 用于重新上传文档或修改文档清洗、分段配置,缺失的信息从源文档复制
|
||||
- 源文档不可为归档的文档
|
||||
- 当传入 <code>original_document_id</code> 时,代表文档进行更新操作,<code>process_rule</code> 为可填项目,不填默认使用源文档的分段方式
|
||||
- 未传入 <code>original_document_id</code> 时,代表文档进行新增操作,<code>process_rule</code> 为必填
|
||||
|
||||
- indexing_technique 索引方式
|
||||
- <code>indexing_technique</code> 索引方式
|
||||
- <code>high_quality</code> 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引
|
||||
- <code>economy</code> 经济:使用 Keyword Table Index 的倒排索引进行构建
|
||||
- <code>economy</code> 经济:使用 keyword table index 的倒排索引进行构建
|
||||
|
||||
- process_rule 处理规则
|
||||
- <code>process_rule</code> 处理规则
|
||||
- <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义
|
||||
- <code>rules</code> (object) 自定义规则(自动模式下,该字段为空)
|
||||
- <code>pre_processing_rules</code> (array[object]) 预处理规则
|
||||
@ -166,7 +166,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>enabled</code> (bool) 是否选中该规则,不传入文档 ID 时代表默认值
|
||||
- <code>segmentation</code> (object) 分段规则
|
||||
- <code>separator</code> 自定义分段标识符,目前仅允许设置一个分隔符。默认为 \n
|
||||
- <code>max_tokens</code> 最大长度 (token) 默认为 1000
|
||||
- <code>max_tokens</code> 最大长度(token)默认为 1000
|
||||
</Property>
|
||||
<Property name='file' type='multipart/form-data' key='file'>
|
||||
需要上传的文件。
|
||||
@ -177,11 +177,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/document/create_by_file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
label="/datasets/{dataset_id}/document/create-by-file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create_by_file' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/document/create-by-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'data="{\"name\":\"Dify\",\"indexing_technique\":\"high_quality\",\"process_rule\":{\"rules\":{\"pre_processing_rules\":[{\"id\":\"remove_extra_spaces\",\"enabled\":true},{\"id\":\"remove_urls_emails\",\"enabled\":true}],\"segmentation\":{\"separator\":\"###\",\"max_tokens\":500}},\"mode\":\"custom\"}}";type=text/plain' \
|
||||
--form 'file=@"/path/to/file"'
|
||||
@ -221,7 +221,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets'
|
||||
@ -245,13 +245,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>economy</code> 经济
|
||||
</Property>
|
||||
<Property name='permission' type='string' key='permission'>
|
||||
权限(选填,默认only_me)
|
||||
权限(选填,默认 only_me)
|
||||
- <code>only_me</code> 仅自己
|
||||
- <code>all_team_members</code> 所有团队成员
|
||||
- <code>partial_members</code> 部分团队成员
|
||||
</Property>
|
||||
<Property name='provider' type='string' key='provider'>
|
||||
provider,(选填,默认 vendor)
|
||||
Provider(选填,默认 vendor)
|
||||
- <code>vendor</code> 上传文件
|
||||
- <code>external</code> 外部知识库
|
||||
</Property>
|
||||
@ -264,9 +264,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name", "permission": "only_me"}'`}
|
||||
>
|
||||
@ -306,7 +306,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets'
|
||||
@ -369,7 +369,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}'
|
||||
@ -406,13 +406,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update_by_text'
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
||||
method='POST'
|
||||
title='通过文本更新文档 '
|
||||
name='#update_by_text'
|
||||
name='#update-by-text'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -431,7 +431,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='name' type='string' key='name'>
|
||||
文档名称 (选填)
|
||||
文档名称(选填)
|
||||
</Property>
|
||||
<Property name='text' type='string' key='text'>
|
||||
文档内容(选填)
|
||||
@ -448,7 +448,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>enabled</code> (bool) 是否选中该规则,不传入文档 ID 时代表默认值
|
||||
- <code>segmentation</code> (object) 分段规则
|
||||
- <code>separator</code> 自定义分段标识符,目前仅允许设置一个分隔符。默认为 \n
|
||||
- <code>max_tokens</code> 最大长度 (token) 默认为 1000
|
||||
- <code>max_tokens</code> 最大长度(token)默认为 1000
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -456,11 +456,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update_by_text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name","text": "text"}'`}
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update-by-text"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-text' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "name","text": "text"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_text' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-text' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -503,13 +503,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update_by_file'
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
||||
method='POST'
|
||||
title='通过文件更新文档 '
|
||||
name='#update_by_file'
|
||||
name='#update-by-file'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -528,7 +528,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='name' type='string' key='name'>
|
||||
文档名称 (选填)
|
||||
文档名称(选填)
|
||||
</Property>
|
||||
<Property name='file' type='multipart/form-data' key='file'>
|
||||
需要上传的文件
|
||||
@ -545,7 +545,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- <code>enabled</code> (bool) 是否选中该规则,不传入文档 ID 时代表默认值
|
||||
- <code>segmentation</code> (object) 分段规则
|
||||
- <code>separator</code> 自定义分段标识符,目前仅允许设置一个分隔符。默认为 \n
|
||||
- <code>max_tokens</code> 最大长度 (token) 默认为 1000
|
||||
- <code>max_tokens</code> 最大长度(token)默认为 1000
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
@ -553,11 +553,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update_by_file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"name":"Dify","indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/update-by-file"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-file' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'data="{"name":"Dify","indexing_technique":"high_quality","process_rule":{"rules":{"pre_processing_rules":[{"id":"remove_extra_spaces","enabled":true},{"id":"remove_urls_emails","enabled":true}],"segmentation":{"separator":"###","max_tokens":500}},"mode":"custom"}}";type=text/plain' \\\n--form 'file=@"/path/to/file"'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update_by_file' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/update-by-file' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'data="{\"name\":\"Dify\",\"indexing_technique\":\"high_quality\",\"process_rule\":{\"rules\":{\"pre_processing_rules\":[{\"id\":\"remove_extra_spaces\",\"enabled\":true},{\"id\":\"remove_urls_emails\",\"enabled\":true}],\"segmentation\":{\"separator\":\"###\",\"max_tokens\":500}},\"mode\":\"custom\"}}";type=text/plain' \
|
||||
--form 'file=@"/path/to/file"'
|
||||
@ -597,7 +597,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
||||
@ -652,7 +652,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||
@ -694,7 +694,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents'
|
||||
@ -769,7 +769,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||
@ -793,7 +793,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Properties>
|
||||
<Property name='segments' type='object list' key='segments'>
|
||||
- <code>content</code> (text) 文本内容/问题内容,必填
|
||||
- <code>answer</code> (text) 答案内容,非必填,如果知识库的模式为qa模式则传值
|
||||
- <code>answer</code> (text) 答案内容,非必填,如果知识库的模式为 Q&A 模式则传值
|
||||
- <code>keywords</code> (list) 关键字,非必填
|
||||
</Property>
|
||||
</Properties>
|
||||
@ -855,7 +855,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||
@ -933,7 +933,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||
@ -979,7 +979,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||
@ -1006,7 +1006,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Properties>
|
||||
<Property name='segment' type='object' key='segment'>
|
||||
- <code>content</code> (text) 文本内容/问题内容,必填
|
||||
- <code>answer</code> (text) 答案内容,非必填,如果知识库的模式为qa模式则传值
|
||||
- <code>answer</code> (text) 答案内容,非必填,如果知识库的模式为 Q&A 模式则传值
|
||||
- <code>keywords</code> (list) 关键字,非必填
|
||||
- <code>enabled</code> (bool) false/true,非必填
|
||||
</Property>
|
||||
@ -1068,13 +1068,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/hit_testing'
|
||||
url='/datasets/{dataset_id}/retrieve'
|
||||
method='POST'
|
||||
title='知识库召回测试'
|
||||
name='#dataset_hit_testing'
|
||||
title='检索知识库'
|
||||
name='#dataset_retrieval'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -1088,23 +1088,23 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='query' type='string' key='query'>
|
||||
召回关键词
|
||||
检索关键词
|
||||
</Property>
|
||||
<Property name='retrieval_model' type='object' key='retrieval_model'>
|
||||
召回参数(选填,如不填,按照默认方式召回)
|
||||
检索参数(选填,如不填,按照默认方式召回)
|
||||
- <code>search_method</code> (text) 检索方法:以下三个关键字之一,必填
|
||||
- <code>keyword_search</code> 关键字检索
|
||||
- <code>semantic_search</code> 语义检索
|
||||
- <code>full_text_search</code> 全文检索
|
||||
- <code>hybrid_search</code> 混合检索
|
||||
- <code>reranking_enable</code> (bool) 是否启用 Reranking,非必填,如果检索模式为semantic_search模式或者hybrid_search则传值
|
||||
- <code>reranking_enable</code> (bool) 是否启用 Reranking,非必填,如果检索模式为 semantic_search 模式或者 hybrid_search 则传值
|
||||
- <code>reranking_mode</code> (object) Rerank模型配置,非必填,如果启用了 reranking 则传值
|
||||
- <code>reranking_provider_name</code> (string) Rerank 模型提供商
|
||||
- <code>reranking_model_name</code> (string) Rerank 模型名称
|
||||
- <code>weights</code> (double) 混合检索模式下语意检索的权重设置
|
||||
- <code>top_k</code> (integer) 返回结果数量,非必填
|
||||
- <code>score_threshold_enabled</code> (bool) 是否开启Score阈值
|
||||
- <code>score_threshold</code> (double) Score阈值
|
||||
- <code>score_threshold_enabled</code> (bool) 是否开启 score 阈值
|
||||
- <code>score_threshold</code> (double) Score 阈值
|
||||
</Property>
|
||||
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
|
||||
未启用字段
|
||||
@ -1115,26 +1115,26 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/hit_testing"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
label="/datasets/{dataset_id}/retrieve"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/retrieve' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/retrieve' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -1214,7 +1214,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Row>
|
||||
|
||||
|
||||
---
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
|
||||
@ -23,8 +23,9 @@ export default function AppSelector() {
|
||||
params: {},
|
||||
})
|
||||
|
||||
if (localStorage?.getItem('console_token'))
|
||||
localStorage.removeItem('console_token')
|
||||
localStorage.removeItem('setup_status')
|
||||
localStorage.removeItem('console_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
|
||||
router.push('/signin')
|
||||
}
|
||||
|
||||
@ -33,6 +33,10 @@ import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
|
||||
// type
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
|
||||
export interface IGetAutomaticResProps {
|
||||
mode: AppType
|
||||
@ -68,7 +72,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
onFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
currentProvider,
|
||||
currentModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||
const tryList = [
|
||||
{
|
||||
icon: RiTerminalBoxLine,
|
||||
@ -191,6 +198,19 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
<div className={`leading-[28px] text-lg font-bold ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
||||
<div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.generate.description')}</div>
|
||||
</div>
|
||||
<div className='flex items-center mb-8'>
|
||||
<ModelIcon
|
||||
className='shrink-0 mr-1.5 '
|
||||
provider={currentProvider}
|
||||
modelName={currentModel?.model}
|
||||
/>
|
||||
<ModelName
|
||||
className='grow'
|
||||
modelItem={currentModel!}
|
||||
showMode
|
||||
showFeatures
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{t('appDebug.generate.tryIt')}</div>
|
||||
|
||||
@ -105,6 +105,15 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
<div className='text-[13px] text-gray-400'>{t('appDebug.codegen.loading')}</div>
|
||||
</div>
|
||||
)
|
||||
const renderNoData = (
|
||||
<div className='w-0 grow flex flex-col items-center px-8 justify-center h-full space-y-3'>
|
||||
<Generator className='w-14 h-14 text-gray-300' />
|
||||
<div className='leading-5 text-center text-[13px] font-normal text-gray-400'>
|
||||
<div>{t('appDebug.codegen.noDataLine1')}</div>
|
||||
<div>{t('appDebug.codegen.noDataLine2')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -157,6 +166,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && renderLoading}
|
||||
{!isLoading && !res && renderNoData}
|
||||
{(!isLoading && res) && (
|
||||
<div className='w-0 grow p-6 pb-0 h-full'>
|
||||
<div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{t('appDebug.codegen.resTitle')}</div>
|
||||
|
||||
@ -15,6 +15,7 @@ import { AppType } from '@/types/app'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import {
|
||||
getMultipleRetrievalConfig,
|
||||
getSelectedDatasetsMode,
|
||||
} from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -38,6 +39,7 @@ const DatasetConfig: FC = () => {
|
||||
isAgent,
|
||||
datasetConfigs,
|
||||
setDatasetConfigs,
|
||||
setRerankSettingModalOpen,
|
||||
} = useContext(ConfigContext)
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
|
||||
@ -55,6 +57,20 @@ const DatasetConfig: FC = () => {
|
||||
...(datasetConfigs as any),
|
||||
...retrievalConfig,
|
||||
})
|
||||
const {
|
||||
allExternal,
|
||||
allInternal,
|
||||
mixtureInternalAndExternal,
|
||||
mixtureHighQualityAndEconomic,
|
||||
inconsistentEmbeddingModel,
|
||||
} = getSelectedDatasetsMode(filteredDataSets)
|
||||
|
||||
if (
|
||||
(allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
|
||||
|| mixtureInternalAndExternal
|
||||
|| allExternal
|
||||
)
|
||||
setRerankSettingModalOpen(true)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
|
||||
@ -266,7 +266,7 @@ const ConfigContent: FC<Props> = ({
|
||||
<div className='mt-2'>
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
selectedDatasetsMode.allEconomic && (
|
||||
selectedDatasetsMode.allEconomic && !selectedDatasetsMode.mixtureInternalAndExternal && (
|
||||
<div
|
||||
className='flex items-center'
|
||||
onClick={handleDisabledSwitchClick}
|
||||
|
||||
@ -12,6 +12,7 @@ import { RETRIEVE_TYPE } from '@/types/app'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { RerankingModeEnum } from '@/models/datasets'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import type { DatasetConfigs } from '@/models/debug'
|
||||
import {
|
||||
@ -47,7 +48,10 @@ const ParamsConfig = ({
|
||||
const isValid = () => {
|
||||
let errMsg = ''
|
||||
if (tempDataSetConfigs.retrieval_model === RETRIEVE_TYPE.multiWay) {
|
||||
if (!tempDataSetConfigs.reranking_model?.reranking_model_name && (rerankDefaultModel && !isRerankDefaultModelValid))
|
||||
if (tempDataSetConfigs.reranking_enable
|
||||
&& tempDataSetConfigs.reranking_mode === RerankingModeEnum.RerankingModel
|
||||
&& !isRerankDefaultModelValid
|
||||
)
|
||||
errMsg = t('appDebug.datasetConfig.rerankModelRequired')
|
||||
}
|
||||
if (errMsg) {
|
||||
@ -62,7 +66,9 @@ const ParamsConfig = ({
|
||||
if (!isValid())
|
||||
return
|
||||
const config = { ...tempDataSetConfigs }
|
||||
if (config.retrieval_model === RETRIEVE_TYPE.multiWay && !config.reranking_model) {
|
||||
if (config.retrieval_model === RETRIEVE_TYPE.multiWay
|
||||
&& config.reranking_mode === RerankingModeEnum.RerankingModel
|
||||
&& !config.reranking_model) {
|
||||
config.reranking_model = {
|
||||
reranking_provider_name: rerankDefaultModel?.provider?.provider,
|
||||
reranking_model_name: rerankDefaultModel?.model,
|
||||
|
||||
@ -252,12 +252,18 @@ const Configuration: FC = () => {
|
||||
}
|
||||
hideSelectDataSet()
|
||||
const {
|
||||
allEconomic,
|
||||
allExternal,
|
||||
allInternal,
|
||||
mixtureInternalAndExternal,
|
||||
mixtureHighQualityAndEconomic,
|
||||
inconsistentEmbeddingModel,
|
||||
} = getSelectedDatasetsMode(newDatasets)
|
||||
|
||||
if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel)
|
||||
if (
|
||||
(allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
|
||||
|| mixtureInternalAndExternal
|
||||
|| allExternal
|
||||
)
|
||||
setRerankSettingModalOpen(true)
|
||||
|
||||
const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs
|
||||
|
||||
@ -36,6 +36,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import TextGeneration from '@/app/components/app/text-generate/item'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import MessageLogModal from '@/app/components/base/message-log-modal'
|
||||
import PromptLogModal from '@/app/components/base/prompt-log-modal'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
@ -168,11 +169,13 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
||||
const { userProfile: { timezone } } = useAppContext()
|
||||
const { formatTime } = useTimestamp()
|
||||
const { onClose, appDetail } = useContext(DrawerContext)
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
currentLogItem: state.currentLogItem,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
showPromptLogModal: state.showPromptLogModal,
|
||||
setShowPromptLogModal: state.setShowPromptLogModal,
|
||||
currentLogModalActiveTab: state.currentLogModalActiveTab,
|
||||
})))
|
||||
const { t } = useTranslation()
|
||||
@ -192,8 +195,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
||||
conversation_id: detail.id,
|
||||
limit: 10,
|
||||
}
|
||||
if (allChatItems.at(-1)?.id)
|
||||
params.first_id = allChatItems.at(-1)?.id.replace('question-', '')
|
||||
if (allChatItems[0]?.id)
|
||||
params.first_id = allChatItems[0]?.id.replace('question-', '')
|
||||
const messageRes = await fetchChatMessages({
|
||||
url: `/apps/${appDetail?.id}/chat-messages`,
|
||||
params,
|
||||
@ -557,6 +560,16 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
||||
defaultTab={currentLogModalActiveTab}
|
||||
/>
|
||||
)}
|
||||
{showPromptLogModal && (
|
||||
<PromptLogModal
|
||||
width={width}
|
||||
currentLogItem={currentLogItem}
|
||||
onCancel={() => {
|
||||
setCurrentLogItem()
|
||||
setShowPromptLogModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,18 +8,22 @@ import classNames from 'classnames'
|
||||
|
||||
import { ImagePlus } from '../icons/src/vender/line/images'
|
||||
import { useDraggableUploader } from './hooks'
|
||||
import { checkIsAnimatedImage } from './utils'
|
||||
import { ALLOW_FILE_EXTENSIONS } from '@/types/app'
|
||||
|
||||
type UploaderProps = {
|
||||
className?: string
|
||||
onImageCropped?: (tempUrl: string, croppedAreaPixels: Area, fileName: string) => void
|
||||
onUpload?: (file?: File) => void
|
||||
}
|
||||
|
||||
const Uploader: FC<UploaderProps> = ({
|
||||
className,
|
||||
onImageCropped,
|
||||
onUpload,
|
||||
}) => {
|
||||
const [inputImage, setInputImage] = useState<{ file: File; url: string }>()
|
||||
const [isAnimatedImage, setIsAnimatedImage] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (inputImage)
|
||||
@ -34,12 +38,19 @@ const Uploader: FC<UploaderProps> = ({
|
||||
if (!inputImage)
|
||||
return
|
||||
onImageCropped?.(inputImage.url, croppedAreaPixels, inputImage.file.name)
|
||||
onUpload?.(undefined)
|
||||
}
|
||||
|
||||
const handleLocalFileInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (file)
|
||||
if (file) {
|
||||
setInputImage({ file, url: URL.createObjectURL(file) })
|
||||
checkIsAnimatedImage(file).then((isAnimatedImage) => {
|
||||
setIsAnimatedImage(!!isAnimatedImage)
|
||||
if (isAnimatedImage)
|
||||
onUpload?.(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
@ -52,6 +63,26 @@ const Uploader: FC<UploaderProps> = ({
|
||||
|
||||
const inputRef = createRef<HTMLInputElement>()
|
||||
|
||||
const handleShowImage = () => {
|
||||
if (isAnimatedImage) {
|
||||
return (
|
||||
<img src={inputImage?.url} alt='' />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Cropper
|
||||
image={inputImage?.url}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
aspect={1}
|
||||
onCropChange={setCrop}
|
||||
onCropComplete={onCropComplete}
|
||||
onZoomChange={setZoom}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(className, 'w-full px-3 py-1.5')}>
|
||||
<div
|
||||
@ -79,15 +110,7 @@ const Uploader: FC<UploaderProps> = ({
|
||||
</div>
|
||||
<div className="text-xs pointer-events-none">Supports PNG, JPG, JPEG, WEBP and GIF</div>
|
||||
</>
|
||||
: <Cropper
|
||||
image={inputImage.url}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
aspect={1}
|
||||
onCropChange={setCrop}
|
||||
onCropComplete={onCropComplete}
|
||||
onZoomChange={setZoom}
|
||||
/>
|
||||
: handleShowImage()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -74,6 +74,11 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
setImageCropInfo({ tempUrl, croppedAreaPixels, fileName })
|
||||
}
|
||||
|
||||
const [uploadImageInfo, setUploadImageInfo] = useState<{ file?: File }>()
|
||||
const handleUpload = async (file?: File) => {
|
||||
setUploadImageInfo({ file })
|
||||
}
|
||||
|
||||
const handleSelect = async () => {
|
||||
if (activeTab === 'emoji') {
|
||||
if (emoji) {
|
||||
@ -85,9 +90,13 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!imageCropInfo)
|
||||
if (!imageCropInfo && !uploadImageInfo)
|
||||
return
|
||||
setUploading(true)
|
||||
if (imageCropInfo.file) {
|
||||
handleLocalFileUpload(imageCropInfo.file)
|
||||
return
|
||||
}
|
||||
const blob = await getCroppedImg(imageCropInfo.tempUrl, imageCropInfo.croppedAreaPixels, imageCropInfo.fileName)
|
||||
const file = new File([blob], imageCropInfo.fileName, { type: blob.type })
|
||||
handleLocalFileUpload(file)
|
||||
@ -121,7 +130,7 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
<Divider className='m-0' />
|
||||
|
||||
<EmojiPickerInner className={activeTab === 'emoji' ? 'block' : 'hidden'} onSelect={handleSelectEmoji} />
|
||||
<Uploader className={activeTab === 'image' ? 'block' : 'hidden'} onImageCropped={handleImageCropped} />
|
||||
<Uploader className={activeTab === 'image' ? 'block' : 'hidden'} onImageCropped={handleImageCropped} onUpload={handleUpload}/>
|
||||
|
||||
<Divider className='m-0' />
|
||||
<div className='w-full flex items-center justify-center p-3 gap-2'>
|
||||
|
||||
@ -115,3 +115,52 @@ export default async function getCroppedImg(
|
||||
}, mimeType)
|
||||
})
|
||||
}
|
||||
|
||||
export function checkIsAnimatedImage(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader()
|
||||
|
||||
fileReader.onload = function (e) {
|
||||
const arr = new Uint8Array(e.target.result)
|
||||
|
||||
// Check file extension
|
||||
const fileName = file.name.toLowerCase()
|
||||
if (fileName.endsWith('.gif')) {
|
||||
// If file is a GIF, assume it's animated
|
||||
resolve(true)
|
||||
}
|
||||
// Check for WebP signature (RIFF and WEBP)
|
||||
else if (isWebP(arr)) {
|
||||
resolve(checkWebPAnimation(arr)) // Check if it's animated
|
||||
}
|
||||
else {
|
||||
resolve(false) // Not a GIF or WebP
|
||||
}
|
||||
}
|
||||
|
||||
fileReader.onerror = function (err) {
|
||||
reject(err) // Reject the promise on error
|
||||
}
|
||||
|
||||
// Read the file as an array buffer
|
||||
fileReader.readAsArrayBuffer(file)
|
||||
})
|
||||
}
|
||||
|
||||
// Function to check for WebP signature
|
||||
function isWebP(arr) {
|
||||
return (
|
||||
arr[0] === 0x52 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x46
|
||||
&& arr[8] === 0x57 && arr[9] === 0x45 && arr[10] === 0x42 && arr[11] === 0x50
|
||||
) // "WEBP"
|
||||
}
|
||||
|
||||
// Function to check if the WebP is animated (contains ANIM chunk)
|
||||
function checkWebPAnimation(arr) {
|
||||
// Search for the ANIM chunk in WebP to determine if it's animated
|
||||
for (let i = 12; i < arr.length - 4; i++) {
|
||||
if (arr[i] === 0x41 && arr[i + 1] === 0x4E && arr[i + 2] === 0x49 && arr[i + 3] === 0x4D)
|
||||
return true // Found animation
|
||||
}
|
||||
return false // No animation chunk found
|
||||
}
|
||||
|
||||
@ -1804,6 +1804,280 @@ exports[`build chat item tree and get thread messages should get thread messages
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`build chat item tree and get thread messages should work with partial messages 1`] = `
|
||||
[
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"agent_thoughts": [
|
||||
{
|
||||
"chain_id": null,
|
||||
"created_at": 1726105809,
|
||||
"files": [],
|
||||
"id": "1019cd79-d141-4f9f-880a-fc1441cfd802",
|
||||
"message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
"observation": "",
|
||||
"position": 1,
|
||||
"thought": "Sure! My number is 54. Your turn!",
|
||||
"tool": "",
|
||||
"tool_input": "",
|
||||
"tool_labels": {},
|
||||
},
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"agent_thoughts": [
|
||||
{
|
||||
"chain_id": null,
|
||||
"created_at": 1726105822,
|
||||
"files": [],
|
||||
"id": "0773bec7-b992-4a53-92b2-20ebaeae8798",
|
||||
"message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed",
|
||||
"observation": "",
|
||||
"position": 1,
|
||||
"thought": "My number is 4729. Your turn!",
|
||||
"tool": "",
|
||||
"tool_input": "",
|
||||
"tool_labels": {},
|
||||
},
|
||||
],
|
||||
"children": [],
|
||||
"content": "My number is 4729. Your turn!",
|
||||
"conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
|
||||
"feedbackDisabled": false,
|
||||
"id": "324bce32-c98c-435d-a66b-bac974ebb5ed",
|
||||
"input": {
|
||||
"inputs": {},
|
||||
"query": "3306",
|
||||
},
|
||||
"isAnswer": true,
|
||||
"log": [
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "Sure! My number is 54. Your turn!",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "3306",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "My number is 4729. Your turn!",
|
||||
},
|
||||
],
|
||||
"message_files": [],
|
||||
"more": {
|
||||
"latency": "1.30",
|
||||
"time": "09/11/2024 09:50 PM",
|
||||
"tokens": 66,
|
||||
},
|
||||
"parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed",
|
||||
"siblingIndex": 0,
|
||||
"workflow_run_id": null,
|
||||
},
|
||||
],
|
||||
"content": "3306",
|
||||
"id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed",
|
||||
"isAnswer": false,
|
||||
"message_files": [],
|
||||
"parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"agent_thoughts": [
|
||||
{
|
||||
"chain_id": null,
|
||||
"created_at": 1726107812,
|
||||
"files": [],
|
||||
"id": "5ca650f3-982c-4399-8b95-9ea241c76707",
|
||||
"message_id": "684b5396-4e91-4043-88e9-aabe48b21acc",
|
||||
"observation": "",
|
||||
"position": 1,
|
||||
"thought": "My number is 4821. Your turn!",
|
||||
"tool": "",
|
||||
"tool_input": "",
|
||||
"tool_labels": {},
|
||||
},
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"agent_thoughts": [
|
||||
{
|
||||
"chain_id": null,
|
||||
"created_at": 1726111024,
|
||||
"files": [],
|
||||
"id": "095cacab-afad-4387-a41d-1662578b8b13",
|
||||
"message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c",
|
||||
"observation": "",
|
||||
"position": 1,
|
||||
"thought": "My number is 1456. Your turn!",
|
||||
"tool": "",
|
||||
"tool_input": "",
|
||||
"tool_labels": {},
|
||||
},
|
||||
],
|
||||
"children": [],
|
||||
"content": "My number is 1456. Your turn!",
|
||||
"conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
|
||||
"feedbackDisabled": false,
|
||||
"id": "19904a7b-7494-4ed8-b72c-1d18668cea8c",
|
||||
"input": {
|
||||
"inputs": {},
|
||||
"query": "1003",
|
||||
},
|
||||
"isAnswer": true,
|
||||
"log": [
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "Sure! My number is 54. Your turn!",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "3306",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "My number is 4821. Your turn!",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "1003",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "My number is 1456. Your turn!",
|
||||
},
|
||||
],
|
||||
"message_files": [],
|
||||
"more": {
|
||||
"latency": "1.38",
|
||||
"time": "09/11/2024 11:17 PM",
|
||||
"tokens": 86,
|
||||
},
|
||||
"parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c",
|
||||
"siblingIndex": 0,
|
||||
"workflow_run_id": null,
|
||||
},
|
||||
],
|
||||
"content": "1003",
|
||||
"id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c",
|
||||
"isAnswer": false,
|
||||
"message_files": [],
|
||||
"parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc",
|
||||
},
|
||||
],
|
||||
"content": "My number is 4821. Your turn!",
|
||||
"conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
|
||||
"feedbackDisabled": false,
|
||||
"id": "684b5396-4e91-4043-88e9-aabe48b21acc",
|
||||
"input": {
|
||||
"inputs": {},
|
||||
"query": "3306",
|
||||
},
|
||||
"isAnswer": true,
|
||||
"log": [
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "Sure! My number is 54. Your turn!",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "3306",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "My number is 4821. Your turn!",
|
||||
},
|
||||
],
|
||||
"message_files": [],
|
||||
"more": {
|
||||
"latency": "1.48",
|
||||
"time": "09/11/2024 10:23 PM",
|
||||
"tokens": 66,
|
||||
},
|
||||
"parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc",
|
||||
"siblingIndex": 1,
|
||||
"workflow_run_id": null,
|
||||
},
|
||||
],
|
||||
"content": "3306",
|
||||
"id": "question-684b5396-4e91-4043-88e9-aabe48b21acc",
|
||||
"isAnswer": false,
|
||||
"message_files": [],
|
||||
"parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
},
|
||||
],
|
||||
"content": "Sure! My number is 54. Your turn!",
|
||||
"conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
|
||||
"feedbackDisabled": false,
|
||||
"id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
"input": {
|
||||
"inputs": {},
|
||||
"query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
},
|
||||
"isAnswer": true,
|
||||
"log": [
|
||||
{
|
||||
"files": [],
|
||||
"role": "user",
|
||||
"text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
},
|
||||
{
|
||||
"files": [],
|
||||
"role": "assistant",
|
||||
"text": "Sure! My number is 54. Your turn!",
|
||||
},
|
||||
],
|
||||
"message_files": [],
|
||||
"more": {
|
||||
"latency": "1.52",
|
||||
"time": "09/11/2024 09:50 PM",
|
||||
"tokens": 46,
|
||||
},
|
||||
"parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
"siblingIndex": 0,
|
||||
"workflow_run_id": null,
|
||||
},
|
||||
],
|
||||
"content": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
|
||||
"id": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
|
||||
"isAnswer": false,
|
||||
"message_files": [],
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`build chat item tree and get thread messages should work with real world messages 1`] = `
|
||||
[
|
||||
{
|
||||
|
||||
@ -255,4 +255,10 @@ describe('build chat item tree and get thread messages', () => {
|
||||
const threadMessages6_2 = getThreadMessages(tree6, 'ff4c2b43-48a5-47ad-9dc5-08b34ddba61b')
|
||||
expect(threadMessages6_2).toMatchSnapshot()
|
||||
})
|
||||
|
||||
const partialMessages = (realWorldMessages as ChatItemInTree[]).slice(-10)
|
||||
const tree7 = buildChatItemTree(partialMessages)
|
||||
it('should work with partial messages', () => {
|
||||
expect(tree7).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@ -134,6 +134,12 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] {
|
||||
}
|
||||
}
|
||||
|
||||
// If no messages have parentMessageId=null (indicating a root node),
|
||||
// then we likely have a partial chat history. In this case,
|
||||
// use the first available message as the root node.
|
||||
if (rootNodes.length === 0 && allMessages.length > 0)
|
||||
rootNodes.push(map[allMessages[0]!.id]!)
|
||||
|
||||
return rootNodes
|
||||
}
|
||||
|
||||
|
||||
@ -3,5 +3,6 @@ export const IMG_SIZE_LIMIT = 10 * 1024 * 1024
|
||||
export const FILE_SIZE_LIMIT = 15 * 1024 * 1024
|
||||
export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024
|
||||
export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024
|
||||
export const MAX_FILE_UPLOAD_LIMIT = 10
|
||||
|
||||
export const FILE_URL_REGEX = /^(https?|ftp):\/\//
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
@ -35,17 +34,9 @@ const FileInAttachmentItem = ({
|
||||
onRemove,
|
||||
onReUpload,
|
||||
}: FileInAttachmentItemProps) => {
|
||||
const { id, name, type, progress, supportFileType, base64Url, url } = file
|
||||
const ext = getFileExtension(name, type)
|
||||
const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file
|
||||
const ext = getFileExtension(name, type, isRemote)
|
||||
const isImageFile = supportFileType === SupportUploadFileTypes.image
|
||||
const nameArr = useMemo(() => {
|
||||
const nameMatch = name.match(/(.+)\.([^.]+)$/)
|
||||
|
||||
if (nameMatch)
|
||||
return [nameMatch[1], nameMatch[2]]
|
||||
|
||||
return [name, '']
|
||||
}, [name])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
@ -75,12 +66,7 @@ const FileInAttachmentItem = ({
|
||||
className='flex items-center mb-0.5 system-xs-medium text-text-secondary truncate'
|
||||
title={file.name}
|
||||
>
|
||||
<div className='truncate'>{nameArr[0]}</div>
|
||||
{
|
||||
nameArr[1] && (
|
||||
<span>.{nameArr[1]}</span>
|
||||
)
|
||||
}
|
||||
<div className='truncate'>{name}</div>
|
||||
</div>
|
||||
<div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{
|
||||
@ -93,7 +79,11 @@ const FileInAttachmentItem = ({
|
||||
<span className='mx-1 system-2xs-medium'>•</span>
|
||||
)
|
||||
}
|
||||
<span>{formatFileSize(file.size || 0)}</span>
|
||||
{
|
||||
!!file.size && (
|
||||
<span>{formatFileSize(file.size)}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 flex items-center'>
|
||||
|
||||
@ -31,8 +31,8 @@ const FileItem = ({
|
||||
onRemove,
|
||||
onReUpload,
|
||||
}: FileItemProps) => {
|
||||
const { id, name, type, progress, url } = file
|
||||
const ext = getFileExtension(name, type)
|
||||
const { id, name, type, progress, url, isRemote } = file
|
||||
const ext = getFileExtension(name, type, isRemote)
|
||||
const uploadError = progress === -1
|
||||
|
||||
return (
|
||||
@ -75,7 +75,9 @@ const FileItem = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
{formatFileSize(file.size || 0)}
|
||||
{
|
||||
!!file.size && formatFileSize(file.size)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
showDownloadAction && (
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
AUDIO_SIZE_LIMIT,
|
||||
FILE_SIZE_LIMIT,
|
||||
IMG_SIZE_LIMIT,
|
||||
MAX_FILE_UPLOAD_LIMIT,
|
||||
VIDEO_SIZE_LIMIT,
|
||||
} from '@/app/components/base/file-uploader/constants'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
@ -25,7 +26,7 @@ import { TransferMethod } from '@/types/app'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { formatFileSize } from '@/utils/format'
|
||||
import { fetchRemoteFileInfo } from '@/service/common'
|
||||
import { uploadRemoteFileInfo } from '@/service/common'
|
||||
import type { FileUploadConfigResponse } from '@/models/common'
|
||||
|
||||
export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) => {
|
||||
@ -33,12 +34,14 @@ export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) =>
|
||||
const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT
|
||||
const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT
|
||||
const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT
|
||||
const maxFileUploadLimit = Number(fileUploadConfig?.workflow_file_upload_limit) || MAX_FILE_UPLOAD_LIMIT
|
||||
|
||||
return {
|
||||
imgSizeLimit,
|
||||
docSizeLimit,
|
||||
audioSizeLimit,
|
||||
videoSizeLimit,
|
||||
maxFileUploadLimit,
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +52,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
const params = useParams()
|
||||
const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileConfig.fileUploadConfig)
|
||||
|
||||
const checkSizeLimit = (fileType: string, fileSize: number) => {
|
||||
const checkSizeLimit = useCallback((fileType: string, fileSize: number) => {
|
||||
switch (fileType) {
|
||||
case SupportUploadFileTypes.image: {
|
||||
if (fileSize > imgSizeLimit) {
|
||||
@ -120,7 +123,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [audioSizeLimit, docSizeLimit, imgSizeLimit, notify, t, videoSizeLimit])
|
||||
|
||||
const handleAddFile = useCallback((newFile: FileEntity) => {
|
||||
const {
|
||||
@ -188,6 +191,17 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
}
|
||||
}, [fileStore, notify, t, handleUpdateFile, params])
|
||||
|
||||
const startProgressTimer = useCallback((fileId: string) => {
|
||||
const timer = setInterval(() => {
|
||||
const files = fileStore.getState().files
|
||||
const file = files.find(file => file.id === fileId)
|
||||
|
||||
if (file && file.progress < 80 && file.progress >= 0)
|
||||
handleUpdateFile({ ...file, progress: file.progress + 20 })
|
||||
else
|
||||
clearTimeout(timer)
|
||||
}, 200)
|
||||
}, [fileStore, handleUpdateFile])
|
||||
const handleLoadFileFromLink = useCallback((url: string) => {
|
||||
const allowedFileTypes = fileConfig.allowed_file_types
|
||||
|
||||
@ -197,19 +211,27 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
type: '',
|
||||
size: 0,
|
||||
progress: 0,
|
||||
transferMethod: TransferMethod.remote_url,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: '',
|
||||
url,
|
||||
isRemote: true,
|
||||
}
|
||||
handleAddFile(uploadingFile)
|
||||
startProgressTimer(uploadingFile.id)
|
||||
|
||||
fetchRemoteFileInfo(url).then((res) => {
|
||||
uploadRemoteFileInfo(url, !!params.token).then((res) => {
|
||||
const newFile = {
|
||||
...uploadingFile,
|
||||
type: res.file_type,
|
||||
size: res.file_length,
|
||||
type: res.mime_type,
|
||||
size: res.size,
|
||||
progress: 100,
|
||||
supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
|
||||
supportFileType: getSupportFileType(res.name, res.mime_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
|
||||
uploadedId: res.id,
|
||||
url: res.url,
|
||||
}
|
||||
if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
|
||||
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
}
|
||||
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
@ -219,7 +241,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
})
|
||||
}, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types])
|
||||
}, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types, fileConfig.allowed_file_extensions, startProgressTimer])
|
||||
|
||||
const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
|
||||
|
||||
|
||||
@ -29,4 +29,5 @@ export type FileEntity = {
|
||||
uploadedId?: string
|
||||
base64Url?: string
|
||||
url?: string
|
||||
isRemote?: boolean
|
||||
}
|
||||
|
||||
@ -43,10 +43,13 @@ export const fileUpload: FileUpload = ({
|
||||
})
|
||||
}
|
||||
|
||||
export const getFileExtension = (fileName: string, fileMimetype: string) => {
|
||||
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
|
||||
if (fileMimetype)
|
||||
return mime.getExtension(fileMimetype) || ''
|
||||
|
||||
if (isRemote)
|
||||
return ''
|
||||
|
||||
if (fileName) {
|
||||
const fileNamePair = fileName.split('.')
|
||||
const fileNamePairLength = fileNamePair.length
|
||||
|
||||
@ -133,6 +133,7 @@ const ImageList: FC<ImageListProps> = ({
|
||||
<ImagePreview
|
||||
url={imagePreviewUrl}
|
||||
onCancel={() => setImagePreviewUrl('')}
|
||||
title=''
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiSearchLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
@ -12,6 +12,7 @@ type SearchInputProps = {
|
||||
onChange: (v: string) => void
|
||||
white?: boolean
|
||||
}
|
||||
|
||||
const SearchInput: FC<SearchInputProps> = ({
|
||||
placeholder,
|
||||
className,
|
||||
@ -21,6 +22,7 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [focus, setFocus] = useState<boolean>(false)
|
||||
const isComposing = useRef<boolean>(false)
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
@ -45,7 +47,14 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
placeholder={placeholder || t('common.operation.search')!}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value)
|
||||
if (!isComposing.current)
|
||||
onChange(e.target.value)
|
||||
}}
|
||||
onCompositionStart={() => {
|
||||
isComposing.current = true
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
isComposing.current = false
|
||||
}}
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => setFocus(false)}
|
||||
|
||||
@ -126,7 +126,7 @@ const Select: FC<ISelectProps> = ({
|
||||
</Combobox.Button>
|
||||
</div>
|
||||
|
||||
{filteredItems.length > 0 && (
|
||||
{(filteredItems.length > 0 && open) && (
|
||||
<Combobox.Options className={`absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm ${overlayClassName}`}>
|
||||
{filteredItems.map((item: Item) => (
|
||||
<Combobox.Option
|
||||
|
||||
@ -74,7 +74,7 @@ const UpgradeBtn: FC<Props> = ({
|
||||
onClick={onClick}
|
||||
>
|
||||
<GoldCoin className='mr-1 w-3.5 h-3.5' />
|
||||
<div className='text-xs font-normal'>{t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`)}</div>
|
||||
<div className='text-xs font-normal text-nowrap'>{t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`)}</div>
|
||||
<Sparkles
|
||||
className='absolute -right-1 -top-2 w-4 h-5 bg-cover'
|
||||
/>
|
||||
|
||||
@ -39,6 +39,7 @@ export const Heading = function H2({
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<span id={name?.replace(/^#/, '')} className='relative -top-28' />
|
||||
<div className="flex items-center gap-x-3" >
|
||||
<span className={`font-mono text-[0.625rem] font-semibold leading-6 rounded-lg px-1.5 ring-1 ring-inset ${style}`}>{method}</span>
|
||||
{/* <span className="h-0.5 w-0.5 rounded-full bg-zinc-300 dark:bg-zinc-600"></span> */}
|
||||
|
||||
@ -656,6 +656,11 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<Property name='pinned' type='bool' key='pinned'>
|
||||
Return only pinned conversations as `true`, only non-pinned as `false`
|
||||
</Property>
|
||||
<Property name='sort_by' type='string' key='sort_by'>
|
||||
Sorting Field (Optional), Default: -updated_at (sorted in descending order by update time)
|
||||
- Available Values: created_at, -created_at, updated_at, -updated_at
|
||||
- The symbol before the field represents the order or reverse, "-" represents reverse order.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
@ -691,6 +691,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='pinned' type='bool' key='pinned'>
|
||||
只返回置顶 true,只返回非置顶 false
|
||||
</Property>
|
||||
<Property name='sort_by' type='string' key='sort_by'>
|
||||
排序字段(选题),默认 -updated_at(按更新时间倒序排列)
|
||||
- 可选值:created_at, -created_at, updated_at, -updated_at
|
||||
- 字段前面的符号代表顺序或倒序,-代表倒序
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
@ -690,6 +690,11 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<Property name='pinned' type='bool' key='pinned'>
|
||||
Return only pinned conversations as `true`, only non-pinned as `false`
|
||||
</Property>
|
||||
<Property name='sort_by' type='string' key='sort_by'>
|
||||
Sorting Field (Optional), Default: -updated_at (sorted in descending order by update time)
|
||||
- Available Values: created_at, -created_at, updated_at, -updated_at
|
||||
- The symbol before the field represents the order or reverse, "-" represents reverse order.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
@ -705,6 +705,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='pinned' type='bool' key='pinned'>
|
||||
只返回置顶 true,只返回非置顶 false
|
||||
</Property>
|
||||
<Property name='sort_by' type='string' key='sort_by'>
|
||||
排序字段(选题),默认 -updated_at(按更新时间倒序排列)
|
||||
- 可选值:created_at, -created_at, updated_at, -updated_at
|
||||
- 字段前面的符号代表顺序或倒序,-代表倒序
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
@ -28,7 +28,7 @@ const Category: FC<ICategoryProps> = ({
|
||||
allCategoriesEn,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const isAllCategories = !list.includes(value as AppCategory)
|
||||
const isAllCategories = !list.includes(value as AppCategory) || value === allCategoriesEn
|
||||
|
||||
const itemClassName = (isSelected: boolean) => cn(
|
||||
'flex items-center px-3 py-[7px] h-[32px] rounded-lg border-[0.5px] border-transparent text-gray-700 font-medium leading-[18px] cursor-pointer hover:bg-gray-200',
|
||||
@ -44,7 +44,7 @@ const Category: FC<ICategoryProps> = ({
|
||||
<ThumbsUp className='mr-1 w-3.5 h-3.5' />
|
||||
{t('explore.apps.allCategories')}
|
||||
</div>
|
||||
{list.map(name => (
|
||||
{list.filter(name => name !== allCategoriesEn).map(name => (
|
||||
<div
|
||||
key={name}
|
||||
className={itemClassName(name === value)}
|
||||
|
||||
@ -46,8 +46,9 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
params: {},
|
||||
})
|
||||
|
||||
if (localStorage?.getItem('console_token'))
|
||||
localStorage.removeItem('console_token')
|
||||
localStorage.removeItem('setup_status')
|
||||
localStorage.removeItem('console_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
|
||||
router.push('/signin')
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import type { InstallState } from '@/app/components/plugins/types'
|
||||
@ -35,7 +35,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
: '',
|
||||
selectedVersion: '',
|
||||
selectedPackage: '',
|
||||
releases: [],
|
||||
releases: updatePayload ? updatePayload.originalPackageInfo.releases : [],
|
||||
})
|
||||
const [uniqueIdentifier, setUniqueIdentifier] = useState<string | null>(null)
|
||||
const [manifest, setManifest] = useState<PluginDeclaration | null>(null)
|
||||
@ -133,11 +133,6 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (state.step === InstallStepFromGitHub.selectPackage)
|
||||
handleUrlSubmit()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
|
||||
@ -74,6 +74,7 @@ const Action: FC<Props> = ({
|
||||
repo: meta!.repo,
|
||||
version: meta!.version,
|
||||
package: meta!.package,
|
||||
releases: fetchedReleases,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -155,6 +155,7 @@ export type UpdateFromGitHubPayload = {
|
||||
repo: string
|
||||
version: string
|
||||
package: string
|
||||
releases: GitHubRepoReleaseResponse[]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import { SWRConfig } from 'swr'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import useRefreshToken from '@/hooks/use-refresh-token'
|
||||
import { fetchSetupStatus } from '@/service/common'
|
||||
|
||||
interface SwrInitorProps {
|
||||
@ -15,12 +14,11 @@ const SwrInitor = ({
|
||||
}: SwrInitorProps) => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const pathname = usePathname()
|
||||
const { getNewAccessToken } = useRefreshToken()
|
||||
const consoleToken = searchParams.get('access_token')
|
||||
const refreshToken = searchParams.get('refresh_token')
|
||||
const consoleToken = decodeURIComponent(searchParams.get('access_token') || '')
|
||||
const refreshToken = decodeURIComponent(searchParams.get('refresh_token') || '')
|
||||
const consoleTokenFromLocalStorage = localStorage?.getItem('console_token')
|
||||
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
||||
const pathname = usePathname()
|
||||
const [init, setInit] = useState(false)
|
||||
|
||||
const isSetupFinished = useCallback(async () => {
|
||||
@ -41,25 +39,6 @@ const SwrInitor = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setRefreshToken = useCallback(async () => {
|
||||
try {
|
||||
if (!(consoleToken || refreshToken || consoleTokenFromLocalStorage || refreshTokenFromLocalStorage))
|
||||
return Promise.reject(new Error('No token found'))
|
||||
|
||||
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
||||
await getNewAccessToken()
|
||||
|
||||
if (consoleToken && refreshToken) {
|
||||
localStorage.setItem('console_token', consoleToken)
|
||||
localStorage.setItem('refresh_token', refreshToken)
|
||||
await getNewAccessToken()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}, [consoleToken, refreshToken, consoleTokenFromLocalStorage, refreshTokenFromLocalStorage, getNewAccessToken])
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
@ -68,9 +47,15 @@ const SwrInitor = ({
|
||||
router.replace('/install')
|
||||
return
|
||||
}
|
||||
await setRefreshToken()
|
||||
if (searchParams.has('access_token') || searchParams.has('refresh_token'))
|
||||
if (!((consoleToken && refreshToken) || (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage))) {
|
||||
router.replace('/signin')
|
||||
return
|
||||
}
|
||||
if (searchParams.has('access_token') || searchParams.has('refresh_token')) {
|
||||
consoleToken && localStorage.setItem('console_token', consoleToken)
|
||||
refreshToken && localStorage.setItem('refresh_token', refreshToken)
|
||||
router.replace(pathname)
|
||||
}
|
||||
|
||||
setInit(true)
|
||||
}
|
||||
@ -78,7 +63,7 @@ const SwrInitor = ({
|
||||
router.replace('/signin')
|
||||
}
|
||||
})()
|
||||
}, [isSetupFinished, setRefreshToken, router, pathname, searchParams])
|
||||
}, [isSetupFinished, router, pathname, searchParams, consoleToken, refreshToken, consoleTokenFromLocalStorage, refreshTokenFromLocalStorage])
|
||||
|
||||
return init
|
||||
? (
|
||||
|
||||
@ -340,7 +340,9 @@ export const NODES_INITIAL_DATA = {
|
||||
...ListFilterDefault.defaultValue,
|
||||
},
|
||||
}
|
||||
|
||||
export const MAX_ITERATION_PARALLEL_NUM = 10
|
||||
export const MIN_ITERATION_PARALLEL_NUM = 1
|
||||
export const DEFAULT_ITER_TIMES = 1
|
||||
export const NODE_WIDTH = 240
|
||||
export const X_OFFSET = 60
|
||||
export const NODE_WIDTH_X_OFFSET = NODE_WIDTH + X_OFFSET
|
||||
|
||||
@ -644,6 +644,11 @@ export const useNodesInteractions = () => {
|
||||
newNode.data.isInIteration = true
|
||||
newNode.data.iteration_id = prevNode.parentId
|
||||
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
|
||||
if (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner) {
|
||||
const parentIterNodeIndex = nodes.findIndex(node => node.id === prevNode.parentId)
|
||||
const iterNodeData: IterationNodeType = nodes[parentIterNodeIndex].data
|
||||
iterNodeData._isShowTips = true
|
||||
}
|
||||
}
|
||||
|
||||
const newEdge: Edge = {
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
NodeRunningStatus,
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import { DEFAULT_ITER_TIMES } from '../constants'
|
||||
import { useWorkflowUpdate } from './use-workflow-interactions'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { IOtherOptions } from '@/service/base'
|
||||
@ -170,11 +171,13 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setIterParallelLogMap,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
setIterParallelLogMap(new Map())
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.task_id = task_id
|
||||
draft.result = {
|
||||
@ -244,6 +247,8 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
iterParallelLogMap,
|
||||
setIterParallelLogMap,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
@ -259,10 +264,21 @@ export const useWorkflowRun = () => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing.find(trace => trace.node_id === node?.parentId)
|
||||
const currIteration = iterations?.details![node.data.iteration_index] || iterations?.details![iterations.details!.length - 1]
|
||||
currIteration?.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
if (!data.parallel_run_id) {
|
||||
currIteration?.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
}
|
||||
else {
|
||||
if (!iterParallelLogMap.has(data.parallel_run_id))
|
||||
iterParallelLogMap.set(data.parallel_run_id, [{ ...data, status: NodeRunningStatus.Running } as any])
|
||||
else
|
||||
iterParallelLogMap.get(data.parallel_run_id)!.push({ ...data, status: NodeRunningStatus.Running } as any)
|
||||
setIterParallelLogMap(iterParallelLogMap)
|
||||
if (iterations)
|
||||
iterations.details = Array.from(iterParallelLogMap.values())
|
||||
}
|
||||
}))
|
||||
}
|
||||
else {
|
||||
@ -309,6 +325,8 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
iterParallelLogMap,
|
||||
setIterParallelLogMap,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
@ -317,21 +335,21 @@ export const useWorkflowRun = () => {
|
||||
const nodes = getNodes()
|
||||
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId
|
||||
if (nodeParentId) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node
|
||||
if (!data.execution_metadata.parallel_mode_run_id) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node
|
||||
|
||||
if (iterations && iterations.details) {
|
||||
const iterationIndex = data.execution_metadata?.iteration_index || 0
|
||||
if (!iterations.details[iterationIndex])
|
||||
iterations.details[iterationIndex] = []
|
||||
if (iterations && iterations.details) {
|
||||
const iterationIndex = data.execution_metadata?.iteration_index || 0
|
||||
if (!iterations.details[iterationIndex])
|
||||
iterations.details[iterationIndex] = []
|
||||
|
||||
const currIteration = iterations.details[iterationIndex]
|
||||
const nodeIndex = currIteration.findIndex(node =>
|
||||
node.node_id === data.node_id && (
|
||||
node.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || node.parallel_id === data.execution_metadata?.parallel_id),
|
||||
)
|
||||
if (data.status === NodeRunningStatus.Succeeded) {
|
||||
const currIteration = iterations.details[iterationIndex]
|
||||
const nodeIndex = currIteration.findIndex(node =>
|
||||
node.node_id === data.node_id && (
|
||||
node.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || node.parallel_id === data.execution_metadata?.parallel_id),
|
||||
)
|
||||
if (nodeIndex !== -1) {
|
||||
currIteration[nodeIndex] = {
|
||||
...currIteration[nodeIndex],
|
||||
@ -344,8 +362,40 @@ export const useWorkflowRun = () => {
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}))
|
||||
}
|
||||
else {
|
||||
// open parallel mode
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node
|
||||
|
||||
if (iterations && iterations.details) {
|
||||
const iterRunID = data.execution_metadata?.parallel_mode_run_id
|
||||
|
||||
const currIteration = iterParallelLogMap.get(iterRunID)
|
||||
const nodeIndex = currIteration?.findIndex(node =>
|
||||
node.node_id === data.node_id && (
|
||||
node?.parallel_run_id === data.execution_metadata?.parallel_mode_run_id),
|
||||
)
|
||||
if (currIteration) {
|
||||
if (nodeIndex !== undefined && nodeIndex !== -1) {
|
||||
currIteration[nodeIndex] = {
|
||||
...currIteration[nodeIndex],
|
||||
...data,
|
||||
} as any
|
||||
}
|
||||
else {
|
||||
currIteration.push({
|
||||
...data,
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
setIterParallelLogMap(iterParallelLogMap)
|
||||
iterations.details = Array.from(iterParallelLogMap.values())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
@ -379,6 +429,7 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setIterTimes,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
@ -388,6 +439,7 @@ export const useWorkflowRun = () => {
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setIterTimes(DEFAULT_ITER_TIMES)
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
@ -431,6 +483,8 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
iterTimes,
|
||||
setIterTimes,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const { data } = params
|
||||
@ -445,13 +499,14 @@ export const useWorkflowRun = () => {
|
||||
if (iteration.details!.length >= iteration.metadata.iterator_length!)
|
||||
return
|
||||
}
|
||||
iteration?.details!.push([])
|
||||
if (!data.parallel_mode_run_id)
|
||||
iteration?.details!.push([])
|
||||
}))
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._iterationIndex = data.index > 0 ? data.index : 1
|
||||
currentNode.data._iterationIndex = iterTimes
|
||||
setIterTimes(iterTimes + 1)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
@ -464,6 +519,7 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setIterTimes,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
@ -480,7 +536,7 @@ export const useWorkflowRun = () => {
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
setIterTimes(DEFAULT_ITER_TIMES)
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ interface Props {
|
||||
isFocus: boolean
|
||||
isInNode?: boolean
|
||||
onGenerated?: (prompt: string) => void
|
||||
codeLanguages: CodeLanguage
|
||||
codeLanguages?: CodeLanguage
|
||||
fileList?: FileEntity[]
|
||||
showFileList?: boolean
|
||||
showCodeGenerator?: boolean
|
||||
@ -78,7 +78,7 @@ const Base: FC<Props> = ({
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
{headerRight}
|
||||
{showCodeGenerator && (
|
||||
{showCodeGenerator && codeLanguages && (
|
||||
<div className='ml-1'>
|
||||
<CodeGeneratorButton onGenerated={onGenerated} codeLanguages={codeLanguages}/>
|
||||
</div>
|
||||
|
||||
@ -31,6 +31,7 @@ export interface Props {
|
||||
noWrapper?: boolean
|
||||
isExpand?: boolean
|
||||
showFileList?: boolean
|
||||
onGenerated?: (value: string) => void
|
||||
showCodeGenerator?: boolean
|
||||
}
|
||||
|
||||
@ -64,6 +65,7 @@ const CodeEditor: FC<Props> = ({
|
||||
noWrapper,
|
||||
isExpand,
|
||||
showFileList,
|
||||
onGenerated,
|
||||
showCodeGenerator = false,
|
||||
}) => {
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
@ -151,9 +153,6 @@ const CodeEditor: FC<Props> = ({
|
||||
|
||||
return isFocus ? 'focus-theme' : 'blur-theme'
|
||||
})()
|
||||
const handleGenerated = (code: string) => {
|
||||
handleEditorChange(code)
|
||||
}
|
||||
|
||||
const main = (
|
||||
<>
|
||||
@ -205,7 +204,7 @@ const CodeEditor: FC<Props> = ({
|
||||
isFocus={isFocus && !readOnly}
|
||||
minHeight={minHeight}
|
||||
isInNode={isInNode}
|
||||
onGenerated={handleGenerated}
|
||||
onGenerated={onGenerated}
|
||||
codeLanguages={language}
|
||||
fileList={fileList}
|
||||
showFileList={showFileList}
|
||||
|
||||
@ -12,15 +12,15 @@ import Tooltip from '@/app/components/base/tooltip'
|
||||
interface Props {
|
||||
className?: string
|
||||
title: JSX.Element | string | DefaultTFuncReturn
|
||||
tooltip?: React.ReactNode
|
||||
isSubTitle?: boolean
|
||||
tooltip?: string
|
||||
supportFold?: boolean
|
||||
children?: JSX.Element | string | null
|
||||
operations?: JSX.Element
|
||||
inline?: boolean
|
||||
}
|
||||
|
||||
const Filed: FC<Props> = ({
|
||||
const Field: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
isSubTitle,
|
||||
@ -60,4 +60,4 @@ const Filed: FC<Props> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Filed)
|
||||
export default React.memo(Field)
|
||||
|
||||
@ -39,7 +39,13 @@ const FileUploadSetting: FC<Props> = ({
|
||||
allowed_file_extensions,
|
||||
} = payload
|
||||
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
||||
const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileUploadConfigResponse)
|
||||
const {
|
||||
imgSizeLimit,
|
||||
docSizeLimit,
|
||||
audioSizeLimit,
|
||||
videoSizeLimit,
|
||||
maxFileUploadLimit,
|
||||
} = useFileSizeLimit(fileUploadConfigResponse)
|
||||
|
||||
const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
|
||||
const newPayload = produce(payload, (draft) => {
|
||||
@ -156,7 +162,7 @@ const FileUploadSetting: FC<Props> = ({
|
||||
<InputNumberWithSlider
|
||||
value={max_length}
|
||||
min={1}
|
||||
max={10}
|
||||
max={maxFileUploadLimit}
|
||||
onChange={handleMaxUploadNumLimitChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -106,32 +106,29 @@ const useOneStepRun = <T>({
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
const targetVar = isSystem ? allOutputVars.find(item => !!item.isStartNode) : allOutputVars.find(v => v.nodeId === valueSelector[0])
|
||||
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
|
||||
if (isSystem)
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
if (!curr)
|
||||
return
|
||||
for (let i = 1; i < valueSelector.length; i++) {
|
||||
const key = valueSelector[i]
|
||||
const isLast = i === valueSelector.length - 1
|
||||
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
// conversation variable is start with 'conversation.'
|
||||
curr = curr?.find((v: any) => v.variable.replace('conversation.', '') === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
else {
|
||||
if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
if (Array.isArray(curr))
|
||||
curr = curr.find((v: any) => v.variable.replace('conversation.', '') === key)
|
||||
|
||||
return res
|
||||
if (isLast)
|
||||
return curr
|
||||
else if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
const checkValid = checkValidFns[data.type]
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import type { IterationNodeType } from '../iteration/types'
|
||||
import {
|
||||
NodeSourceHandle,
|
||||
NodeTargetHandle,
|
||||
@ -34,6 +35,7 @@ import NodeControl from './components/node-control'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type BaseNodeProps = {
|
||||
children: ReactElement
|
||||
@ -166,9 +168,27 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
/>
|
||||
<div
|
||||
title={data.title}
|
||||
className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate'
|
||||
className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate flex items-center'
|
||||
>
|
||||
{data.title}
|
||||
<div>
|
||||
{data.title}
|
||||
</div>
|
||||
{
|
||||
data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
|
||||
<Tooltip popupContent={
|
||||
<div className='w-[180px]'>
|
||||
<div className='font-extrabold'>
|
||||
{t('workflow.nodes.iteration.parallelModeEnableTitle')}
|
||||
</div>
|
||||
{t('workflow.nodes.iteration.parallelModeEnableDesc')}
|
||||
</div>}
|
||||
>
|
||||
<div className='flex justify-center items-center px-[5px] py-[3px] ml-1 border-[1px] border-text-warning rounded-[5px] text-text-warning system-2xs-medium-uppercase '>
|
||||
{t('workflow.nodes.iteration.parallelModeUpper')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
||||
|
||||
326
web/app/components/workflow/nodes/code/code-parser.spec.ts
Normal file
326
web/app/components/workflow/nodes/code/code-parser.spec.ts
Normal file
@ -0,0 +1,326 @@
|
||||
import { VarType } from '../../types'
|
||||
import { extractFunctionParams, extractReturnType } from './code-parser'
|
||||
import { CodeLanguage } from './types'
|
||||
|
||||
const SAMPLE_CODES = {
|
||||
python3: {
|
||||
noParams: 'def main():',
|
||||
singleParam: 'def main(param1):',
|
||||
multipleParams: `def main(param1, param2, param3):
|
||||
return {"result": param1}`,
|
||||
withTypes: `def main(param1: str, param2: int, param3: List[str]):
|
||||
result = process_data(param1, param2)
|
||||
return {"output": result}`,
|
||||
withDefaults: `def main(param1: str = "default", param2: int = 0):
|
||||
return {"data": param1}`,
|
||||
},
|
||||
javascript: {
|
||||
noParams: 'function main() {',
|
||||
singleParam: 'function main(param1) {',
|
||||
multipleParams: `function main(param1, param2, param3) {
|
||||
return { result: param1 }
|
||||
}`,
|
||||
withComments: `// Main function
|
||||
function main(param1, param2) {
|
||||
// Process data
|
||||
return { output: process(param1, param2) }
|
||||
}`,
|
||||
withSpaces: 'function main( param1 , param2 ) {',
|
||||
},
|
||||
}
|
||||
|
||||
describe('extractFunctionParams', () => {
|
||||
describe('Python3', () => {
|
||||
test('handles no parameters', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.python3.noParams, CodeLanguage.python3)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
test('extracts single parameter', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.python3.singleParam, CodeLanguage.python3)
|
||||
expect(result).toEqual(['param1'])
|
||||
})
|
||||
|
||||
test('extracts multiple parameters', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.python3.multipleParams, CodeLanguage.python3)
|
||||
expect(result).toEqual(['param1', 'param2', 'param3'])
|
||||
})
|
||||
|
||||
test('handles type hints', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.python3.withTypes, CodeLanguage.python3)
|
||||
expect(result).toEqual(['param1', 'param2', 'param3'])
|
||||
})
|
||||
|
||||
test('handles default values', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.python3.withDefaults, CodeLanguage.python3)
|
||||
expect(result).toEqual(['param1', 'param2'])
|
||||
})
|
||||
})
|
||||
|
||||
// JavaScriptのテストケース
|
||||
describe('JavaScript', () => {
|
||||
test('handles no parameters', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.javascript.noParams, CodeLanguage.javascript)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
test('extracts single parameter', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.javascript.singleParam, CodeLanguage.javascript)
|
||||
expect(result).toEqual(['param1'])
|
||||
})
|
||||
|
||||
test('extracts multiple parameters', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.javascript.multipleParams, CodeLanguage.javascript)
|
||||
expect(result).toEqual(['param1', 'param2', 'param3'])
|
||||
})
|
||||
|
||||
test('handles comments in code', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.javascript.withComments, CodeLanguage.javascript)
|
||||
expect(result).toEqual(['param1', 'param2'])
|
||||
})
|
||||
|
||||
test('handles whitespace', () => {
|
||||
const result = extractFunctionParams(SAMPLE_CODES.javascript.withSpaces, CodeLanguage.javascript)
|
||||
expect(result).toEqual(['param1', 'param2'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const RETURN_TYPE_SAMPLES = {
|
||||
python3: {
|
||||
singleReturn: `
|
||||
def main(param1):
|
||||
return {"result": "value"}`,
|
||||
|
||||
multipleReturns: `
|
||||
def main(param1, param2):
|
||||
return {"result": "value", "status": "success"}`,
|
||||
|
||||
noReturn: `
|
||||
def main():
|
||||
print("Hello")`,
|
||||
|
||||
complexReturn: `
|
||||
def main():
|
||||
data = process()
|
||||
return {"result": data, "count": 42, "messages": ["hello"]}`,
|
||||
nestedObject: `
|
||||
def main(name, age, city):
|
||||
return {
|
||||
'personal_info': {
|
||||
'name': name,
|
||||
'age': age,
|
||||
'city': city
|
||||
},
|
||||
'timestamp': int(time.time()),
|
||||
'status': 'active'
|
||||
}`,
|
||||
},
|
||||
|
||||
javascript: {
|
||||
singleReturn: `
|
||||
function main(param1) {
|
||||
return { result: "value" }
|
||||
}`,
|
||||
|
||||
multipleReturns: `
|
||||
function main(param1) {
|
||||
return { result: "value", status: "success" }
|
||||
}`,
|
||||
|
||||
withParentheses: `
|
||||
function main() {
|
||||
return ({ result: "value", status: "success" })
|
||||
}`,
|
||||
|
||||
noReturn: `
|
||||
function main() {
|
||||
console.log("Hello")
|
||||
}`,
|
||||
|
||||
withQuotes: `
|
||||
function main() {
|
||||
return { "result": 'value', 'status': "success" }
|
||||
}`,
|
||||
nestedObject: `
|
||||
function main(name, age, city) {
|
||||
return {
|
||||
personal_info: {
|
||||
name: name,
|
||||
age: age,
|
||||
city: city
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
status: 'active'
|
||||
}
|
||||
}`,
|
||||
withJSDoc: `
|
||||
/**
|
||||
* Creates a user profile with personal information and metadata
|
||||
* @param {string} name - The user's name
|
||||
* @param {number} age - The user's age
|
||||
* @param {string} city - The user's city of residence
|
||||
* @returns {Object} An object containing the user profile
|
||||
*/
|
||||
function main(name, age, city) {
|
||||
return {
|
||||
result: {
|
||||
personal_info: {
|
||||
name: name,
|
||||
age: age,
|
||||
city: city
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
status: 'active'
|
||||
}
|
||||
};
|
||||
}`,
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
describe('extractReturnType', () => {
|
||||
// Python3のテスト
|
||||
describe('Python3', () => {
|
||||
test('extracts single return value', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.singleReturn, CodeLanguage.python3)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('extracts multiple return values', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.multipleReturns, CodeLanguage.python3)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('returns empty object when no return statement', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.noReturn, CodeLanguage.python3)
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
test('handles complex return statement', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.complexReturn, CodeLanguage.python3)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
count: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
messages: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
test('handles nested object structure', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.nestedObject, CodeLanguage.python3)
|
||||
expect(result).toEqual({
|
||||
personal_info: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
timestamp: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// JavaScriptのテスト
|
||||
describe('JavaScript', () => {
|
||||
test('extracts single return value', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.singleReturn, CodeLanguage.javascript)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('extracts multiple return values', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.multipleReturns, CodeLanguage.javascript)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('handles return with parentheses', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withParentheses, CodeLanguage.javascript)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('returns empty object when no return statement', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.noReturn, CodeLanguage.javascript)
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
test('handles quoted keys', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withQuotes, CodeLanguage.javascript)
|
||||
expect(result).toEqual({
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
test('handles nested object structure', () => {
|
||||
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.nestedObject, CodeLanguage.javascript)
|
||||
expect(result).toEqual({
|
||||
personal_info: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
timestamp: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
status: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
86
web/app/components/workflow/nodes/code/code-parser.ts
Normal file
86
web/app/components/workflow/nodes/code/code-parser.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { VarType } from '../../types'
|
||||
import type { OutputVar } from './types'
|
||||
import { CodeLanguage } from './types'
|
||||
|
||||
export const extractFunctionParams = (code: string, language: CodeLanguage) => {
|
||||
if (language === CodeLanguage.json)
|
||||
return []
|
||||
|
||||
const patterns: Record<Exclude<CodeLanguage, CodeLanguage.json>, RegExp> = {
|
||||
[CodeLanguage.python3]: /def\s+main\s*\((.*?)\)/,
|
||||
[CodeLanguage.javascript]: /function\s+main\s*\((.*?)\)/,
|
||||
}
|
||||
const match = code.match(patterns[language])
|
||||
const params: string[] = []
|
||||
|
||||
if (match?.[1]) {
|
||||
params.push(...match[1].split(',')
|
||||
.map(p => p.trim())
|
||||
.filter(Boolean)
|
||||
.map(p => p.split(':')[0].trim()),
|
||||
)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
export const extractReturnType = (code: string, language: CodeLanguage): OutputVar => {
|
||||
const codeWithoutComments = code.replace(/\/\*\*[\s\S]*?\*\//, '')
|
||||
console.log(codeWithoutComments)
|
||||
|
||||
const returnIndex = codeWithoutComments.indexOf('return')
|
||||
if (returnIndex === -1)
|
||||
return {}
|
||||
|
||||
// returnから始まる部分文字列を取得
|
||||
const codeAfterReturn = codeWithoutComments.slice(returnIndex)
|
||||
|
||||
let bracketCount = 0
|
||||
let startIndex = codeAfterReturn.indexOf('{')
|
||||
|
||||
if (language === CodeLanguage.javascript && startIndex === -1) {
|
||||
const parenStart = codeAfterReturn.indexOf('(')
|
||||
if (parenStart !== -1)
|
||||
startIndex = codeAfterReturn.indexOf('{', parenStart)
|
||||
}
|
||||
|
||||
if (startIndex === -1)
|
||||
return {}
|
||||
|
||||
let endIndex = -1
|
||||
|
||||
for (let i = startIndex; i < codeAfterReturn.length; i++) {
|
||||
if (codeAfterReturn[i] === '{')
|
||||
bracketCount++
|
||||
if (codeAfterReturn[i] === '}') {
|
||||
bracketCount--
|
||||
if (bracketCount === 0) {
|
||||
endIndex = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex === -1)
|
||||
return {}
|
||||
|
||||
const returnContent = codeAfterReturn.slice(startIndex + 1, endIndex - 1)
|
||||
console.log(returnContent)
|
||||
|
||||
const result: OutputVar = {}
|
||||
|
||||
const keyRegex = /['"]?(\w+)['"]?\s*:(?![^{]*})/g
|
||||
const matches = returnContent.matchAll(keyRegex)
|
||||
|
||||
for (const match of matches) {
|
||||
console.log(`Found key: "${match[1]}" from match: "${match[0]}"`)
|
||||
const key = match[1]
|
||||
result[key] = {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
}
|
||||
}
|
||||
|
||||
console.log(result)
|
||||
|
||||
return result
|
||||
}
|
||||
@ -5,6 +5,7 @@ import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confir
|
||||
import useConfig from './use-config'
|
||||
import type { CodeNodeType } from './types'
|
||||
import { CodeLanguage } from './types'
|
||||
import { extractFunctionParams, extractReturnType } from './code-parser'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
@ -12,10 +13,9 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.code'
|
||||
|
||||
const codeLanguages = [
|
||||
@ -38,6 +38,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
readOnly,
|
||||
inputs,
|
||||
outputKeyOrders,
|
||||
handleCodeAndVarsChange,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
handleRemoveVariable,
|
||||
@ -61,6 +62,18 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
setInputVarValues,
|
||||
} = useConfig(id, data)
|
||||
|
||||
const handleGeneratedCode = (value: string) => {
|
||||
const params = extractFunctionParams(value, inputs.code_language)
|
||||
const codeNewInput = params.map((p) => {
|
||||
return {
|
||||
variable: p,
|
||||
value_selector: [],
|
||||
}
|
||||
})
|
||||
const returnTypes = extractReturnType(value, inputs.code_language)
|
||||
handleCodeAndVarsChange(value, codeNewInput, returnTypes)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
@ -92,6 +105,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
language={inputs.code_language}
|
||||
value={inputs.code}
|
||||
onChange={handleCodeChange}
|
||||
onGenerated={handleGeneratedCode}
|
||||
showCodeGenerator={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -3,7 +3,7 @@ import produce from 'immer'
|
||||
import useVarList from '../_base/hooks/use-var-list'
|
||||
import useOutputVarList from '../_base/hooks/use-output-var-list'
|
||||
import { BlockEnum, VarType } from '../../types'
|
||||
import type { Var } from '../../types'
|
||||
import type { Var, Variable } from '../../types'
|
||||
import { useStore } from '../../store'
|
||||
import type { CodeNodeType, OutputVar } from './types'
|
||||
import { CodeLanguage } from './types'
|
||||
@ -136,7 +136,15 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
|
||||
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.code = code
|
||||
draft.variables = inputVariables
|
||||
draft.outputs = outputVariables
|
||||
})
|
||||
setInputs(newInputs)
|
||||
syncOutputKeyOrders(outputVariables)
|
||||
}, [inputs, setInputs, syncOutputKeyOrders])
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
@ -163,6 +171,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
runResult,
|
||||
handleCodeAndVarsChange,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,24 +78,24 @@ const useConfig = (id: string, payload: IfElseNodeType) => {
|
||||
})
|
||||
|
||||
const handleAddCase = useCallback(() => {
|
||||
const newInputs = produce(inputs, () => {
|
||||
if (inputs.cases) {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
if (draft.cases) {
|
||||
const case_id = uuid4()
|
||||
inputs.cases.push({
|
||||
draft.cases.push({
|
||||
case_id,
|
||||
logical_operator: LogicalOperator.and,
|
||||
conditions: [],
|
||||
})
|
||||
if (inputs._targetBranches) {
|
||||
const elseCaseIndex = inputs._targetBranches.findIndex(branch => branch.id === 'false')
|
||||
if (draft._targetBranches) {
|
||||
const elseCaseIndex = draft._targetBranches.findIndex(branch => branch.id === 'false')
|
||||
if (elseCaseIndex > -1) {
|
||||
inputs._targetBranches = branchNameCorrect([
|
||||
...inputs._targetBranches.slice(0, elseCaseIndex),
|
||||
draft._targetBranches = branchNameCorrect([
|
||||
...draft._targetBranches.slice(0, elseCaseIndex),
|
||||
{
|
||||
id: case_id,
|
||||
name: '',
|
||||
},
|
||||
...inputs._targetBranches.slice(elseCaseIndex),
|
||||
...draft._targetBranches.slice(elseCaseIndex),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import { BlockEnum, ErrorHandleMode } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import type { IterationNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
import {
|
||||
ALL_CHAT_AVAILABLE_BLOCKS,
|
||||
ALL_COMPLETION_AVAILABLE_BLOCKS,
|
||||
} from '@/app/components/workflow/constants'
|
||||
const i18nPrefix = 'workflow'
|
||||
|
||||
const nodeDefault: NodeDefault<IterationNodeType> = {
|
||||
@ -10,25 +13,45 @@ const nodeDefault: NodeDefault<IterationNodeType> = {
|
||||
iterator_selector: [],
|
||||
output_selector: [],
|
||||
_children: [],
|
||||
_isShowTips: false,
|
||||
is_parallel: false,
|
||||
parallel_nums: 10,
|
||||
error_handle_mode: ErrorHandleMode.Terminated,
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(
|
||||
type => type !== BlockEnum.End,
|
||||
)
|
||||
return nodes
|
||||
},
|
||||
getAvailableNextNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
return nodes
|
||||
},
|
||||
checkValid(payload: IterationNodeType, t: any) {
|
||||
let errorMessages = ''
|
||||
|
||||
if (!errorMessages && (!payload.iterator_selector || payload.iterator_selector.length === 0))
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.input`) })
|
||||
if (
|
||||
!errorMessages
|
||||
&& (!payload.iterator_selector || payload.iterator_selector.length === 0)
|
||||
) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, {
|
||||
field: t(`${i18nPrefix}.nodes.iteration.input`),
|
||||
})
|
||||
}
|
||||
|
||||
if (!errorMessages && (!payload.output_selector || payload.output_selector.length === 0))
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.output`) })
|
||||
if (
|
||||
!errorMessages
|
||||
&& (!payload.output_selector || payload.output_selector.length === 0)
|
||||
) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, {
|
||||
field: t(`${i18nPrefix}.nodes.iteration.output`),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
|
||||
@ -8,12 +8,16 @@ import {
|
||||
useNodesInitialized,
|
||||
useViewport,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IterationStartNodeDumb } from '../iteration-start'
|
||||
import { useNodeIterationInteractions } from './use-interactions'
|
||||
import type { IterationNodeType } from './types'
|
||||
import AddBlock from './add-block'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
const Node: FC<NodeProps<IterationNodeType>> = ({
|
||||
id,
|
||||
@ -22,11 +26,20 @@ const Node: FC<NodeProps<IterationNodeType>> = ({
|
||||
const { zoom } = useViewport()
|
||||
const nodesInitialized = useNodesInitialized()
|
||||
const { handleNodeIterationRerender } = useNodeIterationInteractions()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
if (nodesInitialized)
|
||||
handleNodeIterationRerender(id)
|
||||
}, [nodesInitialized, id, handleNodeIterationRerender])
|
||||
if (data.is_parallel && data._isShowTips) {
|
||||
Toast.notify({
|
||||
type: 'warning',
|
||||
message: t(`${i18nPrefix}.answerNodeWarningDesc`),
|
||||
duration: 5000,
|
||||
})
|
||||
data._isShowTips = false
|
||||
}
|
||||
}, [nodesInitialized, id, handleNodeIterationRerender, data, t])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
|
||||
@ -8,11 +8,17 @@ import VarReferencePicker from '../_base/components/variable/var-reference-picke
|
||||
import Split from '../_base/components/split'
|
||||
import ResultPanel from '../../run/result-panel'
|
||||
import IterationResultPanel from '../../run/iteration-result-panel'
|
||||
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
|
||||
import type { IterationNodeType } from './types'
|
||||
import useConfig from './use-config'
|
||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { ErrorHandleMode, InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Select from '@/app/components/base/select'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
@ -21,7 +27,20 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const responseMethod = [
|
||||
{
|
||||
value: ErrorHandleMode.Terminated,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.operationTerminated`),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleMode.ContinueOnError,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.continueOnError`),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleMode.RemoveAbnormalOutput,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.removeAbnormalOutput`),
|
||||
},
|
||||
]
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
@ -47,6 +66,9 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
setIterator,
|
||||
iteratorInputKey,
|
||||
iterationRunResult,
|
||||
changeParallel,
|
||||
changeErrorResponseMode,
|
||||
changeParallelNums,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
@ -87,6 +109,39 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div className='px-4 pb-2'>
|
||||
<Field title={t(`${i18nPrefix}.parallelMode`)} tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.parallelPanelDesc`)}</div>} inline>
|
||||
<Switch defaultValue={inputs.is_parallel} onChange={changeParallel} />
|
||||
</Field>
|
||||
</div>
|
||||
{
|
||||
inputs.is_parallel && (<div className='px-4 pb-2'>
|
||||
<Field title={t(`${i18nPrefix}.MaxParallelismTitle`)} isSubTitle tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.MaxParallelismDesc`)}</div>}>
|
||||
<div className='flex row'>
|
||||
<Input type='number' wrapperClassName='w-18 mr-4 ' max={MAX_ITERATION_PARALLEL_NUM} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} />
|
||||
<Slider
|
||||
value={inputs.parallel_nums}
|
||||
onChange={changeParallelNums}
|
||||
max={MAX_ITERATION_PARALLEL_NUM}
|
||||
min={MIN_ITERATION_PARALLEL_NUM}
|
||||
className=' flex-shrink-0 flex-1 mt-4'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</Field>
|
||||
</div>)
|
||||
}
|
||||
<div className='px-4 py-2'>
|
||||
<Divider className='h-[1px]'/>
|
||||
</div>
|
||||
|
||||
<div className='px-4 py-2'>
|
||||
<Field title={t(`${i18nPrefix}.errorResponseMethod`)} >
|
||||
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false}>
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
BlockEnum,
|
||||
CommonNodeType,
|
||||
ErrorHandleMode,
|
||||
ValueSelector,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
@ -12,4 +13,8 @@ export type IterationNodeType = CommonNodeType & {
|
||||
iterator_selector: ValueSelector
|
||||
output_selector: ValueSelector
|
||||
output_type: VarType // output type.
|
||||
is_parallel: boolean // open the parallel mode or not
|
||||
parallel_nums: number // the numbers of parallel
|
||||
error_handle_mode: ErrorHandleMode // how to handle error in the iteration
|
||||
_isShowTips: boolean // when answer node in parallel mode iteration show tips
|
||||
}
|
||||
|
||||
@ -8,12 +8,13 @@ import {
|
||||
useWorkflow,
|
||||
} from '../../hooks'
|
||||
import { VarType } from '../../types'
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar, toNodeOutputVars } from '../_base/components/variable/utils'
|
||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
||||
import type { IterationNodeType } from './types'
|
||||
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
|
||||
const DELIMITER = '@@@@@'
|
||||
const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
@ -184,6 +185,25 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
})
|
||||
}, [iteratorInputKey, runInputData, setRunInputData])
|
||||
|
||||
const changeParallel = useCallback((value: boolean) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.is_parallel = value
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const changeErrorResponseMode = useCallback((item: Item) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.error_handle_mode = item.value as ErrorHandleMode
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
const changeParallelNums = useCallback((num: number) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.parallel_nums = num
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
@ -210,6 +230,9 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
setIterator,
|
||||
iteratorInputKey,
|
||||
iterationRunResult,
|
||||
changeParallel,
|
||||
changeErrorResponseMode,
|
||||
changeParallelNums,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -240,7 +240,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
if (
|
||||
(allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
|
||||
|| mixtureInternalAndExternal
|
||||
|| (allExternal && newDatasets.length > 1)
|
||||
|| allExternal
|
||||
)
|
||||
setRerankModelOpen(true)
|
||||
}, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel])
|
||||
|
||||
@ -9,6 +9,8 @@ import { produce, setAutoFreeze } from 'immer'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useWorkflowRun } from '../../hooks'
|
||||
import { NodeRunningStatus, WorkflowRunningStatus } from '../../types'
|
||||
import { useWorkflowStore } from '../../store'
|
||||
import { DEFAULT_ITER_TIMES } from '../../constants'
|
||||
import type {
|
||||
ChatItem,
|
||||
Inputs,
|
||||
@ -43,6 +45,7 @@ export const useChat = (
|
||||
const { notify } = useToastContext()
|
||||
const { handleRun } = useWorkflowRun()
|
||||
const hasStopResponded = useRef(false)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const conversationId = useRef('')
|
||||
const taskIdRef = useRef('')
|
||||
const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
|
||||
@ -52,6 +55,9 @@ export const useChat = (
|
||||
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
|
||||
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
|
||||
|
||||
const {
|
||||
setIterTimes,
|
||||
} = workflowStore.getState()
|
||||
useEffect(() => {
|
||||
setAutoFreeze(false)
|
||||
return () => {
|
||||
@ -102,15 +108,16 @@ export const useChat = (
|
||||
handleResponding(false)
|
||||
if (stopChat && taskIdRef.current)
|
||||
stopChat(taskIdRef.current)
|
||||
|
||||
setIterTimes(DEFAULT_ITER_TIMES)
|
||||
if (suggestedQuestionsAbortControllerRef.current)
|
||||
suggestedQuestionsAbortControllerRef.current.abort()
|
||||
}, [handleResponding, stopChat])
|
||||
}, [handleResponding, setIterTimes, stopChat])
|
||||
|
||||
const handleRestart = useCallback(() => {
|
||||
conversationId.current = ''
|
||||
taskIdRef.current = ''
|
||||
handleStop()
|
||||
setIterTimes(DEFAULT_ITER_TIMES)
|
||||
const newChatList = config?.opening_statement
|
||||
? [{
|
||||
id: `${Date.now()}`,
|
||||
@ -126,6 +133,7 @@ export const useChat = (
|
||||
config,
|
||||
handleStop,
|
||||
handleUpdateChatList,
|
||||
setIterTimes,
|
||||
])
|
||||
|
||||
const updateCurrentQA = useCallback(({
|
||||
|
||||
@ -60,36 +60,67 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
}, [notify, getResultCallback])
|
||||
|
||||
const formatNodeList = useCallback((list: NodeTracing[]) => {
|
||||
const allItems = list.reverse()
|
||||
const allItems = [...list].reverse()
|
||||
const result: NodeTracing[] = []
|
||||
allItems.forEach((item) => {
|
||||
const { node_type, execution_metadata } = item
|
||||
if (node_type !== BlockEnum.Iteration) {
|
||||
const isInIteration = !!execution_metadata?.iteration_id
|
||||
const groupMap = new Map<string, NodeTracing[]>()
|
||||
|
||||
if (isInIteration) {
|
||||
const iterationNode = result.find(node => node.node_id === execution_metadata?.iteration_id)
|
||||
const iterationDetails = iterationNode?.details
|
||||
const currentIterationIndex = execution_metadata?.iteration_index ?? 0
|
||||
|
||||
if (Array.isArray(iterationDetails)) {
|
||||
if (iterationDetails.length === 0 || !iterationDetails[currentIterationIndex])
|
||||
iterationDetails[currentIterationIndex] = [item]
|
||||
else
|
||||
iterationDetails[currentIterationIndex].push(item)
|
||||
}
|
||||
return
|
||||
}
|
||||
// not in iteration
|
||||
result.push(item)
|
||||
|
||||
return
|
||||
}
|
||||
const processIterationNode = (item: NodeTracing) => {
|
||||
result.push({
|
||||
...item,
|
||||
details: [],
|
||||
})
|
||||
}
|
||||
const updateParallelModeGroup = (runId: string, item: NodeTracing, iterationNode: NodeTracing) => {
|
||||
if (!groupMap.has(runId))
|
||||
groupMap.set(runId, [item])
|
||||
else
|
||||
groupMap.get(runId)!.push(item)
|
||||
if (item.status === 'failed') {
|
||||
iterationNode.status = 'failed'
|
||||
iterationNode.error = item.error
|
||||
}
|
||||
|
||||
iterationNode.details = Array.from(groupMap.values())
|
||||
}
|
||||
const updateSequentialModeGroup = (index: number, item: NodeTracing, iterationNode: NodeTracing) => {
|
||||
const { details } = iterationNode
|
||||
if (details) {
|
||||
if (!details[index])
|
||||
details[index] = [item]
|
||||
else
|
||||
details[index].push(item)
|
||||
}
|
||||
|
||||
if (item.status === 'failed') {
|
||||
iterationNode.status = 'failed'
|
||||
iterationNode.error = item.error
|
||||
}
|
||||
}
|
||||
const processNonIterationNode = (item: NodeTracing) => {
|
||||
const { execution_metadata } = item
|
||||
if (!execution_metadata?.iteration_id) {
|
||||
result.push(item)
|
||||
return
|
||||
}
|
||||
|
||||
const iterationNode = result.find(node => node.node_id === execution_metadata.iteration_id)
|
||||
if (!iterationNode || !Array.isArray(iterationNode.details))
|
||||
return
|
||||
|
||||
const { parallel_mode_run_id, iteration_index = 0 } = execution_metadata
|
||||
|
||||
if (parallel_mode_run_id)
|
||||
updateParallelModeGroup(parallel_mode_run_id, item, iterationNode)
|
||||
else
|
||||
updateSequentialModeGroup(iteration_index, item, iterationNode)
|
||||
}
|
||||
|
||||
allItems.forEach((item) => {
|
||||
item.node_type === BlockEnum.Iteration
|
||||
? processIterationNode(item)
|
||||
: processNonIterationNode(item)
|
||||
})
|
||||
|
||||
return result
|
||||
}, [])
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiCloseLine,
|
||||
RiErrorWarningLine,
|
||||
} from '@remixicon/react'
|
||||
import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows'
|
||||
import TracingPanel from './tracing-panel'
|
||||
@ -27,7 +28,7 @@ const IterationResultPanel: FC<Props> = ({
|
||||
noWrap,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [expandedIterations, setExpandedIterations] = useState<Record<number, boolean>>([])
|
||||
const [expandedIterations, setExpandedIterations] = useState<Record<number, boolean>>({})
|
||||
|
||||
const toggleIteration = useCallback((index: number) => {
|
||||
setExpandedIterations(prev => ({
|
||||
@ -71,10 +72,19 @@ const IterationResultPanel: FC<Props> = ({
|
||||
<span className='system-sm-semibold-uppercase text-text-primary flex-grow'>
|
||||
{t(`${i18nPrefix}.iteration`)} {index + 1}
|
||||
</span>
|
||||
<RiArrowRightSLine className={cn(
|
||||
'w-4 h-4 text-text-tertiary transition-transform duration-200 flex-shrink-0',
|
||||
expandedIterations[index] && 'transform rotate-90',
|
||||
)} />
|
||||
{
|
||||
iteration.some(item => item.status === 'failed')
|
||||
? (
|
||||
<RiErrorWarningLine className='w-4 h-4 text-text-destructive' />
|
||||
)
|
||||
: (< RiArrowRightSLine className={
|
||||
cn(
|
||||
'w-4 h-4 text-text-tertiary transition-transform duration-200 flex-shrink-0',
|
||||
expandedIterations[index] && 'transform rotate-90',
|
||||
)} />
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{expandedIterations[index] && <div
|
||||
|
||||
@ -72,7 +72,16 @@ const NodePanel: FC<Props> = ({
|
||||
|
||||
return iteration_length
|
||||
}
|
||||
const getErrorCount = (details: NodeTracing[][] | undefined) => {
|
||||
if (!details || details.length === 0)
|
||||
return 0
|
||||
|
||||
return details.reduce((acc, iteration) => {
|
||||
if (iteration.some(item => item.status === 'failed'))
|
||||
acc++
|
||||
return acc
|
||||
}, 0)
|
||||
}
|
||||
useEffect(() => {
|
||||
setCollapseState(!nodeInfo.expand)
|
||||
}, [nodeInfo.expand, setCollapseState])
|
||||
@ -136,7 +145,12 @@ const NodePanel: FC<Props> = ({
|
||||
onClick={handleOnShowIterationDetail}
|
||||
>
|
||||
<Iteration className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
<div className='flex-1 text-left system-sm-medium text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}</div>
|
||||
<div className='flex-1 text-left system-sm-medium text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}{getErrorCount(nodeInfo.details) > 0 && (
|
||||
<>
|
||||
{t('workflow.nodes.iteration.comma')}
|
||||
{t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })}
|
||||
</>
|
||||
)}</div>
|
||||
{justShowIterationNavArrow
|
||||
? (
|
||||
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
|
||||
@ -21,6 +21,7 @@ import type {
|
||||
WorkflowRunningData,
|
||||
} from './types'
|
||||
import { WorkflowContext } from './context'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
// #TODO chatVar#
|
||||
// const MOCK_DATA = [
|
||||
@ -166,6 +167,10 @@ interface Shape {
|
||||
setShowImportDSLModal: (showImportDSLModal: boolean) => void
|
||||
showTips: string
|
||||
setShowTips: (showTips: string) => void
|
||||
iterTimes: number
|
||||
setIterTimes: (iterTimes: number) => void
|
||||
iterParallelLogMap: Map<string, NodeTracing[]>
|
||||
setIterParallelLogMap: (iterParallelLogMap: Map<string, NodeTracing[]>) => void
|
||||
}
|
||||
|
||||
export const createWorkflowStore = () => {
|
||||
@ -281,6 +286,11 @@ export const createWorkflowStore = () => {
|
||||
setShowImportDSLModal: showImportDSLModal => set(() => ({ showImportDSLModal })),
|
||||
showTips: '',
|
||||
setShowTips: showTips => set(() => ({ showTips })),
|
||||
iterTimes: 1,
|
||||
setIterTimes: iterTimes => set(() => ({ iterTimes })),
|
||||
iterParallelLogMap: new Map<string, NodeTracing[]>(),
|
||||
setIterParallelLogMap: iterParallelLogMap => set(() => ({ iterParallelLogMap })),
|
||||
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -37,7 +37,12 @@ export enum ControlMode {
|
||||
Hand = 'hand',
|
||||
}
|
||||
|
||||
export interface Branch {
|
||||
export enum ErrorHandleMode {
|
||||
Terminated = 'terminated',
|
||||
ContinueOnError = 'continue-on-error',
|
||||
RemoveAbnormalOutput = 'remove-abnormal-output',
|
||||
}
|
||||
export type Branch = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ import type {
|
||||
ToolWithProvider,
|
||||
ValueSelector,
|
||||
} from './types'
|
||||
import { BlockEnum } from './types'
|
||||
import { BlockEnum, ErrorHandleMode } from './types'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
@ -267,8 +267,13 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration)
|
||||
node.data._children = iterationNodeMap[node.id] || []
|
||||
if (node.data.type === BlockEnum.Iteration) {
|
||||
const iterationNodeData = node.data as IterationNodeType
|
||||
iterationNodeData._children = iterationNodeMap[node.id] || []
|
||||
iterationNodeData.is_parallel = iterationNodeData.is_parallel || false
|
||||
iterationNodeData.parallel_nums = iterationNodeData.parallel_nums || 10
|
||||
iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
|
||||
@ -12,11 +12,9 @@ import cn from '@/utils/classnames'
|
||||
import { getSystemFeatures, invitationCheck } from '@/service/common'
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import useRefreshToken from '@/hooks/use-refresh-token'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
|
||||
const NormalForm = () => {
|
||||
const { getNewAccessToken } = useRefreshToken()
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
@ -38,7 +36,6 @@ const NormalForm = () => {
|
||||
if (consoleToken && refreshToken) {
|
||||
localStorage.setItem('console_token', consoleToken)
|
||||
localStorage.setItem('refresh_token', refreshToken)
|
||||
getNewAccessToken()
|
||||
router.replace('/apps')
|
||||
return
|
||||
}
|
||||
@ -71,7 +68,7 @@ const NormalForm = () => {
|
||||
setSystemFeatures(defaultSystemFeatures)
|
||||
}
|
||||
finally { setIsLoading(false) }
|
||||
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, getNewAccessToken])
|
||||
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink])
|
||||
useEffect(() => {
|
||||
init()
|
||||
}, [init])
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
'use client'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { fetchNewToken } from '@/service/common'
|
||||
import { fetchWithRetry } from '@/utils'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const useRefreshToken = () => {
|
||||
const router = useRouter()
|
||||
const timer = useRef<NodeJS.Timeout>()
|
||||
const advanceTime = useRef<number>(5 * 60 * 1000)
|
||||
|
||||
const getExpireTime = useCallback((token: string) => {
|
||||
if (!token)
|
||||
return 0
|
||||
const decoded = jwtDecode(token)
|
||||
return (decoded.exp || 0) * 1000
|
||||
}, [])
|
||||
|
||||
const getCurrentTimeStamp = useCallback(() => {
|
||||
return dayjs.utc().valueOf()
|
||||
}, [])
|
||||
|
||||
const handleError = useCallback(() => {
|
||||
localStorage?.removeItem('is_refreshing')
|
||||
localStorage?.removeItem('console_token')
|
||||
localStorage?.removeItem('refresh_token')
|
||||
router.replace('/signin')
|
||||
}, [])
|
||||
|
||||
const getNewAccessToken = useCallback(async () => {
|
||||
const currentAccessToken = localStorage?.getItem('console_token')
|
||||
const currentRefreshToken = localStorage?.getItem('refresh_token')
|
||||
if (!currentAccessToken || !currentRefreshToken) {
|
||||
handleError()
|
||||
return new Error('No access token or refresh token found')
|
||||
}
|
||||
if (localStorage?.getItem('is_refreshing') === '1') {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = setTimeout(() => {
|
||||
getNewAccessToken()
|
||||
}, 1000)
|
||||
return null
|
||||
}
|
||||
const currentTokenExpireTime = getExpireTime(currentAccessToken)
|
||||
if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime) {
|
||||
localStorage?.setItem('is_refreshing', '1')
|
||||
const [e, res] = await fetchWithRetry(fetchNewToken({
|
||||
body: { refresh_token: currentRefreshToken },
|
||||
}) as Promise<CommonResponse & { data: { access_token: string; refresh_token: string } }>)
|
||||
if (e) {
|
||||
handleError()
|
||||
return e
|
||||
}
|
||||
const { access_token, refresh_token } = res.data
|
||||
localStorage?.setItem('is_refreshing', '0')
|
||||
localStorage?.setItem('console_token', access_token)
|
||||
localStorage?.setItem('refresh_token', refresh_token)
|
||||
const newTokenExpireTime = getExpireTime(access_token)
|
||||
clearTimeout(timer.current)
|
||||
timer.current = setTimeout(() => {
|
||||
getNewAccessToken()
|
||||
}, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
|
||||
}
|
||||
else {
|
||||
const newTokenExpireTime = getExpireTime(currentAccessToken)
|
||||
clearTimeout(timer.current)
|
||||
timer.current = setTimeout(() => {
|
||||
getNewAccessToken()
|
||||
}, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
|
||||
}
|
||||
return null
|
||||
}, [getExpireTime, getCurrentTimeStamp, handleError])
|
||||
|
||||
const handleVisibilityChange = useCallback(() => {
|
||||
if (document.visibilityState === 'visible')
|
||||
getNewAccessToken()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
return () => {
|
||||
window.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
clearTimeout(timer.current)
|
||||
localStorage?.removeItem('is_refreshing')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
getNewAccessToken,
|
||||
}
|
||||
}
|
||||
|
||||
export default useRefreshToken
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iteration',
|
||||
iteration_other: '{{count}} Iterationen',
|
||||
currentIteration: 'Aktuelle Iteration',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'beendet',
|
||||
removeAbnormalOutput: 'remove-abnormale_ausgabe',
|
||||
continueOnError: 'Fehler "Fortfahren bei"',
|
||||
},
|
||||
MaxParallelismTitle: 'Maximale Parallelität',
|
||||
parallelMode: 'Paralleler Modus',
|
||||
errorResponseMethod: 'Methode der Fehlerantwort',
|
||||
error_one: '{{Anzahl}} Fehler',
|
||||
error_other: '{{Anzahl}} Irrtümer',
|
||||
MaxParallelismDesc: 'Die maximale Parallelität wird verwendet, um die Anzahl der Aufgaben zu steuern, die gleichzeitig in einer einzigen Iteration ausgeführt werden.',
|
||||
parallelPanelDesc: 'Im parallelen Modus unterstützen Aufgaben in der Iteration die parallele Ausführung.',
|
||||
parallelModeEnableDesc: 'Im parallelen Modus unterstützen Aufgaben innerhalb von Iterationen die parallele Ausführung. Sie können dies im Eigenschaftenbereich auf der rechten Seite konfigurieren.',
|
||||
answerNodeWarningDesc: 'Warnung im parallelen Modus: Antwortknoten, Zuweisungen von Konversationsvariablen und persistente Lese-/Schreibvorgänge innerhalb von Iterationen können Ausnahmen verursachen.',
|
||||
parallelModeEnableTitle: 'Paralleler Modus aktiviert',
|
||||
parallelModeUpper: 'PARALLELER MODUS',
|
||||
comma: ',',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -224,6 +224,8 @@ const translation = {
|
||||
description: 'The Code Generator uses configured models to generate high-quality code based on your instructions. Please provide clear and detailed instructions.',
|
||||
instruction: 'Instructions',
|
||||
instructionPlaceholder: 'Enter detailed description of the code you want to generate.',
|
||||
noDataLine1: 'Describe your use case on the left,',
|
||||
noDataLine2: 'the code preview will show here.',
|
||||
generate: 'Generate',
|
||||
generatedCodeTitle: 'Generated Code',
|
||||
loading: 'Generating code...',
|
||||
|
||||
@ -556,6 +556,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iteration',
|
||||
iteration_other: '{{count}} Iterations',
|
||||
currentIteration: 'Current Iteration',
|
||||
comma: ', ',
|
||||
error_one: '{{count}} Error',
|
||||
error_other: '{{count}} Errors',
|
||||
parallelMode: 'Parallel Mode',
|
||||
parallelModeUpper: 'PARALLEL MODE',
|
||||
parallelModeEnableTitle: 'Parallel Mode Enabled',
|
||||
parallelModeEnableDesc: 'In parallel mode, tasks within iterations support parallel execution. You can configure this in the properties panel on the right.',
|
||||
parallelPanelDesc: 'In parallel mode, tasks in the iteration support parallel execution.',
|
||||
MaxParallelismTitle: 'Maximum parallelism',
|
||||
MaxParallelismDesc: 'The maximum parallelism is used to control the number of tasks executed simultaneously in a single iteration.',
|
||||
errorResponseMethod: 'Error response method',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'terminated',
|
||||
continueOnError: 'continue-on-error',
|
||||
removeAbnormalOutput: 'remove-abnormal-output',
|
||||
},
|
||||
answerNodeWarningDesc: 'Parallel mode warning: Answer nodes, conversation variable assignments, and persistent read/write operations within iterations may cause exceptions.',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Add Note',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iteración',
|
||||
iteration_other: '{{count}} Iteraciones',
|
||||
currentIteration: 'Iteración actual',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Terminado',
|
||||
continueOnError: 'Continuar en el error',
|
||||
removeAbnormalOutput: 'eliminar-salida-anormal',
|
||||
},
|
||||
comma: ',',
|
||||
errorResponseMethod: 'Método de respuesta a errores',
|
||||
error_one: '{{conteo}} Error',
|
||||
parallelPanelDesc: 'En el modo paralelo, las tareas de la iteración admiten la ejecución en paralelo.',
|
||||
MaxParallelismTitle: 'Máximo paralelismo',
|
||||
error_other: '{{conteo}} Errores',
|
||||
parallelMode: 'Modo paralelo',
|
||||
parallelModeEnableDesc: 'En el modo paralelo, las tareas dentro de las iteraciones admiten la ejecución en paralelo. Puede configurar esto en el panel de propiedades a la derecha.',
|
||||
parallelModeUpper: 'MODO PARALELO',
|
||||
MaxParallelismDesc: 'El paralelismo máximo se utiliza para controlar el número de tareas ejecutadas simultáneamente en una sola iteración.',
|
||||
answerNodeWarningDesc: 'Advertencia de modo paralelo: Los nodos de respuesta, las asignaciones de variables de conversación y las operaciones de lectura/escritura persistentes dentro de las iteraciones pueden provocar excepciones.',
|
||||
parallelModeEnableTitle: 'Modo paralelo habilitado',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Agregar nota',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} تکرار',
|
||||
iteration_other: '{{count}} تکرارها',
|
||||
currentIteration: 'تکرار فعلی',
|
||||
ErrorMethod: {
|
||||
continueOnError: 'ادامه در خطا',
|
||||
operationTerminated: 'فسخ',
|
||||
removeAbnormalOutput: 'حذف خروجی غیرطبیعی',
|
||||
},
|
||||
error_one: '{{تعداد}} خطا',
|
||||
error_other: '{{تعداد}} خطاهای',
|
||||
parallelMode: 'حالت موازی',
|
||||
errorResponseMethod: 'روش پاسخ به خطا',
|
||||
parallelModeEnableTitle: 'حالت موازی فعال است',
|
||||
parallelModeUpper: 'حالت موازی',
|
||||
comma: ',',
|
||||
parallelModeEnableDesc: 'در حالت موازی، وظایف درون تکرارها از اجرای موازی پشتیبانی می کنند. می توانید این را در پانل ویژگی ها در سمت راست پیکربندی کنید.',
|
||||
MaxParallelismTitle: 'حداکثر موازی سازی',
|
||||
parallelPanelDesc: 'در حالت موازی، وظایف در تکرار از اجرای موازی پشتیبانی می کنند.',
|
||||
MaxParallelismDesc: 'حداکثر موازی سازی برای کنترل تعداد وظایف اجرا شده به طور همزمان در یک تکرار واحد استفاده می شود.',
|
||||
answerNodeWarningDesc: 'هشدار حالت موازی: گره های پاسخ، تکالیف متغیر مکالمه و عملیات خواندن/نوشتن مداوم در تکرارها ممکن است باعث استثنائات شود.',
|
||||
},
|
||||
note: {
|
||||
addNote: 'افزودن یادداشت',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Itération',
|
||||
iteration_other: '{{count}} Itérations',
|
||||
currentIteration: 'Itération actuelle',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Terminé',
|
||||
removeAbnormalOutput: 'remove-abnormal-output',
|
||||
continueOnError: 'continuer sur l’erreur',
|
||||
},
|
||||
comma: ',',
|
||||
error_one: '{{compte}} Erreur',
|
||||
error_other: '{{compte}} Erreurs',
|
||||
parallelModeEnableDesc: 'En mode parallèle, les tâches au sein des itérations prennent en charge l’exécution parallèle. Vous pouvez le configurer dans le panneau des propriétés à droite.',
|
||||
parallelModeUpper: 'MODE PARALLÈLE',
|
||||
parallelPanelDesc: 'En mode parallèle, les tâches de l’itération prennent en charge l’exécution parallèle.',
|
||||
MaxParallelismDesc: 'Le parallélisme maximal est utilisé pour contrôler le nombre de tâches exécutées simultanément en une seule itération.',
|
||||
errorResponseMethod: 'Méthode de réponse aux erreurs',
|
||||
MaxParallelismTitle: 'Parallélisme maximal',
|
||||
answerNodeWarningDesc: 'Avertissement en mode parallèle : les nœuds de réponse, les affectations de variables de conversation et les opérations de lecture/écriture persistantes au sein des itérations peuvent provoquer des exceptions.',
|
||||
parallelModeEnableTitle: 'Mode parallèle activé',
|
||||
parallelMode: 'Mode parallèle',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Ajouter note',
|
||||
|
||||
@ -577,6 +577,23 @@ const translation = {
|
||||
iteration_one: '{{count}} इटरेशन',
|
||||
iteration_other: '{{count}} इटरेशन्स',
|
||||
currentIteration: 'वर्तमान इटरेशन',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'समाप्त',
|
||||
continueOnError: 'जारी रखें-पर-त्रुटि',
|
||||
removeAbnormalOutput: 'निकालें-असामान्य-आउटपुट',
|
||||
},
|
||||
comma: ',',
|
||||
error_other: '{{गिनती}} त्रुटियों',
|
||||
error_one: '{{गिनती}} चूक',
|
||||
parallelMode: 'समानांतर मोड',
|
||||
parallelModeUpper: 'समानांतर मोड',
|
||||
errorResponseMethod: 'त्रुटि प्रतिक्रिया विधि',
|
||||
MaxParallelismTitle: 'अधिकतम समांतरता',
|
||||
parallelModeEnableTitle: 'समानांतर मोड सक्षम किया गया',
|
||||
parallelModeEnableDesc: 'समानांतर मोड में, पुनरावृत्तियों के भीतर कार्य समानांतर निष्पादन का समर्थन करते हैं। आप इसे दाईं ओर गुण पैनल में कॉन्फ़िगर कर सकते हैं।',
|
||||
parallelPanelDesc: 'समानांतर मोड में, पुनरावृत्ति में कार्य समानांतर निष्पादन का समर्थन करते हैं।',
|
||||
MaxParallelismDesc: 'अधिकतम समांतरता का उपयोग एकल पुनरावृत्ति में एक साथ निष्पादित कार्यों की संख्या को नियंत्रित करने के लिए किया जाता है।',
|
||||
answerNodeWarningDesc: 'समानांतर मोड चेतावनी: उत्तर नोड्स, वार्तालाप चर असाइनमेंट, और पुनरावृत्तियों के भीतर लगातार पढ़ने/लिखने की कार्रवाई अपवाद पैदा कर सकती है।',
|
||||
},
|
||||
note: {
|
||||
addNote: 'नोट जोड़ें',
|
||||
|
||||
@ -584,6 +584,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iterazione',
|
||||
iteration_other: '{{count}} Iterazioni',
|
||||
currentIteration: 'Iterazione Corrente',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Terminato',
|
||||
continueOnError: 'continua sull\'errore',
|
||||
removeAbnormalOutput: 'rimuovi-output-anomalo',
|
||||
},
|
||||
error_one: '{{conteggio}} Errore',
|
||||
parallelMode: 'Modalità parallela',
|
||||
MaxParallelismTitle: 'Parallelismo massimo',
|
||||
error_other: '{{conteggio}} Errori',
|
||||
parallelModeEnableDesc: 'In modalità parallela, le attività all\'interno delle iterazioni supportano l\'esecuzione parallela. È possibile configurare questa opzione nel pannello delle proprietà a destra.',
|
||||
MaxParallelismDesc: 'Il parallelismo massimo viene utilizzato per controllare il numero di attività eseguite contemporaneamente in una singola iterazione.',
|
||||
errorResponseMethod: 'Metodo di risposta all\'errore',
|
||||
parallelModeEnableTitle: 'Modalità parallela abilitata',
|
||||
parallelModeUpper: 'MODALITÀ PARALLELA',
|
||||
comma: ',',
|
||||
parallelPanelDesc: 'In modalità parallela, le attività nell\'iterazione supportano l\'esecuzione parallela.',
|
||||
answerNodeWarningDesc: 'Avviso in modalità parallela: i nodi di risposta, le assegnazioni di variabili di conversazione e le operazioni di lettura/scrittura persistenti all\'interno delle iterazioni possono causare eccezioni.',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Aggiungi Nota',
|
||||
|
||||
@ -224,6 +224,8 @@ const translation = {
|
||||
description: 'コードジェネレーターは、設定されたモデルを使用して指示に基づいて高品質なコードを生成します。明確で詳細な指示を提供してください。',
|
||||
instruction: '指示',
|
||||
instructionPlaceholder: '生成したいコードの詳細な説明を入力してください。',
|
||||
noDataLine1: '左側に使用例を記入してください,',
|
||||
noDataLine2: 'コードのプレビューがこちらに表示されます。',
|
||||
generate: '生成',
|
||||
generatedCodeTitle: '生成されたコード',
|
||||
loading: 'コードを生成中...',
|
||||
|
||||
@ -39,10 +39,10 @@ const translation = {
|
||||
workflowWarning: '現在ベータ版です',
|
||||
chatbotType: 'チャットボットのオーケストレーション方法',
|
||||
basic: '基本',
|
||||
basicTip: '初心者向け。後で Chatflow に切り替えることができます',
|
||||
basicTip: '初心者向け。後で「チャットフロー」に切り替えることができます',
|
||||
basicFor: '初心者向け',
|
||||
basicDescription: '基本オーケストレートは、組み込みのプロンプトを変更する機能がなく、簡単な設定を使用してチャットボット アプリをオーケストレートします。初心者向けです。',
|
||||
advanced: 'Chatflow',
|
||||
advanced: 'チャットフロー',
|
||||
advancedFor: '上級ユーザー向け',
|
||||
advancedDescription: 'ワークフロー オーケストレートは、ワークフロー形式でチャットボットをオーケストレートし、組み込みのプロンプトを編集する機能を含む高度なカスタマイズを提供します。経験豊富なユーザー向けです。',
|
||||
captionName: 'アプリのアイコンと名前',
|
||||
|
||||
@ -558,6 +558,23 @@ const translation = {
|
||||
iteration_one: '{{count}} イテレーション',
|
||||
iteration_other: '{{count}} イテレーション',
|
||||
currentIteration: '現在のイテレーション',
|
||||
ErrorMethod: {
|
||||
operationTerminated: '終了',
|
||||
continueOnError: 'エラー時に続行',
|
||||
removeAbnormalOutput: 'アブノーマルアウトプットの削除',
|
||||
},
|
||||
comma: ',',
|
||||
error_other: '{{カウント}}エラー',
|
||||
error_one: '{{カウント}}エラー',
|
||||
parallelModeUpper: 'パラレルモード',
|
||||
parallelMode: 'パラレルモード',
|
||||
MaxParallelismTitle: '最大並列処理',
|
||||
errorResponseMethod: 'エラー応答方式',
|
||||
parallelPanelDesc: '並列モードでは、イテレーションのタスクは並列実行をサポートします。',
|
||||
parallelModeEnableDesc: '並列モードでは、イテレーション内のタスクは並列実行をサポートします。これは、右側のプロパティパネルで構成できます。',
|
||||
parallelModeEnableTitle: 'パラレルモード有効',
|
||||
MaxParallelismDesc: '最大並列処理は、1 回の反復で同時に実行されるタスクの数を制御するために使用されます。',
|
||||
answerNodeWarningDesc: '並列モードの警告: 応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。',
|
||||
},
|
||||
note: {
|
||||
addNote: 'コメントを追加',
|
||||
|
||||
@ -169,7 +169,7 @@ const translation = {
|
||||
deleteConfirmTip: '확인하려면 등록된 이메일에서 다음 내용을 로 보내주세요 ',
|
||||
myAccount: '내 계정',
|
||||
studio: '디파이 스튜디오',
|
||||
account: '계좌',
|
||||
account: '계정',
|
||||
},
|
||||
members: {
|
||||
team: '팀',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} 반복',
|
||||
iteration_other: '{{count}} 반복',
|
||||
currentIteration: '현재 반복',
|
||||
ErrorMethod: {
|
||||
operationTerminated: '종료',
|
||||
continueOnError: '오류 발생 시 계속',
|
||||
removeAbnormalOutput: '비정상 출력 제거',
|
||||
},
|
||||
comma: ',',
|
||||
error_one: '{{개수}} 오류',
|
||||
parallelMode: '병렬 모드',
|
||||
errorResponseMethod: '오류 응답 방법',
|
||||
parallelModeUpper: '병렬 모드',
|
||||
MaxParallelismTitle: '최대 병렬 처리',
|
||||
error_other: '{{개수}} 오류',
|
||||
parallelModeEnableTitle: 'Parallel Mode Enabled(병렬 모드 사용)',
|
||||
parallelPanelDesc: '병렬 모드에서 반복의 작업은 병렬 실행을 지원합니다.',
|
||||
parallelModeEnableDesc: '병렬 모드에서는 반복 내의 작업이 병렬 실행을 지원합니다. 오른쪽의 속성 패널에서 이를 구성할 수 있습니다.',
|
||||
MaxParallelismDesc: '최대 병렬 처리는 단일 반복에서 동시에 실행되는 작업 수를 제어하는 데 사용됩니다.',
|
||||
answerNodeWarningDesc: '병렬 모드 경고: 응답 노드, 대화 변수 할당 및 반복 내의 지속적인 읽기/쓰기 작업으로 인해 예외가 발생할 수 있습니다.',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -355,7 +355,7 @@ const translation = {
|
||||
openingStatement: {
|
||||
title: 'Wstęp do rozmowy',
|
||||
add: 'Dodaj',
|
||||
writeOpner: 'Napisz wstęp',
|
||||
writeOpener: 'Napisz wstęp',
|
||||
placeholder:
|
||||
'Tutaj napisz swoją wiadomość wprowadzającą, możesz użyć zmiennych, spróbuj wpisać {{variable}}.',
|
||||
openingQuestion: 'Pytania otwierające',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iteracja',
|
||||
iteration_other: '{{count}} Iteracje',
|
||||
currentIteration: 'Bieżąca iteracja',
|
||||
ErrorMethod: {
|
||||
continueOnError: 'kontynuacja w przypadku błędu',
|
||||
operationTerminated: 'Zakończone',
|
||||
removeAbnormalOutput: 'usuń-nieprawidłowe-wyjście',
|
||||
},
|
||||
comma: ',',
|
||||
parallelModeUpper: 'TRYB RÓWNOLEGŁY',
|
||||
parallelModeEnableTitle: 'Włączony tryb równoległy',
|
||||
MaxParallelismTitle: 'Maksymalna równoległość',
|
||||
error_one: '{{liczba}} Błąd',
|
||||
error_other: '{{liczba}} Błędy',
|
||||
parallelPanelDesc: 'W trybie równoległym zadania w iteracji obsługują wykonywanie równoległe.',
|
||||
parallelMode: 'Tryb równoległy',
|
||||
MaxParallelismDesc: 'Maksymalna równoległość służy do kontrolowania liczby zadań wykonywanych jednocześnie w jednej iteracji.',
|
||||
parallelModeEnableDesc: 'W trybie równoległym zadania w iteracjach obsługują wykonywanie równoległe. Możesz to skonfigurować w panelu właściwości po prawej stronie.',
|
||||
answerNodeWarningDesc: 'Ostrzeżenie w trybie równoległym: węzły odpowiedzi, przypisania zmiennych konwersacji i trwałe operacje odczytu/zapisu w iteracjach mogą powodować wyjątki.',
|
||||
errorResponseMethod: 'Metoda odpowiedzi na błąd',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iteração',
|
||||
iteration_other: '{{count}} Iterações',
|
||||
currentIteration: 'Iteração atual',
|
||||
ErrorMethod: {
|
||||
continueOnError: 'continuar em erro',
|
||||
removeAbnormalOutput: 'saída anormal de remoção',
|
||||
operationTerminated: 'Terminada',
|
||||
},
|
||||
MaxParallelismTitle: 'Paralelismo máximo',
|
||||
parallelModeEnableTitle: 'Modo paralelo ativado',
|
||||
errorResponseMethod: 'Método de resposta de erro',
|
||||
error_other: '{{contagem}} Erros',
|
||||
parallelMode: 'Modo paralelo',
|
||||
parallelModeUpper: 'MODO PARALELO',
|
||||
error_one: '{{contagem}} Erro',
|
||||
parallelModeEnableDesc: 'No modo paralelo, as tarefas dentro das iterações dão suporte à execução paralela. Você pode configurar isso no painel de propriedades à direita.',
|
||||
comma: ',',
|
||||
MaxParallelismDesc: 'O paralelismo máximo é usado para controlar o número de tarefas executadas simultaneamente em uma única iteração.',
|
||||
answerNodeWarningDesc: 'Aviso de modo paralelo: nós de resposta, atribuições de variáveis de conversação e operações persistentes de leitura/gravação em iterações podem causar exceções.',
|
||||
parallelPanelDesc: 'No modo paralelo, as tarefas na iteração dão suporte à execução paralela.',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Iterație',
|
||||
iteration_other: '{{count}} Iterații',
|
||||
currentIteration: 'Iterație curentă',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Încheiată',
|
||||
continueOnError: 'continuare-la-eroare',
|
||||
removeAbnormalOutput: 'elimină-ieșire-anormală',
|
||||
},
|
||||
parallelModeEnableTitle: 'Modul paralel activat',
|
||||
errorResponseMethod: 'Metoda de răspuns la eroare',
|
||||
comma: ',',
|
||||
parallelModeEnableDesc: 'În modul paralel, sarcinile din iterații acceptă execuția paralelă. Puteți configura acest lucru în panoul de proprietăți din dreapta.',
|
||||
parallelModeUpper: 'MOD PARALEL',
|
||||
MaxParallelismTitle: 'Paralelism maxim',
|
||||
parallelMode: 'Mod paralel',
|
||||
error_other: '{{număr}} Erori',
|
||||
error_one: '{{număr}} Eroare',
|
||||
parallelPanelDesc: 'În modul paralel, activitățile din iterație acceptă execuția paralelă.',
|
||||
MaxParallelismDesc: 'Paralelismul maxim este utilizat pentru a controla numărul de sarcini executate simultan într-o singură iterație.',
|
||||
answerNodeWarningDesc: 'Avertisment modul paralel: Nodurile de răspuns, atribuirea variabilelor de conversație și operațiunile persistente de citire/scriere în iterații pot cauza excepții.',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Итерация',
|
||||
iteration_other: '{{count}} Итераций',
|
||||
currentIteration: 'Текущая итерация',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Прекращено',
|
||||
continueOnError: 'продолжить по ошибке',
|
||||
removeAbnormalOutput: 'удалить аномальный вывод',
|
||||
},
|
||||
comma: ',',
|
||||
error_other: '{{Количество}} Ошибки',
|
||||
errorResponseMethod: 'Метод реагирования на ошибку',
|
||||
MaxParallelismTitle: 'Максимальный параллелизм',
|
||||
parallelModeUpper: 'ПАРАЛЛЕЛЬНЫЙ РЕЖИМ',
|
||||
error_one: '{{Количество}} Ошибка',
|
||||
parallelModeEnableTitle: 'Параллельный режим включен',
|
||||
parallelMode: 'Параллельный режим',
|
||||
parallelPanelDesc: 'В параллельном режиме задачи в итерации поддерживают параллельное выполнение.',
|
||||
parallelModeEnableDesc: 'В параллельном режиме задачи в итерациях поддерживают параллельное выполнение. Вы можете настроить это на панели свойств справа.',
|
||||
MaxParallelismDesc: 'Максимальный параллелизм используется для управления количеством задач, выполняемых одновременно в одной итерации.',
|
||||
answerNodeWarningDesc: 'Предупреждение о параллельном режиме: узлы ответов, присвоение переменных диалога и постоянные операции чтения и записи в итерациях могут вызывать исключения.',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Добавить заметку',
|
||||
|
||||
@ -558,6 +558,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Yineleme',
|
||||
iteration_other: '{{count}} Yineleme',
|
||||
currentIteration: 'Mevcut Yineleme',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Sonlandırıldı',
|
||||
continueOnError: 'Hata Üzerine Devam Et',
|
||||
removeAbnormalOutput: 'anormal çıktıyı kaldır',
|
||||
},
|
||||
parallelModeUpper: 'PARALEL MOD',
|
||||
parallelMode: 'Paralel Mod',
|
||||
MaxParallelismTitle: 'Maksimum paralellik',
|
||||
error_one: '{{sayı}} Hata',
|
||||
errorResponseMethod: 'Hata yanıtı yöntemi',
|
||||
comma: ',',
|
||||
parallelModeEnableTitle: 'Paralel Mod Etkin',
|
||||
error_other: '{{sayı}} Hata',
|
||||
parallelPanelDesc: 'Paralel modda, yinelemedeki görevler paralel yürütmeyi destekler.',
|
||||
answerNodeWarningDesc: 'Paralel mod uyarısı: Yinelemeler içindeki yanıt düğümleri, konuşma değişkeni atamaları ve kalıcı okuma/yazma işlemleri özel durumlara neden olabilir.',
|
||||
parallelModeEnableDesc: 'Paralel modda, yinelemeler içindeki görevler paralel yürütmeyi destekler. Bunu sağdaki özellikler panelinde yapılandırabilirsiniz.',
|
||||
MaxParallelismDesc: 'Maksimum paralellik, tek bir yinelemede aynı anda yürütülen görevlerin sayısını kontrol etmek için kullanılır.',
|
||||
},
|
||||
note: {
|
||||
addNote: 'Not Ekle',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Ітерація',
|
||||
iteration_other: '{{count}} Ітерацій',
|
||||
currentIteration: 'Поточна ітерація',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Припинено',
|
||||
continueOnError: 'Продовжити після помилки',
|
||||
removeAbnormalOutput: 'видалити-ненормальний-вивід',
|
||||
},
|
||||
error_one: '{{count}} Помилка',
|
||||
comma: ',',
|
||||
MaxParallelismTitle: 'Максимальна паралельність',
|
||||
parallelModeUpper: 'ПАРАЛЕЛЬНИЙ РЕЖИМ',
|
||||
error_other: '{{count}} Помилки',
|
||||
parallelMode: 'Паралельний режим',
|
||||
parallelModeEnableTitle: 'Увімкнено паралельний режим',
|
||||
errorResponseMethod: 'Метод реагування на помилку',
|
||||
parallelPanelDesc: 'У паралельному режимі завдання в ітерації підтримують паралельне виконання.',
|
||||
parallelModeEnableDesc: 'У паралельному режимі завдання всередині ітерацій підтримують паралельне виконання. Ви можете налаштувати це на панелі властивостей праворуч.',
|
||||
MaxParallelismDesc: 'Максимальний паралелізм використовується для контролю числа завдань, що виконуються одночасно за одну ітерацію.',
|
||||
answerNodeWarningDesc: 'Попередження в паралельному режимі: вузли відповідей, призначення змінних розмови та постійні операції читання/запису в межах ітерацій можуть спричинити винятки.',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}} Lặp',
|
||||
iteration_other: '{{count}} Lặp',
|
||||
currentIteration: 'Lặp hiện tại',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'Chấm dứt',
|
||||
removeAbnormalOutput: 'loại bỏ-bất thường-đầu ra',
|
||||
continueOnError: 'Tiếp tục lỗi',
|
||||
},
|
||||
comma: ',',
|
||||
error_other: '{{đếm}} Lỗi',
|
||||
error_one: '{{đếm}} Lỗi',
|
||||
MaxParallelismTitle: 'Song song tối đa',
|
||||
parallelPanelDesc: 'Ở chế độ song song, các tác vụ trong quá trình lặp hỗ trợ thực thi song song.',
|
||||
parallelMode: 'Chế độ song song',
|
||||
parallelModeEnableTitle: 'Đã bật Chế độ song song',
|
||||
errorResponseMethod: 'Phương pháp phản hồi lỗi',
|
||||
MaxParallelismDesc: 'Tính song song tối đa được sử dụng để kiểm soát số lượng tác vụ được thực hiện đồng thời trong một lần lặp.',
|
||||
answerNodeWarningDesc: 'Cảnh báo chế độ song song: Các nút trả lời, bài tập biến hội thoại và các thao tác đọc/ghi liên tục trong các lần lặp có thể gây ra ngoại lệ.',
|
||||
parallelModeEnableDesc: 'Trong chế độ song song, các tác vụ trong các lần lặp hỗ trợ thực thi song song. Bạn có thể định cấu hình điều này trong bảng thuộc tính ở bên phải.',
|
||||
parallelModeUpper: 'CHẾ ĐỘ SONG SONG',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -224,6 +224,8 @@ const translation = {
|
||||
description: '代码生成器使用配置的模型根据您的指令生成高质量的代码。请提供清晰详细的说明。',
|
||||
instruction: '指令',
|
||||
instructionPlaceholder: '请输入您想要生成的代码的详细描述。',
|
||||
noDataLine1: '在左侧描述您的用例,',
|
||||
noDataLine2: '代码预览将在此处显示。',
|
||||
generate: '生成',
|
||||
generatedCodeTitle: '生成的代码',
|
||||
loading: '正在生成代码...',
|
||||
|
||||
@ -556,6 +556,23 @@ const translation = {
|
||||
iteration_one: '{{count}}个迭代',
|
||||
iteration_other: '{{count}}个迭代',
|
||||
currentIteration: '当前迭代',
|
||||
comma: ',',
|
||||
error_one: '{{count}}个失败',
|
||||
error_other: '{{count}}个失败',
|
||||
parallelMode: '并行模式',
|
||||
parallelModeUpper: '并行模式',
|
||||
parallelModeEnableTitle: '并行模式启用',
|
||||
parallelModeEnableDesc: '启用并行模式时迭代内的任务支持并行执行。你可以在右侧的属性面板中进行配置。',
|
||||
parallelPanelDesc: '在并行模式下,迭代中的任务支持并行执行。',
|
||||
MaxParallelismTitle: '最大并行度',
|
||||
MaxParallelismDesc: '最大并行度用于控制单次迭代中同时执行的任务数量。',
|
||||
errorResponseMethod: '错误响应方法',
|
||||
ErrorMethod: {
|
||||
operationTerminated: '错误时终止',
|
||||
continueOnError: '忽略错误并继续',
|
||||
removeAbnormalOutput: '移除错误输出',
|
||||
},
|
||||
answerNodeWarningDesc: '并行模式警告:在迭代中,回答节点、会话变量赋值和工具持久读/写操作可能会导致异常。',
|
||||
},
|
||||
note: {
|
||||
addNote: '添加注释',
|
||||
|
||||
@ -557,6 +557,23 @@ const translation = {
|
||||
iteration_one: '{{count}}個迭代',
|
||||
iteration_other: '{{count}}個迭代',
|
||||
currentIteration: '當前迭代',
|
||||
ErrorMethod: {
|
||||
operationTerminated: '終止',
|
||||
removeAbnormalOutput: 'remove-abnormal-output',
|
||||
continueOnError: '出錯時繼續',
|
||||
},
|
||||
comma: ',',
|
||||
parallelMode: '並行模式',
|
||||
parallelModeEnableTitle: 'Parallel Mode 已啟用',
|
||||
MaxParallelismTitle: '最大並行度',
|
||||
parallelModeUpper: '並行模式',
|
||||
parallelPanelDesc: '在並行模式下,反覆運算中的任務支援並行執行。',
|
||||
error_one: '{{count}}錯誤',
|
||||
errorResponseMethod: '錯誤回應方法',
|
||||
parallelModeEnableDesc: '在並行模式下,反覆運算中的任務支援並行執行。您可以在右側的 properties 面板中進行配置。',
|
||||
answerNodeWarningDesc: '並行模式警告:反覆運算中的應答節點、對話變數賦值和持久讀/寫操作可能會導致異常。',
|
||||
error_other: '{{count}}錯誤',
|
||||
MaxParallelismDesc: '最大並行度用於控制在單個反覆運算中同時執行的任務數。',
|
||||
},
|
||||
note: {
|
||||
editor: {
|
||||
|
||||
@ -216,7 +216,7 @@ export interface FileUploadConfigResponse {
|
||||
file_size_limit: number // default is 15MB
|
||||
audio_file_size_limit?: number // default is 50MB
|
||||
video_file_size_limit?: number // default is 100MB
|
||||
|
||||
workflow_file_upload_limit?: number // default is 10
|
||||
}
|
||||
|
||||
export type InvitationResult = {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dify-web",
|
||||
"version": "0.10.2",
|
||||
"version": "0.11.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { API_PREFIX, IS_CE_EDITION, MARKETPLACE_API_PREFIX, PUBLIC_API_PREFIX } from '@/config'
|
||||
import { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { VisionFile } from '@/types/app'
|
||||
@ -368,42 +369,8 @@ const baseFetch = <T>(
|
||||
if (!/^(2|3)\d{2}$/.test(String(res.status))) {
|
||||
const bodyJson = res.json()
|
||||
switch (res.status) {
|
||||
case 401: {
|
||||
if (isMarketplaceAPI)
|
||||
return
|
||||
|
||||
if (isPublicAPI) {
|
||||
return bodyJson.then((data: ResponseError) => {
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
|
||||
if (data.code === 'unauthorized') {
|
||||
removeAccessToken()
|
||||
globalThis.location.reload()
|
||||
}
|
||||
|
||||
return Promise.reject(data)
|
||||
})
|
||||
}
|
||||
const loginUrl = `${globalThis.location.origin}/signin`
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (data.code === 'init_validate_failed' && IS_CE_EDITION && !silent)
|
||||
Toast.notify({ type: 'error', message: data.message, duration: 4000 })
|
||||
else if (data.code === 'not_init_validated' && IS_CE_EDITION)
|
||||
globalThis.location.href = `${globalThis.location.origin}/init`
|
||||
else if (data.code === 'not_setup' && IS_CE_EDITION)
|
||||
globalThis.location.href = `${globalThis.location.origin}/install`
|
||||
else if (location.pathname !== '/signin' || !IS_CE_EDITION)
|
||||
globalThis.location.href = loginUrl
|
||||
else if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
}).catch(() => {
|
||||
// Handle any other errors
|
||||
globalThis.location.href = loginUrl
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 401:
|
||||
return Promise.reject(resClone)
|
||||
case 403:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!silent)
|
||||
@ -499,7 +466,9 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string, search
|
||||
export const ssePost = (
|
||||
url: string,
|
||||
fetchOptions: FetchOptionType,
|
||||
{
|
||||
otherOptions: IOtherOptions,
|
||||
) => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
onData,
|
||||
onCompleted,
|
||||
@ -522,8 +491,7 @@ export const ssePost = (
|
||||
onTextReplace,
|
||||
onError,
|
||||
getAbortController,
|
||||
}: IOtherOptions,
|
||||
) => {
|
||||
} = otherOptions
|
||||
const abortController = new AbortController()
|
||||
|
||||
const options = Object.assign({}, baseOptions, {
|
||||
@ -547,21 +515,29 @@ export const ssePost = (
|
||||
globalThis.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
if (!/^(2|3)\d{2}$/.test(String(res.status))) {
|
||||
res.json().then((data: any) => {
|
||||
if (isPublicAPI) {
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
if (res.status === 401) {
|
||||
refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
ssePost(url, fetchOptions, otherOptions)
|
||||
}).catch(() => {
|
||||
res.json().then((data: any) => {
|
||||
if (isPublicAPI) {
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
|
||||
if (data.code === 'unauthorized') {
|
||||
removeAccessToken()
|
||||
globalThis.location.reload()
|
||||
}
|
||||
if (res.status === 401)
|
||||
return
|
||||
}
|
||||
Toast.notify({ type: 'error', message: data.message || 'Server Error' })
|
||||
})
|
||||
onError?.('Server Error')
|
||||
if (data.code === 'unauthorized') {
|
||||
removeAccessToken()
|
||||
globalThis.location.reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
res.json().then((data) => {
|
||||
Toast.notify({ type: 'error', message: data.message || 'Server Error' })
|
||||
})
|
||||
onError?.('Server Error')
|
||||
}
|
||||
return
|
||||
}
|
||||
return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
|
||||
@ -584,7 +560,54 @@ export const ssePost = (
|
||||
|
||||
// base request
|
||||
export const request = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return baseFetch<T>(url, options, otherOptions || {})
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const otherOptionsForBaseFetch = otherOptions || {}
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => {
|
||||
if (errResp?.status === 401) {
|
||||
return refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject)
|
||||
}).catch(() => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
silent,
|
||||
} = otherOptionsForBaseFetch
|
||||
const bodyJson = errResp.json()
|
||||
if (isPublicAPI) {
|
||||
return bodyJson.then((data: ResponseError) => {
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
|
||||
if (data.code === 'unauthorized') {
|
||||
removeAccessToken()
|
||||
globalThis.location.reload()
|
||||
}
|
||||
|
||||
return Promise.reject(data)
|
||||
})
|
||||
}
|
||||
const loginUrl = `${globalThis.location.origin}/signin`
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (data.code === 'init_validate_failed' && IS_CE_EDITION && !silent)
|
||||
Toast.notify({ type: 'error', message: data.message, duration: 4000 })
|
||||
else if (data.code === 'not_init_validated' && IS_CE_EDITION)
|
||||
globalThis.location.href = `${globalThis.location.origin}/init`
|
||||
else if (data.code === 'not_setup' && IS_CE_EDITION)
|
||||
globalThis.location.href = `${globalThis.location.origin}/install`
|
||||
else if (location.pathname !== '/signin' || !IS_CE_EDITION)
|
||||
globalThis.location.href = loginUrl
|
||||
else if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
}).catch(() => {
|
||||
// Handle any other errors
|
||||
globalThis.location.href = loginUrl
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject(errResp)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// request methods
|
||||
|
||||
@ -320,9 +320,10 @@ export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boo
|
||||
export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse>(url, { body })
|
||||
|
||||
export const fetchRemoteFileInfo = (url: string) => {
|
||||
return get<{ file_type: string; file_length: number }>(`/remote-files/${url}`)
|
||||
export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => {
|
||||
return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic })
|
||||
}
|
||||
|
||||
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
||||
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
||||
|
||||
|
||||
75
web/service/refresh-token.ts
Normal file
75
web/service/refresh-token.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { apiPrefix } from '@/config'
|
||||
import { fetchWithRetry } from '@/utils'
|
||||
|
||||
let isRefreshing = false
|
||||
function waitUntilTokenRefreshed() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
function _check() {
|
||||
const isRefreshingSign = localStorage.getItem('is_refreshing')
|
||||
if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
|
||||
setTimeout(() => {
|
||||
_check()
|
||||
}, 1000)
|
||||
}
|
||||
else {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
_check()
|
||||
})
|
||||
}
|
||||
|
||||
// only one request can send
|
||||
async function getNewAccessToken(): Promise<void> {
|
||||
try {
|
||||
const isRefreshingSign = localStorage.getItem('is_refreshing')
|
||||
if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
|
||||
await waitUntilTokenRefreshed()
|
||||
}
|
||||
else {
|
||||
globalThis.localStorage.setItem('is_refreshing', '1')
|
||||
isRefreshing = true
|
||||
const refresh_token = globalThis.localStorage.getItem('refresh_token')
|
||||
|
||||
// Do not use baseFetch to refresh tokens.
|
||||
// If a 401 response occurs and baseFetch itself attempts to refresh the token,
|
||||
// it can lead to an infinite loop if the refresh attempt also returns 401.
|
||||
// To avoid this, handle token refresh separately in a dedicated function
|
||||
// that does not call baseFetch and uses a single retry mechanism.
|
||||
const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;utf-8',
|
||||
},
|
||||
body: JSON.stringify({ refresh_token }),
|
||||
}))
|
||||
if (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
else {
|
||||
if (ret.status === 401)
|
||||
return Promise.reject(ret)
|
||||
|
||||
const { data } = await ret.json()
|
||||
globalThis.localStorage.setItem('console_token', data.access_token)
|
||||
globalThis.localStorage.setItem('refresh_token', data.refresh_token)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
finally {
|
||||
isRefreshing = false
|
||||
globalThis.localStorage.removeItem('is_refreshing')
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshAccessTokenOrRelogin(timeout: number) {
|
||||
return Promise.race([new Promise<void>((resolve, reject) => setTimeout(() => {
|
||||
isRefreshing = false
|
||||
globalThis.localStorage.removeItem('is_refreshing')
|
||||
reject(new Error('request timeout'))
|
||||
}, timeout)), getNewAccessToken()])
|
||||
}
|
||||
@ -244,6 +244,8 @@ html[data-theme="dark"] {
|
||||
|
||||
--color-components-Avatar-default-avatar-bg: #222225;
|
||||
|
||||
--color-components-label-gray: #C8CEDA24;
|
||||
|
||||
--color-text-primary: #FBFBFC;
|
||||
--color-text-secondary: #D9D9DE;
|
||||
--color-text-tertiary: #C8CEDA99;
|
||||
|
||||
@ -244,6 +244,8 @@ html[data-theme="light"] {
|
||||
|
||||
--color-components-Avatar-default-avatar-bg: #D0D5DC;
|
||||
|
||||
--color-components-label-gray: #F2F4F7;
|
||||
|
||||
--color-text-primary: #101828;
|
||||
--color-text-secondary: #354052;
|
||||
--color-text-tertiary: #676F83;
|
||||
|
||||
@ -244,6 +244,8 @@ const vars = {
|
||||
|
||||
'components-Avatar-default-avatar-bg': 'var(--color-components-Avatar-default-avatar-bg)',
|
||||
|
||||
'components-label-gray': 'var(--color-components-label-gray)',
|
||||
|
||||
'text-primary': 'var(--color-text-primary)',
|
||||
'text-secondary': 'var(--color-text-secondary)',
|
||||
'text-tertiary': 'var(--color-text-tertiary)',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user