Merge branch 'feat/rag-pipeline' into deploy/rag-dev

feat/datasource
zxhlyh 12 months ago
commit 9d6371e0a3

@ -369,6 +369,7 @@ class DatasetTagsApi(DatasetApiResource):
)
parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str)
args = parser.parse_args()
args["type"] = "knowledge"
tag = TagService.update_tags(args, args.get("tag_id"))
binding_count = TagService.get_tag_binding_count(args.get("tag_id"))

@ -206,12 +206,16 @@ class DocumentAddByFileApi(DatasetApiResource):
knowledge_config = KnowledgeConfig(**args)
DocumentService.document_create_args_validate(knowledge_config)
dataset_process_rule = dataset.latest_process_rule if "process_rule" not in args else None
if not knowledge_config.original_document_id and not dataset_process_rule and not knowledge_config.process_rule:
raise ValueError("process_rule is required.")
try:
documents, batch = DocumentService.save_document_with_dataset_id(
dataset=dataset,
knowledge_config=knowledge_config,
account=dataset.created_by_account,
dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None,
dataset_process_rule=dataset_process_rule,
created_from="api",
)
except ProviderTokenNotInitError as ex:

@ -720,7 +720,7 @@ STOPWORDS = {
"",
"",
"",
" ",
" ",
"0",
"1",
"2",
@ -731,16 +731,6 @@ STOPWORDS = {
"7",
"8",
"9",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",

@ -261,7 +261,7 @@ class OracleVector(BaseVector):
words = pseg.cut(query)
current_entity = ""
for word, pos in words:
if pos in {"nr", "Ng", "eng", "nz", "n", "ORG", "v"}: # nr: 人名, ns: 地名, nt: 机构名
if pos in {"nr", "Ng", "eng", "nz", "n", "ORG", "v"}: # nr: 人名ns: 地名,nt: 机构名
current_entity += word
else:
if current_entity:

@ -86,8 +86,8 @@ class KnowledgeRetrievalNode(LLMNode):
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error="Query is required."
)
# TODO(-LAN-): Move this check outside.
# check rate limit
if self.tenant_id:
knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(self.tenant_id)
if knowledge_rate_limit.enabled:
current_time = int(time.time() * 1000)

@ -46,6 +46,8 @@ class TagService:
@staticmethod
def get_tag_by_tag_name(tag_type: str, current_tenant_id: str, tag_name: str) -> list:
if not tag_type or not tag_name:
return []
tags = (
db.session.query(Tag)
.filter(Tag.name == tag_name, Tag.tenant_id == current_tenant_id, Tag.type == tag_type)
@ -88,7 +90,7 @@ class TagService:
@staticmethod
def update_tags(args: dict, tag_id: str) -> Tag:
if TagService.get_tag_by_tag_name(args["type"], current_user.current_tenant_id, args["name"]):
if TagService.get_tag_by_tag_name(args.get("type", ""), current_user.current_tenant_id, args.get("name", "")):
raise ValueError("Tag name already exists")
tag = db.session.query(Tag).filter(Tag.id == tag_id).first()
if not tag:

@ -231,7 +231,7 @@ export const useFile = (fileConfig: FileUpload) => {
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') })
notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${file.type}` })
handleRemoveFile(uploadingFile.id)
}
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
@ -257,7 +257,7 @@ export const useFile = (fileConfig: FileUpload) => {
const handleLocalFileUpload = useCallback((file: File) => {
if (!isAllowedFileExtension(file.name, file.type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${file.type}` })
return
}
const allowedFileTypes = fileConfig.allowed_file_types

