Merge branch 'fix/chore-fix' into dev/plugin-deploy

This commit is contained in:
Yeuoly
2024-11-25 17:19:51 +08:00
250 changed files with 7636 additions and 1975 deletions

View File

@ -12,34 +12,46 @@ import ConfigContext from '@/context/debug-configuration'
// import { Resolution } from '@/types/app'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import Switch from '@/app/components/base/switch'
import type { FileUpload } from '@/app/components/base/features/types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
const ConfigVision: FC = () => {
const { t } = useTranslation()
const { isShowVisionConfig } = useContext(ConfigContext)
const { isShowVisionConfig, isAllowVideoUpload } = useContext(ConfigContext)
const file = useFeatures(s => s.features.file)
const featuresStore = useFeaturesStore()
const handleChange = useCallback((data: FileUpload) => {
const isImageEnabled = file?.allowed_file_types?.includes(SupportUploadFileTypes.image) ?? false
const handleChange = useCallback((value: boolean) => {
const {
features,
setFeatures,
} = featuresStore!.getState()
const newFeatures = produce(features, (draft) => {
draft.file = {
...draft.file,
enabled: data.enabled,
image: {
enabled: data.enabled,
detail: data.image?.detail,
transfer_methods: data.image?.transfer_methods,
number_limits: data.image?.number_limits,
},
if (value) {
draft.file!.allowed_file_types = Array.from(new Set([
...(draft.file?.allowed_file_types || []),
SupportUploadFileTypes.image,
...(isAllowVideoUpload ? [SupportUploadFileTypes.video] : []),
]))
}
else {
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
type => type !== SupportUploadFileTypes.image && (isAllowVideoUpload ? type !== SupportUploadFileTypes.video : true),
)
}
if (draft.file) {
draft.file.enabled = (draft.file.allowed_file_types?.length ?? 0) > 0
draft.file.image = {
...(draft.file.image || {}),
enabled: value,
}
}
})
setFeatures(newFeatures)
}, [featuresStore])
}, [featuresStore, isAllowVideoUpload])
if (!isShowVisionConfig)
return null
@ -89,11 +101,8 @@ const ConfigVision: FC = () => {
<ParamConfig />
<div className='ml-1 mr-3 w-[1px] h-3.5 bg-divider-subtle'></div>
<Switch
defaultValue={file?.enabled}
onChange={value => handleChange({
...(file || {}),
enabled: value,
})}
defaultValue={isImageEnabled}
onChange={handleChange}
size='md'
/>
</div>

View File

@ -0,0 +1,78 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import { useContext } from 'use-context-selector'
import { Document } from '@/app/components/base/icons/src/vender/features'
import Tooltip from '@/app/components/base/tooltip'
import ConfigContext from '@/context/debug-configuration'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import Switch from '@/app/components/base/switch'
const ConfigDocument: FC = () => {
const { t } = useTranslation()
const file = useFeatures(s => s.features.file)
const featuresStore = useFeaturesStore()
const { isShowDocumentConfig } = useContext(ConfigContext)
const isDocumentEnabled = file?.allowed_file_types?.includes(SupportUploadFileTypes.document) ?? false
const handleChange = useCallback((value: boolean) => {
const {
features,
setFeatures,
} = featuresStore!.getState()
const newFeatures = produce(features, (draft) => {
if (value) {
draft.file!.allowed_file_types = Array.from(new Set([
...(draft.file?.allowed_file_types || []),
SupportUploadFileTypes.document,
]))
}
else {
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
type => type !== SupportUploadFileTypes.document,
)
}
if (draft.file)
draft.file.enabled = (draft.file.allowed_file_types?.length ?? 0) > 0
})
setFeatures(newFeatures)
}, [featuresStore])
if (!isShowDocumentConfig)
return null
return (
<div className='mt-2 flex items-center gap-2 p-2 rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn'>
<div className='shrink-0 p-1'>
<div className='p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-indigo-indigo-600'>
<Document className='w-4 h-4 text-text-primary-on-surface' />
</div>
</div>
<div className='grow flex items-center'>
<div className='mr-1 text-text-secondary system-sm-semibold'>{t('appDebug.feature.documentUpload.title')}</div>
<Tooltip
popupContent={
<div className='w-[180px]' >
{t('appDebug.feature.documentUpload.description')}
</div>
}
/>
</div>
<div className='shrink-0 flex items-center'>
<div className='ml-1 mr-3 w-[1px] h-3.5 bg-divider-subtle'></div>
<Switch
defaultValue={isDocumentEnabled}
onChange={handleChange}
size='md'
/>
</div>
</div>
)
}
export default React.memo(ConfigDocument)

View File

@ -7,6 +7,7 @@ import { useFormattingChangedDispatcher } from '../debug/hooks'
import DatasetConfig from '../dataset-config'
import HistoryPanel from '../config-prompt/conversation-history/history-panel'
import ConfigVision from '../config-vision'
import ConfigDocument from './config-document'
import AgentTools from './agent/agent-tools'
import ConfigContext from '@/context/debug-configuration'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
@ -82,6 +83,8 @@ const Config: FC = () => {
<ConfigVision />
<ConfigDocument />
{/* Chat History */}
{isAdvancedMode && isChatApp && modelModeType === ModelModeType.completion && (
<HistoryPanel

View File

@ -453,7 +453,8 @@ const Configuration: FC = () => {
}
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
const isShowDocumentConfig = !!currModel?.features?.includes(ModelFeatureEnum.document)
const isAllowVideoUpload = !!currModel?.features?.includes(ModelFeatureEnum.video)
// *** web app features ***
const featuresData: FeaturesData = useMemo(() => {
return {
@ -474,7 +475,7 @@ const Configuration: FC = () => {
transfer_methods: modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
},
enabled: !!(modelConfig.file_upload?.enabled || modelConfig.file_upload?.image?.enabled),
allowed_file_types: modelConfig.file_upload?.allowed_file_types || [SupportUploadFileTypes.image, SupportUploadFileTypes.video],
allowed_file_types: modelConfig.file_upload?.allowed_file_types || [],
allowed_file_extensions: modelConfig.file_upload?.allowed_file_extensions || [...FILE_EXTS[SupportUploadFileTypes.image], ...FILE_EXTS[SupportUploadFileTypes.video]].map(ext => `.${ext}`),
allowed_file_upload_methods: modelConfig.file_upload?.allowed_file_upload_methods || modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
number_limits: modelConfig.file_upload?.number_limits || modelConfig.file_upload?.image?.number_limits || 3,
@ -883,6 +884,8 @@ const Configuration: FC = () => {
isShowVisionConfig,
visionConfig,
setVisionConfig: handleSetVisionConfig,
isAllowVideoUpload,
isShowDocumentConfig,
rerankSettingModalOpen,
setRerankSettingModalOpen,
}}

View File

@ -12,9 +12,13 @@ import Input from '@/app/components/base/input'
import Modal from '@/app/components/base/modal'
import { ToastContext } from '@/app/components/base/toast'
import {
importApp,
importAppFromUrl,
importDSL,
importDSLConfirm,
} from '@/service/apps'
import {
DSLImportMode,
DSLImportStatus,
} from '@/models/app'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
@ -45,6 +49,9 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
const [currentTab, setCurrentTab] = useState(activeTab)
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
const { mutateAsync } = useMutationCheckDependenciesBeforeImportDSL()
const [showErrorModal, setShowErrorModal] = useState(false)
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
const [importId, setImportId] = useState<string>()
const readFile = (file: File) => {
const reader = new FileReader()
@ -68,6 +75,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
const isCreatingRef = useRef(false)
const onCreate: MouseEventHandler = async () => {
if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
return
@ -77,27 +85,56 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
return
isCreatingRef.current = true
try {
let app
let response
if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
app = await importApp({
data: fileContent || '',
response = await importDSL({
mode: DSLImportMode.YAML_CONTENT,
yaml_content: fileContent || '',
})
await mutateAsync({ dslString: fileContent })
}
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
app = await importAppFromUrl({
url: dslUrlValue || '',
response = await importDSL({
mode: DSLImportMode.YAML_URL,
yaml_url: dslUrlValue || '',
})
await mutateAsync({ url: dslUrlValue })
}
if (onSuccess)
onSuccess()
if (onClose)
onClose()
notify({ type: 'success', message: t('app.newApp.appCreated') })
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
getRedirection(isCurrentWorkspaceEditor, app, push)
if (!response)
return
const { id, status, app_id, imported_dsl_version, current_dsl_version } = response
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
if (onSuccess)
onSuccess()
if (onClose)
onClose()
notify({
type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'),
children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
})
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push)
}
else if (status === DSLImportStatus.PENDING) {
setVersions({
importedVersion: imported_dsl_version ?? '',
systemVersion: current_dsl_version ?? '',
})
if (onClose)
onClose()
setTimeout(() => {
setShowErrorModal(true)
}, 300)
setImportId(id)
}
else {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
}
}
catch (e) {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
@ -105,6 +142,38 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
isCreatingRef.current = false
}
const onDSLConfirm: MouseEventHandler = async () => {
try {
if (!importId)
return
const response = await importDSLConfirm({
import_id: importId,
})
const { status, app_id } = response
if (status === DSLImportStatus.COMPLETED) {
if (onSuccess)
onSuccess()
if (onClose)
onClose()
notify({
type: 'success',
message: t('app.newApp.appCreated'),
})
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push)
}
else if (status === DSLImportStatus.FAILED) {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
}
}
catch (e) {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
}
}
const tabs = [
{
key: CreateFromDSLModalTab.FROM_FILE,
@ -127,74 +196,96 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
}, [isAppsFull, currentTab, currentFile, dslUrlValue])
return (
<Modal
className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
isShow={show}
onClose={() => { }}
>
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
{t('app.importFromDSL')}
<div
className='flex items-center w-8 h-8 cursor-pointer'
onClick={() => onClose()}
>
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
<>
<Modal
className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
isShow={show}
onClose={() => { }}
>
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
{t('app.importFromDSL')}
<div
className='flex items-center w-8 h-8 cursor-pointer'
onClick={() => onClose()}
>
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
</div>
</div>
</div>
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
{
tabs.map(tab => (
<div
key={tab.key}
className={cn(
'relative flex items-center h-full cursor-pointer',
currentTab === tab.key && 'text-text-primary',
)}
onClick={() => setCurrentTab(tab.key)}
>
{tab.label}
{
currentTab === tab.key && (
<div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
)
}
</div>
))
}
</div>
<div className='px-6 py-4'>
{
currentTab === CreateFromDSLModalTab.FROM_FILE && (
<Uploader
className='mt-0'
file={currentFile}
updateFile={handleFile}
/>
)
}
{
currentTab === CreateFromDSLModalTab.FROM_URL && (
<div>
<div className='mb-1 system-md-semibold leading6'>DSL URL</div>
<Input
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
value={dslUrlValue}
onChange={e => setDslUrlValue(e.target.value)}
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
{
tabs.map(tab => (
<div
key={tab.key}
className={cn(
'relative flex items-center h-full cursor-pointer',
currentTab === tab.key && 'text-text-primary',
)}
onClick={() => setCurrentTab(tab.key)}
>
{tab.label}
{
currentTab === tab.key && (
<div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
)
}
</div>
))
}
</div>
<div className='px-6 py-4'>
{
currentTab === CreateFromDSLModalTab.FROM_FILE && (
<Uploader
className='mt-0'
file={currentFile}
updateFile={handleFile}
/>
</div>
)
}
</div>
{isAppsFull && (
<div className='px-6'>
<AppsFull className='mt-0' loc='app-create-dsl' />
)
}
{
currentTab === CreateFromDSLModalTab.FROM_URL && (
<div>
<div className='mb-1 system-md-semibold leading6'>DSL URL</div>
<Input
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
value={dslUrlValue}
onChange={e => setDslUrlValue(e.target.value)}
/>
</div>
)
}
</div>
)}
<div className='flex justify-end px-6 py-5'>
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
</div>
</Modal>
{isAppsFull && (
<div className='px-6'>
<AppsFull className='mt-0' loc='app-create-dsl' />
</div>
)}
<div className='flex justify-end px-6 py-5'>
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
</div>
</Modal>
<Modal
isShow={showErrorModal}
onClose={() => setShowErrorModal(false)}
className='w-[480px]'
>
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
<br />
<div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
</div>
</div>
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
<Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>
</div>
</Modal>
</>
)
}

View File

@ -6,6 +6,7 @@ import {
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { formatFileSize } from '@/utils/format'
import cn from '@/utils/classnames'
import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files'
import { ToastContext } from '@/app/components/base/toast'
@ -58,8 +59,13 @@ const Uploader: FC<Props> = ({
updateFile(files[0])
}
const selectHandle = () => {
if (fileUploader.current)
const originalFile = file
if (fileUploader.current) {
fileUploader.current.value = ''
fileUploader.current.click()
// If no file is selected, restore the original file
fileUploader.current.oncancel = () => updateFile(originalFile)
}
}
const removeFile = () => {
if (fileUploader.current)
@ -96,7 +102,7 @@ const Uploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex items-center h-20 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal', dragging && 'bg-[#F5F8FF] border border-[#B2CCFF]')}>
<div className={cn('flex items-center h-12 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal', dragging && 'bg-[#F5F8FF] border border-[#B2CCFF]')}>
<div className='w-full flex items-center justify-center space-x-2'>
<UploadCloud01 className='w-6 h-6 mr-2' />
<div className='text-gray-500'>
@ -108,17 +114,23 @@ const Uploader: FC<Props> = ({
</div>
)}
{file && (
<div className={cn('flex items-center h-20 px-6 rounded-xl bg-gray-50 border border-gray-200 text-sm font-normal group', 'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]')}>
<YamlIcon className="shrink-0" />
<div className='flex ml-2 w-0 grow'>
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-gray-800'>{file.name.replace(/(.yaml|.yml)$/, '')}</span>
<span className='shrink-0 text-gray-500'>.yml</span>
<div className={cn('flex items-center rounded-lg bg-components-panel-on-panel-item-bg border-[0.5px] border-components-panel-border shadow-xs group', 'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]')}>
<div className='flex p-3 justify-center items-center'>
<YamlIcon className="w-6 h-6 shrink-0" />
</div>
<div className='flex py-1 pr-2 grow flex-col items-start gap-0.5'>
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-text-secondary font-inter text-[12px] font-medium leading-4'>{file.name}</span>
<div className='flex h-3 items-center gap-1 self-stretch text-text-tertiary font-inter text-[10px] font-medium leading-3 uppercase'>
<span>YAML</span>
<span className='text-text-quaternary'>·</span>
<span>{formatFileSize(file.size)}</span>
</div>
</div>
<div className='hidden group-hover:flex items-center'>
<Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
<div className='mx-2 w-px h-4 bg-gray-200' />
<div className='p-2 cursor-pointer' onClick={removeFile}>
<RiDeleteBinLine className='w-4 h-4 text-gray-500' />
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
</div>
</div>
</div>

View File

@ -1804,8 +1804,85 @@ 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`] = `
exports[`build chat item tree and get thread messages should work with partial messages 1 1`] = `
[
{
"children": [
{
"agent_thoughts": [
{
"chain_id": null,
"created_at": 1726105799,
"files": [],
"id": "9730d587-9268-4683-9dd9-91a1cab9510b",
"message_id": "4c5d0841-1206-463e-95d8-71f812877658",
"observation": "",
"position": 1,
"thought": "I'll go with 112. Your turn!",
"tool": "",
"tool_input": "",
"tool_labels": {},
},
],
"children": [],
"content": "I'll go with 112. Your turn!",
"conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
"feedbackDisabled": false,
"id": "4c5d0841-1206-463e-95d8-71f812877658",
"input": {
"inputs": {},
"query": "99",
},
"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, I'll play! My number is 57. Your turn!",
},
{
"files": [],
"role": "user",
"text": "58",
},
{
"files": [],
"role": "assistant",
"text": "I choose 83. What's your next number?",
},
{
"files": [],
"role": "user",
"text": "99",
},
{
"files": [],
"role": "assistant",
"text": "I'll go with 112. Your turn!",
},
],
"message_files": [],
"more": {
"latency": "1.49",
"time": "09/11/2024 09:50 PM",
"tokens": 86,
},
"parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658",
"siblingIndex": 0,
"workflow_run_id": null,
},
],
"content": "99",
"id": "question-4c5d0841-1206-463e-95d8-71f812877658",
"isAnswer": false,
"message_files": [],
"parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d",
},
{
"children": [
{
@ -2078,6 +2155,178 @@ exports[`build chat item tree and get thread messages should work with partial m
]
`;
exports[`build chat item tree and get thread messages should work with partial messages 2 1`] = `
[
{
"children": [
{
"children": [],
"content": "237.",
"id": "ebb73fe2-15de-46dd-aab5-75416d8448eb",
"isAnswer": true,
"parentMessageId": "question-ebb73fe2-15de-46dd-aab5-75416d8448eb",
"siblingIndex": 0,
},
],
"content": "123",
"id": "question-ebb73fe2-15de-46dd-aab5-75416d8448eb",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418",
},
{
"children": [
{
"children": [],
"content": "My number is 256.",
"id": "3553d508-3850-462e-8594-078539f940f9",
"isAnswer": true,
"parentMessageId": "question-3553d508-3850-462e-8594-078539f940f9",
"siblingIndex": 1,
},
],
"content": "123",
"id": "question-3553d508-3850-462e-8594-078539f940f9",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418",
},
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [],
"content": "My number is 3e (approximately 8.15).",
"id": "9eac3bcc-8d3b-4e56-a12b-44c34cebc719",
"isAnswer": true,
"parentMessageId": "question-9eac3bcc-8d3b-4e56-a12b-44c34cebc719",
"siblingIndex": 0,
},
],
"content": "e",
"id": "question-9eac3bcc-8d3b-4e56-a12b-44c34cebc719",
"isAnswer": false,
"parentMessageId": "5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
},
],
"content": "My number is 2π (approximately 6.28).",
"id": "5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
"isAnswer": true,
"parentMessageId": "question-5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
"siblingIndex": 0,
},
],
"content": "π",
"id": "question-5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
"isAnswer": false,
"parentMessageId": "46a49bb9-0881-459e-8c6a-24d20ae48d2f",
},
],
"content": "My number is 145.",
"id": "46a49bb9-0881-459e-8c6a-24d20ae48d2f",
"isAnswer": true,
"parentMessageId": "question-46a49bb9-0881-459e-8c6a-24d20ae48d2f",
"siblingIndex": 0,
},
],
"content": "78",
"id": "question-46a49bb9-0881-459e-8c6a-24d20ae48d2f",
"isAnswer": false,
"parentMessageId": "3cded945-855a-4a24-aab7-43c7dd54664c",
},
],
"content": "My number is 7.89.",
"id": "3cded945-855a-4a24-aab7-43c7dd54664c",
"isAnswer": true,
"parentMessageId": "question-3cded945-855a-4a24-aab7-43c7dd54664c",
"siblingIndex": 0,
},
],
"content": "3.11",
"id": "question-3cded945-855a-4a24-aab7-43c7dd54664c",
"isAnswer": false,
"parentMessageId": "a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
},
],
"content": "My number is 22.",
"id": "a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
"isAnswer": true,
"parentMessageId": "question-a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
"siblingIndex": 0,
},
],
"content": "-5",
"id": "question-a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
"isAnswer": false,
"parentMessageId": "93bac05d-1470-4ac9-b090-fe21cd7c3d55",
},
],
"content": "My number is 4782.",
"id": "93bac05d-1470-4ac9-b090-fe21cd7c3d55",
"isAnswer": true,
"parentMessageId": "question-93bac05d-1470-4ac9-b090-fe21cd7c3d55",
"siblingIndex": 0,
},
],
"content": "3306",
"id": "question-93bac05d-1470-4ac9-b090-fe21cd7c3d55",
"isAnswer": false,
"parentMessageId": "9e51a13b-7780-4565-98dc-f2d8c3b1758f",
},
],
"content": "My number is 2048.",
"id": "9e51a13b-7780-4565-98dc-f2d8c3b1758f",
"isAnswer": true,
"parentMessageId": "question-9e51a13b-7780-4565-98dc-f2d8c3b1758f",
"siblingIndex": 0,
},
],
"content": "1024",
"id": "question-9e51a13b-7780-4565-98dc-f2d8c3b1758f",
"isAnswer": false,
"parentMessageId": "507f9df9-1f06-4a57-bb38-f00228c42c22",
},
],
"content": "My number is 259.",
"id": "507f9df9-1f06-4a57-bb38-f00228c42c22",
"isAnswer": true,
"parentMessageId": "question-507f9df9-1f06-4a57-bb38-f00228c42c22",
"siblingIndex": 2,
},
],
"content": "123",
"id": "question-507f9df9-1f06-4a57-bb38-f00228c42c22",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418",
},
]
`;
exports[`build chat item tree and get thread messages should work with real world messages 1`] = `
[
{

View File

@ -0,0 +1,122 @@
[
{
"id": "question-ebb73fe2-15de-46dd-aab5-75416d8448eb",
"content": "123",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418"
},
{
"id": "ebb73fe2-15de-46dd-aab5-75416d8448eb",
"content": "237.",
"isAnswer": true,
"parentMessageId": "question-ebb73fe2-15de-46dd-aab5-75416d8448eb"
},
{
"id": "question-3553d508-3850-462e-8594-078539f940f9",
"content": "123",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418"
},
{
"id": "3553d508-3850-462e-8594-078539f940f9",
"content": "My number is 256.",
"isAnswer": true,
"parentMessageId": "question-3553d508-3850-462e-8594-078539f940f9"
},
{
"id": "question-507f9df9-1f06-4a57-bb38-f00228c42c22",
"content": "123",
"isAnswer": false,
"parentMessageId": "57c989f9-3fa4-4dec-9ee5-c3568dd27418"
},
{
"id": "507f9df9-1f06-4a57-bb38-f00228c42c22",
"content": "My number is 259.",
"isAnswer": true,
"parentMessageId": "question-507f9df9-1f06-4a57-bb38-f00228c42c22"
},
{
"id": "question-9e51a13b-7780-4565-98dc-f2d8c3b1758f",
"content": "1024",
"isAnswer": false,
"parentMessageId": "507f9df9-1f06-4a57-bb38-f00228c42c22"
},
{
"id": "9e51a13b-7780-4565-98dc-f2d8c3b1758f",
"content": "My number is 2048.",
"isAnswer": true,
"parentMessageId": "question-9e51a13b-7780-4565-98dc-f2d8c3b1758f"
},
{
"id": "question-93bac05d-1470-4ac9-b090-fe21cd7c3d55",
"content": "3306",
"isAnswer": false,
"parentMessageId": "9e51a13b-7780-4565-98dc-f2d8c3b1758f"
},
{
"id": "93bac05d-1470-4ac9-b090-fe21cd7c3d55",
"content": "My number is 4782.",
"isAnswer": true,
"parentMessageId": "question-93bac05d-1470-4ac9-b090-fe21cd7c3d55"
},
{
"id": "question-a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
"content": "-5",
"isAnswer": false,
"parentMessageId": "93bac05d-1470-4ac9-b090-fe21cd7c3d55"
},
{
"id": "a956de3d-ef95-4d90-84fe-f7a26ef28cd7",
"content": "My number is 22.",
"isAnswer": true,
"parentMessageId": "question-a956de3d-ef95-4d90-84fe-f7a26ef28cd7"
},
{
"id": "question-3cded945-855a-4a24-aab7-43c7dd54664c",
"content": "3.11",
"isAnswer": false,
"parentMessageId": "a956de3d-ef95-4d90-84fe-f7a26ef28cd7"
},
{
"id": "3cded945-855a-4a24-aab7-43c7dd54664c",
"content": "My number is 7.89.",
"isAnswer": true,
"parentMessageId": "question-3cded945-855a-4a24-aab7-43c7dd54664c"
},
{
"id": "question-46a49bb9-0881-459e-8c6a-24d20ae48d2f",
"content": "78",
"isAnswer": false,
"parentMessageId": "3cded945-855a-4a24-aab7-43c7dd54664c"
},
{
"id": "46a49bb9-0881-459e-8c6a-24d20ae48d2f",
"content": "My number is 145.",
"isAnswer": true,
"parentMessageId": "question-46a49bb9-0881-459e-8c6a-24d20ae48d2f"
},
{
"id": "question-5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
"content": "π",
"isAnswer": false,
"parentMessageId": "46a49bb9-0881-459e-8c6a-24d20ae48d2f"
},
{
"id": "5c56a2b3-f057-42a0-9b2c-52a35713cd8c",
"content": "My number is 2π (approximately 6.28).",
"isAnswer": true,
"parentMessageId": "question-5c56a2b3-f057-42a0-9b2c-52a35713cd8c"
},
{
"id": "question-9eac3bcc-8d3b-4e56-a12b-44c34cebc719",
"content": "e",
"isAnswer": false,
"parentMessageId": "5c56a2b3-f057-42a0-9b2c-52a35713cd8c"
},
{
"id": "9eac3bcc-8d3b-4e56-a12b-44c34cebc719",
"content": "My number is 3e (approximately 8.15).",
"isAnswer": true,
"parentMessageId": "question-9eac3bcc-8d3b-4e56-a12b-44c34cebc719"
}
]

View File

@ -7,6 +7,7 @@ import mixedTestMessages from './mixedTestMessages.json'
import multiRootNodesMessages from './multiRootNodesMessages.json'
import multiRootNodesWithLegacyTestMessages from './multiRootNodesWithLegacyTestMessages.json'
import realWorldMessages from './realWorldMessages.json'
import partialMessages from './partialMessages.json'
function visitNode(tree: ChatItemInTree | ChatItemInTree[], path: string): ChatItemInTree {
return get(tree, path)
@ -256,9 +257,15 @@ describe('build chat item tree and get thread messages', () => {
expect(threadMessages6_2).toMatchSnapshot()
})
const partialMessages = (realWorldMessages as ChatItemInTree[]).slice(-10)
const tree7 = buildChatItemTree(partialMessages)
it('should work with partial messages', () => {
const partialMessages1 = (realWorldMessages as ChatItemInTree[]).slice(-10)
const tree7 = buildChatItemTree(partialMessages1)
it('should work with partial messages 1', () => {
expect(tree7).toMatchSnapshot()
})
const partialMessages2 = (partialMessages as ChatItemInTree[])
const tree8 = buildChatItemTree(partialMessages2)
it('should work with partial messages 2', () => {
expect(tree8).toMatchSnapshot()
})
})

View File

@ -102,21 +102,21 @@ const ChatInputArea = ({
setCurrentIndex(historyRef.current.length)
handleSend()
}
else if (e.key === 'ArrowUp' && !e.shiftKey && !e.nativeEvent.isComposing) {
// When the up key is pressed, output the previous element
else if (e.key === 'ArrowUp' && !e.shiftKey && !e.nativeEvent.isComposing && e.metaKey) {
// When the cmd + up key is pressed, output the previous element
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1)
setQuery(historyRef.current[currentIndex - 1])
}
}
else if (e.key === 'ArrowDown' && !e.shiftKey && !e.nativeEvent.isComposing) {
// When the down key is pressed, output the next element
else if (e.key === 'ArrowDown' && !e.shiftKey && !e.nativeEvent.isComposing && e.metaKey) {
// When the cmd + down key is pressed, output the next element
if (currentIndex < historyRef.current.length - 1) {
setCurrentIndex(currentIndex + 1)
setQuery(historyRef.current[currentIndex + 1])
}
else if (currentIndex === historyRef.current.length - 1) {
// If it is the last element, clear the input box
// If it is the last element, clear the input box
setCurrentIndex(historyRef.current.length)
setQuery('')
}

View File

@ -127,19 +127,16 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] {
lastAppendedLegacyAnswer = answerNode
}
else {
if (!parentMessageId)
if (
!parentMessageId
|| !allMessages.some(item => item.id === parentMessageId) // parent message might not be fetched yet, in this case we will append the question to the root nodes
)
rootNodes.push(questionNode)
else
map[parentMessageId]?.children!.push(questionNode)
}
}
// 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

@ -1,17 +1,31 @@
import type { CSSProperties, FC } from 'react'
import React from 'react'
import s from './style.module.css'
import { type VariantProps, cva } from 'class-variance-authority'
import classNames from '@/utils/classnames'
type Props = {
type?: 'horizontal' | 'vertical'
// orientation?: 'left' | 'right' | 'center'
const dividerVariants = cva(
'bg-divider-regular',
{
variants: {
type: {
horizontal: 'w-full h-[0.5px] my-2',
vertical: 'w-[1px] h-full mx-2',
},
},
defaultVariants: {
type: 'horizontal',
},
},
)
type DividerProps = {
className?: string
style?: CSSProperties
}
} & VariantProps<typeof dividerVariants>
const Divider: FC<Props> = ({ type = 'horizontal', className = '', style }) => {
const Divider: FC<DividerProps> = ({ type, className = '', style }) => {
return (
<div className={`${s.divider} ${s[type]} ${className}`} style={style}></div>
<div className={classNames(dividerVariants({ type }), className)} style={style}></div>
)
}

View File

@ -1,9 +0,0 @@
.divider {
@apply bg-gray-200;
}
.horizontal {
@apply w-full h-[0.5px] my-2;
}
.vertical {
@apply w-[1px] h-full mx-2;
}

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22ZM7 6V10H11V6H7ZM7 12V14H17V12H7ZM7 16V18H17V16H7ZM13 7V9H17V7H13Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 292 B

View File

@ -0,0 +1,23 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"xmlns": "http://www.w3.org/2000/svg",
"viewBox": "0 0 24 24",
"fill": "currentColor"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22ZM7 6V10H11V6H7ZM7 12V14H17V12H7ZM7 16V18H17V16H7ZM13 7V9H17V7H13Z"
},
"children": []
}
]
},
"name": "Document"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Document.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Document'
export default Icon

