mirror of
https://github.com/langgenius/dify.git
synced 2026-04-24 12:55:49 +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>
|
||||
|
||||
Reference in New Issue
Block a user