Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins

This commit is contained in:
twwu
2024-11-08 11:33:42 +08:00
335 changed files with 8036 additions and 2041 deletions

View File

@ -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>
)

View File

@ -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'>
{

View File

@ -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'>
keywordchoosable
Keyword (optional)
</Property>
<Property name='status' type='string' key='status'>
Search statuscompleted
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 contentrequired
- <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>

View File

@ -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>

View File

@ -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')
}

View File

@ -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>

View File

@ -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>

View File

@ -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()
}

View File

@ -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}

View File

@ -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,

View File

@ -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

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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'>

View File

@ -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
}

View File

@ -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`] = `
[
{

View File

@ -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()
})
})

View File

@ -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
}

View File

@ -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):\/\//

View File

@ -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'>

View File

@ -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 && (

View File

@ -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(() => { }, [])

View File

@ -29,4 +29,5 @@ export type FileEntity = {
uploadedId?: string
base64Url?: string
url?: string
isRemote?: boolean
}

View File

@ -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

View File

@ -133,6 +133,7 @@ const ImageList: FC<ImageListProps> = ({
<ImagePreview
url={imagePreviewUrl}
onCancel={() => setImagePreviewUrl('')}
title=''
/>
)}
</div>

View File

@ -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)}

View File

@ -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

View File

@ -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'
/>

View File

@ -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> */}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)}

View File

@ -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')
}

View File

@ -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}

View File

@ -74,6 +74,7 @@ const Action: FC<Props> = ({
repo: meta!.repo,
version: meta!.version,
package: meta!.package,
releases: fetchedReleases,
},
},
},

View File

@ -155,6 +155,7 @@ export type UpdateFromGitHubPayload = {
repo: string
version: string
package: string
releases: GitHubRepoReleaseResponse[]
}
}

View File

@ -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
? (

View File

@ -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

View File

@ -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 = {

View File

@ -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)!

View File

@ -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>

View File

@ -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}

View File

@ -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)

View File

@ -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>

View File

@ -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]

View File

@ -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 && (

View 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,
},
})
})
})
})

View 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
}

View File

@ -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>

View File

@ -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,
}
}

View File

@ -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),
])
}
}

View File

@ -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,

View File

@ -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(

View File

@ -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}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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])

View File

@ -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(({

View File

@ -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
}, [])

View File

@ -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

View File

@ -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' />

View File

@ -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 })),
}))
}

View File

@ -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
}

View File

@ -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
})

View File

@ -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])

View File

@ -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

View File

@ -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: {

View File

@ -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...',

View File

@ -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',

View File

@ -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',

View File

@ -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: 'افزودن یادداشت',

View File

@ -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 lerreur',
},
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 lexé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 litération prennent en charge lexé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',

View File

@ -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: 'नोट जोड़ें',

View File

@ -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',

View File

@ -224,6 +224,8 @@ const translation = {
description: 'コードジェネレーターは、設定されたモデルを使用して指示に基づいて高品質なコードを生成します。明確で詳細な指示を提供してください。',
instruction: '指示',
instructionPlaceholder: '生成したいコードの詳細な説明を入力してください。',
noDataLine1: '左側に使用例を記入してください,',
noDataLine2: 'コードのプレビューがこちらに表示されます。',
generate: '生成',
generatedCodeTitle: '生成されたコード',
loading: 'コードを生成中...',

View File

@ -39,10 +39,10 @@ const translation = {
workflowWarning: '現在ベータ版です',
chatbotType: 'チャットボットのオーケストレーション方法',
basic: '基本',
basicTip: '初心者向け。後で Chatflow に切り替えることができます',
basicTip: '初心者向け。後で「チャットフロー」に切り替えることができます',
basicFor: '初心者向け',
basicDescription: '基本オーケストレートは、組み込みのプロンプトを変更する機能がなく、簡単な設定を使用してチャットボット アプリをオーケストレートします。初心者向けです。',
advanced: 'Chatflow',
advanced: 'チャットフロー',
advancedFor: '上級ユーザー向け',
advancedDescription: 'ワークフロー オーケストレートは、ワークフロー形式でチャットボットをオーケストレートし、組み込みのプロンプトを編集する機能を含む高度なカスタマイズを提供します。経験豊富なユーザー向けです。',
captionName: 'アプリのアイコンと名前',

View File

@ -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: 'コメントを追加',

View File

@ -169,7 +169,7 @@ const translation = {
deleteConfirmTip: '확인하려면 등록된 이메일에서 다음 내용을 로 보내주세요 ',
myAccount: '내 계정',
studio: '디파이 스튜디오',
account: '계',
account: '계',
},
members: {
team: '팀',

View File

@ -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: {

View File

@ -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',

View File

@ -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: {

View File

@ -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: {

View File

@ -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: {

View File

@ -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: 'Добавить заметку',

View File

@ -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',

View File

@ -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: {

View File

@ -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: {

View File

@ -224,6 +224,8 @@ const translation = {
description: '代码生成器使用配置的模型根据您的指令生成高质量的代码。请提供清晰详细的说明。',
instruction: '指令',
instructionPlaceholder: '请输入您想要生成的代码的详细描述。',
noDataLine1: '在左侧描述您的用例,',
noDataLine2: '代码预览将在此处显示。',
generate: '生成',
generatedCodeTitle: '生成的代码',
loading: '正在生成代码...',

View File

@ -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: '添加注释',

View File

@ -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: {

View File

@ -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 = {

View File

@ -1,6 +1,6 @@
{
"name": "dify-web",
"version": "0.10.2",
"version": "0.11.0",
"private": true,
"engines": {
"node": ">=18.17.0"

View File

@ -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

View File

@ -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 } })

View 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()])
}

View File

@ -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;

View File

@ -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;

View File

@ -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