View File

@ -7,3 +7,4 @@ export { default as Microphone01 } from './Microphone01'
export { default as TextToAudio } from './TextToAudio'
export { default as VirtualAssistant } from './VirtualAssistant'
export { default as Vision } from './Vision'
export { default as Document } from './Document'

View File

@ -3,16 +3,19 @@ import type { ReactNode } from 'react'
import React, { useEffect, useState } from 'react'
import { createRoot } from 'react-dom/client'
import {
CheckCircleIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
XCircleIcon,
} from '@heroicons/react/20/solid'
RiAlertFill,
RiCheckboxCircleFill,
RiCloseLine,
RiErrorWarningFill,
RiInformation2Fill,
} from '@remixicon/react'
import { createContext, useContext } from 'use-context-selector'
import ActionButton from '@/app/components/base/action-button'
import classNames from '@/utils/classnames'
export type IToastProps = {
type?: 'success' | 'error' | 'warning' | 'info'
size?: 'md' | 'sm'
duration?: number
message: string
children?: ReactNode
@ -21,60 +24,55 @@ export type IToastProps = {
}
type IToastContext = {
notify: (props: IToastProps) => void
close: () => void
}
export const ToastContext = createContext<IToastContext>({} as IToastContext)
export const useToastContext = () => useContext(ToastContext)
const Toast = ({
type = 'info',
size = 'md',
message,
children,
className,
}: IToastProps) => {
const { close } = useToastContext()
// sometimes message is react node array. Not handle it.
if (typeof message !== 'string')
return null
return <div className={classNames(
className,
'fixed rounded-md p-4 my-4 mx-8 z-[9999]',
'fixed w-[360px] rounded-xl my-4 mx-8 flex-grow z-[9999] overflow-hidden',
size === 'md' ? 'p-3' : 'p-2',
'border border-components-panel-border-subtle bg-components-panel-bg-blur shadow-sm',
'top-0',
'right-0',
type === 'success' ? 'bg-green-50' : '',
type === 'error' ? 'bg-red-50' : '',
type === 'warning' ? 'bg-yellow-50' : '',
type === 'info' ? 'bg-blue-50' : '',
)}>
<div className="flex">
<div className="flex-shrink-0">
{type === 'success' && <CheckCircleIcon className="w-5 h-5 text-green-400" aria-hidden="true" />}
{type === 'error' && <XCircleIcon className="w-5 h-5 text-red-400" aria-hidden="true" />}
{type === 'warning' && <ExclamationTriangleIcon className="w-5 h-5 text-yellow-400" aria-hidden="true" />}
{type === 'info' && <InformationCircleIcon className="w-5 h-5 text-blue-400" aria-hidden="true" />}
<div className={`absolute inset-0 opacity-40 ${
(type === 'success' && 'bg-[linear-gradient(92deg,rgba(23,178,106,0.25)_0%,rgba(255,255,255,0.00)_100%)]')
|| (type === 'warning' && 'bg-[linear-gradient(92deg,rgba(247,144,9,0.25)_0%,rgba(255,255,255,0.00)_100%)]')
|| (type === 'error' && 'bg-[linear-gradient(92deg,rgba(240,68,56,0.25)_0%,rgba(255,255,255,0.00)_100%)]')
|| (type === 'info' && 'bg-[linear-gradient(92deg,rgba(11,165,236,0.25)_0%,rgba(255,255,255,0.00)_100%)]')
}`}
/>
<div className={`flex ${size === 'md' ? 'gap-1' : 'gap-0.5'}`}>
<div className={`flex justify-center items-center ${size === 'md' ? 'p-0.5' : 'p-1'}`}>
{type === 'success' && <RiCheckboxCircleFill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-success`} aria-hidden="true" />}
{type === 'error' && <RiErrorWarningFill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-destructive`} aria-hidden="true" />}
{type === 'warning' && <RiAlertFill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-warning-secondary`} aria-hidden="true" />}
{type === 'info' && <RiInformation2Fill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-accent`} aria-hidden="true" />}
</div>
<div className="ml-3">
<h3 className={
classNames(
'text-sm font-medium',
type === 'success' ? 'text-green-800' : '',
type === 'error' ? 'text-red-800' : '',
type === 'warning' ? 'text-yellow-800' : '',
type === 'info' ? 'text-blue-800' : '',
)
}>{message}</h3>
{children && <div className={
classNames(
'mt-2 text-sm',
type === 'success' ? 'text-green-700' : '',
type === 'error' ? 'text-red-700' : '',
type === 'warning' ? 'text-yellow-700' : '',
type === 'info' ? 'text-blue-700' : '',
)
}>
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} flex-col items-start gap-1 flex-grow`}>
<div className='text-text-primary system-sm-semibold'>{message}</div>
{children && <div className='text-text-secondary system-xs-regular'>
{children}
</div>
}
</div>
<ActionButton className='z-[1000]' onClick={close}>
<RiCloseLine className='w-4 h-4 flex-shrink-0 text-text-tertiary' />
</ActionButton>
</div>
</div>
}
@ -106,6 +104,7 @@ export const ToastProvider = ({
setMounted(true)
setParams(props)
},
close: () => setMounted(false),
}}>
{mounted && <Toast {...params} />}
{children}
@ -114,16 +113,17 @@ export const ToastProvider = ({
Toast.notify = ({
type,
size = 'md',
message,
duration,
className,
}: Pick<IToastProps, 'type' | 'message' | 'duration' | 'className'>) => {
}: Pick<IToastProps, 'type' | 'size' | 'message' | 'duration' | 'className'>) => {
const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000
if (typeof window === 'object') {
const holder = document.createElement('div')
const root = createRoot(holder)
root.render(<Toast type={type} message={message} duration={duration} className={className} />)
root.render(<Toast type={type} size={size} message={message} duration={duration} className={className} />)
document.body.appendChild(holder)
setTimeout(() => {
if (holder)

View File

@ -5,12 +5,16 @@ import { useTranslation } from 'react-i18next'
import { RiListUnordered } from '@remixicon/react'
import TemplateEn from './template/template.en.mdx'
import TemplateZh from './template/template.zh.mdx'
import TemplateJa from './template/template.ja.mdx'
import TemplateAdvancedChatEn from './template/template_advanced_chat.en.mdx'
import TemplateAdvancedChatZh from './template/template_advanced_chat.zh.mdx'
import TemplateAdvancedChatJa from './template/template_advanced_chat.ja.mdx'
import TemplateWorkflowEn from './template/template_workflow.en.mdx'
import TemplateWorkflowZh from './template/template_workflow.zh.mdx'
import TemplateWorkflowJa from './template/template_workflow.ja.mdx'
import TemplateChatEn from './template/template_chat.en.mdx'
import TemplateChatZh from './template/template_chat.zh.mdx'
import TemplateChatJa from './template/template_chat.ja.mdx'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
@ -57,7 +61,6 @@ const Doc = ({ appDetail }: IDocProps) => {
// Run after component has rendered
setTimeout(extractTOC, 0)
}, [appDetail, locale])
return (
<div className="flex">
<div className={`fixed right-8 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
@ -98,16 +101,52 @@ const Doc = ({ appDetail }: IDocProps) => {
</div>
<article className="prose prose-xl" >
{(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && (
locale !== LanguagesSupported[1] ? <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
(() => {
switch (locale) {
case LanguagesSupported[1]:
return <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
case LanguagesSupported[7]:
return <TemplateChatJa appDetail={appDetail} variables={variables} inputs={inputs} />
default:
return <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} />
}
})()
)}
{appDetail?.mode === 'advanced-chat' && (
locale !== LanguagesSupported[1] ? <TemplateAdvancedChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateAdvancedChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
(() => {
switch (locale) {
case LanguagesSupported[1]:
return <TemplateAdvancedChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
case LanguagesSupported[7]:
return <TemplateAdvancedChatJa appDetail={appDetail} variables={variables} inputs={inputs} />
default:
return <TemplateAdvancedChatEn appDetail={appDetail} variables={variables} inputs={inputs} />
}
})()
)}
{appDetail?.mode === 'workflow' && (
locale !== LanguagesSupported[1] ? <TemplateWorkflowEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateWorkflowZh appDetail={appDetail} variables={variables} inputs={inputs} />
(() => {
switch (locale) {
case LanguagesSupported[1]:
return <TemplateWorkflowZh appDetail={appDetail} variables={variables} inputs={inputs} />
case LanguagesSupported[7]:
return <TemplateWorkflowJa appDetail={appDetail} variables={variables} inputs={inputs} />
default:
return <TemplateWorkflowEn appDetail={appDetail} variables={variables} inputs={inputs} />
}
})()
)}
{appDetail?.mode === 'completion' && (
locale !== LanguagesSupported[1] ? <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} />
(() => {
switch (locale) {
case LanguagesSupported[1]:
return <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} />
case LanguagesSupported[7]:
return <TemplateJa appDetail={appDetail} variables={variables} inputs={inputs} />
default:
return <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} />
}
})()
)}
</article>
</div>