@ -22,7 +22,7 @@ import { FILE_EXTS } from '../prompt-editor/constants'
jest.mock('mime', () => ({
__esModule: true,
default: {
getExtension: jest.fn(),
getAllExtensions: jest.fn(),
},
}))
@ -58,12 +58,27 @@ describe('file-uploader utils', () => {
describe('getFileExtension', () => {
it('should get extension from mimetype', () => {
jest.mocked(mime.getExtension).mockReturnValue('pdf')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
expect(getFileExtension('file', 'application/pdf')).toBe('pdf')
})
it('should get extension from mimetype and file name 1', () => {
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
expect(getFileExtension('file.pdf', 'application/pdf')).toBe('pdf')
})
it('should get extension from mimetype with multiple ext candidates with filename hint', () => {
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
expect(getFileExtension('file.pem', 'application/x-x509-ca-cert')).toBe('pem')
})
it('should get extension from mimetype with multiple ext candidates without filename hint', () => {
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
expect(getFileExtension('file', 'application/x-x509-ca-cert')).toBe('der')
})
it('should get extension from filename if mimetype fails', () => {
jest.mocked(mime.getExtension).mockReturnValue(null)
jest.mocked(mime.getAllExtensions).mockReturnValue(null)
expect(getFileExtension('file.txt', '')).toBe('txt')
expect(getFileExtension('file.txt.docx', '')).toBe('docx')
expect(getFileExtension('file', '')).toBe('')
@ -76,157 +91,157 @@ describe('file-uploader utils', () => {
describe('getFileAppearanceType', () => {
it('should identify gif files', () => {
jest.mocked(mime.getExtension).mockReturnValue('gif')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['gif']))
expect(getFileAppearanceType('image.gif', 'image/gif'))
.toBe(FileAppearanceTypeEnum.gif)
})
it('should identify image files', () => {
jest.mocked(mime.getExtension).mockReturnValue('jpg')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpg']))
expect(getFileAppearanceType('image.jpg', 'image/jpeg'))
.toBe(FileAppearanceTypeEnum.image)
jest.mocked(mime.getExtension).mockReturnValue('jpeg')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpeg']))
expect(getFileAppearanceType('image.jpeg', 'image/jpeg'))
.toBe(FileAppearanceTypeEnum.image)
jest.mocked(mime.getExtension).mockReturnValue('png')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['png']))
expect(getFileAppearanceType('image.png', 'image/png'))
.toBe(FileAppearanceTypeEnum.image)
jest.mocked(mime.getExtension).mockReturnValue('webp')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webp']))
expect(getFileAppearanceType('image.webp', 'image/webp'))
.toBe(FileAppearanceTypeEnum.image)
jest.mocked(mime.getExtension).mockReturnValue('svg')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['svg']))
expect(getFileAppearanceType('image.svg', 'image/svgxml'))
.toBe(FileAppearanceTypeEnum.image)
})
it('should identify video files', () => {
jest.mocked(mime.getExtension).mockReturnValue('mp4')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp4']))
expect(getFileAppearanceType('video.mp4', 'video/mp4'))
.toBe(FileAppearanceTypeEnum.video)
jest.mocked(mime.getExtension).mockReturnValue('mov')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mov']))
expect(getFileAppearanceType('video.mov', 'video/quicktime'))
.toBe(FileAppearanceTypeEnum.video)
jest.mocked(mime.getExtension).mockReturnValue('mpeg')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpeg']))
expect(getFileAppearanceType('video.mpeg', 'video/mpeg'))
.toBe(FileAppearanceTypeEnum.video)
jest.mocked(mime.getExtension).mockReturnValue('webm')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webm']))
expect(getFileAppearanceType('video.web', 'video/webm'))
.toBe(FileAppearanceTypeEnum.video)
})
it('should identify audio files', () => {
jest.mocked(mime.getExtension).mockReturnValue('mp3')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp3']))
expect(getFileAppearanceType('audio.mp3', 'audio/mpeg'))
.toBe(FileAppearanceTypeEnum.audio)
jest.mocked(mime.getExtension).mockReturnValue('m4a')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['m4a']))
expect(getFileAppearanceType('audio.m4a', 'audio/mp4'))
.toBe(FileAppearanceTypeEnum.audio)
jest.mocked(mime.getExtension).mockReturnValue('wav')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['wav']))
expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav'))
.toBe(FileAppearanceTypeEnum.audio)
jest.mocked(mime.getExtension).mockReturnValue('amr')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['amr']))
expect(getFileAppearanceType('audio.amr', 'audio/AMR'))
.toBe(FileAppearanceTypeEnum.audio)
jest.mocked(mime.getExtension).mockReturnValue('mpga')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpga']))
expect(getFileAppearanceType('audio.mpga', 'audio/mpeg'))
.toBe(FileAppearanceTypeEnum.audio)
})
it('should identify code files', () => {
jest.mocked(mime.getExtension).mockReturnValue('html')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['html']))
expect(getFileAppearanceType('index.html', 'text/html'))
.toBe(FileAppearanceTypeEnum.code)
})
it('should identify PDF files', () => {
jest.mocked(mime.getExtension).mockReturnValue('pdf')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
expect(getFileAppearanceType('doc.pdf', 'application/pdf'))
.toBe(FileAppearanceTypeEnum.pdf)
})
it('should identify markdown files', () => {
jest.mocked(mime.getExtension).mockReturnValue('md')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['md']))
expect(getFileAppearanceType('file.md', 'text/markdown'))
.toBe(FileAppearanceTypeEnum.markdown)
jest.mocked(mime.getExtension).mockReturnValue('markdown')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['markdown']))
expect(getFileAppearanceType('file.markdown', 'text/markdown'))
.toBe(FileAppearanceTypeEnum.markdown)
jest.mocked(mime.getExtension).mockReturnValue('mdx')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mdx']))
expect(getFileAppearanceType('file.mdx', 'text/mdx'))
.toBe(FileAppearanceTypeEnum.markdown)
})
it('should identify excel files', () => {
jest.mocked(mime.getExtension).mockReturnValue('xlsx')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xlsx']))
expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
.toBe(FileAppearanceTypeEnum.excel)
jest.mocked(mime.getExtension).mockReturnValue('xls')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xls']))
expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel'))
.toBe(FileAppearanceTypeEnum.excel)
})
it('should identify word files', () => {
jest.mocked(mime.getExtension).mockReturnValue('doc')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['doc']))
expect(getFileAppearanceType('doc.doc', 'application/msword'))
.toBe(FileAppearanceTypeEnum.word)
jest.mocked(mime.getExtension).mockReturnValue('docx')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['docx']))
expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
.toBe(FileAppearanceTypeEnum.word)
})
it('should identify word files', () => {
jest.mocked(mime.getExtension).mockReturnValue('ppt')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['ppt']))
expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint'))
.toBe(FileAppearanceTypeEnum.ppt)
jest.mocked(mime.getExtension).mockReturnValue('pptx')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pptx']))
expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
.toBe(FileAppearanceTypeEnum.ppt)
})
it('should identify document files', () => {
jest.mocked(mime.getExtension).mockReturnValue('txt')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['txt']))
expect(getFileAppearanceType('file.txt', 'text/plain'))
.toBe(FileAppearanceTypeEnum.document)
jest.mocked(mime.getExtension).mockReturnValue('csv')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['csv']))
expect(getFileAppearanceType('file.csv', 'text/csv'))
.toBe(FileAppearanceTypeEnum.document)
jest.mocked(mime.getExtension).mockReturnValue('msg')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['msg']))
expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook'))
.toBe(FileAppearanceTypeEnum.document)
jest.mocked(mime.getExtension).mockReturnValue('eml')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['eml']))
expect(getFileAppearanceType('file.eml', 'message/rfc822'))
.toBe(FileAppearanceTypeEnum.document)
jest.mocked(mime.getExtension).mockReturnValue('xml')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xml']))
expect(getFileAppearanceType('file.xml', 'application/rssxml'))
.toBe(FileAppearanceTypeEnum.document)
jest.mocked(mime.getExtension).mockReturnValue('epub')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['epub']))
expect(getFileAppearanceType('file.epub', 'application/epubzip'))
.toBe(FileAppearanceTypeEnum.document)
})
it('should handle null mime extension', () => {
jest.mocked(mime.getExtension).mockReturnValue(null)
jest.mocked(mime.getAllExtensions).mockReturnValue(null)
expect(getFileAppearanceType('file.txt', 'text/plain'))
.toBe(FileAppearanceTypeEnum.document)
})
@ -360,7 +375,7 @@ describe('file-uploader utils', () => {
describe('isAllowedFileExtension', () => {
it('should validate allowed file extensions', () => {
jest.mocked(mime.getExtension).mockReturnValue('pdf')
jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
expect(isAllowedFileExtension(
'test.pdf',
'application/pdf',

@ -42,19 +42,38 @@ export const fileUpload: FileUpload = ({
})
}
const additionalExtensionMap = new Map<string, string[]>([
['text/x-markdown', ['md']],
])
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
let extension = ''
if (fileMimetype)
extension = mime.getExtension(fileMimetype) || ''
if (fileName && !extension) {
let extensions = new Set<string>()
if (fileMimetype) {
const extensionsFromMimeType = mime.getAllExtensions(fileMimetype) || new Set<string>()
const additionalExtensions = additionalExtensionMap.get(fileMimetype) || []
extensions = new Set<string>([
...extensionsFromMimeType,
...additionalExtensions,
])
}
let extensionInFileName = ''
if (fileName) {
const fileNamePair = fileName.split('.')
const fileNamePairLength = fileNamePair.length
if (fileNamePairLength > 1)
extension = fileNamePair[fileNamePairLength - 1]
if (fileNamePairLength > 1) {
extensionInFileName = fileNamePair[fileNamePairLength - 1].toLowerCase()
if (extensions.has(extensionInFileName))
extension = extensionInFileName
}
}
if (!extension) {
if (extensions.size > 0)
extension = extensions.values().next().value.toLowerCase()
else
extension = ''
extension = extensionInFileName
}
if (isRemote)

@ -33,5 +33,6 @@ export const preprocessThinkTag = (content: string) => {
return flow([
(str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'),
(str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'),
(str: string) => str.replace(/(<\/details>)(?![^\S\r\n]*[\r\n])(?![^\S\r\n]*$)/g, '$1\n'),
])(content)
}

@ -192,15 +192,15 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- original_document_id が渡されない場合、新しい操作が実行され、process_rule が必要です。
- <code>indexing_technique</code> インデックスモード
- <code>high_quality</code> 高品質: 埋め込みモデルを使用してベクトルデータベースインデックスを構築
- <code>economy</code> 経済: キーワードテーブルインデックスの反転インデックスを構築
- <code>high_quality</code> 高品質埋め込みモデルを使用してベクトルデータベースインデックスを構築
- <code>economy</code> 経済キーワードテーブルインデックスの反転インデックスを構築
- <code>doc_form</code> インデックス化された内容の形式
- <code>text_model</code> テキストドキュメントは直接埋め込まれます; `economy` モードではこの形式がデフォルト
- <code>hierarchical_model</code> 親子モード
- <code>qa_model</code> Q&A モード: 分割されたドキュメントの質問と回答ペアを生成し、質問を埋め込みます
- <code>qa_model</code> Q&A モード分割されたドキュメントの質問と回答ペアを生成し、質問を埋め込みます
- <code>doc_language</code> Q&A モードでは、ドキュメントの言語を指定します。例: <code>English</code>, <code>Chinese</code>
- <code>doc_language</code> Q&A モードでは、ドキュメントの言語を指定します。例<code>English</code>, <code>Chinese</code>
- <code>process_rule</code> 処理ルール
- <code>mode</code> (string) クリーニング、セグメンテーションモード、自動 / カスタム
@ -214,7 +214,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>segmentation</code> (object) セグメンテーションルール
- <code>separator</code> カスタムセグメント識別子。現在は 1 つの区切り文字のみ設定可能。デフォルトは \n
- <code>max_tokens</code> 最大長 (トークン) デフォルトは 1000
- <code>parent_mode</code> 親チャンクの検索モード: <code>full-doc</code> 全文検索 / <code>paragraph</code> 段落検索
- <code>parent_mode</code> 親チャンクの検索モード<code>full-doc</code> 全文検索 / <code>paragraph</code> 段落検索
- <code>subchunk_segmentation</code> (object) 子チャンクルール
- <code>separator</code> セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは <code>***</code>
- <code>max_tokens</code> 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります
@ -324,7 +324,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>partial_members</code> 一部のメンバー
</Property>
<Property name='provider' type='string' key='provider'>
プロバイダー (オプション、デフォルト: vendor)
プロバイダー (オプション、デフォルトvendor)
- <code>vendor</code> ベンダー
- <code>external</code> 外部ナレッジ
</Property>

@ -60,7 +60,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
<Property name='files' type='array[object]' key='files'>
上传的文件。
- `type` (string) 支持类型:图片 `image`(目前仅支持图片格式) 。
- `transfer_method` (string) 传递方式:
- `transfer_method` (string) 传递方式
- `remote_url`: 图片地址。
- `local_file`: 上传文件。
- `url` 图片地址。(仅当传递方式为 `remote_url` 时)。
@ -622,10 +622,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme` (string) 聊天颜色主题hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_type` (string) 图标类型`emoji`-表情,`image`-图片
- `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
@ -879,7 +879,7 @@ ___
动作,只能是 'enable' 或 'disable'
</Property>
<Property name='embedding_provider_name' type='string' key='embedding_provider_name'>
指定的嵌入模型提供商, 必须先在系统内设定好接入的模型对应的是provider字段
指定的嵌入模型提供商必须先在系统内设定好接入的模型,对应的是 provider 字段
</Property>
<Property name='embedding_model_name' type='string' key='embedding_model_name'>
指定的嵌入模型,对应的是 model 字段

@ -1347,10 +1347,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme` (string) 聊天颜色主题hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_type` (string) 图标类型`emoji`-表情,`image`-图片
- `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
@ -1604,7 +1604,7 @@ ___
动作,只能是 'enable' 或 'disable'
</Property>
<Property name='embedding_provider_name' type='string' key='embedding_provider_name'>
指定的嵌入模型提供商, 必须先在系统内设定好接入的模型对应的是provider字段
指定的嵌入模型提供商必须先在系统内设定好接入的模型,对应的是 provider 字段
</Property>
<Property name='embedding_model_name' type='string' key='embedding_model_name'>
指定的嵌入模型,对应的是 model 字段

@ -1353,10 +1353,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme` (string) 聊天颜色主题hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_type` (string) 图标类型`emoji`-表情,`image`-图片
- `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述

@ -534,7 +534,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
- `workflow_run` (object) Workflow 执行日志
- `id` (string) 标识
- `version` (string) 版本
- `status` (string) 执行状态, `running` / `succeeded` / `failed` / `stopped`
- `status` (string) 执行状态`running` / `succeeded` / `failed` / `stopped`
- `error` (string) (可选) 错误
- `elapsed_time` (float) 耗时,单位秒
- `total_tokens` (int) 消耗的 token 数量
@ -741,8 +741,8 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_type` (string) 图标类型`emoji`-表情,`image`-图片
- `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述

@ -28,7 +28,7 @@ const FieldListContainer = ({
return inputFields.map((content) => {
return ({
id: content.variable,
name: content.variable,
...content,
})
})
}, [inputFields])
@ -40,6 +40,7 @@ const FieldListContainer = ({
setList={onListSortChange}
handle='.handle'
ghostClass='opacity-50'
group='rag-pipeline-input-field'
animation={150}
disabled={readonly}
>

@ -36,9 +36,10 @@ export const useFieldList = (
const handleListSortChange = useCallback((list: SortableItem[]) => {
const newInputFields = list.map((item) => {
return inputFieldsRef.current.find(field => field.variable === item.name)
const { id, ...filed } = item
return filed
})
handleInputFieldsChange(newInputFields as InputVar[])
handleInputFieldsChange(newInputFields)
}, [handleInputFieldsChange])
const [editingField, setEditingField] = useState<InputVar | undefined>()
@ -62,12 +63,12 @@ export const useFieldList = (
setRemoveIndex(index as number)
return
}
const newInputFields = inputFieldsRef.current.splice(index, 1)
const newInputFields = inputFieldsRef.current.filter((_, i) => i !== index)
handleInputFieldsChange(newInputFields)
}, [handleInputFieldsChange, isVarUsedInNodes, nodeId, showRemoveVarConfirm])
const onRemoveVarConfirm = useCallback(() => {
const newInputFields = inputFieldsRef.current.splice(removedIndex, 1)
const newInputFields = inputFieldsRef.current.filter((_, i) => i !== removedIndex)
handleInputFieldsChange(newInputFields)
removeUsedVarInNodes(removedVar)
hideRemoveVarConfirm()

@ -56,16 +56,14 @@ const FieldList = ({
<RiAddLine className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</div>
{inputFields.length > 0 && (
<FieldListContainer
className='flex flex-col gap-y-1 px-4 pb-2'
className='flex flex-col gap-y-1 px-4 pb-1'
inputFields={inputFields}
onEditField={handleOpenInputFieldEditor}
onRemoveField={handleRemoveField}
onListSortChange={handleListSortChange}
readonly={readonly}
/>
)}
{showInputFieldEditor && (
<InputFieldEditor
show={showInputFieldEditor}

@ -1,4 +1,5 @@
import type { InputVar } from '@/models/pipeline'
export type SortableItem = {
id: string
name: string
}
} & InputVar

@ -2,22 +2,30 @@ import {
memo,
useCallback,
useMemo,
useRef,
useState,
} from 'react'
import { useStore } from '@/app/components/workflow/store'
import { RiCloseLine } from '@remixicon/react'
import { RiCloseLine, RiEyeLine } from '@remixicon/react'
import type { Node } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import DialogWrapper from './dialog-wrapper'
import FieldList from './field-list'
import FooterTip from './footer-tip'
import SharedInputs from './label-right-content/shared-inputs'
import GlobalInputs from './label-right-content/global-inputs'
import Datasource from './label-right-content/datasource'
import { useNodes } from 'reactflow'
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
// import produce from 'immer'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
import type { InputVar, RAGPipelineVariables } from '@/models/pipeline'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import Tooltip from '@/app/components/base/tooltip'
import cn from '@/utils/classnames'
import PreviewPanel from './preview'
import { useDebounceFn, useUnmount } from 'ahooks'
type InputFieldDialogProps = {
readonly?: boolean
@ -32,19 +40,9 @@ const InputFieldDialog = ({
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
const ragPipelineVariables = useStore(state => state.ragPipelineVariables)
const setRagPipelineVariables = useStore(state => state.setRagPipelineVariables)
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const [previewPanelOpen, setPreviewPanelOpen] = useState(false)
const datasourceNodeDataMap = useMemo(() => {
const datasourceNodeDataMap: Record<string, DataSourceNodeType> = {}
const datasourceNodes: Node<DataSourceNodeType>[] = nodes.filter(node => node.data.type === BlockEnum.DataSource)
datasourceNodes.forEach((node) => {
const { id, data } = node
datasourceNodeDataMap[id] = data
})
return datasourceNodeDataMap
}, [nodes])
const inputFieldsMap = useMemo(() => {
const getInputFieldsMap = () => {
const inputFieldsMap: Record<string, InputVar[]> = {}
ragPipelineVariables?.forEach((variable) => {
const { belong_to_node_id: nodeId, ...varConfig } = variable
@ -54,15 +52,36 @@ const InputFieldDialog = ({
inputFieldsMap[nodeId] = [varConfig]
})
return inputFieldsMap
}, [ragPipelineVariables])
}
const inputFieldsMap = useRef(getInputFieldsMap())
const updateInputFields = useCallback(async (key: string, value: InputVar[]) => {
const NewInputFieldsMap = produce(inputFieldsMap, (draft) => {
draft[key] = value
const { doSyncWorkflowDraft } = useNodesSyncDraft()
useUnmount(async () => {
await doSyncWorkflowDraft()
})
const { run: syncWorkflowDraft } = useDebounceFn(() => {
doSyncWorkflowDraft()
}, {
wait: 500,
})
const datasourceNodeDataMap = useMemo(() => {
const datasourceNodeDataMap: Record<string, DataSourceNodeType> = {}
const datasourceNodes: Node<DataSourceNodeType>[] = nodes.filter(node => node.data.type === BlockEnum.DataSource)
datasourceNodes.forEach((node) => {
const { id, data } = node
datasourceNodeDataMap[id] = data
})
return datasourceNodeDataMap
}, [nodes])
const updateInputFields = useCallback((key: string, value: InputVar[]) => {
inputFieldsMap.current[key] = value
const newRagPipelineVariables: RAGPipelineVariables = []
Object.keys(NewInputFieldsMap).forEach((key) => {
const inputFields = NewInputFieldsMap[key]
Object.keys(inputFieldsMap.current).forEach((key) => {
const inputFields = inputFieldsMap.current[key]
inputFields.forEach((inputField) => {
newRagPipelineVariables.push({
...inputField,
@ -71,14 +90,19 @@ const InputFieldDialog = ({
})
})
setRagPipelineVariables?.(newRagPipelineVariables)
await doSyncWorkflowDraft()
}, [doSyncWorkflowDraft, inputFieldsMap, setRagPipelineVariables])
syncWorkflowDraft()
}, [setRagPipelineVariables, syncWorkflowDraft])
const closePanel = useCallback(() => {
setShowInputFieldDialog?.(false)
}, [setShowInputFieldDialog])
const togglePreviewPanel = useCallback(() => {
setPreviewPanelOpen(prev => !prev)
}, [])
return (
<>
<DialogWrapper
show={!!showInputFieldDialog}
onClose={closePanel}
@ -88,6 +112,19 @@ const InputFieldDialog = ({
<div className='system-xl-semibold grow'>
{t('datasetPipeline.inputFieldPanel.title')}
</div>
<Button
variant={'ghost'}
size='small'
className={cn(
'shrink-0 gap-x-px px-1.5',
previewPanelOpen && 'bg-state-accent-active text-text-accent',
)}
onClick={togglePreviewPanel}
>
<RiEyeLine className='size-3.5' />
<span className='px-[3px]'>{t('datasetPipeline.operations.preview')}</span>
</Button>
<Divider type='vertical' className='mx-1 h-3' />
<button
type='button'
className='flex size-6 shrink-0 items-center justify-center p-0.5'
@ -96,14 +133,24 @@ const InputFieldDialog = ({
<RiCloseLine className='size-4 text-text-tertiary' />
</button>
</div>
<div className='system-sm-regular px-4 py-1 text-text-tertiary'>
<div className='system-sm-regular px-4 pb-2 pt-1 text-text-tertiary'>
{t('datasetPipeline.inputFieldPanel.description')}
</div>
<div className='flex grow flex-col overflow-y-auto'>
{/* Datasources Inputs */}
{/* Unique Inputs for Each Entrance */}
<div className='flex h-6 items-center gap-x-0.5 px-4 pt-2'>
<span className='system-sm-semibold-uppercase text-text-secondary'>
{t('datasetPipeline.inputFieldPanel.uniqueInputs.title')}
</span>
<Tooltip
popupContent={t('datasetPipeline.inputFieldPanel.uniqueInputs.tooltip')}
popupClassName='max-w-[240px]'
/>
</div>
<div className='flex flex-col gap-y-1 py-1'>
{
Object.keys(datasourceNodeDataMap).map((key) => {
const inputFields = inputFieldsMap[key] || []
const inputFields = inputFieldsMap.current[key] || []
return (
<FieldList
key={key}
@ -111,25 +158,33 @@ const InputFieldDialog = ({
LabelRightContent={<Datasource nodeData={datasourceNodeDataMap[key]} />}
inputFields={inputFields}
readonly={readonly}
labelClassName='pt-2 pb-1'
labelClassName='pt-1 pb-1'
handleInputFieldsChange={updateInputFields}
/>
)
})
}
{/* Shared Inputs */}
</div>
{/* Global Inputs */}
<FieldList
nodeId='shared'
LabelRightContent={<SharedInputs />}
inputFields={inputFieldsMap.shared || []}
LabelRightContent={<GlobalInputs />}
inputFields={inputFieldsMap.current.shared || []}
readonly={readonly}
labelClassName='pt-1 pb-2'
labelClassName='pt-2 pb-1'
handleInputFieldsChange={updateInputFields}
/>
</div>
<FooterTip />
</div>
</DialogWrapper>
{previewPanelOpen && (
<PreviewPanel
show={previewPanelOpen}
onClose={togglePreviewPanel}
/>
)}
</>
)
}

@ -2,20 +2,20 @@ import Tooltip from '@/app/components/base/tooltip'
import React from 'react'
import { useTranslation } from 'react-i18next'
const SharedInputs = () => {
const GlobalInputs = () => {
const { t } = useTranslation()
return (
<div className='flex items-center gap-x-1'>
<span className='system-sm-semibold-uppercase text-text-secondary'>
{t('datasetPipeline.inputFieldPanel.sharedInputs.title')}
{t('datasetPipeline.inputFieldPanel.globalInputs.title')}
</span>
<Tooltip
popupContent={t('datasetPipeline.inputFieldPanel.sharedInputs.tooltip')}
popupClassName='!w-[300px]'
popupContent={t('datasetPipeline.inputFieldPanel.globalInputs.tooltip')}
popupClassName='w-[240px]'
/>
</div>
)
}
export default React.memo(SharedInputs)
export default React.memo(GlobalInputs)

@ -0,0 +1,16 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
const DataSource = () => {
const { t } = useTranslation()
return (
<div className='flex flex-col'>
<div className='system-sm-semibold-uppercase px-4 pt-2 text-text-secondary'>
{t('datasetPipeline.inputFieldPanel.preview.stepOneTitle')}
</div>
</div>
)
}
export default React.memo(DataSource)

@ -0,0 +1,54 @@
import { Fragment, useCallback } from 'react'
import type { ReactNode } from 'react'
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react'
import cn from '@/utils/classnames'
type DialogWrapperProps = {
className?: string
panelWrapperClassName?: string
children: ReactNode
show: boolean
onClose?: () => void
}
const DialogWrapper = ({
className,
panelWrapperClassName,
children,
show,
onClose,
}: DialogWrapperProps) => {
const close = useCallback(() => onClose?.(), [onClose])
return (
<Transition appear show={show} as={Fragment}>
<Dialog as='div' className='relative z-40' onClose={close}>
<TransitionChild>
<div className={cn(
'fixed inset-0 bg-black/25',
'data-[closed]:opacity-0',
'data-[enter]:opacity-100 data-[enter]:duration-300 data-[enter]:ease-out',
'data-[leave]:opacity-0 data-[leave]:duration-200 data-[leave]:ease-in',
)} />
</TransitionChild>
<div className='fixed inset-0'>
<div className={cn('flex min-h-full flex-col items-end justify-start pb-1 pt-[116px]', panelWrapperClassName)}>
<TransitionChild>
<DialogPanel className={cn(
'relative flex w-[480px] grow flex-col overflow-hidden rounded-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl shadow-shadow-shadow-5 transition-all',
'data-[closed]:scale-95 data-[closed]:opacity-0',
'data-[enter]:scale-100 data-[enter]:opacity-100 data-[enter]:duration-300 data-[enter]:ease-out',
'data-[leave]:scale-95 data-[leave]:opacity-0 data-[leave]:duration-200 data-[leave]:ease-in',
className,
)}>
{children}
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</Transition >
)
}
export default DialogWrapper

@ -0,0 +1,41 @@
import { RiCloseLine } from '@remixicon/react'
import DialogWrapper from './dialog-wrapper'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
type PreviewPanelProps = {
show: boolean
onClose: () => void
}
const PreviewPanel = ({
show,
onClose,
}: PreviewPanelProps) => {
const { t } = useTranslation()
return (
<DialogWrapper
show={show}
onClose={onClose}
panelWrapperClassName='pr-[424px]'
>
<div className='flex items-center gap-x-2 px-4 pt-1'>
<div className='grow py-1'>
<Badge className='border-text-accent-secondary bg-components-badge-bg-dimm text-text-accent-secondary'>
{t('datasetPipeline.operations.preview')}
</Badge>
</div>
<button
type='button'
className='flex size-6 shrink-0 items-center justify-center'
onClick={onClose}
>
<RiCloseLine className='size-4 text-text-tertiary' />
</button>
</div>
</DialogWrapper>
)
}
export default PreviewPanel

@ -27,6 +27,8 @@ import type { PublishWorkflowParams } from '@/types/workflow'
import { useToastContext } from '@/app/components/base/toast'
import { useParams, useRouter } from 'next/navigation'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useInvalid } from '@/service/use-base'
import { publishedPipelineInfoQueryKeyPrefix } from '@/service/use-pipeline'
const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P']
@ -45,6 +47,8 @@ const Popup = () => {
const { notify } = useToastContext()
const workflowStore = useWorkflowStore()
const invalidPublishedPipelineInfo = useInvalid([...publishedPipelineInfoQueryKeyPrefix, pipelineId])
const handlePublish = useCallback(async (params?: PublishWorkflowParams) => {
if (await handleCheckBeforePublish()) {
const res = await publishWorkflow({
@ -58,12 +62,13 @@ const Popup = () => {
notify({ type: 'success', message: t('common.api.actionSuccess') })
workflowStore.getState().setPublishedAt(res.created_at)
mutateDatasetRes?.()
invalidPublishedPipelineInfo()
}
}
else {
throw new Error('Checklist failed')
}
}, [handleCheckBeforePublish, publishWorkflow, pipelineId, notify, t, workflowStore, mutateDatasetRes])
}, [handleCheckBeforePublish, publishWorkflow, pipelineId, notify, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo])
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
e.preventDefault()

@ -56,6 +56,10 @@ export const isRagVariableVar = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'rag'
}
export const isSpecialVar = (prefix: string): boolean => {
return ['sys', 'env', 'conversation', 'rag'].includes(prefix)
}
const inputVarTypeToVarType = (type: InputVarType): VarType => {
return ({
[InputVarType.number]: VarType.number,
@ -527,7 +531,7 @@ const formatItem = (
const isCurrentMatched = filterVar(v, (() => {
const variableArr = v.variable.split('.')
const [first] = variableArr
if (first === 'sys' || first === 'env' || first === 'conversation' || first === 'rag')
if (isSpecialVar(first))
return variableArr
return [...selector, ...variableArr]

@ -18,7 +18,7 @@ import { checkKeys } from '@/utils/var'
import type { StructuredOutput } from '../../../llm/types'
import { Type } from '../../../llm/types'
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
import { varTypeToStructType } from './utils'
import { isSpecialVar, varTypeToStructType } from './utils'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import { FILE_STRUCT } from '@/app/components/workflow/constants'
import { Loop } from '@/app/components/base/icons/src/vender/workflow'
@ -286,7 +286,7 @@ const VarReferenceVars: FC<Props> = ({
}
const filteredVars = vars.filter((v) => {
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.') || v.variable.startsWith('rag.'))
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
return children.length > 0
}).filter((node) => {
if (!searchText)
@ -297,7 +297,7 @@ const VarReferenceVars: FC<Props> = ({
})
return children.length > 0
}).map((node) => {
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.') || v.variable.startsWith('rag.'))
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
if (searchText) {
const searchTextLower = searchText.toLowerCase()
if (!node.title.toLowerCase().includes(searchTextLower))

@ -86,7 +86,7 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
return (
<div >
{
!isAuthorized && !showAuthModal && (
!isAuthorized && !showAuthModal && !isLocalFile && (
<Group>
<Button
variant='primary'
@ -100,7 +100,7 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
)
}
{
isAuthorized && (
isAuthorized && !isLocalFile && (
<GroupField
groupProps={{
withBorderBottom: true,
@ -166,7 +166,7 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
}
</OutputVars>
{
showAuthModal && (
showAuthModal && !isLocalFile && (
<ConfigCredential
dataSourceItem={currentDataSource!}
onCancel={hideAuthModal}

@ -18,10 +18,16 @@ const nodeDefault: NodeDefault<KnowledgeBaseNodeType> = {
score_threshold: 0.5,
},
},
checkValid() {
checkValid(payload) {
const { chunk_structure } = payload
let errorMessage = ''
if (!chunk_structure)
errorMessage = 'Chunk structure is required.'
return {
isValid: true,
errorMessage: '',
isValid: !errorMessage,
errorMessage,
}
},
}

@ -172,7 +172,7 @@ const InputVarList: FC<Props> = ({
return (
<div key={variable} className='space-y-1'>
<div className='flex h-[18px] items-center space-x-2'>
<div className='flex items-center space-x-2 leading-[18px]'>
<span className='code-sm-semibold text-text-secondary'>{label[language] || label.en_US}</span>
<span className='system-xs-regular text-text-tertiary'>{paramType(type)}</span>
{required && <span className='system-xs-regular text-util-colors-orange-dark-orange-dark-600'>Required</span>}

@ -27,6 +27,7 @@ const translation = {
process: 'Process',
dataSource: 'Data Source',
saveAndProcess: 'Save & Process',
preview: 'Preview',
},
knowledgeNameAndIcon: 'Knowledge name & icon',
knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base',
@ -66,12 +67,20 @@ const translation = {
inputFieldPanel: {
title: 'User Input Fields',
description: 'User input fields are used to define and collect variables required during the pipeline execution process. Users can customize the field type and flexibly configure the input value to meet the needs of different data sources or document processing steps.',
sharedInputs: {
title: 'Shared Inputs',
tooltip: 'Shared Inputs are available to all downstream nodes across data sources. For example, variables like delimiter and maximum chunk length can be uniformly applied when processing documents from multiple sources.',
uniqueInputs: {
title: 'Unique Inputs for Each Entrance',
tooltip: 'Unique Inputs are only accessible to the selected data source and its downstream nodes. Users won\'t need to fill it in when choosing other data sources. Only input fields referenced by data source variables will appear in the first step(Data Source). All other fields will be shown in the second step(Process Documents).',
},
globalInputs: {
title: 'Global Inputs for All Entrances',
tooltip: 'Global Inputs are shared across all nodes. Users will need to fill them in when selecting any data source. For example, fields like delimiter and maximum chunk length can be uniformly applied across multiple data sources. Only input fields referenced by Data Source variables appear in the first step (Data Source). All other fields show up in the second step (Process Documents).',
},
addInputField: 'Add Input Field',
editInputField: 'Edit Input Field',
preview: {
stepOneTitle: 'Data Source',
stepTwoTitle: 'Process Documents',
},
},
addDocuments: {
title: 'Add Documents',

@ -24,7 +24,7 @@ const translation = {
desc: {
front: 'Your information and use of Education Verified status are subject to our',
and: 'and',
end: '. By submitting',
end: '. By submitting:',
termsOfService: 'Terms of Service',
privacyPolicy: 'Privacy Policy',
},

@ -1,6 +1,6 @@
const translation = {
title: 'ज्ञान सेटिंग्ज',
desc: 'यहां आप ज्ञान की संपत्ति और कार्य प्रक्रियाओं को modify कर सकते हैं',
desc: 'यहां आप ज्ञान की संपत्ति और कार्य प्रक्रियाओं को modify कर सकते हैं.',
form: {
name: 'ज्ञान नाम',
namePlaceholder: 'कृपया ज्ञान नाम दर्ज करें',

@ -44,7 +44,7 @@ const translation = {
title: '一括インポート',
csvUploadTitle: 'CSV ファイルをここにドラッグ&ドロップするか、',
browse: '参照',
tip: 'CSVファイルは以下の構造に準拠する必要があります:',
tip: 'CSV ファイルは以下の構造に準拠する必要があります',
question: '質問',
answer: '回答',
contentTitle: 'チャンクの内容',

@ -228,7 +228,7 @@ const translation = {
description: 'コードジェネレーターは、設定されたモデルを使用して指示に基づいて高品質なコードを生成します。明確で詳細な指示を提供してください。',
instruction: '指示',
instructionPlaceholder: '生成したいコードの詳細な説明を入力してください。',
noDataLine1: '左側に使用例を記入してください,',
noDataLine1: '左側に使用例を記入してください',
noDataLine2: 'コードのプレビューがこちらに表示されます。',
generate: '生成',
generatedCodeTitle: '生成されたコード',
@ -247,7 +247,7 @@ const translation = {
instructionPlaceHolder: '具体的で明確な指示を入力してください。',
generate: '生成',
resTitle: '生成されたプロンプト',
noDataLine1: '左側に使用例を記入してください,',
noDataLine1: '左側に使用例を記入してください',
noDataLine2: 'オーケストレーションのプレビューがこちらに表示されます。',
apply: '適用',
noData: '左側にユースケースを入力すると、こちらでプレビューができます。',
@ -298,7 +298,7 @@ const translation = {
message: '変更が破棄され、最後に公開された構成が復元されます。',
},
errorMessage: {
nameOfKeyRequired: 'キーの名前: {{key}} が必要です',
nameOfKeyRequired: 'キーの名前{{key}} が必要です',
valueOfVarRequired: '{{key}} の値は空にできません',
queryRequired: 'リクエストテキストが必要です。',
waitForResponse: '前のメッセージへの応答が完了するまでお待ちください。',
@ -376,7 +376,7 @@ const translation = {
custom: {
name: '他のファイルタイプ',
description: '他のファイルタイプを指定する。',
createPlaceholder: '+ 拡張子, 例:.doc',
createPlaceholder: '+ 拡張子例:.doc',
},
},
'uploadFileTypes': 'アップロードされたファイルのタイプ',

@ -73,9 +73,9 @@ const translation = {
appCreateFailed: 'アプリの作成に失敗しました',
Confirm: '確認する',
caution: '注意',
appCreateDSLErrorPart2: '続行しますか?',
appCreateDSLErrorPart4: 'システムがサポートするDSLバージョン:',
appCreateDSLErrorPart3: '現在のアプリケーションの DSL バージョン:',
appCreateDSLErrorPart2: '続行しますか',
appCreateDSLErrorPart4: 'システムがサポートする DSL バージョン:',
appCreateDSLErrorPart3: '現在のアプリケーションの DSL バージョン',
appCreateDSLErrorTitle: 'バージョンの非互換性',
appCreateDSLWarning: '注意:DSL のバージョンの違いは、特定の機能に影響を与える可能性があります',
appCreateDSLErrorPart1: 'DSL バージョンに大きな違いが検出されました。インポートを強制すると、アプリケーションが誤動作する可能性があります。',
@ -95,7 +95,7 @@ const translation = {
forAdvanced: '上級ユーザー向け',
chooseAppType: 'アプリタイプを選択',
learnMore: '詳細情報',
noIdeaTip: 'アイデアがありませんか?テンプレートをご覧ください',
noIdeaTip: 'アイデアがありませんかテンプレートをご覧ください',
chatbotShortDescription: '簡単なセットアップの LLM ベースのチャットボット',
chatbotUserDescription: '簡単な設定で LLM ベースのチャットボットを迅速に構築します。Chatflow は後で切り替えることができます。',
workflowUserDescription: 'ドラッグ&ドロップの簡易性で自律型 AI ワークフローを視覚的に構築',

@ -124,7 +124,7 @@ const translation = {
description: 'オープンソース版の無料プラン',
price: '無料',
btnText: 'コミュニティ版を始めましょう',
includesTitle: '無料機能:',
includesTitle: '無料機能',
features: [
'パブリックリポジトリの全コア機能',
'シングルワークスペース',
@ -138,7 +138,7 @@ const translation = {
price: '従量制',
priceTip: 'クラウドマーケットプレイス基準',
btnText: 'プレミアム版を取得',
includesTitle: 'コミュニティ版機能に加えて:',
includesTitle: 'コミュニティ版機能に加えて',
comingSoon: 'Microsoft Azure & Google Cloud 近日対応',
features: [
'クラウドプロバイダーによる自己管理',
@ -154,7 +154,7 @@ const translation = {
price: 'カスタム',
priceTip: '年間契約専用',
btnText: '営業に相談',
includesTitle: 'プレミアム版機能に加えて:',
includesTitle: 'プレミアム版機能に加えて',
features: [
'エンタープライズ向け拡張ソリューション',
'商用ライセンス認可',

@ -565,10 +565,10 @@ const translation = {
citation: {
title: '引用',
linkToDataset: 'ナレッジへのリンク',
characters: '文字数:',
hitCount: '検索回数:',
vectorHash: 'ベクトルハッシュ:',
hitScore: '検索スコア:',
characters: '文字数',
hitCount: '検索回数',
vectorHash: 'ベクトルハッシュ',
hitScore: '検索スコア',
},
inputPlaceholder: '{{botName}} と話す',
thought: '思考',

@ -85,9 +85,9 @@ const translation = {
excludePaths: 'パスを除外する',
includeOnlyPaths: 'パスのみを含める',
extractOnlyMainContent: 'メインコンテンツのみを抽出する (ヘッダー、ナビ、フッターなどは抽出しない)',
exceptionErrorTitle: 'Firecrawl ジョブの実行中に例外が発生しました:',
exceptionErrorTitle: 'Firecrawl ジョブの実行中に例外が発生しました',
unknownError: '不明なエラー',
totalPageScraped: 'スクレイピングされた総ページ数:',
totalPageScraped: 'スクレイピングされた総ページ数',
selectAll: 'すべて選択',
resetAll: 'すべてリセット',
scrapTimeInfo: '{{time}} 秒以内に合計 {{total}} ページをスクレイピングしました',
@ -175,7 +175,7 @@ const translation = {
separatorTip: '区切り文字は、テキストを区切るために使用される文字です。\\n\\n と \\n は、段落と行を区切るために一般的に使用される区切り記号です。カンマ (\\n\\n,\\n) と組み合わせると、最大チャンク長を超えると、段落は行で区切られます。自分で定義した特別な区切り文字を使用することもできます (例:***)。',
maxLengthCheck: 'チャンクの最大長は {{limit}} 未満にする必要があります',
previewChunkTip: 'プレビューを読み込むには、左側の \'チャンクをプレビュー\' ボタンをクリックしてください',
previewChunkCount: '推定チャンク数: {{count}}',
previewChunkCount: '推定チャンク数{{count}}',
switch: '切り替え',
qaSwitchHighQualityTipTitle: 'Q&A 形式には高品質なインデックスが必要です',
qaSwitchHighQualityTipContent: '現在、高品質なインデックス作成のみが Q&A 形式の分割をサポートしています。高品質モードに切り替えますか?',

@ -65,7 +65,7 @@ const translation = {
title: '一括追加',
csvUploadTitle: 'CSV ファイルをここにドラッグアンドドロップするか、',
browse: '参照',
tip: 'CSVファイルは次の構造に準拠する必要があります:',
tip: 'CSV ファイルは次の構造に準拠する必要があります',
question: '質問',
answer: '回答',
contentTitle: 'チャンクの内容',
@ -359,7 +359,7 @@ const translation = {
characters_one: '文字',
characters_other: '文字',
hitCount: '検索回数',
vectorHash: 'ベクトルハッシュ: ',
vectorHash: 'ベクトルハッシュ',
questionPlaceholder: 'ここに質問を追加',
questionEmpty: '質問は空にできません',
answerPlaceholder: 'ここに回答を追加',

@ -76,7 +76,7 @@ const translation = {
verify: '確かめる',
verificationCodePlaceholder: '6 桁のコードを入力してください',
useAnotherMethod: '別の方法を使用する',
didNotReceiveCode: 'コードが届きませんか?',
didNotReceiveCode: 'コードが届きませんか',
resend: '再送',
verificationCode: '認証コード',
tips: '<strong>確認コードを{{email}}に送信します。</strong>',

@ -162,7 +162,7 @@ const translation = {
},
error: {
fetchReleasesError: 'リリースを取得できません。後でもう一度お試しください。',
inValidGitHubUrl: '無効なGitHub URLです。有効なURLを次の形式で入力してください: https://github.com/owner/repo',
inValidGitHubUrl: '無効な GitHub URL です。有効な URL を次の形式で入力してください:https://github.com/owner/repo',
noReleasesFound: 'リリースは見つかりません。GitHub リポジトリまたは入力 URL を確認してください。',
},
marketplace: {

@ -56,7 +56,7 @@ const translation = {
noData: 'AI がコンテンツを生成します',
csvUploadTitle: 'CSV ファイルをドロップするか',
browse: 'ファイルを選択',
csvStructureTitle: 'CSV形式要件:',
csvStructureTitle: 'CSV 形式要件:',
downloadTemplate: 'テンプレートを取得',
field: '',
batchFailed: {
@ -67,9 +67,9 @@ const translation = {
errorMsg: {
empty: 'ファイル内容が空です',
fileStructNotMatch: 'ファイル形式が不正です',
emptyLine: '{{rowIndex}}行目: 内容が空です',
invalidLine: '{{rowIndex}}行目: {{varName}}の入力が必要です',
moreThanMaxLengthLine: '{{rowIndex}}行目: {{varName}}が制限長({{maxLength}})を超過',
emptyLine: '{{rowIndex}}行目内容が空です',
invalidLine: '{{rowIndex}}行目{{varName}}の入力が必要です',
moreThanMaxLengthLine: '{{rowIndex}}行目{{varName}}が制限長({{maxLength}})を超過',
atLeastOne: '1 行以上のデータが必要です',
},
},

@ -40,7 +40,7 @@ const translation = {
name: '名前',
toolNamePlaceHolder: 'ツール名を入力してください',
nameForToolCall: 'ツールコールの名前',
nameForToolCallPlaceHolder: '機械認識に使用される名前, 例えば、getCurrentWeather、list_pets',
nameForToolCallPlaceHolder: '機械認識に使用される名前例えば、getCurrentWeather、list_pets',
nameForToolCallTip: '数字、文字、アンダースコアのみがサポートされます。',
description: 'ツールの説明',
descriptionPlaceholder: 'ツールの使い方の簡単な説明。例えば、特定の場所の温度を知るためなど。',

@ -336,7 +336,7 @@ const translation = {
defaultValue: {
title: 'デフォルト値',
desc: '例外発生時のデフォルト出力',
tip: '例外発生時に返される値:',
tip: '例外発生時に返される値',
inLog: 'ノード例外 - デフォルト値を出力',
output: 'デフォルト値出力',
},
@ -363,7 +363,7 @@ const translation = {
retryFailedTimes: '{{times}}回再試行失敗',
times: '回',
ms: 'ミリ秒',
retries: '再試行回数: {{num}}',
retries: '再試行回数{{num}}',
},
},
start: {
@ -623,7 +623,7 @@ const translation = {
assigner: {
'assignedVariable': '代入された変数',
'writeMode': '書き込みモード',
'writeModeTip': '代入された変数が配列の場合, 末尾に追記モードを追加する。',
'writeModeTip': '代入された変数が配列の場合末尾に追記モードを追加する。',
'over-write': '上書き',
'append': '追記',
'plus': 'プラス',
@ -732,7 +732,7 @@ const translation = {
parallelModeEnableDesc: '並列モードでは、イテレーション内のタスクは並列実行をサポートします。これは、右側のプロパティパネルで構成できます。',
parallelModeEnableTitle: 'パラレルモード有効',
MaxParallelismDesc: '最大並列処理は、1 回の反復で同時に実行されるタスクの数を制御するために使用されます。',
answerNodeWarningDesc: '並列モードの警告: 応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。',
answerNodeWarningDesc: '並列モードの警告応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。',
},
loop: {
deleteTitle: 'ループノードを削除しますか?',
@ -760,8 +760,8 @@ const translation = {
inputMode: '入力モード',
exitConditionTip: 'ループノードには少なくとも 1 つの終了条件が必要です',
loopNode: 'ループノード',
currentLoopCount: '現在のループ回数: {{count}}',
totalLoopCount: '総ループ回数: {{count}}',
currentLoopCount: '現在のループ回数{{count}}',
totalLoopCount: '総ループ回数{{count}}',
error_other: '{{count}} エラー',
error_one: '{{count}} エラー',
comma: ',',
@ -791,7 +791,7 @@ const translation = {
},
inputVar: '入力変数',
learnMore: '詳細はこちら',
supportFileTypes: 'サポートするファイルタイプ: {{types}}。',
supportFileTypes: 'サポートするファイルタイプ{{types}}。',
},
listFilter: {
outputVars: {

@ -228,7 +228,7 @@ const translation = {
'logic': 'Logic',
'transform': 'Chuyển đổi',
'utilities': 'Tiện ích',
'noResult': 'Không tìm thấy kết quả phù hợp',
'noResult': 'Không tìm thấy kế. t quả phù hợp',
'searchTool': 'Công cụ tìm kiếm',
'agent': 'Chiến lược đại lý',
'plugin': 'Plugin',

@ -1,6 +1,6 @@
const translation = {
welcome: {
firstStepTip: '开始之前,',
firstStepTip: '开始之前',
enterKeyTip: '请先在下方输入你的 OpenAI API Key',
getKeyTip: '从 OpenAI 获取你的 API Key',
placeholder: '你的 OpenAI API Key例如 sk-xxxx',

@ -23,7 +23,7 @@ const translation = {
importFromDSLFile: '文件',
importFromDSLUrl: 'URL',
importFromDSLUrlPlaceholder: '输入 DSL 文件的 URL',
deleteAppConfirmTitle: '确认删除应用?',
deleteAppConfirmTitle: '确认删除应用',
deleteAppConfirmContent:
'删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。',
appDeleted: '应用已删除',
@ -170,7 +170,7 @@ const translation = {
publicKey: '公钥',
secretKey: '密钥',
viewDocsLink: '查看 {{key}} 的文档',
removeConfirmTitle: '删除 {{key}} 配置?',
removeConfirmTitle: '删除 {{key}} 配置',
removeConfirmContent: '当前配置正在使用中,删除它将关闭追踪功能。',
},
weave: {

@ -156,7 +156,7 @@ const translation = {
exploreMarketplace: '探索 Marketplace',
pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。',
datasets: '知识库',
datasetsTips: '即将到来: 上传自己的长文本数据,或通过 Webhook 集成自己的数据源',
datasetsTips: '即将到来上传自己的长文本数据,或通过 Webhook 集成自己的数据源',
newApp: '创建应用',
newDataset: '创建知识库',
tools: '工具',
@ -418,7 +418,7 @@ const translation = {
getFreeTokens: '获得免费 Tokens',
priorityUsing: '优先使用',
deprecated: '已弃用',
confirmDelete: '确认删除?',
confirmDelete: '确认删除',
quotaTip: '剩余免费额度',
loadPresets: '加载预设',
parameters: '参数',

@ -93,9 +93,9 @@ const translation = {
excludePaths: '排除路径',
includeOnlyPaths: '仅包含路径',
extractOnlyMainContent: '仅提取主要内容(无标题、导航、页脚等)',
exceptionErrorTitle: '运行时发生异常:',
exceptionErrorTitle: '运行时发生异常',
unknownError: '未知错误',
totalPageScraped: '抓取页面总数:',
totalPageScraped: '抓取页面总数',
selectAll: '全选',
resetAll: '重置全部',
scrapTimeInfo: '总共在 {{time}}秒 内抓取了 {{total}} 个页面',

@ -27,6 +27,7 @@ const translation = {
process: '处理',
dataSource: '数据源',
saveAndProcess: '保存并处理',
preview: '预览',
},
knowledgeNameAndIcon: '知识库名称和图标',
knowledgeNameAndIconPlaceholder: '请输入知识库名称',
@ -66,12 +67,20 @@ const translation = {
inputFieldPanel: {
title: '用户输入字段',
description: '用户输入字段用于定义和收集流水线执行过程中所需的变量,用户可以自定义字段类型,并灵活配置输入,以满足不同数据源或文档处理的需求。',
sharedInputs: {
title: '共享输入',
tooltip: '共享输入可被数据源中的所有下游节点使用。例如在处理来自多个来源的文档时delimiter分隔符和 maximum chunk length最大分块长度等变量可以统一应用。',
uniqueInputs: {
title: '非共享输入',
tooltip: '非共享输入只能被选定的数据源及其下游节点访问。用户在选择其他数据源时不需要填写它。只有数据源变量引用的输入字段才会出现在第一步数据源中。所有其他字段将在第二步Process Documents中显示。',
},
globalInputs: {
title: '全局共享输入',
tooltip: '全局共享输入在所有节点之间共享。用户在选择任何数据源时都需要填写它们。例如像分隔符delimiter和最大块长度Maximum Chunk Length这样的字段可以跨多个数据源统一应用。只有数据源变量引用的输入字段才会出现在第一步数据源中。所有其他字段都显示在第二步Process Documents中。',
},
addInputField: '添加输入字段',
editInputField: '编辑输入字段',
preview: {
stepOneTitle: '数据源',
stepTwoTitle: '处理文档',
},
},
addDocuments: {
title: '添加文档',

@ -78,8 +78,8 @@ const translation = {
createDatasetIntro: '导入您自己的文本数据或通过 Webhook 实时写入数据以增强 LLM 的上下文。',
deleteDatasetConfirmTitle: '要删除知识库吗?',
deleteDatasetConfirmContent:
'删除知识库是不可逆的。用户将无法再访问您的知识库,所有的提示配置和日志将被永久删除。',
datasetUsedByApp: '某些应用正在使用该知识库。应用将无法再使用该知识库,所有的提示配置和日志将被永久删除。',
'删除知识库是不可逆的。用户将无法再访问您的知识库所有的提示配置和日志将被永久删除。',
datasetUsedByApp: '某些应用正在使用该知识库。应用将无法再使用该知识库所有的提示配置和日志将被永久删除。',
datasetDeleted: '知识库已删除',
datasetDeleteFailed: '删除知识库失败',
selectExternalKnowledgeAPI: {
@ -216,7 +216,7 @@ const translation = {
builtIn: '内置',
builtInDescription: '内置元数据是系统预定义的元数据,您可以在此处查看和管理内置元数据。',
deleteTitle: '确定删除',
deleteContent: '你确定要删除元数据 "{{name}}" 吗?',
deleteContent: '你确定要删除元数据 "{{name}}" 吗',
},
documentMetadata: {
metadataToolTip: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',

@ -64,7 +64,7 @@ const translation = {
registrationNotAllowed: '账户不存在,请联系系统管理员注册账户',
},
license: {
tip: '启动 Dify 社区版之前, 请阅读 GitHub 上的',
tip: '启动 Dify 社区版之前请阅读 GitHub 上的',
link: '开源协议',
},
join: '加入 ',

@ -126,7 +126,7 @@ const translation = {
pluginInfo: '插件信息',
delete: '移除插件',
deleteContentLeft: '是否要移除 ',
deleteContentRight: ' 插件?',
deleteContentRight: ' 插件',
usedInApps: '此插件正在 {{num}} 个应用中使用。',
},
installModal: {

@ -68,8 +68,8 @@ const translation = {
empty: '上传文件的内容不能为空',
fileStructNotMatch: '上传文件的内容与结构不匹配',
emptyLine: '第 {{rowIndex}} 行的内容为空',
invalidLine: '第 {{rowIndex}} 行: {{varName}}值必填',
moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超过最大长度 {{maxLength}}',
invalidLine: '第 {{rowIndex}} 行{{varName}}值必填',
moreThanMaxLengthLine: '第 {{rowIndex}} 行{{varName}}值超过最大长度 {{maxLength}}',
atLeastOne: '上传文件的内容不能少于一条',
},
},

@ -135,7 +135,7 @@ const translation = {
infoAndSetting: '信息和设置',
},
noCustomTool: {
title: '没有自定义工具!',
title: '没有自定义工具',
content: '在此统一添加和管理你的自定义工具,方便构建应用时使用。',
createTool: '创建工具',
},

@ -796,7 +796,7 @@ const translation = {
outputVars: {
text: '提取的文本',
},
supportFileTypes: '支持的文件类型: {{types}}。',
supportFileTypes: '支持的文件类型{{types}}。',
learnMore: '了解更多',
},
listFilter: {

@ -1,6 +1,6 @@
const translation = {
welcome: {
firstStepTip: '開始之前,',
firstStepTip: '開始之前',
enterKeyTip: '請先在下方輸入你的 OpenAI API Key',
getKeyTip: '從 OpenAI 獲取你的 API Key',
placeholder: '你的 OpenAI API Key例如 sk-xxxx',

@ -15,7 +15,7 @@ const translation = {
exportFailed: '匯出 DSL 失敗',
importDSL: '匯入 DSL 檔案',
createFromConfigFile: '透過 DSL 檔案建立',
deleteAppConfirmTitle: '確認刪除應用?',
deleteAppConfirmTitle: '確認刪除應用',
deleteAppConfirmContent:
'刪除應用將無法復原。使用者將無法存取你的應用,所有 Prompt 設定和日誌都將一併被刪除。',
appDeleted: '應用已刪除',

@ -142,7 +142,7 @@ const translation = {
plugins: '外掛',
pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。',
datasets: '知識庫',
datasetsTips: '即將到來: 上傳自己的長文字資料,或透過 Webhook 整合自己的資料來源',
datasetsTips: '即將到來上傳自己的長文字資料,或透過 Webhook 整合自己的資料來源',
newApp: '建立應用',
newDataset: '建立知識庫',
tools: '工具',
@ -398,7 +398,7 @@ const translation = {
getFreeTokens: '獲得免費 Tokens',
priorityUsing: '優先使用',
deprecated: '已棄用',
confirmDelete: '確認刪除?',
confirmDelete: '確認刪除',
quotaTip: '剩餘免費額度',
loadPresets: '載入預設',
parameters: '引數',

@ -7,7 +7,7 @@ const translation = {
createDatasetIntro: '匯入您自己的文字資料或透過 Webhook 實時寫入資料以增強 LLM 的上下文。',
deleteDatasetConfirmTitle: '要刪除知識庫嗎?',
deleteDatasetConfirmContent:
'刪除知識庫是不可逆的。使用者將無法再訪問您的知識庫,所有的提示配置和日誌將被永久刪除。',
'刪除知識庫是不可逆的。使用者將無法再訪問您的知識庫所有的提示配置和日誌將被永久刪除。',
datasetUsedByApp: '這些知識正被一些應用程序使用。應用程序將無法再使用這些知識,所有提示配置和日誌將被永久刪除。',
datasetDeleted: '知識庫已刪除',
datasetDeleteFailed: '刪除知識庫失敗',

@ -57,7 +57,7 @@ const translation = {
registrationNotAllowed: '找不到帳戶。請聯繫系統管理員進行註冊。',
},
license: {
tip: '啟動 Dify 社群版之前, 請閱讀 GitHub 上的',
tip: '啟動 Dify 社群版之前請閱讀 GitHub 上的',
link: '開源協議',
},
join: '加入',

@ -66,8 +66,8 @@ const translation = {
empty: '上傳檔案的內容不能為空',
fileStructNotMatch: '上傳檔案的內容與結構不匹配',
emptyLine: '第 {{rowIndex}} 行的內容為空',
invalidLine: '第 {{rowIndex}} 行: {{varName}}值必填',
moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超過最大長度 {{maxLength}}',
invalidLine: '第 {{rowIndex}} 行{{varName}}值必填',
moreThanMaxLengthLine: '第 {{rowIndex}} 行{{varName}}值超過最大長度 {{maxLength}}',
atLeastOne: '上傳檔案的內容不能少於一條',
},
execution: '執行',

@ -123,7 +123,7 @@ const translation = {
file: '檔',
},
noCustomTool: {
title: '沒有自定義工具!',
title: '沒有自定義工具',
content: '在此統一新增和管理你的自定義工具,方便構建應用時使用。',
createTool: '建立工具',
},

@ -179,9 +179,11 @@ export const useDataSourceList = (enabled: boolean, onSuccess?: (v: DataSourceIt
})
}
export const publishedPipelineInfoQueryKeyPrefix = [NAME_SPACE, 'published-pipeline']
export const usePublishedPipelineInfo = (pipelineId: string) => {
return useQuery<PublishedPipelineInfoResponse>({
queryKey: [NAME_SPACE, 'published-pipeline', pipelineId],
queryKey: [...publishedPipelineInfoQueryKeyPrefix, pipelineId],
queryFn: () => {
return get<PublishedPipelineInfoResponse>(`/rag/pipelines/${pipelineId}/workflows/publish`)
},
@ -212,7 +214,7 @@ export const useDataSourceCredentials = (provider: string, pluginId: string, onS
return useQuery<ToolCredential[]>({
queryKey: [NAME_SPACE, 'datasource-credentials', provider, pluginId],
queryFn: async () => {
const result = await get<ToolCredential[]>(`/auth/datasource/provider/${provider}/plugin/${pluginId}`)
const result = await get<ToolCredential[]>(`/auth/plugin/datasource?provider=${provider}&plugin_id=${pluginId}`)
onSuccess(result)
return result
},
@ -231,8 +233,10 @@ export const useUpdateDataSourceCredentials = (
pluginId,
credentials,
}: { provider: string; pluginId: string; credentials: Record<string, any>; }) => {
return post(`/auth/datasource/provider/${provider}/plugin/${pluginId}`, {
return post('/auth/plugin/datasource', {
body: {
provider,
plugin_id: pluginId,
credentials,
},
}).then(() => {

Loading…
Cancel
Save