mirror of
https://github.com/langgenius/dify.git
synced 2026-03-22 14:57:58 +08:00
Merge branch 'main' into feat/rag-pipeline
This commit is contained in:
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import TracingIcon from './tracing-icon'
|
||||
import ProviderPanel from './provider-panel'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type'
|
||||
import { TracingProvider } from './type'
|
||||
import ProviderConfigModal from './provider-config-modal'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
@ -26,7 +26,8 @@ export type PopupProps = {
|
||||
langSmithConfig: LangSmithConfig | null
|
||||
langFuseConfig: LangFuseConfig | null
|
||||
opikConfig: OpikConfig | null
|
||||
onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void
|
||||
weaveConfig: WeaveConfig | null
|
||||
onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void
|
||||
onConfigRemoved: (provider: TracingProvider) => void
|
||||
}
|
||||
|
||||
@ -40,6 +41,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
langSmithConfig,
|
||||
langFuseConfig,
|
||||
opikConfig,
|
||||
weaveConfig,
|
||||
onConfigUpdated,
|
||||
onConfigRemoved,
|
||||
}) => {
|
||||
@ -63,7 +65,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
}
|
||||
}, [onChooseProvider])
|
||||
|
||||
const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig) => {
|
||||
const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => {
|
||||
onConfigUpdated(currentProvider!, payload)
|
||||
hideConfigModal()
|
||||
}, [currentProvider, hideConfigModal, onConfigUpdated])
|
||||
@ -73,8 +75,8 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
hideConfigModal()
|
||||
}, [currentProvider, hideConfigModal, onConfigRemoved])
|
||||
|
||||
const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig
|
||||
const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig
|
||||
const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig
|
||||
const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig
|
||||
|
||||
const switchContent = (
|
||||
<Switch
|
||||
@ -123,33 +125,51 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
/>
|
||||
)
|
||||
|
||||
const weavePanel = (
|
||||
<ProviderPanel
|
||||
type={TracingProvider.weave}
|
||||
readOnly={readOnly}
|
||||
config={weaveConfig}
|
||||
hasConfigured={!!weaveConfig}
|
||||
onConfig={handleOnConfig(TracingProvider.weave)}
|
||||
isChosen={chosenProvider === TracingProvider.weave}
|
||||
onChoose={handleOnChoose(TracingProvider.weave)}
|
||||
key="weave-provider-panel"
|
||||
/>
|
||||
)
|
||||
const configuredProviderPanel = () => {
|
||||
const configuredPanels: JSX.Element[] = []
|
||||
|
||||
if (langSmithConfig)
|
||||
configuredPanels.push(langSmithPanel)
|
||||
|
||||
if (langFuseConfig)
|
||||
configuredPanels.push(langfusePanel)
|
||||
|
||||
if (langSmithConfig)
|
||||
configuredPanels.push(langSmithPanel)
|
||||
|
||||
if (opikConfig)
|
||||
configuredPanels.push(opikPanel)
|
||||
|
||||
if (weaveConfig)
|
||||
configuredPanels.push(weavePanel)
|
||||
|
||||
return configuredPanels
|
||||
}
|
||||
|
||||
const moreProviderPanel = () => {
|
||||
const notConfiguredPanels: JSX.Element[] = []
|
||||
|
||||
if (!langSmithConfig)
|
||||
notConfiguredPanels.push(langSmithPanel)
|
||||
|
||||
if (!langFuseConfig)
|
||||
notConfiguredPanels.push(langfusePanel)
|
||||
|
||||
if (!langSmithConfig)
|
||||
notConfiguredPanels.push(langSmithPanel)
|
||||
|
||||
if (!opikConfig)
|
||||
notConfiguredPanels.push(opikPanel)
|
||||
|
||||
if (!weaveConfig)
|
||||
notConfiguredPanels.push(weavePanel)
|
||||
|
||||
return notConfiguredPanels
|
||||
}
|
||||
|
||||
@ -158,7 +178,9 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
return langSmithConfig
|
||||
if (currentProvider === TracingProvider.langfuse)
|
||||
return langFuseConfig
|
||||
return opikConfig
|
||||
if (currentProvider === TracingProvider.opik)
|
||||
return opikConfig
|
||||
return weaveConfig
|
||||
}
|
||||
|
||||
return (
|
||||
@ -199,9 +221,10 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
<>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div>
|
||||
<div className='mt-2 space-y-2'>
|
||||
{langSmithPanel}
|
||||
{langfusePanel}
|
||||
{langSmithPanel}
|
||||
{opikPanel}
|
||||
{weavePanel}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -4,4 +4,5 @@ export const docURL = {
|
||||
[TracingProvider.langSmith]: 'https://docs.smith.langchain.com/',
|
||||
[TracingProvider.langfuse]: 'https://docs.langfuse.com',
|
||||
[TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions',
|
||||
[TracingProvider.weave]: 'https://weave-docs.wandb.ai/',
|
||||
}
|
||||
|
||||
@ -7,12 +7,12 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type'
|
||||
import { TracingProvider } from './type'
|
||||
import TracingIcon from './tracing-icon'
|
||||
import ConfigButton from './config-button'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LangfuseIcon, LangsmithIcon, OpikIcon } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { LangfuseIcon, LangsmithIcon, OpikIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps'
|
||||
import type { TracingStatus } from '@/models/app'
|
||||
@ -82,12 +82,15 @@ const Panel: FC = () => {
|
||||
? LangfuseIcon
|
||||
: inUseTracingProvider === TracingProvider.opik
|
||||
? OpikIcon
|
||||
: LangsmithIcon
|
||||
: inUseTracingProvider === TracingProvider.weave
|
||||
? WeaveIcon
|
||||
: LangsmithIcon
|
||||
|
||||
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
|
||||
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
|
||||
const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null)
|
||||
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig)
|
||||
const [weaveConfig, setWeaveConfig] = useState<WeaveConfig | null>(null)
|
||||
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig)
|
||||
|
||||
const fetchTracingConfig = async () => {
|
||||
const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith })
|
||||
@ -99,6 +102,9 @@ const Panel: FC = () => {
|
||||
const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik })
|
||||
if (!OpikHasNotConfig)
|
||||
setOpikConfig(opikConfig as OpikConfig)
|
||||
const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave })
|
||||
if (!weaveHasNotConfig)
|
||||
setWeaveConfig(weaveConfig as WeaveConfig)
|
||||
}
|
||||
|
||||
const handleTracingConfigUpdated = async (provider: TracingProvider) => {
|
||||
@ -110,6 +116,8 @@ const Panel: FC = () => {
|
||||
setLangFuseConfig(tracing_config as LangFuseConfig)
|
||||
else if (provider === TracingProvider.opik)
|
||||
setOpikConfig(tracing_config as OpikConfig)
|
||||
else if (provider === TracingProvider.weave)
|
||||
setWeaveConfig(tracing_config as WeaveConfig)
|
||||
}
|
||||
|
||||
const handleTracingConfigRemoved = (provider: TracingProvider) => {
|
||||
@ -119,6 +127,8 @@ const Panel: FC = () => {
|
||||
setLangFuseConfig(null)
|
||||
else if (provider === TracingProvider.opik)
|
||||
setOpikConfig(null)
|
||||
else if (provider === TracingProvider.weave)
|
||||
setWeaveConfig(null)
|
||||
if (provider === inUseTracingProvider) {
|
||||
handleTracingStatusChange({
|
||||
enabled: false,
|
||||
@ -178,6 +188,7 @@ const Panel: FC = () => {
|
||||
langSmithConfig={langSmithConfig}
|
||||
langFuseConfig={langFuseConfig}
|
||||
opikConfig={opikConfig}
|
||||
weaveConfig={weaveConfig}
|
||||
onConfigUpdated={handleTracingConfigUpdated}
|
||||
onConfigRemoved={handleTracingConfigRemoved}
|
||||
controlShowPopup={controlShowPopup}
|
||||
@ -212,6 +223,7 @@ const Panel: FC = () => {
|
||||
langSmithConfig={langSmithConfig}
|
||||
langFuseConfig={langFuseConfig}
|
||||
opikConfig={opikConfig}
|
||||
weaveConfig={weaveConfig}
|
||||
onConfigUpdated={handleTracingConfigUpdated}
|
||||
onConfigRemoved={handleTracingConfigRemoved}
|
||||
controlShowPopup={controlShowPopup}
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Field from './field'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type'
|
||||
import { TracingProvider } from './type'
|
||||
import { docURL } from './config'
|
||||
import {
|
||||
@ -22,10 +22,10 @@ import Divider from '@/app/components/base/divider'
|
||||
type Props = {
|
||||
appId: string
|
||||
type: TracingProvider
|
||||
payload?: LangSmithConfig | LangFuseConfig | OpikConfig | null
|
||||
payload?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null
|
||||
onRemoved: () => void
|
||||
onCancel: () => void
|
||||
onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void
|
||||
onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void
|
||||
onChosen: (provider: TracingProvider) => void
|
||||
}
|
||||
|
||||
@ -50,6 +50,13 @@ const opikConfigTemplate = {
|
||||
workspace: '',
|
||||
}
|
||||
|
||||
const weaveConfigTemplate = {
|
||||
api_key: '',
|
||||
entity: '',
|
||||
project: '',
|
||||
endpoint: '',
|
||||
}
|
||||
|
||||
const ProviderConfigModal: FC<Props> = ({
|
||||
appId,
|
||||
type,
|
||||
@ -63,7 +70,7 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
const isEdit = !!payload
|
||||
const isAdd = !isEdit
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig>((() => {
|
||||
const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig>((() => {
|
||||
if (isEdit)
|
||||
return payload
|
||||
|
||||
@ -73,7 +80,10 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
else if (type === TracingProvider.langfuse)
|
||||
return langFuseConfigTemplate
|
||||
|
||||
return opikConfigTemplate
|
||||
else if (type === TracingProvider.opik)
|
||||
return opikConfigTemplate
|
||||
|
||||
return weaveConfigTemplate
|
||||
})())
|
||||
const [isShowRemoveConfirm, {
|
||||
setTrue: showRemoveConfirm,
|
||||
@ -127,6 +137,14 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
// const postData = config as OpikConfig
|
||||
}
|
||||
|
||||
if (type === TracingProvider.weave) {
|
||||
const postData = config as WeaveConfig
|
||||
if (!errorMessage && !postData.api_key)
|
||||
errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' })
|
||||
if (!errorMessage && !postData.project)
|
||||
errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) })
|
||||
}
|
||||
|
||||
return errorMessage
|
||||
}, [config, t, type])
|
||||
const handleSave = useCallback(async () => {
|
||||
@ -176,6 +194,40 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
{type === TracingProvider.weave && (
|
||||
<>
|
||||
<Field
|
||||
label='API Key'
|
||||
labelClassName='!text-sm'
|
||||
isRequired
|
||||
value={(config as WeaveConfig).api_key}
|
||||
onChange={handleConfigChange('api_key')}
|
||||
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'API Key' })!}
|
||||
/>
|
||||
<Field
|
||||
label={t(`${I18N_PREFIX}.project`)!}
|
||||
labelClassName='!text-sm'
|
||||
isRequired
|
||||
value={(config as WeaveConfig).project}
|
||||
onChange={handleConfigChange('project')}
|
||||
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.project`) })!}
|
||||
/>
|
||||
<Field
|
||||
label='Entity'
|
||||
labelClassName='!text-sm'
|
||||
value={(config as WeaveConfig).entity}
|
||||
onChange={handleConfigChange('entity')}
|
||||
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'Entity' })!}
|
||||
/>
|
||||
<Field
|
||||
label='Endpoint'
|
||||
labelClassName='!text-sm'
|
||||
value={(config as WeaveConfig).endpoint}
|
||||
onChange={handleConfigChange('endpoint')}
|
||||
placeholder={'https://trace.wandb.ai/'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{type === TracingProvider.langSmith && (
|
||||
<>
|
||||
<Field
|
||||
@ -263,7 +315,6 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className='my-8 flex h-8 items-center justify-between'>
|
||||
<a
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TracingProvider } from './type'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LangfuseIconBig, LangsmithIconBig, OpikIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { LangfuseIconBig, LangsmithIconBig, OpikIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
const I18N_PREFIX = 'app.tracing'
|
||||
@ -27,6 +27,7 @@ const getIcon = (type: TracingProvider) => {
|
||||
[TracingProvider.langSmith]: LangsmithIconBig,
|
||||
[TracingProvider.langfuse]: LangfuseIconBig,
|
||||
[TracingProvider.opik]: OpikIconBig,
|
||||
[TracingProvider.weave]: WeaveIconBig,
|
||||
})[type]
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ export enum TracingProvider {
|
||||
langSmith = 'langsmith',
|
||||
langfuse = 'langfuse',
|
||||
opik = 'opik',
|
||||
weave = 'weave',
|
||||
}
|
||||
|
||||
export type LangSmithConfig = {
|
||||
@ -22,3 +23,10 @@ export type OpikConfig = {
|
||||
workspace: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type WeaveConfig = {
|
||||
api_key: string
|
||||
entity: string
|
||||
project: string
|
||||
endpoint: string
|
||||
}
|
||||
|
||||
@ -231,7 +231,7 @@ const AppPublisher = ({
|
||||
>
|
||||
{t('workflow.common.runApp')}
|
||||
</SuggestedAction>
|
||||
{appDetail?.mode === 'workflow'
|
||||
{appDetail?.mode === 'workflow' || appDetail?.mode === 'completion'
|
||||
? (
|
||||
<SuggestedAction
|
||||
disabled={!publishedAt}
|
||||
|
||||
@ -14,6 +14,7 @@ import Loading from '@/app/components/base/loading'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import cn from '@/utils/classnames'
|
||||
import { basePath } from '@/utils/var'
|
||||
|
||||
export type ISelectDataSetProps = {
|
||||
isShow: boolean
|
||||
@ -111,7 +112,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
||||
}}
|
||||
>
|
||||
<span className='text-text-tertiary'>{t('appDebug.feature.dataSet.noDataSet')}</span>
|
||||
<Link href="/datasets/create" className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link>
|
||||
<Link href={`${basePath}/datasets/create`} className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import { useMount } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { BookOpenIcon } from '@heroicons/react/24/outline'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import cn from '@/utils/classnames'
|
||||
import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio'
|
||||
@ -223,10 +222,6 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
className='resize-none'
|
||||
placeholder={t('datasetSettings.form.descPlaceholder') || ''}
|
||||
/>
|
||||
<a className='mt-2 flex h-[18px] items-center px-3 text-xs text-text-tertiary' href="https://docs.dify.ai/features/datasets#how-to-write-a-good-dataset-description" target='_blank' rel='noopener noreferrer'>
|
||||
<BookOpenIcon className='mr-1 h-[18px] w-3' />
|
||||
{t('datasetSettings.form.descWrite')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
|
||||
@ -191,14 +191,16 @@ const Apps = ({
|
||||
</div>
|
||||
<div className='relative flex flex-1 overflow-y-auto'>
|
||||
{!searchKeywords && <div className='h-full w-[200px] p-4'>
|
||||
<Sidebar current={currCategory as AppCategories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
|
||||
<Sidebar current={currCategory as AppCategories} categories={categories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
|
||||
</div>}
|
||||
<div className='h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2'>
|
||||
{searchFilteredList && searchFilteredList.length > 0 && <>
|
||||
<div className='pb-1 pt-4'>
|
||||
{searchKeywords
|
||||
? <p className='title-md-semi-bold text-text-tertiary'>{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}</p>
|
||||
: <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' />}
|
||||
: <div className='flex h-[22px] items-center'>
|
||||
<AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' />
|
||||
</div>}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@ -1,39 +1,29 @@
|
||||
'use client'
|
||||
import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react'
|
||||
import { RiStickyNoteAddLine, RiThumbUpLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from '@/utils/classnames'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
export enum AppCategories {
|
||||
RECOMMENDED = 'Recommended',
|
||||
ASSISTANT = 'Assistant',
|
||||
AGENT = 'Agent',
|
||||
HR = 'HR',
|
||||
PROGRAMMING = 'Programming',
|
||||
WORKFLOW = 'Workflow',
|
||||
WRITING = 'Writing',
|
||||
}
|
||||
|
||||
type SidebarProps = {
|
||||
current: AppCategories
|
||||
onClick?: (category: AppCategories) => void
|
||||
current: AppCategories | string
|
||||
categories: string[]
|
||||
onClick?: (category: AppCategories | string) => void
|
||||
onCreateFromBlank?: () => void
|
||||
}
|
||||
|
||||
export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) {
|
||||
export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) {
|
||||
const { t } = useTranslation()
|
||||
return <div className="flex h-full w-full flex-col">
|
||||
<ul>
|
||||
<ul className='pt-0.5'>
|
||||
<CategoryItem category={AppCategories.RECOMMENDED} active={current === AppCategories.RECOMMENDED} onClick={onClick} />
|
||||
</ul>
|
||||
<div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div>
|
||||
<div className='system-xs-medium-uppercase mb-0.5 mt-3 px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div>
|
||||
<ul className='flex grow flex-col gap-0.5'>
|
||||
<CategoryItem category={AppCategories.ASSISTANT} active={current === AppCategories.ASSISTANT} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.AGENT} active={current === AppCategories.AGENT} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.HR} active={current === AppCategories.HR} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.PROGRAMMING} active={current === AppCategories.PROGRAMMING} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.WORKFLOW} active={current === AppCategories.WORKFLOW} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.WRITING} active={current === AppCategories.WRITING} onClick={onClick} />
|
||||
{categories.map(category => (<CategoryItem key={category} category={category} active={current === category} onClick={onClick} />))}
|
||||
</ul>
|
||||
<Divider bgStyle='gradient' />
|
||||
<div className='flex cursor-pointer items-center gap-1 px-3 py-1 text-text-tertiary' onClick={onCreateFromBlank}>
|
||||
@ -45,47 +35,26 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar
|
||||
|
||||
type CategoryItemProps = {
|
||||
active: boolean
|
||||
category: AppCategories
|
||||
onClick?: (category: AppCategories) => void
|
||||
category: AppCategories | string
|
||||
onClick?: (category: AppCategories | string) => void
|
||||
}
|
||||
function CategoryItem({ category, active, onClick }: CategoryItemProps) {
|
||||
return <li
|
||||
className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')}
|
||||
className={classNames('p-1 pl-3 h-8 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')}
|
||||
onClick={() => { onClick?.(category) }}>
|
||||
<div className='inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'>
|
||||
<AppCategoryIcon category={category} />
|
||||
</div>
|
||||
{category === AppCategories.RECOMMENDED && <div className='inline-flex h-5 w-5 items-center justify-center rounded-md'>
|
||||
<RiThumbUpLine className='h-4 w-4 text-components-menu-item-text group-[.active]:text-components-menu-item-text-active' />
|
||||
</div>}
|
||||
<AppCategoryLabel category={category}
|
||||
className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} />
|
||||
</li >
|
||||
}
|
||||
|
||||
type AppCategoryLabelProps = {
|
||||
category: AppCategories
|
||||
category: AppCategories | string
|
||||
className?: string
|
||||
}
|
||||
export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) {
|
||||
const { t } = useTranslation()
|
||||
return <span className={className}>{t(`app.newAppFromTemplate.sidebar.${category}`)}</span>
|
||||
}
|
||||
|
||||
type AppCategoryIconProps = {
|
||||
category: AppCategories
|
||||
}
|
||||
function AppCategoryIcon({ category }: AppCategoryIconProps) {
|
||||
if (category === AppCategories.AGENT)
|
||||
return <RiSpeakAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.ASSISTANT)
|
||||
return <RiChatSmileAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.HR)
|
||||
return <RiPassPendingFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.PROGRAMMING)
|
||||
return <RiTerminalBoxFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.RECOMMENDED)
|
||||
return <RiThumbUpFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.WRITING)
|
||||
return <RiQuillPenAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.WORKFLOW)
|
||||
return <RiExchange2Fill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
return <RiAppsFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
return <span className={className}>{category === AppCategories.RECOMMENDED ? t('app.newAppFromTemplate.sidebar.Recommended') : category}</span>
|
||||
}
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type DSLConfirmModalProps = {
|
||||
versions?: {
|
||||
importedVersion: string
|
||||
systemVersion: string
|
||||
}
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
confirmDisabled?: boolean
|
||||
}
|
||||
const DSLConfirmModal = ({
|
||||
versions = { importedVersion: '', systemVersion: '' },
|
||||
onCancel,
|
||||
onConfirm,
|
||||
confirmDisabled = false,
|
||||
}: DSLConfirmModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => onCancel()}
|
||||
className='w-[480px]'
|
||||
>
|
||||
<div className='flex flex-col items-start gap-2 self-stretch pb-4'>
|
||||
<div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
|
||||
<div className='system-md-regular flex grow flex-col text-text-secondary'>
|
||||
<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 items-start justify-end gap-2 self-stretch pt-6'>
|
||||
<Button variant='secondary' onClick={() => onCancel()}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button variant='primary' destructive onClick={onConfirm} disabled={confirmDisabled}>{t('app.newApp.Confirm')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DSLConfirmModal
|
||||
@ -29,7 +29,7 @@ const OPTION_MAP = {
|
||||
iframe: {
|
||||
getContent: (url: string, token: string) =>
|
||||
`<iframe
|
||||
src="${url}${basePath}/chat/${token}"
|
||||
src="${url}${basePath}/chatbot/${token}"
|
||||
style="width: 100%; height: 100%; min-height: 700px"
|
||||
frameborder="0"
|
||||
allow="microphone">
|
||||
@ -48,6 +48,7 @@ const OPTION_MAP = {
|
||||
: ''},
|
||||
systemVariables: {
|
||||
// user_id: 'YOU CAN DEFINE USER ID HERE',
|
||||
// conversation_id: 'YOU CAN DEFINE CONVERSATION ID HERE, IT MUST BE A VALID UUID',
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -39,6 +39,7 @@ export type EmbeddedChatbotContextValue = {
|
||||
chatShouldReloadKey: string
|
||||
isMobile: boolean
|
||||
isInstalledApp: boolean
|
||||
allowResetChat: boolean
|
||||
appId?: string
|
||||
handleFeedback: (messageId: string, feedback: Feedback) => void
|
||||
currentChatInstanceRef: RefObject<{ handleStop: () => void }>
|
||||
@ -67,6 +68,7 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
|
||||
chatShouldReloadKey: '',
|
||||
isMobile: false,
|
||||
isInstalledApp: false,
|
||||
allowResetChat: true,
|
||||
handleFeedback: noop,
|
||||
currentChatInstanceRef: { current: { handleStop: noop } },
|
||||
clearChatList: false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { RiResetLeftLine } from '@remixicon/react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Theme } from '../theme/theme-context'
|
||||
import { CssTransform } from '../theme/utils'
|
||||
@ -16,6 +16,7 @@ import cn from '@/utils/classnames'
|
||||
|
||||
export type IHeaderProps = {
|
||||
isMobile?: boolean
|
||||
allowResetChat?: boolean
|
||||
customerIcon?: React.ReactNode
|
||||
title: string
|
||||
theme?: Theme
|
||||
@ -23,6 +24,7 @@ export type IHeaderProps = {
|
||||
}
|
||||
const Header: FC<IHeaderProps> = ({
|
||||
isMobile,
|
||||
allowResetChat,
|
||||
customerIcon,
|
||||
title,
|
||||
theme,
|
||||
@ -34,6 +36,44 @@ const Header: FC<IHeaderProps> = ({
|
||||
currentConversationId,
|
||||
inputsForms,
|
||||
} = useEmbeddedChatbotContext()
|
||||
|
||||
const isClient = typeof window !== 'undefined'
|
||||
const isIframe = isClient ? window.self !== window.top : false
|
||||
const [parentOrigin, setParentOrigin] = useState('')
|
||||
const [showToggleExpandButton, setShowToggleExpandButton] = useState(false)
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
const handleMessageReceived = useCallback((event: MessageEvent) => {
|
||||
let currentParentOrigin = parentOrigin
|
||||
if (!currentParentOrigin && event.data.type === 'dify-chatbot-config') {
|
||||
currentParentOrigin = event.origin
|
||||
setParentOrigin(event.origin)
|
||||
}
|
||||
if (event.origin !== currentParentOrigin)
|
||||
return
|
||||
if (event.data.type === 'dify-chatbot-config')
|
||||
setShowToggleExpandButton(event.data.payload.isToggledByButton && !event.data.payload.isDraggable)
|
||||
}, [parentOrigin])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isIframe) return
|
||||
|
||||
const listener = (event: MessageEvent) => handleMessageReceived(event)
|
||||
window.addEventListener('message', listener)
|
||||
|
||||
window.parent.postMessage({ type: 'dify-chatbot-iframe-ready' }, '*')
|
||||
|
||||
return () => window.removeEventListener('message', listener)
|
||||
}, [isIframe, handleMessageReceived])
|
||||
|
||||
const handleToggleExpand = useCallback(() => {
|
||||
if (!isIframe || !showToggleExpandButton) return
|
||||
setExpanded(!expanded)
|
||||
window.parent.postMessage({
|
||||
type: 'dify-chatbot-expand-change',
|
||||
}, parentOrigin)
|
||||
}, [isIframe, parentOrigin, showToggleExpandButton, expanded])
|
||||
|
||||
if (!isMobile) {
|
||||
return (
|
||||
<div className='flex h-14 shrink-0 items-center justify-end p-3'>
|
||||
@ -57,7 +97,22 @@ const Header: FC<IHeaderProps> = ({
|
||||
{currentConversationId && (
|
||||
<Divider type='vertical' className='h-3.5' />
|
||||
)}
|
||||
{currentConversationId && (
|
||||
{
|
||||
showToggleExpandButton && (
|
||||
<Tooltip
|
||||
popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleToggleExpand}>
|
||||
{
|
||||
expanded
|
||||
? <RiCollapseDiagonal2Line className='h-[18px] w-[18px]' />
|
||||
: <RiExpandDiagonal2Line className='h-[18px] w-[18px]' />
|
||||
}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
{currentConversationId && allowResetChat && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.resetChat')}
|
||||
>
|
||||
@ -89,7 +144,22 @@ const Header: FC<IHeaderProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
{currentConversationId && (
|
||||
{
|
||||
showToggleExpandButton && (
|
||||
<Tooltip
|
||||
popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleToggleExpand}>
|
||||
{
|
||||
expanded
|
||||
? <RiCollapseDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
|
||||
: <RiExpandDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
|
||||
}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
{currentConversationId && allowResetChat && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.resetChat')}
|
||||
>
|
||||
|
||||
@ -73,9 +73,11 @@ export const useEmbeddedChatbot = () => {
|
||||
const appId = useMemo(() => appData?.app_id, [appData])
|
||||
|
||||
const [userId, setUserId] = useState<string>()
|
||||
const [conversationId, setConversationId] = useState<string>()
|
||||
useEffect(() => {
|
||||
getProcessedSystemVariablesFromUrlParams().then(({ user_id }) => {
|
||||
getProcessedSystemVariablesFromUrlParams().then(({ user_id, conversation_id }) => {
|
||||
setUserId(user_id)
|
||||
setConversationId(conversation_id)
|
||||
})
|
||||
}, [])
|
||||
|
||||
@ -109,7 +111,9 @@ export const useEmbeddedChatbot = () => {
|
||||
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {
|
||||
defaultValue: {},
|
||||
})
|
||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId])
|
||||
const allowResetChat = !conversationId
|
||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '',
|
||||
[appId, conversationIdInfo, userId, conversationId])
|
||||
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
|
||||
if (appId) {
|
||||
let prevValue = conversationIdInfo?.[appId || '']
|
||||
@ -362,6 +366,7 @@ export const useEmbeddedChatbot = () => {
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
appId,
|
||||
currentConversationId,
|
||||
currentConversationItem,
|
||||
|
||||
@ -25,6 +25,7 @@ import cn from '@/utils/classnames'
|
||||
const Chatbot = () => {
|
||||
const {
|
||||
isMobile,
|
||||
allowResetChat,
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
appData,
|
||||
@ -90,6 +91,7 @@ const Chatbot = () => {
|
||||
>
|
||||
<Header
|
||||
isMobile={isMobile}
|
||||
allowResetChat={allowResetChat}
|
||||
title={site?.title || ''}
|
||||
customerIcon={isDify() ? difyIcon : ''}
|
||||
theme={themeBuilder?.theme}
|
||||
@ -153,6 +155,7 @@ const EmbeddedChatbotWrapper = () => {
|
||||
handleNewConversationCompleted,
|
||||
chatShouldReloadKey,
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
appId,
|
||||
handleFeedback,
|
||||
currentChatInstanceRef,
|
||||
@ -187,6 +190,7 @@ const EmbeddedChatbotWrapper = () => {
|
||||
chatShouldReloadKey,
|
||||
isMobile,
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
appId,
|
||||
handleFeedback,
|
||||
currentChatInstanceRef,
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 68 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 68 KiB |
279
web/app/components/base/icons/src/public/tracing/WeaveIcon.json
Normal file
279
web/app/components/base/icons/src/public/tracing/WeaveIcon.json
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './WeaveIcon.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 = 'WeaveIcon'
|
||||
|
||||
export default Icon
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './WeaveIconBig.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 = 'WeaveIconBig'
|
||||
|
||||
export default Icon
|
||||
@ -5,3 +5,5 @@ export { default as LangsmithIcon } from './LangsmithIcon'
|
||||
export { default as OpikIconBig } from './OpikIconBig'
|
||||
export { default as OpikIcon } from './OpikIcon'
|
||||
export { default as TracingIcon } from './TracingIcon'
|
||||
export { default as WeaveIconBig } from './WeaveIconBig'
|
||||
export { default as WeaveIcon } from './WeaveIcon'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { basePath } from '@/utils/var'
|
||||
|
||||
type LogoEmbeddedChatAvatarProps = {
|
||||
className?: string
|
||||
@ -8,7 +9,7 @@ const LogoEmbeddedChatAvatar: FC<LogoEmbeddedChatAvatarProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<img
|
||||
src='/logo/logo-embedded-chat-avatar.png'
|
||||
src={`${basePath}/logo/logo-embedded-chat-avatar.png`}
|
||||
className={`block h-10 w-10 ${className}`}
|
||||
alt='logo'
|
||||
/>
|
||||
|
||||
@ -22,10 +22,6 @@ export function preprocessMermaidCode(code: string): string {
|
||||
.replace(/section\s+([^:]+):/g, (match, sectionName) => `section ${sectionName}:`)
|
||||
// Fix common syntax issues
|
||||
.replace(/fifopacket/g, 'rect')
|
||||
// Ensure graph has direction
|
||||
.replace(/^graph\s+((?:TB|BT|RL|LR)*)/, (match, direction) => {
|
||||
return direction ? match : 'graph TD'
|
||||
})
|
||||
// Clean up empty lines and extra spaces
|
||||
.trim()
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
}
|
||||
|
||||
if (!node)
|
||||
return null
|
||||
return Item
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
||||
@ -68,7 +68,7 @@ const Toast = ({
|
||||
</div>
|
||||
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} grow flex-col items-start gap-1`}>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='system-sm-semibold text-text-primary'>{message}</div>
|
||||
<div className='system-sm-semibold text-text-primary [word-break:break-word]'>{message}</div>
|
||||
{customComponent}
|
||||
</div>
|
||||
{children && <div className='system-xs-regular text-text-secondary'>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { BasicPlan } from '@/app/components/billing/type'
|
||||
import { Plan, type PlanInfo, Priority } from '@/app/components/billing/type'
|
||||
|
||||
const supportModelProviders = 'OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicate'
|
||||
@ -10,7 +11,7 @@ export const contactSalesUrl = 'https://vikgc6bnu1s.typeform.com/dify-business'
|
||||
export const getStartedWithCommunityUrl = 'https://github.com/langgenius/dify'
|
||||
export const getWithPremiumUrl = 'https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6'
|
||||
|
||||
export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
export const ALL_PLANS: Record<BasicPlan, PlanInfo> = {
|
||||
sandbox: {
|
||||
level: 1,
|
||||
price: 0,
|
||||
@ -22,6 +23,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
vectorSpace: '50MB',
|
||||
documentsUploadQuota: 0,
|
||||
documentsRequestQuota: 10,
|
||||
apiRateLimit: 5000,
|
||||
documentProcessingPriority: Priority.standard,
|
||||
messageRequest: 200,
|
||||
annotatedResponse: 10,
|
||||
@ -38,6 +40,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
vectorSpace: '5GB',
|
||||
documentsUploadQuota: 0,
|
||||
documentsRequestQuota: 100,
|
||||
apiRateLimit: NUM_INFINITE,
|
||||
documentProcessingPriority: Priority.priority,
|
||||
messageRequest: 5000,
|
||||
annotatedResponse: 2000,
|
||||
@ -54,6 +57,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
vectorSpace: '20GB',
|
||||
documentsUploadQuota: 0,
|
||||
documentsRequestQuota: 1000,
|
||||
apiRateLimit: NUM_INFINITE,
|
||||
documentProcessingPriority: Priority.topPriority,
|
||||
messageRequest: 10000,
|
||||
annotatedResponse: 5000,
|
||||
@ -62,7 +66,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
}
|
||||
|
||||
export const defaultPlan = {
|
||||
type: Plan.sandbox,
|
||||
type: Plan.sandbox as BasicPlan,
|
||||
usage: {
|
||||
documents: 50,
|
||||
vectorSpace: 1,
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiApps2Line, RiBook2Line, RiBrain2Line, RiChatAiLine, RiFileEditLine, RiFolder6Line, RiGroupLine, RiHardDrive3Line, RiHistoryLine, RiProgress3Line, RiQuestionLine, RiSeoLine } from '@remixicon/react'
|
||||
import { RiApps2Line, RiBook2Line, RiBrain2Line, RiChatAiLine, RiFileEditLine, RiFolder6Line, RiGroupLine, RiHardDrive3Line, RiHistoryLine, RiProgress3Line, RiQuestionLine, RiSeoLine, RiTerminalBoxLine } from '@remixicon/react'
|
||||
import type { BasicPlan } from '../type'
|
||||
import { Plan } from '../type'
|
||||
import { ALL_PLANS, NUM_INFINITE } from '../config'
|
||||
import Toast from '../../base/toast'
|
||||
@ -15,8 +16,8 @@ import { useAppContext } from '@/context/app-context'
|
||||
import { fetchSubscriptionUrls } from '@/service/billing'
|
||||
|
||||
type Props = {
|
||||
currentPlan: Plan
|
||||
plan: Plan
|
||||
currentPlan: BasicPlan
|
||||
plan: BasicPlan
|
||||
planRange: PlanRange
|
||||
canPay: boolean
|
||||
}
|
||||
@ -127,8 +128,8 @@ const PlanItem: FC<Props> = ({
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
{style[plan].icon}
|
||||
<div className='flex items-center'>
|
||||
<div className='text-lg font-semibold uppercase leading-[125%] text-text-primary'>{t(`${i18nPrefix}.name`)}</div>
|
||||
{isMostPopularPlan && <div className='ml-1 flex items-center justify-center rounded-full border-[0.5px] bg-price-premium-badge-background px-1 py-[3px] text-components-premium-badge-grey-text-stop-0 shadow-xs'>
|
||||
<div className='grow text-lg font-semibold uppercase leading-[125%] text-text-primary'>{t(`${i18nPrefix}.name`)}</div>
|
||||
{isMostPopularPlan && <div className='ml-1 flex shrink-0 items-center justify-center rounded-full border-[0.5px] bg-price-premium-badge-background px-1 py-[3px] text-components-premium-badge-grey-text-stop-0 shadow-xs'>
|
||||
<div className='pl-0.5'>
|
||||
<SparklesSoft className='size-3' />
|
||||
</div>
|
||||
@ -205,6 +206,14 @@ const PlanItem: FC<Props> = ({
|
||||
label={t('billing.plansCommon.documentsRequestQuota', { count: planInfo.documentsRequestQuota })}
|
||||
tooltip={t('billing.plansCommon.documentsRequestQuotaTooltip')}
|
||||
/>
|
||||
<KeyValue
|
||||
icon={<RiTerminalBoxLine />}
|
||||
label={
|
||||
planInfo.apiRateLimit === NUM_INFINITE ? `${t('billing.plansCommon.unlimitedApiRate')}`
|
||||
: `${t('billing.plansCommon.apiRateLimitUnit', { count: planInfo.apiRateLimit })} ${t('billing.plansCommon.apiRateLimit')}`
|
||||
}
|
||||
tooltip={planInfo.apiRateLimit === NUM_INFINITE ? null : t('billing.plansCommon.apiRateLimitTooltip') as string}
|
||||
/>
|
||||
<KeyValue
|
||||
icon={<RiProgress3Line />}
|
||||
label={[t(`billing.plansCommon.priority.${planInfo.documentProcessingPriority}`), t('billing.plansCommon.documentProcessingPriority')].join('')}
|
||||
|
||||
@ -9,6 +9,9 @@ export enum Priority {
|
||||
priority = 'priority',
|
||||
topPriority = 'top-priority',
|
||||
}
|
||||
|
||||
export type BasicPlan = Plan.sandbox | Plan.professional | Plan.team
|
||||
|
||||
export type PlanInfo = {
|
||||
level: number
|
||||
price: number
|
||||
@ -20,6 +23,7 @@ export type PlanInfo = {
|
||||
vectorSpace: string
|
||||
documentsUploadQuota: number
|
||||
documentsRequestQuota: number
|
||||
apiRateLimit: number
|
||||
documentProcessingPriority: Priority
|
||||
logHistory: number
|
||||
messageRequest: number
|
||||
@ -60,7 +64,7 @@ export type CurrentPlanInfoBackend = {
|
||||
billing: {
|
||||
enabled: boolean
|
||||
subscription: {
|
||||
plan: Plan
|
||||
plan: BasicPlan
|
||||
}
|
||||
}
|
||||
members: {
|
||||
|
||||
@ -196,22 +196,68 @@ const FileUploader = ({
|
||||
e.stopPropagation()
|
||||
e.target === dragRef.current && setDragging(false)
|
||||
}
|
||||
type FileWithPath = {
|
||||
relativePath?: string
|
||||
} & File
|
||||
const traverseFileEntry = useCallback(
|
||||
(entry: any, prefix = ''): Promise<FileWithPath[]> => {
|
||||
return new Promise((resolve) => {
|
||||
if (entry.isFile) {
|
||||
entry.file((file: FileWithPath) => {
|
||||
file.relativePath = `${prefix}${file.name}`
|
||||
resolve([file])
|
||||
})
|
||||
}
|
||||
else if (entry.isDirectory) {
|
||||
const reader = entry.createReader()
|
||||
const entries: any[] = []
|
||||
const read = () => {
|
||||
reader.readEntries(async (results: FileSystemEntry[]) => {
|
||||
if (!results.length) {
|
||||
const files = await Promise.all(
|
||||
entries.map(ent =>
|
||||
traverseFileEntry(ent, `${prefix}${entry.name}/`),
|
||||
),
|
||||
)
|
||||
resolve(files.flat())
|
||||
}
|
||||
else {
|
||||
entries.push(...results)
|
||||
read()
|
||||
}
|
||||
})
|
||||
}
|
||||
read()
|
||||
}
|
||||
else {
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleDrop = useCallback((e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setDragging(false)
|
||||
if (!e.dataTransfer)
|
||||
return
|
||||
|
||||
let files = [...e.dataTransfer.files] as File[]
|
||||
if (notSupportBatchUpload)
|
||||
files = files.slice(0, 1)
|
||||
|
||||
const validFiles = files.filter(isValid)
|
||||
initialUpload(validFiles)
|
||||
}, [initialUpload, isValid, notSupportBatchUpload])
|
||||
|
||||
const handleDrop = useCallback(
|
||||
async (e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setDragging(false)
|
||||
if (!e.dataTransfer) return
|
||||
const nested = await Promise.all(
|
||||
Array.from(e.dataTransfer.items).map((it) => {
|
||||
const entry = (it as any).webkitGetAsEntry?.()
|
||||
if (entry) return traverseFileEntry(entry)
|
||||
const f = it.getAsFile?.()
|
||||
return f ? Promise.resolve([f]) : Promise.resolve([])
|
||||
}),
|
||||
)
|
||||
let files = nested.flat()
|
||||
if (notSupportBatchUpload) files = files.slice(0, 1)
|
||||
const valid = files.filter(isValid)
|
||||
initialUpload(valid)
|
||||
},
|
||||
[initialUpload, isValid, notSupportBatchUpload, traverseFileEntry],
|
||||
)
|
||||
const selectHandle = () => {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.click()
|
||||
|
||||
@ -17,6 +17,7 @@ type Props = {
|
||||
|
||||
const NoData: FC<Props> = ({
|
||||
onConfig,
|
||||
provider,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -38,7 +39,7 @@ const NoData: FC<Props> = ({
|
||||
} : null,
|
||||
}
|
||||
|
||||
const currentProvider = Object.values(providerConfig).find(provider => provider !== null) || providerConfig[DataSourceProvider.jinaReader]
|
||||
const currentProvider = providerConfig[provider] || providerConfig[DataSourceProvider.jinaReader]
|
||||
|
||||
if (!currentProvider) return null
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ const Doc = ({ appDetail }: IDocProps) => {
|
||||
<div className={`fixed right-8 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
||||
{isTocExpanded
|
||||
? (
|
||||
<nav className="toc w-full rounded-lg bg-components-panel-bg p-4 shadow-md">
|
||||
<nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-components-panel-bg p-4 shadow-md">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-text-primary">{t('appApi.develop.toc')}</h3>
|
||||
<button
|
||||
|
||||
@ -643,13 +643,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -683,10 +681,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
title="Request"
|
||||
tag="PUT"
|
||||
label="/apps/annotations/{annotation_id}"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \
|
||||
curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -699,13 +697,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -763,10 +759,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='action' type='string' key='action'>
|
||||
动作,只能是 'enable' 或 'disable'
|
||||
</Property>
|
||||
<Property name='embedding_model_provider' type='string' key='embedding_model_provider'>
|
||||
<Property name='embedding_provider_name' type='string' key='embedding_provider_name'>
|
||||
指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段
|
||||
</Property>
|
||||
<Property name='embedding_model' type='string' key='embedding_model'>
|
||||
<Property name='embedding_model_name' type='string' key='embedding_model_name'>
|
||||
指定的嵌入模型,对应的是model字段
|
||||
</Property>
|
||||
<Property name='score_threshold' type='number' key='score_threshold'>
|
||||
|
||||
@ -845,6 +845,106 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='Get Conversation Variables'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation.
|
||||
|
||||
### Path Parameters
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
The ID of the conversation to retrieve variables from.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Query Parameters
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
The user identifier, defined by the developer, must ensure uniqueness within the application
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(Optional) The ID of the last record on the current page, default is null.
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
- `limit` (int) Number of items per page
|
||||
- `has_more` (bool) Whether there is a next page
|
||||
- `data` (array[object]) List of variables
|
||||
- `id` (string) Variable ID
|
||||
- `name` (string) Variable name
|
||||
- `value_type` (string) Variable type (string, number, object, etc.)
|
||||
- `value` (string) Variable value
|
||||
- `description` (string) Variable description
|
||||
- `created_at` (int) Creation timestamp
|
||||
- `updated_at` (int) Last update timestamp
|
||||
|
||||
### Errors
|
||||
- 404, `conversation_not_exists`, Conversation not found
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "Customer name extracted from the conversation",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "Order details from the customer",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
@ -1237,13 +1337,11 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1277,10 +1375,10 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
title="Request"
|
||||
tag="PUT"
|
||||
label="/apps/annotations/{annotation_id}"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \
|
||||
curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -1293,13 +1391,11 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1357,10 +1453,10 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<Property name='action' type='string' key='action'>
|
||||
Action, can only be 'enable' or 'disable'
|
||||
</Property>
|
||||
<Property name='embedding_model_provider' type='string' key='embedding_model_provider'>
|
||||
<Property name='embedding_provider_name' type='string' key='embedding_provider_name'>
|
||||
Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional)
|
||||
</Property>
|
||||
<Property name='embedding_model' type='string' key='embedding_model'>
|
||||
<Property name='embedding_model_name' type='string' key='embedding_model_name'>
|
||||
Specified embedding model, corresponding to the model field(Optional)
|
||||
</Property>
|
||||
<Property name='score_threshold' type='number' key='score_threshold'>
|
||||
|
||||
@ -844,6 +844,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='会話変数の取得'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。
|
||||
|
||||
### パスパラメータ
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
変数を取得する会話のID。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### クエリパラメータ
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(Optional)現在のページの最後の記録のID、デフォルトはnullです。
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(Optional)1回のリクエストで返す記録の数、デフォルトは最新の20件です。最大100、最小1。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### レスポンス
|
||||
|
||||
- `limit` (int) ページごとのアイテム数
|
||||
- `has_more` (bool) さらにアイテムがあるかどうか
|
||||
- `data` (array[object]) 変数のリスト
|
||||
- `id` (string) 変数ID
|
||||
- `name` (string) 変数名
|
||||
- `value_type` (string) 変数タイプ(文字列、数値、真偽値など)
|
||||
- `value` (string) 変数値
|
||||
- `description` (string) 変数の説明
|
||||
- `created_at` (int) 作成タイムスタンプ
|
||||
- `updated_at` (int) 最終更新タイムスタンプ
|
||||
|
||||
### エラー
|
||||
- 404, `conversation_not_exists`, 会話が見つかりません
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "会話から抽出された顧客名",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "顧客の注文詳細",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
|
||||
@ -881,6 +881,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='获取对话变量'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。
|
||||
|
||||
### 路径参数
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
要从中检索变量的对话ID。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 查询参数
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
用户标识符,由开发人员定义的规则,在应用程序内必须唯一。
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(选填)当前页最后面一条记录的 ID,默认 null
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 响应
|
||||
|
||||
- `limit` (int) 每页项目数
|
||||
- `has_more` (bool) 是否有更多项目
|
||||
- `data` (array[object]) 变量列表
|
||||
- `id` (string) 变量ID
|
||||
- `name` (string) 变量名称
|
||||
- `value_type` (string) 变量类型(字符串、数字、布尔等)
|
||||
- `value` (string) 变量值
|
||||
- `description` (string) 变量描述
|
||||
- `created_at` (int) 创建时间戳
|
||||
- `updated_at` (int) 最后更新时间戳
|
||||
|
||||
### 错误
|
||||
- 404, `conversation_not_exists`, 对话不存在
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "客户名称(从对话中提取)",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "客户的订单详情",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
@ -1261,13 +1361,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1301,10 +1399,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
title="Request"
|
||||
tag="PUT"
|
||||
label="/apps/annotations/{annotation_id}"
|
||||
targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
targetCode={`curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \
|
||||
curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
@ -1317,13 +1415,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
{
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
"id": "69d48372-ad81-4c75-9c46-2ce197b4d402",
|
||||
"question": "What is your name?",
|
||||
"answer": "I am Dify.",
|
||||
"hit_count": 0,
|
||||
"created_at": 1735625869
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1381,10 +1477,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='action' type='string' key='action'>
|
||||
动作,只能是 'enable' 或 'disable'
|
||||
</Property>
|
||||
<Property name='embedding_model_provider' type='string' key='embedding_model_provider'>
|
||||
<Property name='embedding_provider_name' type='string' key='embedding_provider_name'>
|
||||
指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段
|
||||
</Property>
|
||||
<Property name='embedding_model' type='string' key='embedding_model'>
|
||||
<Property name='embedding_model_name' type='string' key='embedding_model_name'>
|
||||
指定的嵌入模型,对应的是model字段
|
||||
</Property>
|
||||
<Property name='score_threshold' type='number' key='score_threshold'>
|
||||
|
||||
@ -878,6 +878,106 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='Get Conversation Variables'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation.
|
||||
|
||||
### Path Parameters
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
The ID of the conversation to retrieve variables from.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Query Parameters
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
The user identifier, defined by the developer, must ensure uniqueness within the application
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(Optional) The ID of the last record on the current page, default is null.
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
|
||||
- `limit` (int) Number of items per page
|
||||
- `has_more` (bool) Whether there is a next page
|
||||
- `data` (array[object]) List of variables
|
||||
- `id` (string) Variable ID
|
||||
- `name` (string) Variable name
|
||||
- `value_type` (string) Variable type (string, number, object, etc.)
|
||||
- `value` (string) Variable value
|
||||
- `description` (string) Variable description
|
||||
- `created_at` (int) Creation timestamp
|
||||
- `updated_at` (int) Last update timestamp
|
||||
|
||||
### Errors
|
||||
- 404, `conversation_not_exists`, Conversation not found
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "Customer name extracted from the conversation",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "Order details from the customer",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
|
||||
@ -876,6 +876,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='会話変数の取得'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。
|
||||
|
||||
### パスパラメータ
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
変数を取得する会話のID。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### クエリパラメータ
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(Optional)現在のページの最後のレコードのID、デフォルトはnullです。
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(Optional)1回のリクエストで返すレコードの数、デフォルトは最新の20件です。最大100、最小1。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### レスポンス
|
||||
|
||||
- `limit` (int) ページごとのアイテム数
|
||||
- `has_more` (bool) さらにアイテムがあるかどうか
|
||||
- `data` (array[object]) 変数のリスト
|
||||
- `id` (string) 変数ID
|
||||
- `name` (string) 変数名
|
||||
- `value_type` (string) 変数タイプ(文字列、数値、真偽値など)
|
||||
- `value` (string) 変数値
|
||||
- `description` (string) 変数の説明
|
||||
- `created_at` (int) 作成タイムスタンプ
|
||||
- `updated_at` (int) 最終更新タイムスタンプ
|
||||
|
||||
### エラー
|
||||
- 404, `conversation_not_exists`, 会話が見つかりません
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "会話から抽出された顧客名",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "顧客の注文詳細",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
|
||||
@ -893,6 +893,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/conversations/:conversation_id/variables'
|
||||
method='GET'
|
||||
title='获取对话变量'
|
||||
name='#conversation-variables'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。
|
||||
|
||||
### 路径参数
|
||||
|
||||
<Properties>
|
||||
<Property name='conversation_id' type='string' key='conversation_id'>
|
||||
要从中检索变量的对话ID。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 查询参数
|
||||
|
||||
<Properties>
|
||||
<Property name='user' type='string' key='user'>
|
||||
用户标识符,由开发人员定义的规则,在应用程序内必须唯一。
|
||||
</Property>
|
||||
<Property name='last_id' type='string' key='last_id'>
|
||||
(选填)当前页最后面一条记录的 ID,默认 null
|
||||
</Property>
|
||||
<Property name='limit' type='int' key='limit'>
|
||||
(选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 响应
|
||||
|
||||
- `limit` (int) 每页项目数
|
||||
- `has_more` (bool) 是否有更多项目
|
||||
- `data` (array[object]) 变量列表
|
||||
- `id` (string) 变量ID
|
||||
- `name` (string) 变量名称
|
||||
- `value_type` (string) 变量类型(字符串、数字、布尔等)
|
||||
- `value` (string) 变量值
|
||||
- `description` (string) 变量描述
|
||||
- `created_at` (int) 创建时间戳
|
||||
- `updated_at` (int) 最后更新时间戳
|
||||
|
||||
### 错误
|
||||
- 404, `conversation_not_exists`, 对话不存在
|
||||
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Request with variable name filter">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"limit": 100,
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "variable-uuid-1",
|
||||
"name": "customer_name",
|
||||
"value_type": "string",
|
||||
"value": "John Doe",
|
||||
"description": "客户名称(从对话中提取)",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
},
|
||||
{
|
||||
"id": "variable-uuid-2",
|
||||
"name": "order_details",
|
||||
"value_type": "json",
|
||||
"value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}",
|
||||
"description": "客户的订单详情",
|
||||
"created_at": 1650000000000,
|
||||
"updated_at": 1650000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/audio-to-text'
|
||||
method='POST'
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import useSWR from 'swr'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import Toast from '../../base/toast'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import ExploreContext from '@/context/explore-context'
|
||||
@ -14,17 +12,16 @@ import type { App } from '@/models/explore'
|
||||
import Category from '@/app/components/explore/category'
|
||||
import AppCard from '@/app/components/explore/app-card'
|
||||
import { fetchAppDetail, fetchAppList } from '@/service/explore'
|
||||
import { importDSL } from '@/service/apps'
|
||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { DSLImportMode } from '@/models/app'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import {
|
||||
DSLImportMode,
|
||||
} from '@/models/app'
|
||||
import { useImportDSL } from '@/hooks/use-import-dsl'
|
||||
import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal'
|
||||
|
||||
type AppsProps = {
|
||||
onSuccess?: () => void
|
||||
@ -39,8 +36,6 @@ const Apps = ({
|
||||
onSuccess,
|
||||
}: AppsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const { push } = useRouter()
|
||||
const { hasEditPermission } = useContext(ExploreContext)
|
||||
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
|
||||
|
||||
@ -115,7 +110,14 @@ const Apps = ({
|
||||
|
||||
const [currApp, setCurrApp] = React.useState<App | null>(null)
|
||||
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
|
||||
const { handleCheckPluginDependencies } = usePluginDependencies()
|
||||
|
||||
const {
|
||||
handleImportDSL,
|
||||
handleImportDSLConfirm,
|
||||
versions,
|
||||
isFetching,
|
||||
} = useImportDSL()
|
||||
const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false)
|
||||
const onCreate: CreateAppModalProps['onConfirm'] = async ({
|
||||
name,
|
||||
icon_type,
|
||||
@ -123,36 +125,34 @@ const Apps = ({
|
||||
icon_background,
|
||||
description,
|
||||
}) => {
|
||||
const { export_data, mode } = await fetchAppDetail(
|
||||
const { export_data } = await fetchAppDetail(
|
||||
currApp?.app.id as string,
|
||||
)
|
||||
try {
|
||||
const app = await importDSL({
|
||||
mode: DSLImportMode.YAML_CONTENT,
|
||||
yaml_content: export_data,
|
||||
name,
|
||||
icon_type,
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
})
|
||||
setIsShowCreateModal(false)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('app.newApp.appCreated'),
|
||||
})
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
if (app.app_id)
|
||||
await handleCheckPluginDependencies(app.app_id)
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
getRedirection(isCurrentWorkspaceEditor, { id: app.app_id!, mode }, push)
|
||||
}
|
||||
catch {
|
||||
Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
const payload = {
|
||||
mode: DSLImportMode.YAML_CONTENT,
|
||||
yaml_content: export_data,
|
||||
name,
|
||||
icon_type,
|
||||
icon,
|
||||
icon_background,
|
||||
description,
|
||||
}
|
||||
await handleImportDSL(payload, {
|
||||
onSuccess: () => {
|
||||
setIsShowCreateModal(false)
|
||||
},
|
||||
onPending: () => {
|
||||
setShowDSLConfirmModal(true)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const onConfirmDSL = useCallback(async () => {
|
||||
await handleImportDSLConfirm({
|
||||
onSuccess,
|
||||
})
|
||||
}, [handleImportDSLConfirm, onSuccess])
|
||||
|
||||
if (!categories || categories.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center">
|
||||
@ -225,9 +225,20 @@ const Apps = ({
|
||||
appDescription={currApp?.app.description || ''}
|
||||
show={isShowCreateModal}
|
||||
onConfirm={onCreate}
|
||||
confirmDisabled={isFetching}
|
||||
onHide={() => setIsShowCreateModal(false)}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
showDSLConfirmModal && (
|
||||
<DSLConfirmModal
|
||||
versions={versions}
|
||||
onCancel={() => setShowDSLConfirmModal(false)}
|
||||
onConfirm={onConfirmDSL}
|
||||
confirmDisabled={isFetching}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ export type CreateAppModalProps = {
|
||||
description: string
|
||||
use_icon_as_answer_icon?: boolean
|
||||
}) => Promise<void>
|
||||
confirmDisabled?: boolean
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
@ -50,6 +51,7 @@ const CreateAppModal = ({
|
||||
appMode,
|
||||
appUseIconAsAnswerIcon,
|
||||
onConfirm,
|
||||
confirmDisabled,
|
||||
onHide,
|
||||
}: CreateAppModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -160,7 +162,7 @@ const CreateAppModal = ({
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button
|
||||
disabled={(!isEditModal && isAppsFull) || !name.trim()}
|
||||
disabled={(!isEditModal && isAppsFull) || !name.trim() || confirmDisabled}
|
||||
className='ml-2 w-24 gap-1'
|
||||
variant='primary'
|
||||
onClick={handleSubmit}
|
||||
|
||||
@ -3,7 +3,6 @@ import type {
|
||||
Model,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { useLanguage } from '../hooks'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
|
||||
@ -31,7 +30,7 @@ const ModelIcon: FC<ModelIconProps> = ({
|
||||
if (provider?.icon_small) {
|
||||
return (
|
||||
<div className={cn('flex h-5 w-5 items-center justify-center', isDeprecated && 'opacity-50', className)}>
|
||||
<img alt='model-icon' src={basePath + renderI18nObject(provider.icon_small, language)}/>
|
||||
<img alt='model-icon' src={renderI18nObject(provider.icon_small, language)}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ const Input: FC<InputProps> = ({
|
||||
return (
|
||||
<div className='relative'>
|
||||
<input
|
||||
autoComplete="new-password"
|
||||
tabIndex={0}
|
||||
className={`
|
||||
block h-8 w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 text-sm
|
||||
|
||||
@ -14,6 +14,7 @@ import Nav from '../nav'
|
||||
import type { NavItem } from '../nav/nav-selector'
|
||||
import { fetchDatasetDetail, fetchDatasets } from '@/service/datasets'
|
||||
import type { DataSetListResponse } from '@/models/datasets'
|
||||
import { basePath } from '@/utils/var'
|
||||
|
||||
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
|
||||
if (!pageIndex || previousPageData.has_more)
|
||||
@ -56,7 +57,7 @@ const DatasetNav = () => {
|
||||
icon_background: dataset.icon_background,
|
||||
})) as NavItem[]}
|
||||
createText={t('common.menus.newDataset')}
|
||||
onCreate={() => router.push('/datasets/create')}
|
||||
onCreate={() => router.push(`${basePath}/datasets/create`)}
|
||||
onLoadmore={handleLoadmore}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -49,7 +49,7 @@ const Header = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedSegment])
|
||||
return (
|
||||
<div className='flex flex-1 items-center justify-between bg-background-body px-4'>
|
||||
<div className='relative flex flex-1 items-center justify-between bg-background-body'>
|
||||
<div className='flex items-center'>
|
||||
{isMobile && <div
|
||||
className='flex h-8 w-8 cursor-pointer items-center justify-center'
|
||||
@ -59,7 +59,7 @@ const Header = () => {
|
||||
</div>}
|
||||
{
|
||||
!isMobile
|
||||
&& <div className='flex w-64 shrink-0 items-center gap-1.5 self-stretch p-2 pl-3'>
|
||||
&& <div className='flex shrink-0 items-center gap-1.5 self-stretch pl-3'>
|
||||
<Link href="/apps" className='flex h-8 w-8 shrink-0 items-center justify-center gap-2'>
|
||||
<LogoSite className='object-contain' />
|
||||
</Link>
|
||||
@ -84,7 +84,7 @@ const Header = () => {
|
||||
)}
|
||||
{
|
||||
!isMobile && (
|
||||
<div className='flex items-center'>
|
||||
<div className='absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center'>
|
||||
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
|
||||
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
|
||||
@ -92,7 +92,7 @@ const Header = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='flex shrink-0 items-center'>
|
||||
<div className='flex shrink-0 items-center pr-3'>
|
||||
<EnvNav />
|
||||
<div className='mr-2'>
|
||||
<PluginsNav />
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
} from '@remixicon/react'
|
||||
import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react'
|
||||
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { debounce } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
@ -77,7 +77,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
|
||||
<div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
|
||||
{
|
||||
navs.map(nav => (
|
||||
<MenuItems key={nav.id}>
|
||||
<MenuItem key={nav.id}>
|
||||
<div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-gray-700 hover:bg-gray-100' onClick={() => {
|
||||
if (curNav?.id === nav.id)
|
||||
return
|
||||
@ -112,12 +112,12 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
|
||||
{nav.name}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{!isApp && isCurrentWorkspaceEditor && (
|
||||
<MenuButton className='w-full p-1'>
|
||||
<MenuItem as="div" className='w-full p-1'>
|
||||
<div onClick={() => onCreate('')} className={cn(
|
||||
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-gray-100',
|
||||
)}>
|
||||
@ -126,7 +126,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
|
||||
</div>
|
||||
<div className='grow text-left text-[14px] font-normal text-gray-700'>{createText}</div>
|
||||
</div>
|
||||
</MenuButton>
|
||||
</MenuItem>
|
||||
)}
|
||||
{isApp && isCurrentWorkspaceEditor && (
|
||||
<Menu as="div" className="relative h-full w-full">
|
||||
|
||||
@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import InstallMulti from './install-multi'
|
||||
import { useInstallOrUpdate } from '@/service/use-plugins'
|
||||
import useRefreshPluginList from '../../hooks/use-refresh-plugin-list'
|
||||
import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission'
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
type Props = {
|
||||
@ -74,6 +75,7 @@ const Install: FC<Props> = ({
|
||||
installedInfo: installedInfo!,
|
||||
})
|
||||
}
|
||||
const { canInstallPluginFromMarketplace } = useCanInstallPluginFromMarketplace()
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
||||
@ -101,7 +103,7 @@ const Install: FC<Props> = ({
|
||||
<Button
|
||||
variant='primary'
|
||||
className='flex min-w-[72px] space-x-0.5'
|
||||
disabled={!canInstall || isInstalling || selectedPlugins.length === 0}
|
||||
disabled={!canInstall || isInstalling || selectedPlugins.length === 0 || !canInstallPluginFromMarketplace}
|
||||
onClick={handleInstall}
|
||||
>
|
||||
{isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />}
|
||||
|
||||
@ -144,10 +144,18 @@ const PluginPage = ({
|
||||
return activeTab === PLUGIN_PAGE_TABS_MAP.marketplace || values.includes(activeTab)
|
||||
}, [activeTab])
|
||||
|
||||
const handleFileChange = (file: File | null) => {
|
||||
if (!file || !file.name.endsWith('.difypkg')) {
|
||||
setCurrentFile(null)
|
||||
return
|
||||
}
|
||||
|
||||
setCurrentFile(file)
|
||||
}
|
||||
const uploaderProps = useUploader({
|
||||
onFileChange: setCurrentFile,
|
||||
onFileChange: handleFileChange,
|
||||
containerRef,
|
||||
enabled: isPluginsTab,
|
||||
enabled: isPluginsTab && canManagement,
|
||||
})
|
||||
|
||||
const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps
|
||||
|
||||
@ -3,6 +3,8 @@ import { useAppContext } from '@/context/app-context'
|
||||
import Toast from '../../base/toast'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useInvalidatePermissions, useMutationPermissions, usePermissions } from '@/service/use-plugins'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
const hasPermission = (permission: PermissionType | undefined, isAdmin: boolean) => {
|
||||
if (!permission)
|
||||
@ -43,4 +45,17 @@ const usePermission = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useCanInstallPluginFromMarketplace = () => {
|
||||
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||
const { canManagement } = usePermission()
|
||||
|
||||
const canInstallPluginFromMarketplace = useMemo(() => {
|
||||
return enable_marketplace && canManagement
|
||||
}, [enable_marketplace, canManagement])
|
||||
|
||||
return {
|
||||
canInstallPluginFromMarketplace,
|
||||
}
|
||||
}
|
||||
|
||||
export default usePermission
|
||||
|
||||
@ -3,9 +3,15 @@ import ChatVariableButton from '@/app/components/workflow/header/chat-variable-b
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { useIsChatMode } from '../../hooks'
|
||||
|
||||
const ChatVariableTrigger = () => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
if (!isChatMode)
|
||||
return null
|
||||
|
||||
return <ChatVariableButton disabled={nodesReadOnly} />
|
||||
}
|
||||
export default memo(ChatVariableTrigger)
|
||||
|
||||
@ -74,7 +74,7 @@ const WorkflowPanelOnRight = () => {
|
||||
)
|
||||
}
|
||||
{
|
||||
showChatVariablePanel && (
|
||||
showChatVariablePanel && isChatMode && (
|
||||
<ChatVariablePanel />
|
||||
)
|
||||
}
|
||||
|
||||
@ -316,6 +316,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
nodesConnectable={!nodesReadOnly}
|
||||
nodesFocusable={!nodesReadOnly}
|
||||
edgesFocusable={!nodesReadOnly}
|
||||
panOnScroll
|
||||
panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
|
||||
zoomOnPinch={!workflowReadOnly}
|
||||
zoomOnScroll={!workflowReadOnly}
|
||||
|
||||
@ -17,6 +17,7 @@ type Props = {
|
||||
children?: React.JSX.Element | string | null
|
||||
operations?: React.JSX.Element
|
||||
inline?: boolean
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const Field: FC<Props> = ({
|
||||
@ -28,6 +29,7 @@ const Field: FC<Props> = ({
|
||||
operations,
|
||||
inline,
|
||||
supportFold,
|
||||
required,
|
||||
}) => {
|
||||
const [fold, {
|
||||
toggle: toggleFold,
|
||||
@ -38,7 +40,9 @@ const Field: FC<Props> = ({
|
||||
onClick={() => supportFold && toggleFold()}
|
||||
className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}>
|
||||
<div className='flex h-6 items-center'>
|
||||
<div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}>{title}</div>
|
||||
<div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}>
|
||||
{title} {required && <span className='text-text-destructive'>*</span>}
|
||||
</div>
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={tooltip}
|
||||
|
||||
@ -596,17 +596,16 @@ const getIterationItemType = ({
|
||||
arrayType = curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
(valueSelector).slice(1).forEach((key, i) => {
|
||||
for (let i = 1; i < valueSelector.length - 1; i++) {
|
||||
const key = valueSelector[i]
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
arrayType = curr?.type
|
||||
}
|
||||
else {
|
||||
if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : []
|
||||
|
||||
if (isLast)
|
||||
arrayType = curr?.type
|
||||
else if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children || []
|
||||
}
|
||||
}
|
||||
|
||||
switch (arrayType as VarType) {
|
||||
@ -631,7 +630,7 @@ const getLoopItemType = ({
|
||||
}: {
|
||||
valueSelector: ValueSelector
|
||||
beforeNodesOutputVars: NodeOutPutVar[]
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
|
||||
}): VarType => {
|
||||
const outputVarNodeId = valueSelector[0]
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
|
||||
@ -81,7 +81,11 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||
const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey)
|
||||
|
||||
return <div className='my-2'>
|
||||
<Field title={t('workflow.nodes.agent.strategy.label')} className='px-4 py-2' tooltip={t('workflow.nodes.agent.strategy.tooltip')} >
|
||||
<Field
|
||||
required
|
||||
title={t('workflow.nodes.agent.strategy.label')}
|
||||
className='px-4 py-2'
|
||||
tooltip={t('workflow.nodes.agent.strategy.tooltip')} >
|
||||
<AgentStrategy
|
||||
strategy={inputs.agent_strategy_name ? {
|
||||
agent_strategy_provider_name: inputs.agent_strategy_provider_name!,
|
||||
|
||||
@ -117,8 +117,8 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
operations={
|
||||
<AddButton onClick={handleAddOutputVariable} />
|
||||
}
|
||||
required
|
||||
>
|
||||
|
||||
<OutputVarList
|
||||
readonly={readOnly}
|
||||
outputs={inputs.outputs}
|
||||
|
||||
@ -64,6 +64,7 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({
|
||||
<div className='space-y-4 px-4 pb-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVar`)}
|
||||
required
|
||||
>
|
||||
<>
|
||||
<VarReferencePicker
|
||||
|
||||
@ -69,6 +69,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
<div className='space-y-4 px-4 pb-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.api`)}
|
||||
required
|
||||
operations={
|
||||
<div className='flex'>
|
||||
<div
|
||||
@ -126,6 +127,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.body`)}
|
||||
required
|
||||
>
|
||||
<EditBody
|
||||
nodeId={id}
|
||||
|
||||
@ -73,6 +73,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
<div className='space-y-4 px-4 pb-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.input`)}
|
||||
required
|
||||
operations={(
|
||||
<div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div>
|
||||
)}
|
||||
@ -91,6 +92,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
<div className='mt-2 space-y-4 px-4 pb-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.output`)}
|
||||
required
|
||||
operations={(
|
||||
<div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div>
|
||||
)}
|
||||
|
||||
@ -81,6 +81,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||
{/* {JSON.stringify(inputs, null, 2)} */}
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.queryVariable`)}
|
||||
required
|
||||
>
|
||||
<VarReferencePicker
|
||||
nodeId={id}
|
||||
@ -94,6 +95,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.knowledge`)}
|
||||
required
|
||||
operations={
|
||||
<div className='flex items-center space-x-1'>
|
||||
<RetrievalConfig
|
||||
|
||||
@ -46,6 +46,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
||||
<div className='space-y-4 px-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVar`)}
|
||||
required
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
|
||||
@ -147,6 +147,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
<div className='space-y-4 px-4 pb-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.model`)}
|
||||
required
|
||||
>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
@ -295,7 +296,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
onCollapse={setStructuredOutputCollapsed}
|
||||
operations={
|
||||
<div className='mr-4 flex shrink-0 items-center'>
|
||||
{!isModelSupportStructuredOutput && (
|
||||
{(!isModelSupportStructuredOutput && !!inputs.structured_output_enabled) && (
|
||||
<Tooltip noDecoration popupContent={
|
||||
<div className='w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]'>
|
||||
<div className='title-xs-semi-bold text-text-primary'>{t('app.structOutput.modelNotSupported')}</div>
|
||||
|
||||
@ -115,6 +115,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||
<div className='space-y-4 px-4'>
|
||||
<Field
|
||||
title={t(`${i18nCommonPrefix}.model`)}
|
||||
required
|
||||
>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
@ -133,6 +134,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVar`)}
|
||||
required
|
||||
>
|
||||
<>
|
||||
<VarReferencePicker
|
||||
@ -157,6 +159,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||
/>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.extractParameters`)}
|
||||
required
|
||||
operations={
|
||||
!readOnly
|
||||
? (
|
||||
|
||||
@ -63,7 +63,7 @@ const ClassList: FC<Props> = ({
|
||||
return (
|
||||
<Item
|
||||
nodeId={nodeId}
|
||||
key={index}
|
||||
key={list[index].id}
|
||||
payload={item}
|
||||
onChange={handleClassChange(index)}
|
||||
onRemove={handleRemoveClass(index)}
|
||||
|
||||
@ -103,6 +103,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||
<div className='space-y-4 px-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.model`)}
|
||||
required
|
||||
>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
@ -121,6 +122,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVars`)}
|
||||
required
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
@ -143,6 +145,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||
/>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.class`)}
|
||||
required
|
||||
>
|
||||
<ClassList
|
||||
nodeId={id}
|
||||
|
||||
@ -47,8 +47,14 @@ const VariableModal = ({
|
||||
return
|
||||
if (!value)
|
||||
return notify({ type: 'error', message: 'value can not be empty' })
|
||||
if (!env && envList.some(env => env.name === name))
|
||||
|
||||
// Add check for duplicate name when editing
|
||||
if (env && env.name !== name && envList.some(e => e.name === name))
|
||||
return notify({ type: 'error', message: 'name is existed' })
|
||||
// Original check for create new variable
|
||||
if (!env && envList.some(e => e.name === name))
|
||||
return notify({ type: 'error', message: 'name is existed' })
|
||||
|
||||
onSave({
|
||||
id: env ? env.id : uuid4(),
|
||||
value_type: type,
|
||||
|
||||
@ -16,6 +16,7 @@ import Button from '@/app/components/base/button'
|
||||
|
||||
import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
|
||||
import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
|
||||
import { basePath } from '@/utils/var'
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
|
||||
@ -80,12 +81,12 @@ const InstallForm = () => {
|
||||
fetchSetupStatus().then((res: SetupStatusResponse) => {
|
||||
if (res.step === 'finished') {
|
||||
localStorage.setItem('setup_status', 'finished')
|
||||
router.push('/signin')
|
||||
router.push(`${basePath}/signin`)
|
||||
}
|
||||
else {
|
||||
fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
|
||||
if (res.status === 'not_started')
|
||||
router.push('/init')
|
||||
router.push(`${basePath}/init`)
|
||||
})
|
||||
}
|
||||
setLoading(false)
|
||||
|
||||
@ -54,6 +54,9 @@ const LocaleLayout = async ({
|
||||
data-public-indexing-max-segmentation-tokens-length={process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
|
||||
data-public-loop-node-max-count={process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT}
|
||||
data-public-max-iterations-num={process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM}
|
||||
data-public-enable-website-jinareader={process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER}
|
||||
data-public-enable-website-firecrawl={process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL}
|
||||
data-public-enable-website-watercrawl={process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL}
|
||||
>
|
||||
<BrowserInitor>
|
||||
<SentryInitor>
|
||||
|
||||
@ -306,12 +306,12 @@ export const MAX_ITERATIONS_NUM = maxIterationsNum
|
||||
|
||||
export const ENABLE_WEBSITE_JINAREADER = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER !== undefined
|
||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER === 'true'
|
||||
: true
|
||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-jinareader') === 'true' || true
|
||||
|
||||
export const ENABLE_WEBSITE_FIRECRAWL = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL !== undefined
|
||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL === 'true'
|
||||
: true
|
||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-firecrawl') === 'true' || true
|
||||
|
||||
export const ENABLE_WEBSITE_WATERCRAWL = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL !== undefined
|
||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL === 'true'
|
||||
: true
|
||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-watercrawl') === 'true' || true
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||
import type { BasicPlan } from '@/app/components/billing/type'
|
||||
import { Plan, type UsagePlanInfo } from '@/app/components/billing/type'
|
||||
import { fetchCurrentPlanInfo } from '@/service/billing'
|
||||
import { parseCurrentPlan } from '@/app/components/billing/utils'
|
||||
@ -34,7 +35,7 @@ type ProviderContextState = {
|
||||
supportRetrievalMethods: RETRIEVE_METHOD[]
|
||||
isAPIKeySet: boolean
|
||||
plan: {
|
||||
type: Plan
|
||||
type: BasicPlan
|
||||
usage: UsagePlanInfo
|
||||
total: UsagePlanInfo
|
||||
}
|
||||
|
||||
@ -32,4 +32,7 @@ export NEXT_PUBLIC_MAX_TOOLS_NUM=${MAX_TOOLS_NUM}
|
||||
export NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=${ENABLE_WEBSITE_JINAREADER:-true}
|
||||
export NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=${ENABLE_WEBSITE_FIRECRAWL:-true}
|
||||
export NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL=${ENABLE_WEBSITE_WATERCRAWL:-true}
|
||||
export NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=${LOOP_NODE_MAX_COUNT}
|
||||
export NEXT_PUBLIC_MAX_PARALLEL_LIMIT=${MAX_PARALLEL_LIMIT}
|
||||
export NEXT_PUBLIC_MAX_ITERATIONS_NUM=${MAX_ITERATIONS_NUM}
|
||||
pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon
|
||||
|
||||
163
web/hooks/use-import-dsl.ts
Normal file
163
web/hooks/use-import-dsl.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import type {
|
||||
DSLImportMode,
|
||||
DSLImportResponse,
|
||||
} from '@/models/app'
|
||||
import { DSLImportStatus } from '@/models/app'
|
||||
import {
|
||||
importDSL,
|
||||
importDSLConfirm,
|
||||
} from '@/service/apps'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
|
||||
type DSLPayload = {
|
||||
mode: DSLImportMode
|
||||
yaml_content?: string
|
||||
yaml_url?: string
|
||||
name?: string
|
||||
icon_type?: AppIconType
|
||||
icon?: string
|
||||
icon_background?: string
|
||||
description?: string
|
||||
}
|
||||
type ResponseCallback = {
|
||||
onSuccess?: () => void
|
||||
onPending?: (payload: DSLImportResponse) => void
|
||||
onFailed?: () => void
|
||||
}
|
||||
export const useImportDSL = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const [isFetching, setIsFetching] = useState(false)
|
||||
const { handleCheckPluginDependencies } = usePluginDependencies()
|
||||
const isCurrentWorkspaceEditor = useSelector(s => s.isCurrentWorkspaceEditor)
|
||||
const { push } = useRouter()
|
||||
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
|
||||
const importIdRef = useRef<string>('')
|
||||
|
||||
const handleImportDSL = useCallback(async (
|
||||
payload: DSLPayload,
|
||||
{
|
||||
onSuccess,
|
||||
onPending,
|
||||
onFailed,
|
||||
}: ResponseCallback,
|
||||
) => {
|
||||
if (isFetching)
|
||||
return
|
||||
setIsFetching(true)
|
||||
|
||||
try {
|
||||
const response = await importDSL(payload)
|
||||
|
||||
if (!response)
|
||||
return
|
||||
|
||||
const {
|
||||
id,
|
||||
status,
|
||||
app_id,
|
||||
app_mode,
|
||||
imported_dsl_version,
|
||||
current_dsl_version,
|
||||
} = response
|
||||
|
||||
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
|
||||
if (!app_id)
|
||||
return
|
||||
|
||||
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'),
|
||||
})
|
||||
onSuccess?.()
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
await handleCheckPluginDependencies(app_id)
|
||||
getRedirection(isCurrentWorkspaceEditor, { id: app_id, mode: app_mode }, push)
|
||||
}
|
||||
else if (status === DSLImportStatus.PENDING) {
|
||||
setVersions({
|
||||
importedVersion: imported_dsl_version ?? '',
|
||||
systemVersion: current_dsl_version ?? '',
|
||||
})
|
||||
importIdRef.current = id
|
||||
onPending?.(response)
|
||||
}
|
||||
else {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
onFailed?.()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
onFailed?.()
|
||||
}
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [t, notify, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching])
|
||||
|
||||
const handleImportDSLConfirm = useCallback(async (
|
||||
{
|
||||
onSuccess,
|
||||
onFailed,
|
||||
}: Pick<ResponseCallback, 'onSuccess' | 'onFailed'>,
|
||||
) => {
|
||||
if (isFetching)
|
||||
return
|
||||
setIsFetching(true)
|
||||
if (!importIdRef.current)
|
||||
return
|
||||
|
||||
try {
|
||||
const response = await importDSLConfirm({
|
||||
import_id: importIdRef.current,
|
||||
})
|
||||
|
||||
const { status, app_id, app_mode } = response
|
||||
if (!app_id)
|
||||
return
|
||||
|
||||
if (status === DSLImportStatus.COMPLETED) {
|
||||
onSuccess?.()
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('app.newApp.appCreated'),
|
||||
})
|
||||
await handleCheckPluginDependencies(app_id)
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push)
|
||||
}
|
||||
else if (status === DSLImportStatus.FAILED) {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
onFailed?.()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
onFailed?.()
|
||||
}
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [t, notify, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching])
|
||||
|
||||
return {
|
||||
handleImportDSL,
|
||||
handleImportDSLConfirm,
|
||||
versions,
|
||||
isFetching,
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Textdatei hochladen',
|
||||
button: 'Datei hierher ziehen oder',
|
||||
button: 'Dateien und Ordner hierher ziehen oder klicken',
|
||||
browse: 'Durchsuchen',
|
||||
tip: 'Unterstützt {{supportTypes}}. Maximal {{size}}MB pro Datei.',
|
||||
validation: {
|
||||
|
||||
@ -30,6 +30,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Versuchen zu lösen',
|
||||
temporarySystemIssue: 'Entschuldigung, vorübergehendes Systemproblem.',
|
||||
expand: 'Erweitern',
|
||||
collapse: 'Reduzieren',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -161,6 +161,10 @@ const translation = {
|
||||
title: 'Opik',
|
||||
description: 'Opik is an open-source platform for evaluating, testing, and monitoring LLM applications.',
|
||||
},
|
||||
weave: {
|
||||
title: 'Weave',
|
||||
description: 'Weave is an open-source platform for evaluating, testing, and monitoring LLM applications.',
|
||||
},
|
||||
inUse: 'In use',
|
||||
configProvider: {
|
||||
title: 'Config ',
|
||||
|
||||
@ -55,6 +55,10 @@ const translation = {
|
||||
vectorSpaceTooltip: 'Documents with the High Quality indexing mode will consume Knowledge Data Storage resources. When Knowledge Data Storage reaches the limit, new documents will not be uploaded.',
|
||||
documentsRequestQuota: '{{count,number}}/min Knowledge Request Rate Limit',
|
||||
documentsRequestQuotaTooltip: 'Specifies the total number of actions a workspace can perform per minute within the knowledge base, including dataset creation, deletion, updates, document uploads, modifications, archiving, and knowledge base queries. This metric is used to evaluate the performance of knowledge base requests. For example, if a Sandbox user performs 10 consecutive hit tests within one minute, their workspace will be temporarily restricted from performing the following actions for the next minute: dataset creation, deletion, updates, and document uploads or modifications. ',
|
||||
apiRateLimit: 'API Rate Limit',
|
||||
apiRateLimitUnit: '{{count,number}}/day',
|
||||
unlimitedApiRate: 'No API Rate Limit',
|
||||
apiRateLimitTooltip: 'API Rate Limit applies to all requests made through the Dify API, including text generation, chat conversations, workflow executions, and document processing.',
|
||||
documentProcessingPriority: ' Document Processing',
|
||||
documentProcessingPriorityUpgrade: 'Process more data with higher accuracy at faster speeds.',
|
||||
priority: {
|
||||
|
||||
@ -35,7 +35,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Upload file',
|
||||
button: 'Drag and drop file, or',
|
||||
button: 'Drag and drop file or folder, or',
|
||||
browse: 'Browse',
|
||||
tip: 'Supports {{supportTypes}}. Max {{size}}MB each.',
|
||||
validation: {
|
||||
|
||||
@ -34,6 +34,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Try to solve',
|
||||
temporarySystemIssue: 'Sorry, temporary system issue.',
|
||||
expand: 'Expand',
|
||||
collapse: 'Collapse',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -27,7 +27,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Cargar archivo',
|
||||
button: 'Arrastra y suelta el archivo, o',
|
||||
button: 'Arrastre y suelte archivos o carpetas, o',
|
||||
browse: 'Buscar',
|
||||
tip: 'Soporta {{supportTypes}}. Máximo {{size}}MB cada uno.',
|
||||
validation: {
|
||||
|
||||
@ -30,6 +30,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Intentar resolver',
|
||||
temporarySystemIssue: 'Lo sentimos, hay un problema temporal del sistema.',
|
||||
expand: 'Ampliar',
|
||||
collapse: 'Contraer',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -27,7 +27,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'بارگذاری فایل',
|
||||
button: 'کشیدن و رها کردن فایل، یا',
|
||||
button: 'فایل ها یا پوشه ها را بکشید و رها کنید یا',
|
||||
browse: 'مرور',
|
||||
tip: 'پشتیبانی از {{supportTypes}}. حداکثر {{size}}MB هر کدام.',
|
||||
validation: {
|
||||
|
||||
@ -26,6 +26,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'سعی کنید حل کنید',
|
||||
temporarySystemIssue: 'ببخشید، مشکل موقت سیستمی.',
|
||||
expand: 'باز کردن',
|
||||
collapse: 'بستن',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -22,7 +22,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Télécharger le fichier texte',
|
||||
button: 'Glisser et déposer le fichier, ou',
|
||||
button: 'Faites glisser et déposez des fichiers ou des dossiers, ou',
|
||||
browse: 'Parcourir',
|
||||
tip: 'Prend en charge {{supportTypes}}. Max {{size}}MB chacun.',
|
||||
validation: {
|
||||
|
||||
@ -30,6 +30,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Essayez de résoudre',
|
||||
temporarySystemIssue: 'Désolé, problème temporaire du système.',
|
||||
expand: 'Développer',
|
||||
collapse: 'Réduire',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -27,7 +27,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'फ़ाइल अपलोड करें',
|
||||
button: 'फ़ाइल खींचें और छोड़ें, या',
|
||||
button: 'फ़ाइलों या फ़ोल्डरों को खींचें और छोड़ें, या',
|
||||
browse: 'ब्राउज़ करें',
|
||||
tip: 'समर्थित {{supportTypes}}। प्रत्येक अधिकतम {{size}}MB।',
|
||||
validation: {
|
||||
|
||||
@ -30,6 +30,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'समाधान करने का प्रयास करें',
|
||||
temporarySystemIssue: 'अभी सिस्टम में समस्या है, कृपया पुनः प्रयास करें।',
|
||||
expand: 'विस्तार करें',
|
||||
collapse: 'संकुचित करें',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -27,7 +27,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Carica file',
|
||||
button: 'Trascina e rilascia il file, o',
|
||||
button: 'Trascina e rilascia file o cartelle, oppure',
|
||||
browse: 'Sfoglia',
|
||||
tip: 'Supporta {{supportTypes}}. Max {{size}}MB ciascuno.',
|
||||
validation: {
|
||||
|
||||
@ -28,6 +28,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Prova a risolvere',
|
||||
temporarySystemIssue: 'Spiacente, problema temporaneo del sistema.',
|
||||
expand: 'Espandi',
|
||||
collapse: 'Riduci',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -194,6 +194,17 @@ const translation = {
|
||||
noParams: 'パラメータは必要ありません',
|
||||
placeholder: 'アプリを選択...',
|
||||
},
|
||||
structOutput: {
|
||||
moreFillTip: '最大10レベルのネストを表示します',
|
||||
required: '必須',
|
||||
LLMResponse: 'LLMのレスポンス',
|
||||
configure: '設定',
|
||||
notConfiguredTip: '構造化出力が未設定です',
|
||||
structured: '構造化出力',
|
||||
structuredTip: '構造化出力は、モデルが常に指定されたJSONスキーマに準拠した応答を生成することを保証する機能です。',
|
||||
modelNotSupported: 'モデルが対応していません',
|
||||
modelNotSupportedTip: '現在のモデルはこの機能に対応しておらず、自動的にプロンプトインジェクションに切り替わります。',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
||||
@ -54,6 +54,10 @@ const translation = {
|
||||
vectorSpaceTooltip: '高品質インデックスモードのドキュメントは、知識データストレージのリソースを消費します。知識データストレージの上限に達すると、新しいドキュメントはアップロードされません。',
|
||||
documentsRequestQuota: '{{count,number}}/分のナレッジ リクエストのレート制限',
|
||||
documentsRequestQuotaTooltip: 'ナレッジベース内でワークスペースが1分間に実行できる操作の総数を示します。これには、データセットの作成、削除、更新、ドキュメントのアップロード、修正、アーカイブ、およびナレッジベースクエリが含まれます。この指標は、ナレッジベースリクエストのパフォーマンスを評価するために使用されます。例えば、Sandbox ユーザーが1分間に10回連続でヒットテストを実行した場合、そのワークスペースは次の1分間、データセットの作成、削除、更新、ドキュメントのアップロードや修正などの操作を一時的に実行できなくなります。',
|
||||
apiRateLimit: 'APIレート制限',
|
||||
apiRateLimitUnit: '{{count,number}}/日',
|
||||
unlimitedApiRate: '無制限のAPIコール',
|
||||
apiRateLimitTooltip: 'APIレート制限は、テキスト生成、チャットボット、ワークフロー、ドキュメント処理など、Dify API経由のすべてのリクエストに適用されます。',
|
||||
documentProcessingPriority: '文書処理',
|
||||
documentProcessingPriorityUpgrade: 'より高い精度と高速な速度でデータを処理します。',
|
||||
priority: {
|
||||
@ -100,17 +104,17 @@ const translation = {
|
||||
},
|
||||
plans: {
|
||||
sandbox: {
|
||||
name: 'Sandbox(サンドボックス)',
|
||||
name: 'Sandbox',
|
||||
for: '主要機能の無料体験',
|
||||
description: '主要機能を無料で体験',
|
||||
},
|
||||
professional: {
|
||||
name: 'Professional(プロフェッショナル)',
|
||||
name: 'Professional',
|
||||
for: '個人開発者/小規模チーム向け',
|
||||
description: '個人開発者・小規模チームに最適',
|
||||
},
|
||||
team: {
|
||||
name: 'Team(チーム)',
|
||||
name: 'Team',
|
||||
for: '中規模チーム向け',
|
||||
description: '成長期のチームに必要な機能を備えたプラン',
|
||||
},
|
||||
|
||||
@ -56,6 +56,7 @@ const translation = {
|
||||
viewDetails: '詳細を見る',
|
||||
copied: 'コピーしました',
|
||||
in: '中',
|
||||
format: 'フォーマット',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}}は必要です',
|
||||
|
||||
@ -30,7 +30,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'テキストファイルをアップロード',
|
||||
button: 'ファイルをドラッグ&ドロップするか',
|
||||
button: 'ファイルまたはフォルダをドラッグアンドドロップする',
|
||||
browse: '参照',
|
||||
tip: '{{supportTypes}}をサポートしています。1つあたりの最大サイズは{{size}}MBです。',
|
||||
validation: {
|
||||
|
||||
@ -30,6 +30,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: '問題を解決する',
|
||||
temporarySystemIssue: 'システムに一時的な問題が発生しています',
|
||||
expand: '拡大',
|
||||
collapse: '縮小',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -70,6 +70,10 @@ const translation = {
|
||||
pasteHere: 'ここに貼り付け',
|
||||
pointerMode: 'ポインターモード',
|
||||
handMode: 'ハンドモード',
|
||||
exportImage: '画像を出力',
|
||||
exportPNG: 'PNGで出力',
|
||||
exportJPEG: 'JPEGで出力',
|
||||
exportSVG: 'SVGで出力',
|
||||
model: 'モデル',
|
||||
workflowAsTool: 'ワークフローをツールどして公開する',
|
||||
configureRequired: '設定が必要',
|
||||
@ -420,6 +424,34 @@ const translation = {
|
||||
variable: '変数',
|
||||
},
|
||||
sysQueryInUser: 'ユーザーメッセージにsys.queryを含めてください',
|
||||
jsonSchema: {
|
||||
title: '構造化データスキーマ',
|
||||
instruction: '指示',
|
||||
promptTooltip: 'テキスト説明から標準JSONスキーマを自動生成できます。',
|
||||
promptPlaceholder: 'JSONスキーマを入力...',
|
||||
generate: '生成',
|
||||
import: 'JSONインポート',
|
||||
generateJsonSchema: 'スキーマ生成',
|
||||
generationTip: '自然言語で簡単にJSONスキーマを作成可能です。',
|
||||
generating: 'JSONスキーマを生成中...',
|
||||
generatedResult: '生成結果',
|
||||
resultTip: 'こちらが生成された結果です。ご満足いただけない場合は、前の画面に戻ってプロンプトを修正できます。',
|
||||
back: '前に戻る',
|
||||
regenerate: '再生成する',
|
||||
apply: '適用',
|
||||
doc: '構造化出力の詳細を見る',
|
||||
resetDefaults: '初期化',
|
||||
required: '必須項目',
|
||||
addField: 'フィールドを追加',
|
||||
addChildField: 'サブフィールドを追加',
|
||||
showAdvancedOptions: '詳細設定',
|
||||
stringValidations: '文字列検証',
|
||||
fieldNamePlaceholder: 'フィールド名',
|
||||
descriptionPlaceholder: '説明を入力',
|
||||
warningTips: {
|
||||
saveSchema: '編集中のフィールドを確定してから保存してください。',
|
||||
},
|
||||
},
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
queryVariable: '検索変数',
|
||||
|
||||
@ -22,7 +22,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: '텍스트 파일 업로드',
|
||||
button: '파일을 끌어다 놓거나',
|
||||
button: '파일이나 폴더를 끌어서 놓기',
|
||||
browse: '찾아보기',
|
||||
tip: '{{supportTypes}}을(를) 지원합니다. 파일당 최대 크기는 {{size}}MB입니다.',
|
||||
validation: {
|
||||
|
||||
@ -26,6 +26,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: '해결하려고 합니다',
|
||||
temporarySystemIssue: '죄송합니다. 일시적인 시스템 문제가 발생했습니다.',
|
||||
expand: '확장',
|
||||
collapse: '축소',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
@ -22,7 +22,7 @@ const translation = {
|
||||
},
|
||||
uploader: {
|
||||
title: 'Prześlij plik tekstowy',
|
||||
button: 'Przeciągnij i upuść plik lub',
|
||||
button: 'Przeciągnij i upuść pliki lub foldery lub',
|
||||
browse: 'Przeglądaj',
|
||||
tip: 'Obsługuje {{supportTypes}}. Maksymalnie {{size}}MB każdy.',
|
||||
validation: {
|
||||
|
||||
@ -27,6 +27,8 @@ const translation = {
|
||||
},
|
||||
tryToSolve: 'Spróbuj rozwiązać',
|
||||
temporarySystemIssue: 'Przepraszamy, tymczasowy problem systemowy.',
|
||||
expand: 'Rozwiń',
|
||||
collapse: 'Zwiń',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user