View File

@ -0,0 +1,551 @@
import { CodeGroup } from '../code.tsx'
import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from '../md.tsx'
# Completion アプリ API
テキスト生成アプリケーションはセッションレスをサポートし、翻訳、記事作成、要約AI等に最適です。
<div>
### ベースURL
<CodeGroup title="Code" targetCode={props.appDetail.api_base_url}>
```javascript
```
</CodeGroup>
### 認証
サービスAPIは`API-Key`認証を使用します。
<i>**APIキーの漏洩による重大な結果を避けるため、APIキーはサーバーサイドに保存し、クライアントサイドでは共有や保存しないことを強く推奨します。**</i>
すべてのAPIリクエストで、以下のように`Authorization` HTTPヘッダーにAPIキーを含めてください
<CodeGroup title="Code">
```javascript
Authorization: Bearer {API_KEY}
```
</CodeGroup>
</div>
---
<Heading
url='/completion-messages'
method='POST'
title='完了メッセージの作成'
name='#Create-Completion-Message'
/>
<Row>
<Col>
テキスト生成アプリケーションにリクエストを送信します。
### リクエストボディ
<Properties>
<Property name='inputs' type='object' key='inputs'>
アプリで定義された各種変数値を入力できます。
`inputs`パラメータには複数のキー/値ペアが含まれ、各キーは特定の変数に対応し、各値はその変数の具体的な値となります。
テキスト生成アプリケーションでは、少なくとも1つのキー/値ペアの入力が必要です。
- `query` (string) 必須
入力テキスト、処理される内容。
</Property>
<Property name='response_mode' type='string' key='response_mode'>
レスポンス返却モード、以下をサポート:
- `streaming` ストリーミングモード推奨、SSE[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))によるタイプライター風の出力を実装。
- `blocking` ブロッキングモード、実行完了後に結果を返却。(処理が長い場合はリクエストが中断される可能性があります)
<i>Cloudflareの制限により、100秒後に返却なしで中断されます。</i>
</Property>
<Property name='user' type='string' key='user'>
ユーザー識別子、エンドユーザーの身元を定義し、取得や統計に使用します。
アプリケーション内で開発者が一意に定義する必要があります。
</Property>
<Property name='files' type='array[object]' key='files'>
ファイルリスト、モデルがVision機能をサポートしている場合のみ、テキスト理解と質問応答を組み合わせたファイル画像の入力に適しています。
- `type` (string) サポートされるタイプ:`image`(現在は画像タイプのみサポート)
- `transfer_method` (string) 転送方法、画像URLの場合は`remote_url` / ファイルアップロードの場合は`local_file`
- `url` (string) 画像URL転送方法が`remote_url`の場合)
- `upload_file_id` (string) アップロードされたファイルID、事前にファイルアップロードAPIを通じてアップロードする必要があります転送方法が`local_file`の場合)
</Property>
</Properties>
### レスポンス
`response_mode`が`blocking`の場合、CompletionResponseオブジェクトを返却します。
`response_mode`が`streaming`の場合、ChunkCompletionResponseストリームを返却します。
### ChatCompletionResponse
アプリの完全な結果を返却、`Content-Type`は`application/json`です。
- `message_id` (string) 一意のメッセージID
- `mode` (string) アプリモード、固定で`chat`
- `answer` (string) 完全な応答内容
- `metadata` (object) メタデータ
- `usage` (Usage) モデル使用情報
- `retriever_resources` (array[RetrieverResource]) 引用と帰属のリスト
- `created_at` (int) メッセージ作成タイムスタンプ、例1705395332
### ChunkChatCompletionResponse
アプリが出力するストリームチャンクを返却、`Content-Type`は`text/event-stream`です。
各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます:
<CodeGroup>
```streaming {{ title: 'Response' }}
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
```
</CodeGroup>
ストリーミングチャンクの構造は`event`によって異なります:
- `event: message` LLMがテキストチャンクを返すイベント、つまり完全なテキストがチャンク形式で出力されます。
- `task_id` (string) タスクID、リクエストの追跡と以下の生成停止APIに使用
- `message_id` (string) 一意のメッセージID
- `answer` (string) LLMが返したテキストチャンクの内容
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: message_end` メッセージ終了イベント、このイベントを受信するとストリーミングが終了したことを意味します。
- `task_id` (string) タスクID、リクエストの追跡と以下の生成停止APIに使用
- `message_id` (string) 一意のメッセージID
- `metadata` (object) メタデータ
- `usage` (Usage) モデル使用情報
- `retriever_resources` (array[RetrieverResource]) 引用と帰属のリスト
- `event: tts_message` TTS音声ストリームイベント、つまり音声合成出力。内容はMp3形式の音声ブロックで、base64文字列としてエンコードされています。再生時は単にbase64をデコードしてプレーヤーに供給するだけです。このメッセージは自動再生が有効な場合のみ利用可能
- `task_id` (string) タスクID、リクエストの追跡と以下の応答停止インターフェースに使用
- `message_id` (string) 一意のメッセージID
- `audio` (string) 音声合成後の音声、base64テキストコンテンツとしてエンコード、再生時は単にbase64をデコードしてプレーヤーに供給
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: tts_message_end` TTS音声ストリーム終了イベント、このイベントを受信すると音声ストリームが終了したことを示します。
- `task_id` (string) タスクID、リクエストの追跡と以下の応答停止インターフェースに使用
- `message_id` (string) 一意のメッセージID
- `audio` (string) 終了イベントには音声がないため、空文字列
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: message_replace` メッセージ内容置換イベント。
出力内容のモデレーションが有効な場合、コンテンツがフラグ付けされると、このイベントを通じてメッセージ内容が事前設定された返信に置き換えられます。
- `task_id` (string) タスクID、リクエストの追跡と以下の生成停止APIに使用
- `message_id` (string) 一意のメッセージID
- `answer` (string) 置換内容LLMの返信テキストすべてを直接置換
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: error`
ストリーミング処理中に発生した例外は、ストリームイベントの形式で出力され、エラーイベントを受信するとストリームが終了します。
- `task_id` (string) タスクID、リクエストの追跡と以下の生成停止APIに使用
- `message_id` (string) 一意のメッセージID
- `status` (int) HTTPステータスコード
- `code` (string) エラーコード
- `message` (string) エラーメッセージ
- `event: ping` 接続を維持するため10秒ごとのPingイベント。
### エラー
- 404, 会話が存在しません
- 400, `invalid_param`, パラメータ入力異常
- 400, `app_unavailable`, アプリ設定が利用できません
- 400, `provider_not_initialize`, 利用可能なモデル認証情報設定がありません
- 400, `provider_quota_exceeded`, モデル呼び出しクォータ不足
- 400, `model_currently_not_support`, 現在のモデルは利用できません
- 400, `completion_request_error`, テキスト生成に失敗しました
- 500, 内部サーバーエラー
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/completion-messages" targetCode={`curl -X POST '${props.appDetail.api_base_url}/completion-messages' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": {"query": "Hello, world!"},\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/completion-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": {
"query": "Hello, world!"
},
"response_mode": "streaming",
"user": "abc-123"
}'
```
</CodeGroup>
### ブロッキングモード
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"event": "message",
"message_id": "9da23599-e713-473b-982c-4328d4f5c78a",
"mode": "completion",
"answer": "Hello World!...",
"metadata": {
"usage": {
"prompt_tokens": 1033,
"prompt_unit_price": "0.001",
"prompt_price_unit": "0.001",
"prompt_price": "0.0010330",
"completion_tokens": 128,
"completion_unit_price": "0.002",
"completion_price_unit": "0.001",
"completion_price": "0.0002560",
"total_tokens": 1161,
"total_price": "0.0012890",
"currency": "USD",
"latency": 0.7682376249867957
}
},
"created_at": 1705407629
}
```
</CodeGroup>
### ストリーミングモード
<CodeGroup title="Response">
```streaming {{ title: 'Response' }}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": " I", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": "'m", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": " glad", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": " to", "created_at": 1679586595}
data: {"event": "message", "message_id": : "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": " meet", "created_at": 1679586595}
data: {"event": "message", "message_id": : "5ad4cb98-f0c7-4085-b384-88c403be6290", "answer": " you", "created_at": 1679586595}
data: {"event": "message_end", "id": "5e52ce04-874b-4d27-9045-b3bc80def685", "metadata": {"usage": {"prompt_tokens": 1033, "prompt_unit_price": "0.001", "prompt_price_unit": "0.001", "prompt_price": "0.0010330", "completion_tokens": 135, "completion_unit_price": "0.002", "completion_price_unit": "0.001", "completion_price": "0.0002700", "total_tokens": 1168, "total_price": "0.0013030", "currency": "USD", "latency": 1.381760165997548}}}
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/upload'
method='POST'
title='ファイルアップロード'
name='#file-upload'
/>
<Row>
<Col>
メッセージ送信時に使用するファイル(現在は画像のみ対応)をアップロードし、画像とテキストのマルチモーダルな理解を可能にします。
png、jpg、jpeg、webp、gif形式に対応しています。
<i>アップロードされたファイルは、現在のエンドユーザーのみが使用できます。</i>
### リクエストボディ
このインターフェースは`multipart/form-data`リクエストが必要です。
- `file` (File) 必須
アップロードするファイル。
- `user` (string) 必須
開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。
### レスポンス
アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。
- `id` (uuid) ID
- `name` (string) ファイル名
- `size` (int) ファイルサイズ(バイト)
- `extension` (string) ファイル拡張子
- `mime_type` (string) ファイルのMIMEタイプ
- `created_by` (uuid) エンドユーザーID
- `created_at` (timestamp) 作成タイムスタンプ、例1705395332
### エラー
- 400, `no_file_uploaded`, ファイルを提供する必要があります
- 400, `too_many_files`, 現在は1つのファイルのみ受け付けています
- 400, `unsupported_preview`, ファイルがプレビューに対応していません
- 400, `unsupported_estimate`, ファイルが推定に対応していません
- 413, `file_too_large`, ファイルが大きすぎます
- 415, `unsupported_file_type`, サポートされていない拡張子です。現在はドキュメントファイルのみ受け付けています
- 503, `s3_connection_failed`, S3サービスに接続できません
- 503, `s3_permission_denied`, S3へのファイルアップロード権限がありません
- 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています
- 500, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/files/upload' \
--header 'Authorization: Bearer {api_key}' \
--form 'file=@"/path/to/file"'
```
</CodeGroup>
### レスポンス例
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
"name": "example.png",
"size": 1024,
"extension": "png",
"mime_type": "image/png",
"created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13",
"created_at": 1577836800,
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/completion-messages/:task_id/stop'
method='POST'
title='生成の停止'
name='#stop-generatebacks'
/>
<Row>
<Col>
ストリーミングモードでのみサポートされています。
### パス
- `task_id` (string) タスクID、ストリーミングチャンクの返信から取得可能
リクエストボディ
- `user` (string) 必須
ユーザー識別子。エンドユーザーの身元を定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致する必要があります。
### レスポンス
- `result` (string) 常に"success"を返します
</Col>
<Col sticky>
### リクエスト例
<CodeGroup title="Request" tag="POST" label="/completion-messages/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/completion-messages/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{ "user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/completion-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": "abc-123"
}'
```
</CodeGroup>
### レスポンス例
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/:message_id/feedbacks'
method='POST'
title='メッセージフィードバック'
name='#feedbacks'
/>
<Row>
<Col>
エンドユーザーはフィードバックメッセージを提供でき、アプリケーション開発者が期待される出力を最適化するのに役立ちます。
### パス
<Properties>
<Property name='message_id' type='string' key='message_id'>
メッセージID
</Property>
</Properties>
### リクエストボディ
<Properties>
<Property name='rating' type='string' key='rating'>
高評価は`like`、低評価は`dislike`、高評価の取り消しは`null`
</Property>
<Property name='user' type='string' key='user'>
開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。
</Property>
</Properties>
### レスポンス
- `result` (string) 常に"success"を返します
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"rating": "like",
"user": "abc-123"
}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/parameters'
method='GET'
title='アプリケーション情報の取得'
name='#parameters'
/>
<Row>
<Col>
ページ開始時に、機能、入力パラメータ名、タイプ、デフォルト値などの情報を取得するために使用されます。
### クエリ
<Properties>
<Property name='user' type='string' key='user'>
開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。
</Property>
</Properties>
### レスポンス
- `opening_statement` (string) 開始文
- `suggested_questions` (array[string]) 開始時の提案質問リスト
- `suggested_questions_after_answer` (object) 回答後の提案質問を有効にします。
- `enabled` (bool) 有効かどうか
- `speech_to_text` (object) 音声からテキスト
- `enabled` (bool) 有効かどうか
- `retriever_resource` (object) 引用と帰属
- `enabled` (bool) 有効かどうか
- `annotation_reply` (object) 注釈付き返信
- `enabled` (bool) 有効かどうか
- `user_input_form` (array[object]) ユーザー入力フォーム設定
- `text-input` (object) テキスト入力コントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `paragraph` (object) 段落テキスト入力コントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `select` (object) ドロップダウンコントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `options` (array[string]) オプション値
- `file_upload` (object) ファイルアップロード設定
- `image` (object) 画像設定
現在は画像タイプのみ対応:`png`、`jpg`、`jpeg`、`webp`、`gif`
- `enabled` (bool) 有効かどうか
- `number_limits` (int) 画像数制限、デフォルトは3
- `transfer_methods` (array[string]) 転送方法リスト、remote_url、local_file、いずれかを選択
- `system_parameters` (object) システムパラメータ
- `file_size_limit` (int) ドキュメントアップロードサイズ制限MB
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限MB
- `audio_file_size_limit` (int) 音声ファイルアップロードサイズ制限MB
- `video_file_size_limit` (int) 動画ファイルアップロードサイズ制限MB
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/parameters" targetCode={` curl -X GET '${props.appDetail.api_base_url}/parameters?user=abc-123'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/parameters?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"opening_statement": "Hello!",
"suggested_questions_after_answer": {
"enabled": true
},
"speech_to_text": {
"enabled": true
},
"retriever_resource": {
"enabled": true
},
"annotation_reply": {
"enabled": true
},
"user_input_form": [
{
"paragraph": {
"label": "Query",
"variable": "query",
"required": true,
"default": ""
}
}
],
"file_upload": {
"image": {
"enabled": false,
"number_limits": 3,
"detail": "high",
"transfer_methods": [
"remote_url",
"local_file"
]
}
},
"system_parameters": {
"file_size_limit": 15,
"image_file_size_limit": 10,
"audio_file_size_limit": 50,
"video_file_size_limit": 100
}
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/text-to-audio'
method='POST'
title='テキストから音声'
name='#audio'
/>
<Row>
<Col>
テキストを音声に変換します。
### リクエストボディ
<Properties>
<Property name='message_id' type='str' key='text'>
Difyが生成したテキストメッセージの場合、生成されたmessage-idを直接渡すだけです。バックエンドはmessage-idを使用して対応するコンテンツを検索し、音声情報を直接合成します。message_idとtextの両方が同時に提供された場合、message_idが優先されます。
</Property>
<Property name='text' type='str' key='text'>
音声生成コンテンツ。
</Property>
<Property name='user' type='string' key='user'>
開発者が定義したユーザー識別子。アプリ内で一意性を確保する必要があります。
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/text-to-audio" targetCode={`curl -o text-to-audio.mp3 -X POST '${props.appDetail.api_base_url}/text-to-audio' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290",\n "text": "Hello Dify",\n "user": "abc-123"\n}'`}>
```bash {{ title: 'cURL' }}
curl -o text-to-audio.mp3 -X POST '${props.appDetail.api_base_url}/text-to-audio' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290",
"text": "Hello Dify",
"user": "abc-123"
}'
```
</CodeGroup>
<CodeGroup title="headers">
```json {{ title: 'headers' }}
{
"Content-Type": "audio/wav"
}
```
</CodeGroup>
</Col>
</Row>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ Workflow applications offers non-session support and is ideal for translation, a
User identifier, used to define the identity of the end-user for retrieval and statistics.
Should be uniquely defined by the developer within the application.
- `files` (array[object]) Optional
File list, suitable for inputting files combined with text understanding and answering questions, available only when the model supports Vision capability.
File list, suitable for inputting files combined with text understanding and answering questions, available only when the model supports file parsing and understanding capability.
- `type` (string) Supported type:
- `document` ('TXT', 'MD', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB')
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
@ -188,6 +188,19 @@ Workflow applications offers non-session support and is ideal for translation, a
}'
```
</CodeGroup>
<CodeGroup title="File variable example">
```json {{ title: 'File variable example' }}
{
"inputs": {
"{variable_name}": {
"transfer_method": "local_file",
"upload_file_id": "{upload_file_id}",
"type": "{document_type}"
}
}
}
```
</CodeGroup>
### Blocking Mode
<CodeGroup title="Response">
@ -223,7 +236,88 @@ Workflow applications offers non-session support and is ideal for translation, a
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
```
</CodeGroup>
<CodeGroup title="File upload sample code">
```json {{ title: 'File upload sample code' }}
{
import requests
import json
def upload_file(file_path, user):
upload_url = "https://api.dify.ai/v1/files/upload"
headers = {
"Authorization": "Bearer app-xxxxxxxx",
}
try:
print("Upload file...")
with open(file_path, 'rb') as file:
files = {
'file': (file_path, file, 'text/plain') # Make sure the file is uploaded with the appropriate MIME type
}
data = {
"user": user,
"type": "TXT" # Set the file type to TXT
}
response = requests.post(upload_url, headers=headers, files=files, data=data)
if response.status_code == 201: # 201 means creation is successful
print("File uploaded successfully")
return response.json().get("id") # Get the uploaded file ID
else:
print(f"File upload failed, status code: {response.status_code}")
return None
except Exception as e:
print(f"Error occurred: {str(e)}")
return None
def run_workflow(file_id, user, response_mode="blocking"):
workflow_url = "https://api.dify.ai/v1/workflows/run"
headers = {
"Authorization": "Bearer app-xxxxxxxxx",
"Content-Type": "application/json"
}
data = {
"inputs": {
"orig_mail": {
"transfer_method": "local_file",
"upload_file_id": file_id,
"type": "document"
}
},
"response_mode": response_mode,
"user": user
}
try:
print("Run Workflow...")
response = requests.post(workflow_url, headers=headers, json=data)
if response.status_code == 200:
print("Workflow execution successful")
return response.json()
else:
print(f"Workflow execution failed, status code: {response.status_code}")
return {"status": "error", "message": f"Failed to execute workflow, status code: {response.status_code}"}
except Exception as e:
print(f"Error occurred: {str(e)}")
return {"status": "error", "message": str(e)}
# Usage Examples
file_path = "{your_file_path}"
user = "difyuser"
# Upload files
file_id = upload_file(file_path, user)
if file_id:
# The file was uploaded successfully, and the workflow continues to run
result = run_workflow(file_id, user)
print(result)
else:
print("File upload failed and workflow cannot be executed")
}
```
</CodeGroup>
</Col>
</Row>

View File

@ -0,0 +1,701 @@
import { CodeGroup } from '../code.tsx'
import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from '../md.tsx'
# ワークフローアプリAPI
ワークフローアプリケーションは、セッションをサポートせず、翻訳、記事作成、要約AIなどに最適です。
<div>
### ベースURL
<CodeGroup title="コード" targetCode={props.appDetail.api_base_url}>
```javascript
```
</CodeGroup>
### 認証
サービスAPIは`API-Key`認証を使用します。
<i>**APIキーの漏洩を防ぐため、APIキーはクライアント側で共有または保存せず、サーバー側で保存することを強くお勧めします。**</i>
すべてのAPIリクエストにおいて、以下のように`Authorization`HTTPヘッダーにAPIキーを含めてください
<CodeGroup title="コード">
```javascript
Authorization: Bearer {API_KEY}
```
</CodeGroup>
</div>
---
<Heading
url='/workflows/run'
method='POST'
title='ワークフローを実行'
name='#Execute-Workflow'
/>
<Row>
<Col>
ワークフローを実行します。公開されたワークフローがないと実行できません。
### リクエストボディ
- `inputs` (object) 必須
アプリで定義されたさまざまな変数値の入力を許可します。
`inputs`パラメータには複数のキー/値ペアが含まれ、各キーは特定の変数に対応し、各値はその変数の特定の値です。
ワークフローアプリケーションは少なくとも1つのキー/値ペアの入力を必要とします。
変数がファイルタイプの場合、以下の`files`で説明されているキーを持つオブジェクトを指定してください。
- `response_mode` (string) 必須
応答の返却モードを指定します。サポートされているモード:
- `streaming` ストリーミングモード推奨、SSE[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))を通じてタイプライターのような出力を実装します。
- `blocking` ブロッキングモード、実行完了後に結果を返します。(プロセスが長い場合、リクエストが中断される可能性があります)
<i>Cloudflareの制限により、100秒後に応答がない場合、リクエストは中断されます。</i>
- `user` (string) 必須
ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用されます。
アプリケーション内で開発者によって一意に定義される必要があります。
- `files` (array[object]) オプション
ファイルリストは、テキスト理解と質問への回答を組み合わせたファイルの入力に適しています。モデルがファイルの解析と理解機能をサポートしている場合にのみ使用できます。
- `type` (string) サポートされているタイプ:
- `document` ('TXT', 'MD', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB')
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
- `audio` ('MP3', 'M4A', 'WAV', 'WEBM', 'AMR')
- `video` ('MP4', 'MOV', 'MPEG', 'MPGA')
- `transfer_method` (string) 転送方法、画像URLの場合は`remote_url` / ファイルアップロードの場合は`local_file`
- `url` (string) 画像URL転送方法が`remote_url`の場合)
- `upload_file_id` (string) アップロードされたファイルID、事前にファイルアップロードAPIを通じて取得する必要があります転送方法が`local_file`の場合)
### 応答
`response_mode`が`blocking`の場合、CompletionResponseオブジェクトを返します。
`response_mode`が`streaming`の場合、ChunkCompletionResponseストリームを返します。
### CompletionResponse
アプリの結果を返します。`Content-Type`は`application/json`です。
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `data` (object) 結果の詳細
- `id` (string) ワークフロー実行のID
- `workflow_id` (string) 関連するワークフローのID
- `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped`
- `outputs` (json) オプションの出力内容
- `error` (string) オプションのエラー理由
- `elapsed_time` (float) オプションの使用時間(秒)
- `total_tokens` (int) オプションの使用トークン数
- `total_steps` (int) デフォルト0
- `created_at` (timestamp) 開始時間
- `finished_at` (timestamp) 終了時間
### ChunkCompletionResponse
アプリによって出力されたストリームチャンクを返します。`Content-Type`は`text/event-stream`です。
各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます。以下のように表示されます:
<CodeGroup>
```streaming {{ title: '応答' }}
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
```
</CodeGroup>
ストリーミングチャンクの構造は`event`に応じて異なります:
- `event: workflow_started` ワークフローが実行を開始
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `event` (string) `workflow_started`に固定
- `data` (object) 詳細
- `id` (string) ワークフロー実行の一意のID
- `workflow_id` (string) 関連するワークフローのID
- `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1から始まります
- `created_at` (timestamp) 作成タイムスタンプ、例1705395332
- `event: node_started` ノード実行開始
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `event` (string) `node_started`に固定
- `data` (object) 詳細
- `id` (string) ワークフロー実行の一意のID
- `node_id` (string) ードのID
- `node_type` (string) ノードのタイプ
- `title` (string) ノードの名前
- `index` (int) 実行シーケンス番号、トレースノードシーケンスを表示するために使用
- `predecessor_node_id` (string) オプションのプレフィックスードID、キャンバス表示実行パスに使用
- `inputs` (array[object]) ノードで使用されるすべての前のノード変数の内容
- `created_at` (timestamp) 開始のタイムスタンプ、例1705395332
- `event: node_finished` ノード実行終了、同じイベントで異なる状態で成功または失敗
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `event` (string) `node_finished`に固定
- `data` (object) 詳細
- `id` (string) ワークフロー実行の一意のID
- `node_id` (string) ードのID
- `node_type` (string) ノードのタイプ
- `title` (string) ノードの名前
- `index` (int) 実行シーケンス番号、トレースノードシーケンスを表示するために使用
- `predecessor_node_id` (string) オプションのプレフィックスードID、キャンバス表示実行パスに使用
- `inputs` (array[object]) ノードで使用されるすべての前のノード変数の内容
- `process_data` (json) オプションのノードプロセスデータ
- `outputs` (json) オプションの出力内容
- `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped`
- `error` (string) オプションのエラー理由
- `elapsed_time` (float) オプションの使用時間(秒)
- `execution_metadata` (json) メタデータ
- `total_tokens` (int) オプションの使用トークン数
- `total_price` (decimal) オプションの総コスト
- `currency` (string) オプション 例:`USD` / `RMB`
- `created_at` (timestamp) 開始のタイムスタンプ、例1705395332
- `event: workflow_finished` ワークフロー実行終了、同じイベントで異なる状態で成功または失敗
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `event` (string) `workflow_finished`に固定
- `data` (object) 詳細
- `id` (string) ワークフロー実行のID
- `workflow_id` (string) 関連するワークフローのID
- `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped`
- `outputs` (json) オプションの出力内容
- `error` (string) オプションのエラー理由
- `elapsed_time` (float) オプションの使用時間(秒)
- `total_tokens` (int) オプションの使用トークン数
- `total_steps` (int) デフォルト0
- `created_at` (timestamp) 開始時間
- `finished_at` (timestamp) 終了時間
- `event: tts_message` TTSオーディオストリームイベント、つまり音声合成出力。内容はMp3形式のオーディオブロックで、base64文字列としてエンコードされています。再生時には、base64をデコードしてプレーヤーに入力するだけです。このメッセージは自動再生が有効な場合にのみ利用可能
- `task_id` (string) タスクID、リクエスト追跡と以下の停止応答インターフェースに使用
- `message_id` (string) 一意のメッセージID
- `audio` (string) 音声合成後のオーディオ、base64テキストコンテンツとしてエンコードされており、再生時にはbase64をデコードしてプレーヤーに入力するだけです
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: tts_message_end` TTSオーディオストリーム終了イベント。このイベントを受信すると、オーディオストリームの終了を示します。
- `task_id` (string) タスクID、リクエスト追跡と以下の停止応答インターフェースに使用
- `message_id` (string) 一意のメッセージID
- `audio` (string) 終了イベントにはオーディオがないため、これは空の文字列です
- `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: ping` 接続を維持するために10秒ごとに送信されるPingイベント。
### エラー
- 400, `invalid_param`, 異常なパラメータ入力
- 400, `app_unavailable`, アプリの設定が利用できません
- 400, `provider_not_initialize`, 利用可能なモデル資格情報の設定がありません
- 400, `provider_quota_exceeded`, モデル呼び出しのクォータが不足しています
- 400, `model_currently_not_support`, 現在のモデルは利用できません
- 400, `workflow_request_error`, ワークフロー実行に失敗しました
- 500, 内部サーバーエラー
</Col>
<Col sticky>
<CodeGroup title="リクエスト" tag="POST" label="/workflows/run" targetCode={`curl -X POST '${props.appDetail.api_base_url}/workflows/run' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/workflows/run' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": {},
"response_mode": "streaming",
"user": "abc-123"
}'
```
</CodeGroup>
<CodeGroup title="ファイル変数の例">
```json {{ title: 'ファイル変数の例' }}
{
"inputs": {
"{variable_name}": {
"transfer_method": "local_file",
"upload_file_id": "{upload_file_id}",
"type": "{document_type}"
}
}
}
```
</CodeGroup>
### ブロッキングモード
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"workflow_run_id": "djflajgkldjgd",
"task_id": "9da23599-e713-473b-982c-4328d4f5c78a",
"data": {
"id": "fdlsjfjejkghjda",
"workflow_id": "fldjaslkfjlsda",
"status": "succeeded",
"outputs": {
"text": "Nice to meet you."
},
"error": null,
"elapsed_time": 0.875,
"total_tokens": 3562,
"total_steps": 8,
"created_at": 1705407629,
"finished_at": 1727807631
}
}
```
</CodeGroup>
### ストリーミングモード
<CodeGroup title="応答">
```streaming {{ title: '応答' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
```
</CodeGroup>
<CodeGroup title="ファイルアップロードのサンプルコード">
```json {{ title: 'ファイルアップロードのサンプルコード' }}
{
import requests
import json
def upload_file(file_path, user):
upload_url = "https://api.dify.ai/v1/files/upload"
headers = {
"Authorization": "Bearer app-xxxxxxxx",
}
try:
print("ファイルをアップロードしています...")
with open(file_path, 'rb') as file:
files = {
'file': (file_path, file, 'text/plain') # ファイルが適切な MIME タイプでアップロードされていることを確認してください
}
data = {
"user": user,
"type": "TXT" # ファイルタイプをTXTに設定します
}
response = requests.post(upload_url, headers=headers, files=files, data=data)
if response.status_code == 201: # 201 は作成が成功したことを意味します
print("ファイルが正常にアップロードされました")
return response.json().get("id") # アップロードされたファイルIDを取得する
else:
print(f"ファイルのアップロードに失敗しました。ステータス コード: {response.status_code}")
return None
except Exception as e:
print(f"エラーが発生しました: {str(e)}")
return None
def run_workflow(file_id, user, response_mode="blocking"):
workflow_url = "https://api.dify.ai/v1/workflows/run"
headers = {
"Authorization": "Bearer app-xxxxxxxxx",
"Content-Type": "application/json"
}
data = {
"inputs": {
"orig_mail": {
"transfer_method": "local_file",
"upload_file_id": file_id,
"type": "document"
}
},
"response_mode": response_mode,
"user": user
}
try:
print("ワークフローを実行...")
response = requests.post(workflow_url, headers=headers, json=data)
if response.status_code == 200:
print("ワークフローが正常に実行されました")
return response.json()
else:
print(f"ワークフローの実行がステータス コードで失敗しました: {response.status_code}")
return {"status": "error", "message": f"Failed to execute workflow, status code: {response.status_code}"}
except Exception as e:
print(f"エラーが発生しました: {str(e)}")
return {"status": "error", "message": str(e)}
# 使用例
file_path = "{your_file_path}"
user = "difyuser"
# ファイルをアップロードする
file_id = upload_file(file_path, user)
if file_id:
# ファイルは正常にアップロードされました。ワークフローの実行を続行します
result = run_workflow(file_id, user)
print(result)
else:
print("ファイルのアップロードに失敗し、ワークフローを実行できません")
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/run/:workflow_id'
method='GET'
title='ワークフロー実行詳細を取得'
name='#get-workflow-run-detail'
/>
<Row>
<Col>
ワークフロー実行IDに基づいて、ワークフロータスクの現在の実行結果を取得します。
### パス
- `workflow_id` (string) ワークフローID、ストリーミングチャンクの返り値から取得可能
### 応答
- `id` (string) ワークフロー実行のID
- `workflow_id` (string) 関連するワークフローのID
- `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped`
- `inputs` (json) 入力内容
- `outputs` (json) 出力内容
- `error` (string) エラー理由
- `total_steps` (int) タスクの総ステップ数
- `total_tokens` (int) 使用されるトークンの総数
- `created_at` (timestamp) 開始時間
- `finished_at` (timestamp) 終了時間
- `elapsed_time` (float) 使用される総秒数
</Col>
<Col sticky>
### リクエスト例
<CodeGroup title="リクエスト" tag="GET" label="/workflows/run/:workflow_id" targetCode={`curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_id' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_id' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json'
```
</CodeGroup>
### 応答例
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"id": "b1ad3277-089e-42c6-9dff-6820d94fbc76",
"workflow_id": "19eff89f-ec03-4f75-b0fc-897e7effea02",
"status": "succeeded",
"inputs": "{\"sys.files\": [], \"sys.user_id\": \"abc-123\"}",
"outputs": null,
"error": null,
"total_steps": 3,
"total_tokens": 0,
"created_at": "Thu, 18 Jul 2024 03:17:40 -0000",
"finished_at": "Thu, 18 Jul 2024 03:18:10 -0000",
"elapsed_time": 30.098514399956912
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/tasks/:task_id/stop'
method='POST'
title='生成を停止'
name='#stop-generatebacks'
/>
<Row>
<Col>
ストリーミングモードでのみサポートされています。
### パス
- `task_id` (string) タスクID、ストリーミングチャンクの返り値から取得可能
### リクエストボディ
- `user` (string) 必須
ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。
### 応答
- `result` (string) 常に"success"を返します
</Col>
<Col sticky>
### リクエスト例
<CodeGroup title="リクエスト" tag="POST" label="/workflows/tasks/:task_id/stop" targetCode={`curl -X POST '${props.appDetail.api_base_url}/workflows/tasks/:task_id/stop' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json' \\\n--data-raw '{"user": "abc-123"}'`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/workflows/tasks/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": "abc-123"
}'
```
</CodeGroup>
### 応答例
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"result": "success"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/upload'
method='POST'
title='ファイルアップロード'
name='#file-upload'
/>
<Row>
<Col>
メッセージ送信時に使用するためのファイルをアップロードし、画像とテキストのマルチモーダル理解を可能にします。
ワークフローでサポートされている任意の形式をサポートします。
アップロードされたファイルは、現在のエンドユーザーのみが使用できます。
### リクエストボディ
このインターフェースは`multipart/form-data`リクエストを必要とします。
- `file` (File) 必須
アップロードするファイル。
- `user` (string) 必須
ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。
### 応答
アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。
- `id` (uuid) ID
- `name` (string) ファイル名
- `size` (int) ファイルサイズ(バイト)
- `extension` (string) ファイル拡張子
- `mime_type` (string) ファイルのMIMEタイプ
- `created_by` (uuid) エンドユーザーID
- `created_at` (timestamp) 作成タイムスタンプ、例1705395332
### エラー
- 400, `no_file_uploaded`, ファイルが提供されていません
- 400, `too_many_files`, 現在は1つのファイルのみ受け付けています
- 400, `unsupported_preview`, ファイルはプレビューをサポートしていません
- 400, `unsupported_estimate`, ファイルは推定をサポートしていません
- 413, `file_too_large`, ファイルが大きすぎます
- 415, `unsupported_file_type`, サポートされていない拡張子、現在はドキュメントファイルのみ受け付けています
- 503, `s3_connection_failed`, S3サービスに接続できません
- 503, `s3_permission_denied`, S3にファイルをアップロードする権限がありません
- 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています
- 500, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/files/upload' \
--header 'Authorization: Bearer {api_key}' \
--form 'file=@"/path/to/file"'
```
</CodeGroup>
### 応答例
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
"name": "example.png",
"size": 1024,
"extension": "png",
"mime_type": "image/png",
"created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13",
"created_at": 1577836800,
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/parameters'
method='GET'
title='アプリケーション情報を取得'
name='#parameters'
/>
<Row>
<Col>
ページに入る際に、機能、入力パラメータ名、タイプ、デフォルト値などの情報を取得するために使用されます。
### クエリ
<Properties>
<Property name='user' type='string' key='user'>
ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。
</Property>
</Properties>
### 応答
- `user_input_form` (array[object]) ユーザー入力フォームの設定
- `text-input` (object) テキスト入力コントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `paragraph` (object) 段落テキスト入力コントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `select` (object) ドロップダウンコントロール
- `label` (string) 変数表示ラベル名
- `variable` (string) 変数ID
- `required` (bool) 必須かどうか
- `default` (string) デフォルト値
- `options` (array[string]) オプション値
- `file_upload` (object) ファイルアップロード設定
- `image` (object) 画像設定
現在サポートされている画像タイプのみ:`png`, `jpg`, `jpeg`, `webp`, `gif`
- `enabled` (bool) 有効かどうか
- `number_limits` (int) 画像数の制限、デフォルトは3
- `transfer_methods` (array[string]) 転送方法のリスト、remote_url, local_file、いずれかを選択する必要があります
- `system_parameters` (object) システムパラメータ
- `file_size_limit` (int) ドキュメントアップロードサイズ制限MB
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限MB
- `audio_file_size_limit` (int) オーディオファイルアップロードサイズ制限MB
- `video_file_size_limit` (int) ビデオファイルアップロードサイズ制限MB
</Col>
<Col sticky>
<CodeGroup title="リクエスト" tag="GET" label="/parameters" targetCode={` curl -X GET '${props.appDetail.api_base_url}/parameters?user=abc-123'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/parameters?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"user_input_form": [
{
"paragraph": {
"label": "Query",
"variable": "query",
"required": true,
"default": ""
}
}
],
"file_upload": {
"image": {
"enabled": false,
"number_limits": 3,
"detail": "high",
"transfer_methods": [
"remote_url",
"local_file"
]
}
},
"system_parameters": {
"file_size_limit": 15,
"image_file_size_limit": 10,
"audio_file_size_limit": 50,
"video_file_size_limit": 100
}
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/logs'
method='GET'
title='ワークフローログを取得'
name='#Get-Workflow-Logs'
/>
<Row>
<Col>
ワークフローログを返します。最初のページは最新の`{limit}`メッセージを返します。つまり、逆順です。
### クエリ
<Properties>
<Property name='keyword' type='string' key='keyword'>
検索するキーワード
</Property>
<Property name='status' type='string' key='status'>
succeeded/failed/stopped
</Property>
<Property name='page' type='int' key='page'>
現在のページ、デフォルトは1。
</Property>
<Property name='limit' type='int' key='limit'>
1回のリクエストで返すチャット履歴メッセージの数、デフォルトは20。
</Property>
</Properties>
### 応答
- `page` (int) 現在のページ
- `limit` (int) 返されたアイテムの数、入力がシステム制限を超える場合、システム制限量を返します
- `total` (int) 合計アイテム数
- `has_more` (bool) 次のページがあるかどうか
- `data` (array[object]) ログリスト
- `id` (string) ID
- `workflow_run` (object) ワークフロー実行
- `id` (string) ID
- `version` (string) バージョン
- `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped`
- `error` (string) オプションのエラー理由
- `elapsed_time` (float) 使用される総秒数
- `total_tokens` (int) 使用されるトークン数
- `total_steps` (int) デフォルト0
- `created_at` (timestamp) 開始時間
- `finished_at` (timestamp) 終了時間
- `created_from` (string) 作成元
- `created_by_role` (string) 作成者の役割
- `created_by_account` (string) オプションの作成者アカウント
- `created_by_end_user` (object) エンドユーザーによって作成
- `id` (string) ID
- `type` (string) タイプ
- `is_anonymous` (bool) 匿名かどうか
- `session_id` (string) セッションID
- `created_at` (timestamp) 作成時間
</Col>
<Col sticky>
<CodeGroup title="リクエスト" tag="GET" label="/workflows/logs" targetCode={`curl -X GET '${props.appDetail.api_base_url}/workflows/logs'\\\n --header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/workflows/logs?limit=1'
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
### 応答例
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"page": 1,
"limit": 1,
"total": 7,
"has_more": true,
"data": [
{
"id": "e41b93f1-7ca2-40fd-b3a8-999aeb499cc0",
"workflow_run": {
"id": "c0640fc8-03ef-4481-a96c-8a13b732a36e",
"version": "2024-08-01 12:17:09.771832",
"status": "succeeded",
"error": null,
"elapsed_time": 1.3588523610014818,
"total_tokens": 0,
"total_steps": 3,
"created_at": 1726139643,
"finished_at": 1726139644
},
"created_from": "service-api",
"created_by_role": "end_user",
"created_by_account": null,
"created_by_end_user": {
"id": "7f7d9117-dd9d-441d-8970-87e5e7e687a3",
"type": "service_api",
"is_anonymous": false,
"session_id": "abc-123"
},
"created_at": 1726139644
}
]
}
```
</CodeGroup>
</Col>
</Row>

View File

@ -52,7 +52,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
用户标识,用于定义终端用户的身份,方便检索、统计。
由开发者定义规则,需保证用户标识在应用内唯一。
- `files` (array[object]) Optional
文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持 Vision 能力时可用。
文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持该类型文件解析能力时可用。
- `type` (string) 支持类型:
- `document` 具体类型包含:'TXT', 'MD', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB'
- `image` 具体类型包含:'JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'
@ -171,8 +171,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/workflows/run" targetCode={`curl -X POST '${props.appDetail.api_base_url}/workflows/run' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
<CodeGroup title="Request" tag="POST" label="/workflows/run" targetCode={`curl -X POST '${props.appDetail.api_base_url}/workflows/run' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
```bash {{ title: 'cURL' }}
curl -X POST '${props.appDetail.api_base_url}/workflows/run' \
--header 'Authorization: Bearer {api_key}' \
@ -183,7 +182,19 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
"user": "abc-123"
}'
```
</CodeGroup>
<CodeGroup title="File variable example">
```json {{ title: 'File variable example' }}
{
"inputs": {
"{variable_name}": {
"transfer_method": "local_file",
"upload_file_id": "{upload_file_id}",
"type": "{document_type}"
}
}
}
```
</CodeGroup>
### Blocking Mode
<CodeGroup title="Response">
@ -219,7 +230,88 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
```
</CodeGroup>
<CodeGroup title="File upload sample code">
```json {{ title: 'File upload sample code' }}
{
import requests
import json
def upload_file(file_path, user):
upload_url = "https://api.dify.ai/v1/files/upload"
headers = {
"Authorization": "Bearer app-xxxxxxxx",
}
try:
print("上传文件中...")
with open(file_path, 'rb') as file:
files = {
'file': (file_path, file, 'text/plain') # 确保文件以适当的MIME类型上传
}
data = {
"user": user,
"type": "TXT" # 设置文件类型为TXT
}
response = requests.post(upload_url, headers=headers, files=files, data=data)
if response.status_code == 201: # 201 表示创建成功
print("文件上传成功")
return response.json().get("id") # 获取上传的文件 ID
else:
print(f"文件上传失败,状态码: {response.status_code}")
return None
except Exception as e:
print(f"发生错误: {str(e)}")
return None
def run_workflow(file_id, user, response_mode="blocking"):
workflow_url = "https://api.dify.ai/v1/workflows/run"
headers = {
"Authorization": "Bearer app-xxxxxxxxx",
"Content-Type": "application/json"
}
data = {
"inputs": {
"orig_mail": {
"transfer_method": "local_file",
"upload_file_id": file_id,
"type": "document"
}
},
"response_mode": response_mode,
"user": user
}
try:
print("运行工作流...")
response = requests.post(workflow_url, headers=headers, json=data)
if response.status_code == 200:
print("工作流执行成功")
return response.json()
else:
print(f"工作流执行失败,状态码: {response.status_code}")
return {"status": "error", "message": f"Failed to execute workflow, status code: {response.status_code}"}
except Exception as e:
print(f"发生错误: {str(e)}")
return {"status": "error", "message": str(e)}
# 使用示例
file_path = "{your_file_path}"
user = "difyuser"
# 上传文件
file_id = upload_file(file_path, user)
if file_id:
# 文件上传成功,继续运行工作流
result = run_workflow(file_id, user)
print(result)
else:
print("文件上传失败,无法执行工作流")
}
```
</CodeGroup>
</Col>
</Row>

View File

@ -55,6 +55,9 @@ export enum ModelFeatureEnum {
multiToolCall = 'multi-tool-call',
agentThought = 'agent-thought',
vision = 'vision',
video = 'video',
document = 'document',
audio = 'audio',
}
export enum ModelFeatureTextEnum {
@ -62,6 +65,9 @@ export enum ModelFeatureTextEnum {
multiToolCall = 'Multi Tool Call',
agentThought = 'Agent Thought',
vision = 'Vision',
video = 'Video',
document = 'Document',
audio = 'Audio',
}
export enum ModelStatusEnum {

View File

@ -22,6 +22,7 @@ export const useWorkflowTemplate = () => {
...nodesInitialData.llm,
memory: {
window: { enabled: false, size: 10 },
query_prompt_template: '{{#sys.query#}}',
},
selected: true,
},

View File

@ -16,6 +16,7 @@ import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types
import ResultPanel from '@/app/components/workflow/run/result-panel'
import Toast from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
const i18nPrefix = 'workflow.singleRun'
@ -39,6 +40,11 @@ function formatValue(value: string | any, type: InputVarType) {
return JSON.parse(item)
})
}
if (type === InputVarType.multiFiles)
return getProcessedFiles(value)
if (type === InputVarType.singleFile)
return getProcessedFiles([value])[0]
return value
}

View File

@ -160,6 +160,7 @@ const CodeEditor: FC<Props> = ({
hideSearch
vars={availableVars}
onChange={handleSelectVar}
isSupportFileVar={false}
/>
</div>
)}

View File

@ -128,7 +128,7 @@ const PanelOperatorPopup = ({
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
onClick={() => {
onClosePopup()
handleNodesCopy()
handleNodesCopy(id)
}}
>
{t('workflow.common.copy')}

View File

@ -18,6 +18,7 @@ type Props = {
isSupportConstantValue?: boolean
onlyLeafNodeVar?: boolean
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
isSupportFileVar?: boolean
}
const VarList: FC<Props> = ({
@ -29,6 +30,7 @@ const VarList: FC<Props> = ({
isSupportConstantValue,
onlyLeafNodeVar,
filterVar,
isSupportFileVar = true,
}) => {
const { t } = useTranslation()
@ -94,6 +96,7 @@ const VarList: FC<Props> = ({
defaultVarKindType={item.variable_type}
onlyLeafNodeVar={onlyLeafNodeVar}
filterVar={filterVar}
isSupportFileVar={isSupportFileVar}
/>
{!readonly && (
<RemoveButton

View File

@ -59,6 +59,7 @@ type Props = {
isInTable?: boolean
onRemove?: () => void
typePlaceHolder?: string
isSupportFileVar?: boolean
}
const VarReferencePicker: FC<Props> = ({
@ -81,6 +82,7 @@ const VarReferencePicker: FC<Props> = ({
isInTable,
onRemove,
typePlaceHolder,
isSupportFileVar = true,
}) => {
const { t } = useTranslation()
const store = useStoreApi()
@ -382,6 +384,7 @@ const VarReferencePicker: FC<Props> = ({
vars={outputVars}
onChange={handleVarReferenceChange}
itemWidth={isAddBtnTrigger ? 260 : triggerWidth}
isSupportFileVar={isSupportFileVar}
/>
)}
</PortalToFollowElemContent>

View File

@ -8,11 +8,13 @@ type Props = {
vars: NodeOutPutVar[]
onChange: (value: ValueSelector, varDetail: Var) => void
itemWidth?: number
isSupportFileVar?: boolean
}
const VarReferencePopup: FC<Props> = ({
vars,
onChange,
itemWidth,
isSupportFileVar = true,
}) => {
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
return (
@ -24,7 +26,7 @@ const VarReferencePopup: FC<Props> = ({
vars={vars}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar
isSupportFileVar={isSupportFileVar}
/>
</div >
)

View File

@ -89,6 +89,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
list={inputs.variables}
onChange={handleVarListChange}
filterVar={filterVar}
isSupportFileVar={false}
/>
</Field>
<Split />

View File

@ -0,0 +1,154 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BodyType, type HttpNodeType, Method } from '../types'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Toast from '@/app/components/base/toast'
import { useNodesInteractions } from '@/app/components/workflow/hooks'
type Props = {
nodeId: string
isShow: boolean
onHide: () => void
handleCurlImport: (node: HttpNodeType) => void
}
const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: string | null } => {
if (!curlCommand.trim().toLowerCase().startsWith('curl'))
return { node: null, error: 'Invalid cURL command. Command must start with "curl".' }
const node: Partial<HttpNodeType> = {
title: 'HTTP Request',
desc: 'Imported from cURL',
method: Method.get,
url: '',
headers: '',
params: '',
body: { type: BodyType.none, data: '' },
}
const args = curlCommand.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []
for (let i = 1; i < args.length; i++) {
const arg = args[i].replace(/^['"]|['"]$/g, '')
switch (arg) {
case '-X':
case '--request':
if (i + 1 >= args.length)
return { node: null, error: 'Missing HTTP method after -X or --request.' }
node.method = (args[++i].replace(/^['"]|['"]$/g, '') as Method) || Method.get
break
case '-H':
case '--header':
if (i + 1 >= args.length)
return { node: null, error: 'Missing header value after -H or --header.' }
node.headers += (node.headers ? '\n' : '') + args[++i].replace(/^['"]|['"]$/g, '')
break
case '-d':
case '--data':
case '--data-raw':
case '--data-binary':
if (i + 1 >= args.length)
return { node: null, error: 'Missing data value after -d, --data, --data-raw, or --data-binary.' }
node.body = { type: BodyType.rawText, data: args[++i].replace(/^['"]|['"]$/g, '') }
break
case '-F':
case '--form': {
if (i + 1 >= args.length)
return { node: null, error: 'Missing form data after -F or --form.' }
if (node.body?.type !== BodyType.formData)
node.body = { type: BodyType.formData, data: '' }
const formData = args[++i].replace(/^['"]|['"]$/g, '')
const [key, ...valueParts] = formData.split('=')
if (!key)
return { node: null, error: 'Invalid form data format.' }
let value = valueParts.join('=')
// To support command like `curl -F "file=@/path/to/file;type=application/zip"`
// the `;type=application/zip` should translate to `Content-Type: application/zip`
const typeMatch = value.match(/^(.+?);type=(.+)$/)
if (typeMatch) {
const [, actualValue, mimeType] = typeMatch
value = actualValue
node.headers += `${node.headers ? '\n' : ''}Content-Type: ${mimeType}`
}
node.body.data += `${node.body.data ? '\n' : ''}${key}:${value}`
break
}
case '--json':
if (i + 1 >= args.length)
return { node: null, error: 'Missing JSON data after --json.' }
node.body = { type: BodyType.json, data: args[++i].replace(/^['"]|['"]$/g, '') }
break
default:
if (arg.startsWith('http') && !node.url)
node.url = arg
break
}
}
if (!node.url)
return { node: null, error: 'Missing URL or url not start with http.' }
// Extract query params from URL
const urlParts = node.url?.split('?') || []
if (urlParts.length > 1) {
node.url = urlParts[0]
node.params = urlParts[1].replace(/&/g, '\n').replace(/=/g, ': ')
}
return { node: node as HttpNodeType, error: null }
}
const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
const [inputString, setInputString] = useState('')
const { handleNodeSelect } = useNodesInteractions()
const { t } = useTranslation()
const handleSave = useCallback(() => {
const { node, error } = parseCurl(inputString)
if (error) {
Toast.notify({
type: 'error',
message: error,
})
return
}
if (!node)
return
onHide()
handleCurlImport(node)
// Close the panel then open it again to make the panel re-render
handleNodeSelect(nodeId, true)
setTimeout(() => {
handleNodeSelect(nodeId)
}, 0)
}, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport])
return (
<Modal
title={t('workflow.nodes.http.curl.title')}
isShow={isShow}
onClose={onHide}
className='!w-[400px] !max-w-[400px] !p-4'
>
<div>
<textarea
value={inputString}
className='w-full my-3 p-3 text-sm text-gray-900 border-0 rounded-lg grow bg-gray-100 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200 h-40'
onChange={e => setInputString(e.target.value)}
placeholder={t('workflow.nodes.http.curl.placeholder')!}
/>
</div>
<div className='mt-4 flex justify-end space-x-2'>
<Button className='!w-[95px]' onClick={onHide} >{t('common.operation.cancel')}</Button>
<Button className='!w-[95px]' variant='primary' onClick={handleSave} > {t('common.operation.save')}</Button>
</div>
</Modal>
)
}
export default React.memo(CurlPanel)

View File

@ -8,11 +8,13 @@ import EditBody from './components/edit-body'
import AuthorizationModal from './components/authorization'
import type { HttpNodeType } from './types'
import Timeout from './components/timeout'
import CurlPanel from './components/curl-panel'
import cn from '@/utils/classnames'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
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'
@ -53,6 +55,10 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
inputVarValues,
setInputVarValues,
runResult,
isShowCurlPanel,
showCurlPanel,
hideCurlPanel,
handleCurlImport,
} = useConfig(id, data)
// To prevent prompt editor in body not update data.
if (!isDataReady)
@ -64,14 +70,25 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
<Field
title={t(`${i18nPrefix}.api`)}
operations={
<div
onClick={showAuthorization}
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
>
{!readOnly && <Settings01 className='w-3 h-3 text-gray-500' />}
<div className='text-xs font-medium text-gray-500'>
{t(`${i18nPrefix}.authorization.authorization`)}
<span className='ml-1 text-gray-700'>{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span>
<div className='flex'>
<div
onClick={showAuthorization}
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
>
{!readOnly && <Settings01 className='w-3 h-3 text-gray-500' />}
<div className='text-xs font-medium text-gray-500'>
{t(`${i18nPrefix}.authorization.authorization`)}
<span className='ml-1 text-gray-700'>{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span>
</div>
</div>
<div
onClick={showCurlPanel}
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
>
{!readOnly && <FileArrow01 className='w-3 h-3 text-gray-500' />}
<div className='text-xs font-medium text-gray-500'>
{t(`${i18nPrefix}.curl.title`)}
</div>
</div>
</div>
}
@ -180,7 +197,15 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
result={<ResultPanel {...runResult} showSteps={false} />}
/>
)}
</div >
{(isShowCurlPanel && !readOnly) && (
<CurlPanel
nodeId={id}
isShow
onHide={hideCurlPanel}
handleCurlImport={handleCurlImport}
/>
)}
</div>
)
}

View File

@ -164,6 +164,23 @@ const useConfig = (id: string, payload: HttpNodeType) => {
setRunInputData(newPayload)
}, [setRunInputData])
// curl import panel
const [isShowCurlPanel, {
setTrue: showCurlPanel,
setFalse: hideCurlPanel,
}] = useBoolean(false)
const handleCurlImport = useCallback((newNode: HttpNodeType) => {
const newInputs = produce(inputs, (draft: HttpNodeType) => {
draft.method = newNode.method
draft.url = newNode.url
draft.headers = newNode.headers
draft.params = newNode.params
draft.body = newNode.body
})
setInputs(newInputs)
}, [inputs, setInputs])
return {
readOnly,
isDataReady,
@ -203,6 +220,11 @@ const useConfig = (id: string, payload: HttpNodeType) => {
inputVarValues,
setInputVarValues,
runResult,
// curl import
isShowCurlPanel,
showCurlPanel,
hideCurlPanel,
handleCurlImport,
}
}

View File

@ -144,6 +144,7 @@ const ConfigPromptItem: FC<Props> = ({
onEditionTypeChange={onEditionTypeChange}
varList={varList}
handleAddVariable={handleAddVariable}
isSupportFileVar
/>
)
}

View File

@ -67,6 +67,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleStop,
varInputs,
runResult,
filterJinjia2InputVar,
} = useConfig(id, data)
const model = inputs.model
@ -194,7 +195,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
list={inputs.prompt_config?.jinja2_variables || []}
onChange={handleVarListChange}
onVarNameChange={handleVarNameChange}
filterVar={filterVar}
filterVar={filterJinjia2InputVar}
isSupportFileVar={false}
/>
</Field>
)}
@ -233,6 +235,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
hasSetBlockStatus={hasSetBlockStatus}
nodesOutputVars={availableVars}
availableNodes={availableNodesWithParent}
isSupportFileVar
/>
{inputs.memory.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && (

View File

@ -278,11 +278,15 @@ const useConfig = (id: string, payload: LLMNodeType) => {
}, [inputs, setInputs])
const filterInputVar = useCallback((varPayload: Var) => {
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, [])
const filterJinjia2InputVar = useCallback((varPayload: Var) => {
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
}, [])
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, [])
const {
@ -406,6 +410,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
handleRun,
handleStop,
runResult,
filterJinjia2InputVar,
}
}

View File

@ -64,6 +64,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
onChange={handleVarListChange}
onVarNameChange={handleVarNameChange}
filterVar={filterVar}
isSupportFileVar={false}
/>
</Field>
<Split />

View File

@ -10,8 +10,9 @@ import {
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import {
RiAlertLine,
RiAlertFill,
RiCloseLine,
RiFileDownloadLine,
} from '@remixicon/react'
import { WORKFLOW_DATA_UPDATE } from './constants'
import {
@ -21,11 +22,19 @@ import {
initialEdges,
initialNodes,
} from './utils'
import {
importDSL,
importDSLConfirm,
} from '@/service/apps'
import { fetchWorkflowDraft } from '@/service/workflow'
import {
DSLImportMode,
DSLImportStatus,
} from '@/models/app'
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal'
import { ToastContext } from '@/app/components/base/toast'
import { updateWorkflowDraftFromDSL } from '@/service/workflow'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useStore as useAppStore } from '@/app/components/app/store'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
@ -50,6 +59,10 @@ const UpdateDSLModal = ({
const [loading, setLoading] = useState(false)
const { eventEmitter } = useEventEmitterContextContext()
const { mutateAsync, mutate } = useMutationCheckDependenciesBeforeImportDSL()
const [show, setShow] = useState(true)
const [showErrorModal, setShowErrorModal] = useState(false)
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
const [importId, setImportId] = useState<string>()
const readFile = (file: File) => {
const reader = new FileReader()
@ -68,6 +81,51 @@ const UpdateDSLModal = ({
setFileContent('')
}
const handleWorkflowUpdate = async (app_id: string) => {
const {
graph,
features,
hash,
} = await fetchWorkflowDraft(`/apps/${app_id}/workflows/draft`)
const { nodes, edges, viewport } = graph
const newFeatures = {
file: {
image: {
enabled: !!features.file_upload?.image?.enabled,
number_limits: features.file_upload?.image?.number_limits || 3,
transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
},
enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
},
opening: {
enabled: !!features.opening_statement,
opening_statement: features.opening_statement,
suggested_questions: features.suggested_questions,
},
suggested: features.suggested_questions_after_answer || { enabled: false },
speech2text: features.speech_to_text || { enabled: false },
text2speech: features.text_to_speech || { enabled: false },
citation: features.retriever_resource || { enabled: false },
moderation: features.sensitive_word_avoidance || { enabled: false },
}
eventEmitter?.emit({
type: WORKFLOW_DATA_UPDATE,
payload: {
nodes: initialNodes(nodes, edges),
edges: initialEdges(edges, nodes),
viewport,
features: newFeatures,
hash,
},
} as any)
}
const isCreatingRef = useRef(false)
const handleImport: MouseEventHandler = useCallback(async () => {
if (isCreatingRef.current)
@ -109,21 +167,6 @@ const UpdateDSLModal = ({
citation: features.retriever_resource || { enabled: false },
moderation: features.sensitive_word_avoidance || { enabled: false },
}
eventEmitter?.emit({
type: WORKFLOW_DATA_UPDATE,
payload: {
nodes: initialNodes(nodes, edges),
edges: initialEdges(edges, nodes),
viewport,
features: newFeatures,
hash,
},
} as any)
if (onImport)
onImport()
notify({ type: 'success', message: t('workflow.common.importSuccess') })
setLoading(false)
onCancel()
}
}
catch (e) {
@ -133,52 +176,119 @@ const UpdateDSLModal = ({
isCreatingRef.current = false
}, [currentFile, fileContent, onCancel, notify, t, eventEmitter, appDetail, onImport, mutateAsync])
const onUpdateDSLConfirm: MouseEventHandler = async () => {
try {
if (!importId)
return
const response = await importDSLConfirm({
import_id: importId,
})
const { status, app_id } = response
if (status === DSLImportStatus.COMPLETED) {
if (!app_id) {
notify({ type: 'error', message: t('workflow.common.importFailure') })
return
}
handleWorkflowUpdate(app_id)
if (onImport)
onImport()
notify({ type: 'success', message: t('workflow.common.importSuccess') })
setLoading(false)
onCancel()
}
else if (status === DSLImportStatus.FAILED) {
setLoading(false)
notify({ type: 'error', message: t('workflow.common.importFailure') })
}
}
catch (e) {
setLoading(false)
notify({ type: 'error', message: t('workflow.common.importFailure') })
}
}
return (
<Modal
className='p-6 w-[520px] rounded-2xl'
isShow={true}
onClose={() => {}}
>
<div className='flex items-center justify-between mb-6'>
<div className='text-2xl font-semibold text-[#101828]'>{t('workflow.common.importDSL')}</div>
<div className='flex items-center justify-center w-[22px] h-[22px] cursor-pointer' onClick={onCancel}>
<RiCloseLine className='w-5 h-5 text-gray-500' />
<>
<Modal
className='p-6 w-[520px] rounded-2xl'
isShow={show}
onClose={onCancel}
>
<div className='flex items-center justify-between mb-3'>
<div className='title-2xl-semi-bold text-text-primary'>{t('workflow.common.importDSL')}</div>
<div className='flex items-center justify-center w-[22px] h-[22px] cursor-pointer' onClick={onCancel}>
<RiCloseLine className='w-[18px] h-[18px] text-text-tertiary' />
</div>
</div>
<div className='flex relative p-2 mb-2 gap-0.5 flex-grow rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs overflow-hidden'>
<div className='absolute top-0 left-0 w-full h-full opacity-40 bg-[linear-gradient(92deg,rgba(247,144,9,0.25)_0%,rgba(255,255,255,0.00)_100%)]' />
<div className='flex p-1 justify-center items-start'>
<RiAlertFill className='w-4 h-4 flex-shrink-0 text-text-warning-secondary' />
</div>
<div className='flex py-1 flex-col items-start gap-0.5 flex-grow'>
<div className='text-text-primary system-xs-medium whitespace-pre-line'>{t('workflow.common.importDSLTip')}</div>
<div className='flex pt-1 pb-0.5 items-start gap-1 self-stretch'>
<Button
size='small'
variant='secondary'
className='z-[1000]'
onClick={onBackup}
>
<RiFileDownloadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
<div className='flex px-[3px] justify-center items-center gap-1'>
{t('workflow.common.backupCurrentDraft')}
</div>
</Button>
</div>
</div>
</div>
</div>
<div className='flex mb-4 px-4 py-3 bg-[#FFFAEB] rounded-xl border border-[#FEDF89]'>
<RiAlertLine className='shrink-0 mt-0.5 mr-2 w-4 h-4 text-[#F79009]' />
<div>
<div className='mb-2 text-sm font-medium text-[#354052]'>{t('workflow.common.importDSLTip')}</div>
<div className='pt-2 text-text-primary system-md-semibold'>
{t('workflow.common.chooseDSL')}
</div>
<div className='flex w-full py-4 flex-col justify-center items-start gap-4 self-stretch'>
<Uploader
file={currentFile}
updateFile={handleFile}
className='!mt-0 w-full'
/>
</div>
</div>
<div className='flex pt-5 gap-2 items-center justify-end self-stretch'>
<Button onClick={onCancel}>{t('app.newApp.Cancel')}</Button>
<Button
variant='secondary-accent'
onClick={onBackup}
disabled={!currentFile || loading}
variant='warning'
onClick={handleImport}
loading={loading}
>
{t('workflow.common.backupCurrentDraft')}
{t('workflow.common.overwriteAndImport')}
</Button>
</div>
</div>
<div className='mb-8'>
<div className='mb-1 text-[13px] font-semibold text-[#354052]'>
{t('workflow.common.chooseDSL')}
</Modal>
<Modal
isShow={showErrorModal}
onClose={() => setShowErrorModal(false)}
className='w-[480px]'
>
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
<br />
<div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
</div>
</div>
<Uploader
file={currentFile}
updateFile={handleFile}
className='!mt-0'
/>
</div>
<div className='flex justify-end'>
<Button className='mr-2' onClick={onCancel}>{t('app.newApp.Cancel')}</Button>
<Button
disabled={!currentFile || loading}
variant='warning'
onClick={handleImport}
loading={loading}
>
{t('workflow.common.overwriteAndImport')}
</Button>
</div>
</Modal>
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
<Button variant='primary' destructive onClick={onUpdateDSLConfirm}>{t('app.newApp.Confirm')}</Button>
</div>
</Modal>
</>
)
}

View File

@ -2,8 +2,8 @@
@tailwind base;
@tailwind components;
@import "../../themes/light.css";
@import "../../themes/dark.css";
@import '../../themes/light.css';
@import '../../themes/dark.css';
@import "../../themes/manual-light.css";
@import "../../themes/manual-dark.css";

View File

@ -97,6 +97,8 @@ type IDebugConfiguration = {
isShowVisionConfig: boolean
visionConfig: VisionSettings
setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void
isAllowVideoUpload: boolean
isShowDocumentConfig: boolean
rerankSettingModalOpen: boolean
setRerankSettingModalOpen: (rerankSettingModalOpen: boolean) => void
}
@ -244,6 +246,8 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
transfer_methods: [TransferMethod.remote_url],
},
setVisionConfig: () => { },
isAllowVideoUpload: false,
isShowDocumentConfig: false,
rerankSettingModalOpen: false,
setRerankSettingModalOpen: () => { },
})

View File

@ -76,6 +76,7 @@ const translation = {
requestBody: 'Anfragekörper',
pathParams: 'Pfadparameter',
query: 'Anfrage',
toc: 'Inhalt',
},
loading: 'Laden',
regenerate: 'Erneuern',

View File

@ -64,6 +64,14 @@ const translation = {
agentDescription: 'Erstellen Sie einen intelligenten Agenten, der autonom Werkzeuge auswählen kann, um die Aufgaben zu erledigen',
completionDescription: 'Erstellen Sie eine Anwendung, die qualitativ hochwertigen Text auf der Grundlage von Eingabeaufforderungen generiert, z. B. zum Generieren von Artikeln, Zusammenfassungen, Übersetzungen und mehr.',
appDescriptionPlaceholder: 'Geben Sie die Beschreibung der App ein',
caution: 'Vorsicht',
Confirm: 'Bestätigen',
appCreateDSLErrorTitle: 'Inkompatibilität der Version',
appCreateDSLErrorPart2: 'Möchten Sie fortfahren?',
appCreateDSLErrorPart4: 'Systemgestützte DSL-Version:',
appCreateDSLErrorPart1: 'Es wurde ein signifikanter Unterschied bei den DSL-Versionen festgestellt. Das Erzwingen des Imports kann zu Fehlfunktionen der Anwendung führen.',
appCreateDSLErrorPart3: 'Aktuelle DSL-Version der Anwendung:',
appCreateDSLWarning: 'Achtung: Ein unterschiedlicher DSL-Versionsunterschied kann sich auf bestimmte Funktionen auswirken',
},
editApp: 'App bearbeiten',
editAppTitle: 'App-Informationen bearbeiten',

View File

@ -99,6 +99,8 @@ const translation = {
ImageUploadLegacyTip: 'Sie können jetzt Dateitypvariablen im Startformular erstellen. Wir werden die Funktion zum Hochladen von Bildern in Zukunft nicht mehr unterstützen.',
fileUploadTip: 'Die Funktionen zum Hochladen von Bildern wurden auf das Hochladen von Dateien aktualisiert.',
featuresDescription: 'Verbessern Sie die Benutzererfahrung von Web-Apps',
importWarning: 'Vorsicht',
importWarningDetails: 'Der Unterschied zwischen den DSL-Versionen kann sich auf bestimmte Funktionen auswirken',
},
env: {
envPanelTitle: 'Umgebungsvariablen',
@ -408,6 +410,10 @@ const translation = {
type: 'Art',
binaryFileVariable: 'Variable der Binärdatei',
extractListPlaceholder: 'Geben Sie den Index des Listeneintrags ein, geben Sie \'/\' ein, fügen Sie die Variable ein',
curl: {
title: 'Importieren von cURL',
placeholder: 'Fügen Sie hier die cURL-Zeichenfolge ein',
},
},
code: {
inputVars: 'Eingabevariablen',

View File

@ -218,6 +218,10 @@ const translation = {
enableText: 'Features Enabled',
manage: 'Manage',
},
documentUpload: {
title: 'Document',
description: 'Enable Document will allows the model to take in documents and answer questions about them.',
},
},
codegen: {
title: 'Code Generator',

View File

@ -61,10 +61,18 @@ const translation = {
hideTemplates: 'Go back to mode selection',
Create: 'Create',
Cancel: 'Cancel',
Confirm: 'Confirm',
nameNotEmpty: 'Name cannot be empty',
appTemplateNotSelected: 'Please select a template',
appTypeRequired: 'Please select an app type',
appCreated: 'App created',
caution: 'Caution',
appCreateDSLWarning: 'Caution: DSL version difference may affect certain features',
appCreateDSLErrorTitle: 'Version Incompatibility',
appCreateDSLErrorPart1: 'A significant difference in DSL versions has been detected. Forcing the import may cause the application to malfunction.',
appCreateDSLErrorPart2: 'Do you want to continue?',
appCreateDSLErrorPart3: 'Current application DSL version: ',
appCreateDSLErrorPart4: 'System-supported DSL version: ',
appCreateFailed: 'Failed to create app',
},
editApp: 'Edit Info',

View File

@ -369,7 +369,7 @@ const translation = {
deprecated: 'Deprecated',
confirmDelete: 'Confirm deletion?',
quotaTip: 'Remaining available free tokens',
loadPresets: 'Load Presents',
loadPresets: 'Load Presets',
parameters: 'PARAMETERS',
loadBalancing: 'Load balancing',
loadBalancingDescription: 'Reduce pressure with multiple sets of credentials.',

View File

@ -14,7 +14,7 @@ const translation = {
prompt: 'Prompt',
privatePromptConfigTitle: 'Conversation settings',
publicPromptConfigTitle: 'Initial Prompt',
configStatusDes: 'Before start, you can modify conversation settings',
configStatusDes: 'Before starting, you can modify the conversation settings',
configDisabled:
'Previous session settings have been used for this session.',
startChat: 'Start Chat',

View File

@ -75,12 +75,14 @@ const translation = {
viewDetailInTracingPanel: 'View details',
syncingData: 'Syncing data, just a few seconds.',
importDSL: 'Import DSL',
importDSLTip: 'Current draft will be overwritten. Export workflow as backup before importing.',
importDSLTip: 'Current draft will be overwritten.\nExport workflow as backup before importing.',
backupCurrentDraft: 'Backup Current Draft',
chooseDSL: 'Choose DSL(yml) file',
chooseDSL: 'Choose DSL file',
overwriteAndImport: 'Overwrite and Import',
importFailure: 'Import failure',
importSuccess: 'Import success',
importFailure: 'Import Failed',
importWarning: 'Caution',
importWarningDetails: 'DSL version difference may affect certain features',
importSuccess: 'Import Successfully',
parallelRun: 'Parallel Run',
parallelTip: {
click: {
@ -408,6 +410,10 @@ const translation = {
writeLabel: 'Write Timeout',
writePlaceholder: 'Enter write timeout in seconds',
},
curl: {
title: 'Import from cURL',
placeholder: 'Paste cURL string here',
},
},
code: {
inputVars: 'Input Variables',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'Cuerpo de la solicitud',
pathParams: 'Parámetros de ruta',
query: 'Consulta',
toc: 'Contenido',
},
regenerate: 'Regenerar',
}

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Por favor, selecciona un tipo de app',
appCreated: 'App creada',
appCreateFailed: 'Error al crear app',
Confirm: 'Confirmar',
caution: 'Cautela',
appCreateDSLErrorTitle: 'Incompatibilidad de versiones',
appCreateDSLErrorPart2: '¿Quieres continuar?',
appCreateDSLErrorPart4: 'Versión de DSL compatible con el sistema:',
appCreateDSLErrorPart1: 'Se ha detectado una diferencia significativa en las versiones de DSL. Forzar la importación puede hacer que la aplicación no funcione correctamente.',
appCreateDSLWarning: 'Precaución: La diferencia de versión de DSL puede afectar a determinadas funciones',
appCreateDSLErrorPart3: 'Versión actual de DSL de la aplicación:',
},
editApp: 'Editar información',
editAppTitle: 'Editar información de la app',

View File

@ -99,6 +99,8 @@ const translation = {
ImageUploadLegacyTip: 'Ahora puede crear variables de tipo de archivo en el formulario de inicio. Ya no admitiremos la función de carga de imágenes en el futuro.',
featuresDescription: 'Mejorar la experiencia del usuario de la aplicación web',
featuresDocLink: 'Aprende más',
importWarning: 'Cautela',
importWarningDetails: 'La diferencia de versión de DSL puede afectar a ciertas características',
},
env: {
envPanelTitle: 'Variables de Entorno',
@ -408,6 +410,10 @@ const translation = {
type: 'Tipo',
binaryFileVariable: 'Variable de archivo binario',
extractListPlaceholder: 'Introduzca el índice de elementos de la lista, escriba \'/\' insertar variable',
curl: {
title: 'Importar desde cURL',
placeholder: 'Pegar la cadena cURL aquí',
},
},
code: {
inputVars: 'Variables de entrada',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'بدنه درخواست',
pathParams: 'پارامترهای مسیر',
query: 'پرس‌وجو',
toc: 'محتویات',
},
regenerate: 'بازسازی',
}

View File

@ -66,6 +66,14 @@ const translation = {
appTypeRequired: 'لطفاً نوع برنامه را انتخاب کنید',
appCreated: 'برنامه ایجاد شد',
appCreateFailed: 'ایجاد برنامه ناموفق بود',
Confirm: 'تایید',
appCreateDSLErrorTitle: 'ناسازگاری نسخه',
caution: 'احتیاط',
appCreateDSLErrorPart3: 'نسخه DSL برنامه فعلی:',
appCreateDSLErrorPart2: 'آیا می خواهید ادامه دهید؟',
appCreateDSLErrorPart4: 'نسخه DSL پشتیبانی شده توسط سیستم:',
appCreateDSLErrorPart1: 'تفاوت قابل توجهی در نسخه های DSL مشاهده شده است. اجبار به واردات ممکن است باعث اختلال در عملکرد برنامه شود.',
appCreateDSLWarning: 'احتیاط: تفاوت نسخه DSL ممکن است بر ویژگی های خاصی تأثیر بگذارد',
},
editApp: 'ویرایش اطلاعات',
editAppTitle: 'ویرایش اطلاعات برنامه',

View File

@ -99,6 +99,8 @@ const translation = {
featuresDescription: 'بهبود تجربه کاربری برنامه وب',
ImageUploadLegacyTip: 'اکنون می توانید متغیرهای نوع فایل را در فرم شروع ایجاد کنید. ما دیگر از ویژگی آپلود تصویر در آینده پشتیبانی نخواهیم کرد.',
fileUploadTip: 'ویژگی های آپلود تصویر برای آپلود فایل ارتقا یافته است.',
importWarning: 'احتیاط',
importWarningDetails: 'تفاوت نسخه DSL ممکن است بر ویژگی های خاصی تأثیر بگذارد',
},
env: {
envPanelTitle: 'متغیرهای محیطی',
@ -408,6 +410,10 @@ const translation = {
binaryFileVariable: 'متغیر فایل باینری',
type: 'نوع',
extractListPlaceholder: 'فهرست آیتم لیست را وارد کنید، متغیر درج \'/\' را تایپ کنید',
curl: {
title: 'وارد کردن از cURL',
placeholder: 'رشته cURL را اینجا بچسبانید',
},
},
code: {
inputVars: 'متغیرهای ورودی',

View File

@ -76,6 +76,7 @@ const translation = {
requestBody: 'Corps de la Requête',
pathParams: 'Params de chemin',
query: 'Requête',
toc: 'Contenu',
},
loading: 'Chargement',
regenerate: 'Régénérer',

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Veuillez sélectionner un type d\'application',
appCreated: 'Application créée',
appCreateFailed: 'Échec de la création de l\'application',
Confirm: 'Confirmer',
caution: 'Prudence',
appCreateDSLWarning: 'Attention : la différence de version DSL peut affecter certaines fonctionnalités',
appCreateDSLErrorPart4: 'Version DSL prise en charge par le système :',
appCreateDSLErrorPart1: 'Une différence significative entre les versions DSL a été détectée. Forcer limportation peut entraîner un dysfonctionnement de lapplication.',
appCreateDSLErrorTitle: 'Incompatibilité de version',
appCreateDSLErrorPart3: 'Version actuelle de lapplication DSL :',
appCreateDSLErrorPart2: 'Voulez-vous continuer ?',
},
editApp: 'Modifier les informations',
editAppTitle: 'Modifier les informations de l\'application',

View File

@ -99,6 +99,8 @@ const translation = {
ImageUploadLegacyTip: 'Vous pouvez désormais créer des variables de type de fichier dans le formulaire de démarrage. À lavenir, nous ne prendrons plus en charge la fonctionnalité de téléchargement dimages.',
fileUploadTip: 'Les fonctionnalités de téléchargement dimages ont été mises à niveau vers le téléchargement de fichiers.',
featuresDescription: 'Améliorer lexpérience utilisateur de lapplication web',
importWarning: 'Prudence',
importWarningDetails: 'La différence de version DSL peut affecter certaines fonctionnalités',
},
env: {
envPanelTitle: 'Variables d\'Environnement',
@ -408,6 +410,10 @@ const translation = {
binaryFileVariable: 'Variable de fichier binaire',
type: 'Type',
extractListPlaceholder: 'Entrez lindex de lélément de liste, tapez \'/\' insérer la variable',
curl: {
placeholder: 'Collez la chaîne cURL ici',
title: 'Importer à partir de cURL',
},
},
code: {
inputVars: 'Variables de saisie',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'अनुरोध निकाय',
pathParams: 'पथ पैरामीटर',
query: 'प्रश्न',
toc: 'सामग्री',
},
regenerate: 'पुनर्जन्म',
}

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'कृपया एक ऐप प्रकार चुनें',
appCreated: 'ऐप बनाया गया',
appCreateFailed: 'ऐप बनाने में विफल',
Confirm: 'सुदृढ़ करना',
appCreateDSLErrorPart4: 'सिस्टम-समर्थित DSL संस्करण:',
appCreateDSLErrorPart3: 'वर्तमान अनुप्रयोग डीएसएल संस्करण:',
caution: 'सावधानी',
appCreateDSLErrorTitle: 'संस्करण असंगति',
appCreateDSLErrorPart1: 'डीएसएल संस्करणों में एक महत्वपूर्ण अंतर पाया गया है। आयात को बाध्य करने से अनुप्रयोग में खराबी आ सकती है।',
appCreateDSLWarning: 'सावधानी: DSL संस्करण अंतर कुछ सुविधाओं को प्रभावित कर सकता है',
appCreateDSLErrorPart2: 'क्या आप जारी रखना चाहते हैं?',
},
editApp: 'जानकारी संपादित करें',
editAppTitle: 'ऐप जानकारी संपादित करें',

View File

@ -102,6 +102,8 @@ const translation = {
featuresDescription: 'वेब ऐप उपयोगकर्ता अनुभव को बेहतर बनाएं',
fileUploadTip: 'छवि अपलोड सुविधाओं को फ़ाइल अपलोड में अपग्रेड किया गया है।',
ImageUploadLegacyTip: 'अब आप प्रारंभ प्रपत्र में फ़ाइल प्रकार चर बना सकते हैं। हम अब भविष्य में छवि अपलोड सुविधा का समर्थन नहीं करेंगे।',
importWarning: 'सावधानी',
importWarningDetails: 'डीएसएल संस्करण अंतर कुछ सुविधाओं को प्रभावित कर सकता है',
},
env: {
envPanelTitle: 'पर्यावरण चर',
@ -421,6 +423,10 @@ const translation = {
type: 'प्रकार',
binaryFileVariable: 'बाइनरी फ़ाइल चर',
extractListPlaceholder: 'सूची आइटम इंडेक्स दर्ज करें, \'/\' इन्सर्ट वेरिएबल टाइप करें',
curl: {
placeholder: 'यहां cURL स्ट्रिंग पेस्ट करें',
title: 'cURL से आयात करें',
},
},
code: {
inputVars: 'इनपुट वेरिएबल्स',

View File

@ -98,6 +98,7 @@ const translation = {
requestBody: 'Corpo della Richiesta',
pathParams: 'Parametri del Percorso',
query: 'Query',
toc: 'Contenuto',
},
regenerate: 'Rigenerare',
}

View File

@ -68,6 +68,14 @@ const translation = {
appTypeRequired: 'Seleziona un tipo di app',
appCreated: 'App creata',
appCreateFailed: 'Creazione dell\'app fallita',
Confirm: 'Confermare',
appCreateDSLErrorPart2: 'Vuoi continuare?',
appCreateDSLErrorPart3: 'Versione DSL dell\'applicazione corrente:',
appCreateDSLErrorPart1: 'È stata rilevata una differenza significativa nelle versioni DSL. Forzare l\'importazione può causare il malfunzionamento dell\'applicazione.',
caution: 'Cautela',
appCreateDSLErrorTitle: 'Incompatibilità di versione',
appCreateDSLWarning: 'Attenzione: la differenza di versione DSL può influire su alcune funzionalità',
appCreateDSLErrorPart4: 'Versione DSL supportata dal sistema:',
},
editApp: 'Modifica Info',
editAppTitle: 'Modifica Info App',

View File

@ -103,6 +103,8 @@ const translation = {
featuresDescription: 'Migliora l\'esperienza utente dell\'app Web',
fileUploadTip: 'Le funzioni di caricamento delle immagini sono state aggiornate al caricamento dei file.',
ImageUploadLegacyTip: 'Ora è possibile creare variabili di tipo file nel modulo iniziale. In futuro non supporteremo più la funzione di caricamento delle immagini.',
importWarning: 'Cautela',
importWarningDetails: 'La differenza di versione DSL può influire su alcune funzionalità',
},
env: {
envPanelTitle: 'Variabili d\'Ambiente',
@ -425,6 +427,10 @@ const translation = {
binaryFileVariable: 'Variabile file binario',
type: 'Digitare',
extractListPlaceholder: 'Inserisci l\'indice delle voci dell\'elenco, digita \'/\' inserisci la variabile',
curl: {
placeholder: 'Incolla qui la stringa cURL',
title: 'Importazione da cURL',
},
},
code: {
inputVars: 'Variabili di Input',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'リクエストボディ',
pathParams: 'パスパラメータ',
query: 'クエリ',
toc: '内容',
},
regenerate: '再生',
}

View File

@ -359,7 +359,7 @@ const translation = {
'content': 'コンテンツ',
'required': '必須',
'file': {
supportFileTypes: 'サポトされたファイルタイプ',
supportFileTypes: 'サポトされたファイルタイプ',
image: {
name: '画像',
},

View File

@ -67,6 +67,14 @@ const translation = {
appTypeRequired: 'アプリの種類を選択してください',
appCreated: 'アプリが作成されました',
appCreateFailed: 'アプリの作成に失敗しました',
Confirm: '確認する',
caution: '注意',
appCreateDSLErrorPart2: '続行しますか?',
appCreateDSLErrorPart4: 'システムがサポートするDSLバージョン:',
appCreateDSLErrorPart3: '現在のアプリケーションの DSL バージョン:',
appCreateDSLErrorTitle: 'バージョンの非互換性',
appCreateDSLWarning: '注意:DSLのバージョンの違いは、特定の機能に影響を与える可能性があります',
appCreateDSLErrorPart1: 'DSL バージョンに大きな違いが検出されました。インポートを強制すると、アプリケーションが誤動作する可能性があります。',
},
editApp: '情報を編集する',
editAppTitle: 'アプリ情報を編集する',

View File

@ -58,7 +58,7 @@ const translation = {
registrationNotAllowed: 'アカウントが見つかりません。登録するためにシステム管理者に連絡してください。',
},
license: {
tip: 'Dify Community Editionを開始する前に、GitHubの',
tip: 'GitHubのオープンソースライセンスを確認してから、Dify Community Editionを開始してください。',
link: 'オープンソースライセンス',
},
join: '参加する',

View File

@ -99,6 +99,8 @@ const translation = {
addParallelNode: '並列ノードを追加',
parallel: '並列',
branch: 'ブランチ',
importWarning: '注意',
importWarningDetails: 'DSL のバージョンの違いが特定の機能に影響を与える場合があります',
},
env: {
envPanelTitle: '環境変数',
@ -408,6 +410,10 @@ const translation = {
type: 'タイプ',
binaryFileVariable: 'バイナリファイル変数',
extractListPlaceholder: 'リスト項目のインデックスを入力し、変数を挿入 \'/\' と入力します',
curl: {
title: 'cURLからのインポート',
placeholder: 'ここにcURL文字列を貼り付けます',
},
},
code: {
inputVars: '入力変数',

View File

@ -79,6 +79,7 @@ const translation = {
requestBody: '요청 본문',
pathParams: '경로 매개변수',
query: '쿼리',
toc: '목차',
},
regenerate: '재생성',
}

View File

@ -58,6 +58,14 @@ const translation = {
appTypeRequired: '앱 종류를 선택하세요',
appCreated: '앱이 생성되었습니다',
appCreateFailed: '앱 생성 실패',
caution: '주의',
Confirm: '확인하다',
appCreateDSLErrorPart4: '시스템 지원 DSL 버전:',
appCreateDSLErrorTitle: '버전 비호환성',
appCreateDSLErrorPart2: '계속하시겠습니까?',
appCreateDSLErrorPart3: '현재 응용 프로그램 DSL 버전:',
appCreateDSLWarning: '주의: DSL 버전 차이는 특정 기능에 영향을 미칠 수 있습니다.',
appCreateDSLErrorPart1: 'DSL 버전에서 상당한 차이가 감지되었습니다. 강제로 가져오면 응용 프로그램이 오작동할 수 있습니다.',
},
editApp: '정보 편집하기',
editAppTitle: '앱 정보 편집하기',

View File

@ -99,6 +99,8 @@ const translation = {
fileUploadTip: '이미지 업로드 기능이 파일 업로드로 업그레이드되었습니다.',
featuresDescription: '웹앱 사용자 경험 향상',
ImageUploadLegacyTip: '이제 시작 양식에서 파일 형식 변수를 만들 수 있습니다. 앞으로 이미지 업로드 기능은 더 이상 지원되지 않습니다.',
importWarning: '주의',
importWarningDetails: 'DSL 버전 차이는 특정 기능에 영향을 미칠 수 있습니다.',
},
env: {
envPanelTitle: '환경 변수',
@ -408,6 +410,10 @@ const translation = {
type: '형',
binaryFileVariable: '바이너리 파일 변수',
extractListPlaceholder: '목록 항목 인덱스 입력, \'/\' 변수 삽입',
curl: {
title: 'cURL에서 가져오기',
placeholder: '여기에 cURL 문자열 붙여 넣기',
},
},
code: {
inputVars: '입력 변수',

View File

@ -96,6 +96,7 @@ const translation = {
requestBody: 'Ciało żądania',
pathParams: 'Parametry ścieżki',
query: 'Zapytanie',
toc: 'Treść',
},
regenerate: 'Ponownie wygenerować',
}

View File

@ -68,6 +68,14 @@ const translation = {
appTypeRequired: 'Proszę wybrać typ aplikacji',
appCreated: 'Aplikacja utworzona',
appCreateFailed: 'Nie udało się utworzyć aplikacji',
appCreateDSLErrorPart3: 'Aktualna wersja aplikacji DSL:',
appCreateDSLErrorPart2: 'Czy chcesz kontynuować?',
Confirm: 'Potwierdzić',
caution: 'Ostrożność',
appCreateDSLWarning: 'Przestroga: Różnica w wersji DSL może mieć wpływ na niektóre funkcje',
appCreateDSLErrorTitle: 'Niezgodność wersji',
appCreateDSLErrorPart4: 'Wersja DSL obsługiwana przez system:',
appCreateDSLErrorPart1: 'Wykryto istotną różnicę w wersjach DSL. Wymuszenie importu może spowodować nieprawidłowe działanie aplikacji.',
},
editApp: 'Edytuj informacje',
editAppTitle: 'Edytuj informacje o aplikacji',

View File

@ -99,6 +99,8 @@ const translation = {
fileUploadTip: 'Funkcje przesyłania obrazów zostały zaktualizowane do przesyłania plików.',
featuresDescription: 'Ulepszanie środowiska użytkownika aplikacji internetowej',
featuresDocLink: 'Dowiedz się więcej',
importWarning: 'Ostrożność',
importWarningDetails: 'Różnica w wersji DSL może mieć wpływ na niektóre funkcje',
},
env: {
envPanelTitle: 'Zmienne Środowiskowe',
@ -408,6 +410,10 @@ const translation = {
type: 'Typ',
binaryFileVariable: 'Binarna zmienna pliku',
extractListPlaceholder: 'Wprowadź indeks elementu listy, wpisz "/" wstaw zmienną',
curl: {
placeholder: 'Wklej tutaj ciąg cURL',
title: 'Importowanie z cURL',
},
},
code: {
inputVars: 'Zmienne wejściowe',

View File

@ -73,6 +73,7 @@ const translation = {
requestBody: 'Corpo da Solicitação',
pathParams: 'Parâmetros de Caminho',
query: 'Consulta',
toc: 'Conteúdo',
},
play: 'Brincar',
loading: 'Carregamento',

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Por favor, selecione um tipo de aplicativo',
appCreated: 'Aplicativo criado',
appCreateFailed: 'Falha ao criar aplicativo',
caution: 'Cuidado',
appCreateDSLErrorPart1: 'Uma diferença significativa nas versões DSL foi detectada. Forçar a importação pode causar mau funcionamento do aplicativo.',
appCreateDSLErrorPart4: 'Versão DSL suportada pelo sistema:',
Confirm: 'Confirmar',
appCreateDSLErrorTitle: 'Incompatibilidade de versão',
appCreateDSLWarning: 'Cuidado: a diferença de versão DSL pode afetar determinados recursos',
appCreateDSLErrorPart3: 'Versão DSL do aplicativo atual:',
appCreateDSLErrorPart2: 'Você quer continuar?',
},
editApp: 'Editar Informações',
editAppTitle: 'Editar Informações do Aplicativo',

View File

@ -99,6 +99,8 @@ const translation = {
featuresDescription: 'Melhore a experiência do usuário do aplicativo Web',
ImageUploadLegacyTip: 'Agora você pode criar variáveis de tipo de arquivo no formulário inicial. Não daremos mais suporte ao recurso de upload de imagens no futuro.',
fileUploadTip: 'Os recursos de upload de imagens foram atualizados para upload de arquivos.',
importWarning: 'Cuidado',
importWarningDetails: 'A diferença de versão DSL pode afetar determinados recursos',
},
env: {
envPanelTitle: 'Variáveis de Ambiente',
@ -408,6 +410,10 @@ const translation = {
type: 'Tipo',
binaryFileVariable: 'Variável de arquivo binário',
extractListPlaceholder: 'Insira o índice do item da lista, digite \'/\' inserir variável',
curl: {
placeholder: 'Cole a string cURL aqui',
title: 'Importar do cURL',
},
},
code: {
inputVars: 'Variáveis de entrada',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'Corpul cererii',
pathParams: 'Parametrii căii',
query: 'Interogare',
toc: 'Conținut',
},
regenerate: 'Regenera',
}

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Vă rugăm să selectați un tip de aplicație',
appCreated: 'Aplicația a fost creată',
appCreateFailed: 'Crearea aplicației a eșuat',
caution: 'Prudență',
appCreateDSLErrorPart2: 'Vrei să continui?',
Confirm: 'Confirma',
appCreateDSLErrorTitle: 'Incompatibilitate versiune',
appCreateDSLWarning: 'Atenție: diferența de versiune DSL poate afecta anumite caracteristici',
appCreateDSLErrorPart3: 'Versiunea DSL a aplicației curente:',
appCreateDSLErrorPart1: 'A fost detectată o diferență semnificativă în versiunile DSL. Forțarea importului poate cauza funcționarea defectuoasă a aplicației.',
appCreateDSLErrorPart4: 'Versiune DSL suportată de sistem:',
},
editApp: 'Editează Info',
editAppTitle: 'Editează Info Aplicație',

View File

@ -99,6 +99,8 @@ const translation = {
featuresDocLink: 'Află mai multe',
fileUploadTip: 'Funcțiile de încărcare a imaginilor au fost actualizate la încărcarea fișierelor.',
ImageUploadLegacyTip: 'Acum puteți crea variabile de tip de fișier în formularul de pornire. Nu vom mai accepta funcția de încărcare a imaginilor în viitor.',
importWarning: 'Prudență',
importWarningDetails: 'Diferența de versiune DSL poate afecta anumite caracteristici',
},
env: {
envPanelTitle: 'Variabile de Mediu',
@ -408,6 +410,10 @@ const translation = {
type: 'Tip',
binaryFileVariable: 'Variabilă de fișier binar',
extractListPlaceholder: 'Introduceți indexul elementelor din listă, tastați "/" inserați variabila',
curl: {
placeholder: 'Lipiți șirul cURL aici',
title: 'Importați din cURL',
},
},
code: {
inputVars: 'Variabile de intrare',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'Тело запроса',
pathParams: 'Параметры пути',
query: 'Запрос',
toc: 'Содержание',
},
regenerate: 'Регенерировать',
}

View File

@ -66,6 +66,14 @@ const translation = {
appTypeRequired: 'Пожалуйста, выберите тип приложения',
appCreated: 'Приложение создано',
appCreateFailed: 'Не удалось создать приложение',
caution: 'Осторожность',
appCreateDSLErrorPart2: 'Хотите продолжить?',
Confirm: 'Подтверждать',
appCreateDSLErrorTitle: 'Несовместимость версий',
appCreateDSLErrorPart3: 'Актуальная версия приложения DSL:',
appCreateDSLErrorPart4: 'Поддерживаемая системой версия DSL:',
appCreateDSLWarning: 'Внимание: разница в версиях DSL может повлиять на некоторые функции',
appCreateDSLErrorPart1: 'Обнаружена существенная разница в версиях DSL. Принудительный импорт может привести к сбою в работе приложения.',
},
editApp: 'Редактировать информацию',
editAppTitle: 'Редактировать информацию о приложении',

View File

@ -99,6 +99,8 @@ const translation = {
fileUploadTip: 'Функции загрузки изображений были обновлены до загрузки файлов.',
featuresDescription: 'Улучшение взаимодействия с пользователем веб-приложения',
ImageUploadLegacyTip: 'Теперь вы можете создавать переменные типа файла в стартовой форме. В будущем мы больше не будем поддерживать функцию загрузки изображений.',
importWarning: 'Осторожность',
importWarningDetails: 'Разница в версии DSL может повлиять на некоторые функции',
},
env: {
envPanelTitle: 'Переменные среды',
@ -408,6 +410,10 @@ const translation = {
type: 'Тип',
binaryFileVariable: 'Переменная двоичного файла',
extractListPlaceholder: 'Введите индекс элемента списка, введите \'/\' вставьте переменную',
curl: {
placeholder: 'Вставьте сюда строку cURL',
title: 'Импорт из cURL',
},
},
code: {
inputVars: 'Входные переменные',

View File

@ -78,6 +78,7 @@ const translation = {
requestBody: 'Telo zahteve',
pathParams: 'Parametri poti',
query: 'Poizvedba',
toc: 'Vsebino',
},
}

View File

@ -66,6 +66,14 @@ const translation = {
appTypeRequired: 'Izberite vrsto aplikacije',
appCreated: 'Aplikacija ustvarjena',
appCreateFailed: 'Ustvarjanje aplikacije ni uspelo',
appCreateDSLErrorTitle: 'Nezdružljivost različice',
caution: 'Previdnost',
Confirm: 'Potrditi',
appCreateDSLErrorPart1: 'Odkrita je bila pomembna razlika v različicah DSL. Vsiljevanje uvoza lahko povzroči nepravilno delovanje aplikacije.',
appCreateDSLErrorPart3: 'Trenutna različica aplikacije DSL:',
appCreateDSLErrorPart4: 'Sistemsko podprta različica DSL:',
appCreateDSLWarning: 'Pozor: Razlika v različici DSL lahko vpliva na nekatere funkcije',
appCreateDSLErrorPart2: 'Želite nadaljevati?',
},
editApp: 'Uredi informacije',
editAppTitle: 'Uredi informacije o aplikaciji',

View File

@ -99,6 +99,8 @@ const translation = {
featuresDocLink: 'Izvedi več',
featuresDescription: 'Izboljšajte uporabniško izkušnjo spletne aplikacije',
ImageUploadLegacyTip: 'Zdaj lahko ustvarite spremenljivke vrste datoteke v začetnem obrazcu. V prihodnje ne bomo več podpirali funkcije nalaganja slik.',
importWarning: 'Previdnost',
importWarningDetails: 'Razlika v različici DSL lahko vpliva na nekatere funkcije',
},
env: {
envPanelTitle: 'Spremenljivke okolja',
@ -845,6 +847,10 @@ const translation = {
apiPlaceholder: 'Vnesite URL, vnesite \'/\' vstavi spremenljivko',
extractListPlaceholder: 'Vnesite indeks elementa seznama, vnesite \'/\' vstavi spremenljivko',
params: 'Params',
curl: {
title: 'Uvoz iz cURL',
placeholder: 'Tukaj prilepite niz cURL',
},
},
code: {
inputVars: 'Vhodne spremenljivke',

View File

@ -77,6 +77,7 @@ const translation = {
requestBody: 'Request Body',
pathParams: 'Path Params',
query: 'Query',
toc: 'Içeriği',
},
regenerate: 'Yenilemek',
}

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Lütfen bir uygulama türü seçin',
appCreated: 'Uygulama oluşturuldu',
appCreateFailed: 'Uygulama oluşturulamadı',
appCreateDSLErrorPart4: 'Sistem tarafından desteklenen DSL sürümü:',
appCreateDSLErrorPart2: 'Devam etmek istiyor musunuz?',
appCreateDSLWarning: 'Dikkat: DSL sürüm farkı bazı özellikleri etkileyebilir',
appCreateDSLErrorPart1: 'DSL sürümlerinde önemli bir fark tespit edildi. İçe aktarmayı zorlamak, uygulamanın hatalı çalışmasına neden olabilir.',
caution: 'Dikkat',
appCreateDSLErrorPart3: 'Geçerli uygulama DSL sürümü:',
appCreateDSLErrorTitle: 'Sürüm Uyumsuzluğu',
Confirm: 'Onaylamak',
},
editApp: 'Bilgileri Düzenle',
editAppTitle: 'Uygulama Bilgilerini Düzenle',

View File

@ -99,6 +99,8 @@ const translation = {
fileUploadTip: 'Resim yükleme özellikleri, dosya yüklemeye yükseltildi.',
ImageUploadLegacyTip: 'Artık başlangıç formunda dosya türü değişkenleri oluşturabilirsiniz. Gelecekte resim yükleme özelliğini artık desteklemeyeceğiz.',
featuresDescription: 'Web uygulaması kullanıcı deneyimini geliştirin',
importWarningDetails: 'DSL sürüm farkı bazı özellikleri etkileyebilir',
importWarning: 'Dikkat',
},
env: {
envPanelTitle: 'Çevre Değişkenleri',
@ -409,6 +411,10 @@ const translation = {
type: 'Tür',
binaryFileVariable: 'İkili Dosya Değişkeni',
extractListPlaceholder: 'Liste öğesi dizinini girin, \'/\' yazın değişken ekle',
curl: {
placeholder: 'cURL dizesini buraya yapıştırın',
title: 'cURL\'den içe aktar',
},
},
code: {
inputVars: 'Giriş Değişkenleri',

View File

@ -60,6 +60,7 @@ const translation = {
requestBody: 'Тіло запиту',
pathParams: 'Параметри шляху',
query: 'Запит',
toc: 'Вміст',
},
completionMode: {
messageIDTip: 'Ідентифікатор повідомлення',

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Будь ласка, виберіть тип додатка',
appCreated: 'Додаток створено',
appCreateFailed: 'Не вдалося створити додаток',
caution: 'Обережність',
Confirm: 'Підтвердити',
appCreateDSLErrorPart3: 'Поточна версія DSL програми:',
appCreateDSLErrorPart4: 'Версія DSL з підтримкою системи:',
appCreateDSLErrorPart2: 'Хочете продовжити?',
appCreateDSLErrorTitle: 'Несумісність версій',
appCreateDSLErrorPart1: 'Виявлено суттєву різницю у версіях DSL. Примусовий імпорт може призвести до неправильної роботи програми.',
appCreateDSLWarning: 'Увага: різниця у версіях DSL може вплинути на певні функції',
},
editApp: 'Редагувати інформацію',
editAppTitle: 'Редагувати інформацію про додаток',

View File

@ -99,6 +99,8 @@ const translation = {
featuresDescription: 'Покращення взаємодії з користувачем веб-додатку',
fileUploadTip: 'Функції завантаження зображень були оновлені для завантаження файлів.',
ImageUploadLegacyTip: 'Тепер ви можете створювати змінні типу файлу у початковій формі. У майбутньому ми більше не підтримуватимемо функцію завантаження зображень.',
importWarning: 'Обережність',
importWarningDetails: 'Різниця у версіях DSL може впливати на певні функції',
},
env: {
envPanelTitle: 'Змінні середовища',
@ -408,6 +410,10 @@ const translation = {
type: 'Тип',
binaryFileVariable: 'Змінна двійкового файлу',
extractListPlaceholder: 'Введіть індекс елемента списку, введіть \'/\' вставити змінну',
curl: {
title: 'Імпорт з cURL',
placeholder: 'Вставте сюди рядок cURL',
},
},
code: {
inputVars: 'Вхідні змінні',

View File

@ -76,6 +76,7 @@ const translation = {
requestBody: 'Nội dung yêu cầu',
pathParams: 'Tham số đường dẫn',
query: 'Truy vấn',
toc: 'Nội dung',
},
loading: 'Tải',
regenerate: 'Tái tạo',

View File

@ -62,6 +62,14 @@ const translation = {
appTypeRequired: 'Vui lòng chọn loại ứng dụng',
appCreated: 'Ứng dụng đã được tạo',
appCreateFailed: 'Không thể tạo ứng dụng',
Confirm: 'Xác nhận',
caution: 'Thận trọng',
appCreateDSLErrorPart1: 'Một sự khác biệt đáng kể trong các phiên bản DSL đã được phát hiện. Buộc nhập có thể khiến ứng dụng bị trục trặc.',
appCreateDSLErrorPart2: 'Bạn có muốn tiếp tục không?',
appCreateDSLErrorTitle: 'Phiên bản không tương thích',
appCreateDSLErrorPart3: 'Phiên bản DSL ứng dụng hiện tại:',
appCreateDSLWarning: 'Phạt cảnh cáo: Sự khác biệt về phiên bản DSL có thể ảnh hưởng đến một số tính năng nhất định',
appCreateDSLErrorPart4: 'Phiên bản DSL được hệ thống hỗ trợ:',
},
editApp: 'Chỉnh sửa thông tin',
editAppTitle: 'Chỉnh sửa thông tin ứng dụng',

View File

@ -99,6 +99,8 @@ const translation = {
fileUploadTip: 'Các tính năng tải lên hình ảnh đã được nâng cấp để tải tệp lên.',
featuresDescription: 'Nâng cao trải nghiệm người dùng ứng dụng web',
ImageUploadLegacyTip: 'Bây giờ bạn có thể tạo các biến loại tệp trong biểu mẫu bắt đầu. Chúng tôi sẽ không còn hỗ trợ tính năng tải lên hình ảnh trong tương lai.',
importWarning: 'Thận trọng',
importWarningDetails: 'Sự khác biệt về phiên bản DSL có thể ảnh hưởng đến một số tính năng nhất định',
},
env: {
envPanelTitle: 'Biến Môi Trường',
@ -408,6 +410,10 @@ const translation = {
binaryFileVariable: 'Biến tệp nhị phân',
type: 'Kiểu',
extractListPlaceholder: 'Nhập chỉ mục mục danh sách, nhập \'/\' chèn biến',
curl: {
title: 'Nhập từ cURL',
placeholder: 'Dán chuỗi cURL vào đây',
},
},
code: {
inputVars: 'Biến đầu vào',

View File

@ -218,6 +218,10 @@ const translation = {
enableText: '功能已开启',
manage: '管理',
},
documentUpload: {
title: '文档',
description: '启用文档后,模型可以接收文档并回答关于它们的问题。',
},
},
codegen: {
title: '代码生成器',

Some files were not shown because too many files have changed in this diff Show More