Merge branch 'main' into e-300

This commit is contained in:
NFish
2025-05-15 10:34:44 +08:00
523 changed files with 14695 additions and 4125 deletions

View File

@ -6,10 +6,12 @@ NEXT_PUBLIC_EDITION=SELF_HOSTED
# different from api or web app domain.
# example: http://cloud.dify.ai/console/api
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
NEXT_PUBLIC_WEB_PREFIX=http://localhost:3000
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
# console or api domain.
# example: http://udify.app/api
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
NEXT_PUBLIC_PUBLIC_WEB_PREFIX=http://localhost:3000
# The API PREFIX for MARKETPLACE
NEXT_PUBLIC_MARKETPLACE_API_PREFIX=https://marketplace.dify.ai/api/v1
# The URL for MARKETPLACE

View File

@ -31,10 +31,12 @@ NEXT_PUBLIC_EDITION=SELF_HOSTED
# different from api or web app domain.
# example: http://cloud.dify.ai/console/api
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
NEXT_PUBLIC_WEB_PREFIX=http://localhost:3000
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
# console or api domain.
# example: http://udify.app/api
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
NEXT_PUBLIC_PUBLIC_WEB_PREFIX=http://localhost:3000
# SENTRY
NEXT_PUBLIC_SENTRY_DSN=

View File

@ -17,7 +17,7 @@ import AppsContext, { useAppContext } from '@/context/app-context'
import type { HtmlContentProps } from '@/app/components/base/popover'
import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
import { getRedirection } from '@/utils/app-redirection'
import { useProviderContext } from '@/context/provider-context'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
@ -235,7 +235,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
try {
const { installed_apps }: any = await fetchInstalledAppList(app.id) || {}
if (installed_apps?.length > 0)
window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank')
else
throw new Error('No app found in Explore')
}

View File

@ -99,7 +99,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer'

View File

@ -87,7 +87,7 @@ const Container = () => {
return (
<div ref={containerRef} className='scroll-container relative flex grow flex-col overflow-y-auto bg-background-body'>
<div className='sticky top-0 z-10 flex flex-wrap justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'>
<div className='sticky top-0 z-10 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'>
<TabSliderNew
value={activeTab}
onChange={newActiveTab => setActiveTab(newActiveTab)}

View File

@ -121,7 +121,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
</button>
)}
</div>
<article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'dark:prose-invert')}>
<article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'prose-invert')}>
{Template}
</article>
</div>

View File

@ -1,6 +1,6 @@
'use client'
import { useTranslation } from 'react-i18next'
import { basePath } from '@/utils/var'
import Link from 'next/link'
import {
RiAddLine,
RiArrowRightLine,
@ -18,7 +18,7 @@ const CreateAppCard = (
<div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px]
border-components-panel-border transition-all duration-200 ease-in-out'
>
<a ref={ref} className='group flex grow cursor-pointer items-start p-4' href={`${basePath}/datasets/create`}>
<Link ref={ref} className='group flex grow cursor-pointer items-start p-4' href={'/datasets/create'}>
<div className='flex items-center gap-3'>
<div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter
p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
@ -27,12 +27,12 @@ const CreateAppCard = (
</div>
<div className='system-md-semibold text-text-secondary group-hover:text-text-accent'>{t('dataset.createDataset')}</div>
</div>
</a>
</Link>
<div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div>
<a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href={`${basePath}/datasets/connect`}>
<Link className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href={'datasets/connect'}>
<div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div>
<RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' />
</a>
</Link>
</div>
)
}

View File

@ -314,7 +314,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
</Property>
<Property name='indexing_technique' type='string' key='indexing_technique'>
Index technique (optional)
If this is not set, embedding_model, embedding_provider_name and retrieval_model will be set to null
If this is not set, embedding_model, embedding_model_provider and retrieval_model will be set to null
- <code>high_quality</code> High quality
- <code>economy</code> Economy
</Property>
@ -338,7 +338,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='embedding_model' type='str' key='embedding_model'>
Embedding model name (optional)
</Property>
<Property name='embedding_provider_name' type='str' key='embedding_provider_name'>
<Property name='embedding_model_provider' type='str' key='embedding_model_provider'>
Embedding model provider name (optional)
</Property>
<Property name='retrieval_model' type='object' key='retrieval_model'>
@ -1040,10 +1040,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1335,10 +1333,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1620,10 +1616,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -337,7 +337,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='embedding_model' type='str' key='embedding_model'>
埋め込みモデル名(任意)
</Property>
<Property name='embedding_provider_name' type='str' key='embedding_provider_name'>
<Property name='embedding_model_provider' type='str' key='embedding_model_provider'>
埋め込みモデルのプロバイダ名(任意)
</Property>
<Property name='retrieval_model' type='object' key='retrieval_model'>
@ -501,7 +501,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="レスポンス">
```text {{ title: 'Response' }}
```text {{ title: 'レスポンス' }}
204 No Content
```
</CodeGroup>
@ -797,10 +797,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="レスポンス">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'レスポンス' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1092,10 +1090,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="レスポンス">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'レスポンス' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1377,10 +1373,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="レスポンス">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'レスポンス' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -341,7 +341,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='embedding_model' type='str' key='embedding_model'>
Embedding 模型名称
</Property>
<Property name='embedding_provider_name' type='str' key='embedding_provider_name'>
<Property name='embedding_model_provider' type='str' key='embedding_model_provider'>
Embedding 模型供应商
</Property>
<Property name='retrieval_model' type='object' key='retrieval_model'>
@ -1047,10 +1047,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1342,10 +1340,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1628,10 +1624,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -31,7 +31,7 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
import { fetchInstalledAppList } from '@/service/explore'
import EmbeddedModal from '@/app/components/app/overview/embedded'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -89,7 +89,7 @@ const AppPublisher = ({
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {}
const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode
const appURL = `${appBaseURL}${basePath}/${appMode}/${accessToken}`
const appURL = `${appBaseURL}/${appMode}/${accessToken}`
const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '')
const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp, refetch } = useGetUserCanAccessApp({ appId: appDetail?.id, enabled: false })
const { data: appAccessSubjects, isLoading: isGettingAppWhiteListSubjects } = useAppWhiteListSubjects(appDetail?.id, open && systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS)
@ -154,7 +154,7 @@ const AppPublisher = ({
try {
const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {}
if (installed_apps?.length > 0)
window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank')
else
throw new Error('No app found in Explore')
}

View File

@ -46,8 +46,8 @@ const HistoryPanel: FC<Props> = ({
<div className='flex justify-between rounded-b-xl bg-background-section-burn px-3 py-2 text-xs text-text-secondary'>
<div>{t('appDebug.feature.conversationHistory.tip')}
<a href={`${locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
: 'https://docs.dify.ai/features/prompt-engineering'}`}
? 'https://docs.dify.ai/zh-hans/learn-more/extended-reading/prompt-engineering/README'
: 'https://docs.dify.ai/en/features/prompt-engineering'}`}
target='_blank' rel='noopener noreferrer'
className='text-[#155EEF]'>{t('appDebug.feature.conversationHistory.learnMore')}
</a>

View File

@ -14,7 +14,6 @@ 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
@ -112,7 +111,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
}}
>
<span className='text-text-tertiary'>{t('appDebug.feature.dataSet.noDataSet')}</span>
<Link href={`${basePath}/datasets/create`} className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link>
<Link href={'/datasets/create'} className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link>
</div>
)}

View File

@ -25,7 +25,7 @@ const AdvancedModeWarning: FC<Props> = ({
<span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
<a
className='font-medium text-[#155EEF]'
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/guides/application-design/prompt-engineering' : 'features/prompt-engineering'}`}
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? '/guides/features/prompt-engineering' : 'features/prompt-engineering'}`}
target='_blank' rel='noopener noreferrer'
>
{t('appDebug.promptMode.advancedWarning.learnMore')}

View File

@ -14,7 +14,7 @@ import type { AppIconSelection } from '../../base/app-icon-picker'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { ToastContext } from '@/app/components/base/toast'
@ -310,17 +310,17 @@ function AppPreview({ mode }: { mode: AppMode }) {
'chat': {
title: t('app.types.chatbot'),
description: t('app.newApp.chatbotUserDescription'),
link: 'https://docs.dify.ai/guides/application-orchestrate#application_type',
link: 'https://docs.dify.ai/guides/application-orchestrate/readme',
},
'advanced-chat': {
title: t('app.types.advanced'),
description: t('app.newApp.advancedUserDescription'),
link: 'https://docs.dify.ai/guides/workflow',
link: 'https://docs.dify.ai/en/guides/workflow/README',
},
'agent-chat': {
title: t('app.types.agent'),
description: t('app.newApp.agentUserDescription'),
link: 'https://docs.dify.ai/guides/application-orchestrate/agent',
link: 'https://docs.dify.ai/en/guides/application-orchestrate/agent',
},
'completion': {
title: t('app.newApp.completeApp'),
@ -330,7 +330,7 @@ function AppPreview({ mode }: { mode: AppMode }) {
'workflow': {
title: t('app.types.workflow'),
description: t('app.newApp.workflowUserDescription'),
link: 'https://docs.dify.ai/guides/workflow',
link: 'https://docs.dify.ai/en/guides/workflow/README',
},
}
const previewInfo = modeToPreviewInfoMap[mode]
@ -353,11 +353,11 @@ function AppScreenShot({ mode, show }: { mode: AppMode; show: boolean }) {
'workflow': 'Workflow',
}
return <picture>
<source media="(resolution: 1x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}.png`} />
<source media="(resolution: 2x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} />
<source media="(resolution: 3x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} />
<source media="(resolution: 1x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}.png`} />
<source media="(resolution: 2x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} />
<source media="(resolution: 3x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} />
<Image className={show ? '' : 'hidden'}
src={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}.png`}
src={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}.png`}
alt='App Screen Shot'
width={664} height={448} />
</picture>

View File

@ -262,7 +262,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
{
currentTab === CreateFromDSLModalTab.FROM_URL && (
<div>
<div className='system-md-semibold leading6 mb-1'>DSL URL</div>
<div className='system-md-semibold mb-1 text-text-secondary'>DSL URL</div>
<Input
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
value={dslUrlValue}

View File

@ -3,6 +3,7 @@ import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import {
RiDeleteBinLine,
RiUploadCloud2Line,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
@ -10,8 +11,7 @@ import { formatFileSize } from '@/utils/format'
import cn from '@/utils/classnames'
import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files'
import { ToastContext } from '@/app/components/base/toast'
import { UploadCloud01 } from '@/app/components/base/icons/src/vender/line/general'
import Button from '@/app/components/base/button'
import ActionButton from '@/app/components/base/action-button'
export type Props = {
file: File | undefined
@ -102,19 +102,19 @@ const Uploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex h-12 items-center rounded-xl border border-dashed border-gray-200 bg-gray-50 text-sm font-normal', dragging && 'border border-[#B2CCFF] bg-[#F5F8FF]')}>
<div className={cn('flex h-12 items-center rounded-[10px] border border-dashed border-components-dropzone-border bg-components-dropzone-bg text-sm font-normal', dragging && 'border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}>
<div className='flex w-full items-center justify-center space-x-2'>
<UploadCloud01 className='mr-2 h-6 w-6' />
<div className='text-gray-500'>
<RiUploadCloud2Line className='h-6 w-6 text-text-tertiary' />
<div className='text-text-tertiary'>
{t('datasetCreation.stepOne.uploader.button')}
<span className='cursor-pointer pl-1 text-[#155eef]' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span>
<span className='cursor-pointer pl-1 text-text-accent' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span>
</div>
</div>
{dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />}
</div>
)}
{file && (
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', 'hover:border-[#B2CCFF] hover:bg-[#F5F8FF]')}>
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className='flex items-center justify-center p-3'>
<YamlIcon className="h-6 w-6 shrink-0" />
</div>
@ -126,12 +126,10 @@ const Uploader: FC<Props> = ({
<span>{formatFileSize(file.size)}</span>
</div>
</div>
<div className='hidden items-center group-hover:flex'>
<Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
<div className='mx-2 h-4 w-px bg-gray-200' />
<div className='cursor-pointer p-2' onClick={removeFile}>
<div className='hidden items-center pr-3 group-hover:flex'>
<ActionButton onClick={removeFile}>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
</div>
</ActionButton>
</div>
</div>
)}

View File

@ -7,7 +7,6 @@ import { usePathname } from 'next/navigation'
import { useDebounce } from 'ahooks'
import { omit } from 'lodash-es'
import dayjs from 'dayjs'
import { basePath } from '@/utils/var'
import { Trans, useTranslation } from 'react-i18next'
import List from './list'
import Filter, { TIME_PERIOD_MAPPING } from './filter'
@ -110,7 +109,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
? <Loading type='app' />
: total > 0
? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}${basePath}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
}
{/* Show Pagination only if the total is more than the limit */}
{(total && total > APP_PAGE_LIMIT)

View File

@ -20,7 +20,6 @@ import Tooltip from '@/app/components/base/tooltip'
import AppBasic from '@/app/components/app-sidebar/basic'
import { asyncRunSafe, randomString } from '@/utils'
import { useStore as useAppStore } from '@/app/components/app/store'
import { basePath } from '@/utils/var'
import Button from '@/app/components/base/button'
import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider'
@ -101,7 +100,7 @@ function AppCard({
const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api
const { app_base_url, access_token } = appInfo.site ?? {}
const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode
const appUrl = `${app_base_url}${basePath}/${appMode}/${access_token}`
const appUrl = `${app_base_url}/${appMode}/${access_token}`
const apiUrl = appInfo?.api_base_url
const genClickFuncByName = (opName: string) => {

View File

@ -103,7 +103,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
window.open(
`https://docs.dify.ai/${locale !== LanguagesSupported[1]
? 'user-guide/launching-dify-apps/developing-with-apis'
: `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis`
: `${locale.toLowerCase()}/guides/application-publishing/developing-with-apis`
}`,
'_blank',
)

View File

@ -13,7 +13,6 @@ import { IS_CE_EDITION } from '@/config'
import type { SiteInfo } from '@/models/share'
import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context'
import ActionButton from '@/app/components/base/action-button'
import { basePath } from '@/utils/var'
import cn from '@/utils/classnames'
type Props = {
@ -29,7 +28,7 @@ const OPTION_MAP = {
iframe: {
getContent: (url: string, token: string) =>
`<iframe
src="${url}${basePath}/chatbot/${token}"
src="${url}/chatbot/${token}"
style="width: 100%; height: 100%; min-height: 700px"
frameborder="0"
allow="microphone">
@ -44,7 +43,7 @@ const OPTION_MAP = {
isDev: true`
: ''}${IS_CE_EDITION
? `,
baseUrl: '${url}${basePath}'`
baseUrl: '${url}'`
: ''},
systemVariables: {
// user_id: 'YOU CAN DEFINE USER ID HERE',
@ -53,7 +52,7 @@ const OPTION_MAP = {
}
</script>
<script
src="${url}${basePath}/embed.min.js"
src="${url}/embed.min.js"
id="${token}"
defer>
</script>
@ -68,7 +67,7 @@ const OPTION_MAP = {
</style>`,
},
chromePlugin: {
getContent: (url: string, token: string) => `ChatBot URL: ${url}${basePath}/chatbot/${token}`,
getContent: (url: string, token: string) => `ChatBot URL: ${url}/chatbot/${token}`,
},
}
const prefixEmbedded = 'appOverview.overview.appInfo.embedded'

View File

@ -238,7 +238,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
<span>{t(`${prefixSettings}.modalTip`)}</span>
<Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/guides/application-publishing/launch-your-webapp-quickly#setting-up-your-ai-site'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link>
<Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/en/guides/application-publishing/launch-your-webapp-quickly/README'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link>
</div>
</div>
{/* form body */}

View File

@ -11,7 +11,6 @@ import timezone from 'dayjs/plugin/timezone'
import { Trans, useTranslation } from 'react-i18next'
import Link from 'next/link'
import List from './list'
import { basePath } from '@/utils/var'
import Filter, { TIME_PERIOD_MAPPING } from './filter'
import Pagination from '@/app/components/base/pagination'
import Loading from '@/app/components/base/loading'
@ -101,7 +100,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
? <Loading type='app' />
: total > 0
? <List logs={workflowLogs} appDetail={appDetail} onRefresh={mutate} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}${basePath}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
}
{/* Show Pagination only if the total is more than the limit */}
{(total && total > APP_PAGE_LIMIT)

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useChatWithHistoryContext } from '../context'
import Input from '@/app/components/base/input'
@ -112,4 +112,4 @@ const InputsFormContent = ({ showTip }: Props) => {
)
}
export default InputsFormContent
export default memo(InputsFormContent)

View File

@ -424,6 +424,8 @@ export const useChat = (
const response = responseItem as any
if (thought.message_id && !hasSetResponseId)
response.id = thought.message_id
if (thought.conversation_id)
response.conversationId = thought.conversation_id
if (response.agent_thoughts.length === 0) {
response.agent_thoughts.push(thought)

View File

@ -5,6 +5,8 @@ import type {
import {
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import type { ChatItem } from '../types'
@ -52,6 +54,8 @@ const Question: FC<QuestionProps> = ({
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState(content)
const [contentWidth, setContentWidth] = useState(0)
const contentRef = useRef<HTMLDivElement>(null)
const handleEdit = useCallback(() => {
setIsEditing(true)
@ -75,14 +79,31 @@ const Question: FC<QuestionProps> = ({
item.nextSibling && switchSibling?.(item.nextSibling)
}, [switchSibling, item.prevSibling, item.nextSibling])
const getContentWidth = () => {
if (contentRef.current)
setContentWidth(contentRef.current?.clientWidth)
}
useEffect(() => {
if (!contentRef.current)
return
const resizeObserver = new ResizeObserver(() => {
getContentWidth()
})
resizeObserver.observe(contentRef.current)
return () => {
resizeObserver.disconnect()
}
}, [])
return (
<div className='mb-2 flex justify-end pl-14 last:mb-0'>
<div className={cn('group relative mr-4 flex max-w-full items-start', isEditing && 'flex-1')}>
<div className='mb-2 flex justify-end last:mb-0'>
<div className={cn('group relative mr-4 flex max-w-full items-start pl-14', isEditing && 'flex-1')}>
<div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}>
<div className="
absolutegap-0.5 hidden rounded-[10px] border-[0.5px] border-components-actionbar-border
bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex
">
<div
className="absolute hidden gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex"
style={{ right: contentWidth + 8 }}
>
<ActionButton onClick={() => {
copy(content)
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
@ -95,6 +116,7 @@ const Question: FC<QuestionProps> = ({
</div>
</div>
<div
ref={contentRef}
className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900'
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
>

View File

@ -41,6 +41,7 @@ export type ThoughtItem = {
tool_input: string
tool_labels?: { [key: string]: TypeWithI18N }
message_id: string
conversation_id: string
observation: string
position: number
files?: string[]

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useEmbeddedChatbotContext } from '../context'
import Input from '@/app/components/base/input'
@ -112,4 +112,4 @@ const InputsFormContent = ({ showTip }: Props) => {
)
}
export default InputsFormContent
export default memo(InputsFormContent)

View File

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon L">
<g id="Vector">
<path d="M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z" fill="#354052"/>
<path d="M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z" fill="#354052"/>
<path d="M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z" fill="#354052"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View File

@ -0,0 +1,62 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon L"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Vector"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "Collapse"
}

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Collapse.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Collapse'
export default Icon

View File

@ -1,5 +1,6 @@
export { default as AlignLeft } from './AlignLeft'
export { default as BezierCurve03 } from './BezierCurve03'
export { default as Collapse } from './Collapse'
export { default as Colors } from './Colors'
export { default as ImageIndentLeft } from './ImageIndentLeft'
export { default as LeftIndent02 } from './LeftIndent02'

View File

@ -1,5 +1,5 @@
import type { FC } from 'react'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
type LogoEmbeddedChatAvatarProps = {
className?: string
@ -9,7 +9,7 @@ const LogoEmbeddedChatAvatar: FC<LogoEmbeddedChatAvatarProps> = ({
}) => {
return (
<img
src={`${basePath}/logo/logo-embedded-chat-avatar.png`}
src={`${WEB_PREFIX}/logo/logo-embedded-chat-avatar.png`}
className={`block h-10 w-10 ${className}`}
alt='logo'
/>

View File

@ -1,6 +1,6 @@
import classNames from '@/utils/classnames'
import type { FC } from 'react'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
type LogoEmbeddedChatHeaderProps = {
className?: string
@ -14,7 +14,7 @@ const LogoEmbeddedChatHeader: FC<LogoEmbeddedChatHeaderProps> = ({
<source media="(resolution: 2x)" srcSet='/logo/logo-embedded-chat-header@2x.png' />
<source media="(resolution: 3x)" srcSet='/logo/logo-embedded-chat-header@3x.png' />
<img
src={`${basePath}/logo/logo-embedded-chat-header.png`}
src={`${WEB_PREFIX}/logo/logo-embedded-chat-header.png`}
alt='logo'
className={classNames('block h-6 w-auto', className)}
/>

View File

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
import classNames from '@/utils/classnames'
import { useGlobalPublicStore } from '@/context/global-public-context'
@ -13,7 +13,7 @@ const LogoSite: FC<LogoSiteProps> = ({
}) => {
const { systemFeatures } = useGlobalPublicStore()
let src = `${basePath}/logo/logo.png`
let src = `${WEB_PREFIX}/logo/logo.png`
if (systemFeatures.branding.enabled)
src = systemFeatures.branding.workspace_logo

View File

@ -22,7 +22,7 @@ const MarkdownButton = ({ node }: any) => {
return <Button
variant={variant}
size={size}
className={cn('!h-8 select-none !px-3')}
className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')}
onClick={() => {
if (is_valid_url(link)) {
window.open(link, '_blank')

View File

@ -41,9 +41,10 @@ const useThinkTimer = (children: any) => {
const timerRef = useRef<NodeJS.Timeout>()
useEffect(() => {
if (isComplete) return
timerRef.current = setInterval(() => {
if (!isComplete)
setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10)
setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10)
}, 100)
return () => {
@ -53,11 +54,8 @@ const useThinkTimer = (children: any) => {
}, [startTime, isComplete])
useEffect(() => {
if (hasEndThink(children)) {
if (hasEndThink(children))
setIsComplete(true)
if (timerRef.current)
clearInterval(timerRef.current)
}
}, [children])
return { elapsedTime, isComplete }

View File

@ -128,13 +128,19 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
const language = match?.[1]
const languageShowName = getCorrectCapitalizationLanguageName(language || '')
const chartData = useMemo(() => {
const str = String(children).replace(/\n$/, '')
if (language === 'echarts') {
try {
return JSON.parse(String(children).replace(/\n$/, ''))
return JSON.parse(str)
}
catch { }
try {
// eslint-disable-next-line no-new-func, sonarjs/code-eval
return new Function(`return ${str}`)()
}
catch { }
}
return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}')
return JSON.parse('{"title":{"text":"ECharts error - Wrong option."}}')
}, [language, children])
const renderCodeContent = useMemo(() => {

View File

@ -2,47 +2,55 @@
@layer components {
.premium-badge {
@apply inline-flex justify-center items-center rounded-md border box-border border-white/95 text-white
@apply shrink-0 relative inline-flex justify-center items-center rounded-md box-border border border-transparent text-white shadow-xs hover:shadow-lg bg-origin-border overflow-hidden;
background-clip: padding-box, border-box;
}
.allowHover {
@apply cursor-pointer;
}
/* m is for the regular button */
.premium-badge-m {
@apply border shadow-lg !p-1 h-6 w-auto
@apply !p-1 h-6 w-auto
}
.premium-badge-s {
@apply border-[0.5px] shadow-xs !px-1 !py-[3px] h-[18px] w-auto
@apply border-[0.5px] !px-1 !py-[3px] h-[18px] w-auto
}
.premium-badge-blue {
@apply bg-gradient-to-r from-[#5289ffe6] to-[#155aefe6] bg-util-colors-blue-blue-200
@apply bg-util-colors-blue-blue-200;
background-image: linear-gradient(90deg, #5289ffe6 0%, #155aefe6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #155aef 100%);
}
.premium-badge-blue.allowHover:hover {
@apply bg-util-colors-blue-blue-300;
background-image: linear-gradient(90deg, #296dffe6 0%, #004aebe6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #00329e 100%);
}
.premium-badge-indigo {
@apply bg-gradient-to-r from-[#8098f9e6] to-[#444ce7e6] bg-util-colors-indigo-indigo-200
@apply bg-util-colors-indigo-indigo-200;
background-image: linear-gradient(90deg, #8098f9e6 0%, #444ce7e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #6172f3 100%);
}
.premium-badge-indigo.allowHover:hover {
@apply bg-util-colors-indigo-indigo-300;
background-image: linear-gradient(90deg, #6172f3e6 0%, #2d31a6e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #2d31a6 100%);
}
.premium-badge-gray {
@apply bg-gradient-to-r from-[#98a2b2e6] to-[#676f83e6] bg-util-colors-gray-gray-200
@apply bg-util-colors-gray-gray-200;
background-image: linear-gradient(90deg, #98a2b2e6 0%, #676f83e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #676f83 100%);
}
.premium-badge-gray.allowHover:hover {
@apply bg-util-colors-gray-gray-300;
background-image: linear-gradient(90deg, #676f83e6 0%, #354052e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #354052 100%);
}
.premium-badge-orange {
@apply bg-gradient-to-r from-[#ff692ee6] to-[#e04f16e6] bg-util-colors-orange-orange-200
@apply bg-util-colors-orange-orange-200;
background-image: linear-gradient(90deg, #ff692ee6 0%, #e04f16e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #e62e05 100%);
}
.premium-badge-blue.allowHover:hover {
@apply bg-gradient-to-r from-[#296dffe6] to-[#004aebe6] bg-util-colors-blue-blue-300 cursor-pointer
}
.premium-badge-indigo.allowHover:hover {
@apply bg-gradient-to-r from-[#6172f3e6] to-[#2d31a6e6] bg-util-colors-indigo-indigo-300 cursor-pointer
}
.premium-badge-gray.allowHover:hover {
@apply bg-gradient-to-r from-[#676f83e6] to-[#354052e6] bg-util-colors-gray-gray-300 cursor-pointer
}
.premium-badge-orange.allowHover:hover {
@apply bg-gradient-to-r from-[#ff4405e6] to-[#b93815e6] bg-util-colors-orange-orange-300 cursor-pointer
@apply bg-util-colors-orange-orange-300;
background-image: linear-gradient(90deg, #ff4405e6 0%, #b93815e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #e62e05 100%);
}
}

View File

@ -61,13 +61,9 @@ const PremiumBadge: React.FC<PremiumBadgeProps> = ({
{children}
<Highlight
className={classNames(
'absolute top-0 opacity-50 hover:opacity-80',
'absolute top-0 opacity-50 right-1/2 translate-x-[20%] transition-all duration-100 ease-out hover:opacity-80 hover:translate-x-[30%]',
size === 's' ? 'h-[18px] w-12' : 'h-6 w-12',
)}
style={{
right: '50%',
transform: 'translateX(10%)',
}}
/>
</div>
)

View File

@ -0,0 +1,97 @@
'use client'
import { useState } from 'react'
import {
RiCheckLine,
RiComputerLine,
RiMoonLine,
RiSunLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useTheme } from 'next-themes'
import ActionButton from '@/app/components/base/action-button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
export type Theme = 'light' | 'dark' | 'system'
export default function ThemeSelector() {
const { t } = useTranslation()
const { theme, setTheme } = useTheme()
const [open, setOpen] = useState(false)
const handleThemeChange = (newTheme: Theme) => {
setTheme(newTheme)
setOpen(false)
}
const getCurrentIcon = () => {
switch (theme) {
case 'light': return <RiSunLine className='h-4 w-4 text-text-tertiary' />
case 'dark': return <RiMoonLine className='h-4 w-4 text-text-tertiary' />
default: return <RiComputerLine className='h-4 w-4 text-text-tertiary' />
}
}
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{ mainAxis: 6 }}
>
<PortalToFollowElemTrigger
onClick={() => setOpen(!open)}
>
<ActionButton
className={`h-8 w-8 p-[6px] ${open && 'bg-state-base-hover'}`}
>
{getCurrentIcon()}
</ActionButton>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<div className='flex w-[144px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('light')}
>
<RiSunLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.light')}</span>
</div>
{theme === 'light' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('dark')}
>
<RiMoonLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.dark')}</span>
</div>
{theme === 'dark' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
<button
className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleThemeChange('system')}
>
<RiComputerLine className='h-4 w-4 text-text-tertiary' />
<div className='flex grow items-center justify-start px-1'>
<span className='system-md-regular'>{t('common.theme.auto')}</span>
</div>
{theme === 'system' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'>
<RiCheckLine className='h-4 w-4 text-text-accent' />
</div>}
</button>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}

View File

@ -0,0 +1,58 @@
'use client'
import {
RiComputerLine,
RiMoonLine,
RiSunLine,
} from '@remixicon/react'
import { useTheme } from 'next-themes'
import cn from '@/utils/classnames'
export type Theme = 'light' | 'dark' | 'system'
export default function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
const handleThemeChange = (newTheme: Theme) => {
setTheme(newTheme)
}
return (
<div className='flex items-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5'>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'system' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('system')}
>
<div className='p-0.5'>
<RiComputerLine className='h-4 w-4' />
</div>
</div>
<div className={cn('h-[14px] w-px bg-transparent', theme === 'dark' && 'bg-divider-regular')}></div>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'light' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('light')}
>
<div className='p-0.5'>
<RiSunLine className='h-4 w-4' />
</div>
</div>
<div className={cn('h-[14px] w-px bg-transparent', theme === 'system' && 'bg-divider-regular')}></div>
<div
className={cn(
'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
theme === 'dark' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only',
)}
onClick={() => handleThemeChange('dark')}
>
<div className='p-0.5'>
<RiMoonLine className='h-4 w-4' />
</div>
</div>
</div>
)
}

View File

@ -2,7 +2,7 @@ import React, { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import { RiLineHeight } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import { Collapse } from '@/app/components/base/icons/src/public/knowledge'
import { Collapse } from '@/app/components/base/icons/src/vender/line/editor'
type DisplayToggleProps = {
isCollapsed: boolean

View File

@ -264,8 +264,8 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
target='_blank'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/en/guides/knowledge-base/integrate-knowledge-within-application'
}
>
<span>{t('datasetDocuments.list.learnMore')}</span>

View File

@ -16,12 +16,12 @@ const InfoPanel = () => {
</span>
<span className='system-sm-regular text-text-tertiary'>
{t('dataset.connectDatasetIntro.content.front')}
<a className='system-sm-regular ml-1 text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' target='_blank' rel="noopener noreferrer">
<a className='system-sm-regular ml-1 text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/external-knowledge-api' target='_blank' rel="noopener noreferrer">
{t('dataset.connectDatasetIntro.content.link')}
</a>
{t('dataset.connectDatasetIntro.content.end')}
</span>
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/connect-external-knowledge' target='_blank' rel="noopener noreferrer">
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/connect-external-knowledge-base' target='_blank' rel="noopener noreferrer">
{t('dataset.connectDatasetIntro.learnMore')}
</a>
</p>

View File

@ -59,7 +59,7 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
<span>{t('dataset.connectHelper.helper1')}</span>
<span className='system-sm-medium text-text-secondary'>{t('dataset.connectHelper.helper2')}</span>
<span>{t('dataset.connectHelper.helper3')}</span>
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/connect-external-knowledge' target='_blank' rel="noopener noreferrer">
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/connect-external-knowledge-base' target='_blank' rel="noopener noreferrer">
{t('dataset.connectHelper.helper4')}
</a>
<span>{t('dataset.connectHelper.helper5')} </span>

View File

@ -36,7 +36,7 @@ const CreateMetadataModal: FC<Props> = ({
{trigger}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<CreateContent {...createContentProps} onClose={() => setOpen(false)} />
<CreateContent {...createContentProps} onClose={() => setOpen(false)} onBack={() => setOpen(false)} />
</PortalToFollowElemContent>
</PortalToFollowElem >

View File

@ -71,6 +71,7 @@ const SelectMetadataModal: FC<Props> = ({
onSave={handleSave}
hasBack
onBack={() => setStep(Step.select)}
onClose={() => setStep(Step.select)}
/>
)}
</PortalToFollowElemContent>

View File

@ -121,7 +121,7 @@ const Doc = ({ appDetail }: IDocProps) => {
</button>
)}
</div>
<article className={cn('prose-xl prose', theme === Theme.dark && 'dark:prose-invert')} >
<article className={cn('prose-xl prose', theme === Theme.dark && 'prose-invert')} >
{(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && (
(() => {
switch (locale) {

View File

@ -2,20 +2,18 @@
import React, { useEffect, useState } from 'react'
import copy from 'copy-to-clipboard'
import { t } from 'i18next'
import s from './style.module.css'
import Tooltip from '@/app/components/base/tooltip'
import CopyFeedback from '@/app/components/base/copy-feedback'
type IInputCopyProps = {
value?: string
className?: string
readOnly?: boolean
children?: React.ReactNode
}
const InputCopy = ({
value = '',
className,
readOnly = true,
children,
}: IInputCopyProps) => {
const [isCopied, setIsCopied] = useState(false)
@ -45,23 +43,12 @@ const InputCopy = ({
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
position='bottom'
>
{value}
<span className='text-text-secondary'>{value}</span>
</Tooltip>
</div>
</div>
<div className="h-4 shrink-0 border bg-divider-regular" />
<Tooltip
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
position='bottom'
>
<div className="shrink-0 px-0.5">
<div className={`box-border flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={() => {
copy(value)
setIsCopied(true)
}}>
</div>
</div>
</Tooltip>
<div className="h-4 w-px shrink-0 bg-divider-regular" />
<div className='mx-1'><CopyFeedback content={value} /></div>
</div>
</div>
)

View File

@ -383,6 +383,69 @@ The text generation application offers non-session support and is ideal for tran
---
<Heading
url='/app/feedbacks'
method='GET'
title='Get feedbacks of application'
name='#app-feedbacks'
/>
<Row>
<Col>
Get application's feedbacks.
### Query
<Properties>
<Property name='page' type='string' key='page'>
optionalpaginationdefault1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
optional records per page default20
</Property>
</Properties>
### Response
- `data` (List) return apps feedback list.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/text-to-audio'
method='POST'
@ -584,3 +647,62 @@ The text generation application offers non-session support and is ideal for tran
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL.
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -381,6 +381,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Row>
---
<Heading
url='/app/feedbacks'
method='GET'
title='アプリのメッセージの「いいね」とフィードバックを取得'
name='#app-feedbacks'
/>
<Row>
<Col>
アプリのエンドユーザーからのフィードバックや「いいね」を取得します。
### クエリ
<Properties>
<Property name='page' type='string' key='page'>
任意ページ番号。デフォルト値1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
任意1ページあたりの件数。デフォルト値20
</Property>
</Properties>
### レスポンス
- `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/text-to-audio'
method='POST'
@ -582,3 +645,62 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### レスポンス
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -355,6 +355,68 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Col>
</Row>
---
<Heading
url='/app/feedbacks'
method='GET'
title='Get feedbacks of application'
name='#app-feedbacks'
/>
<Row>
<Col>
Get application's feedbacks.
### Query
<Properties>
<Property name='page' type='string' key='page'>
optionalpaginationdefault1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
optional records per page default20
</Property>
</Properties>
### Response
- `data` (List) return apps feedback list.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
@ -445,7 +507,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
</Col>
</Row>
---
<Heading
@ -550,6 +611,64 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'
@ -738,8 +857,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -487,6 +487,69 @@ Chat applications support session persistence, allowing previous chat history to
---
<Heading
url='/app/feedbacks'
method='GET'
title='Get feedbacks of application'
name='#app-feedbacks'
/>
<Row>
<Col>
Get application's feedbacks.
### Query
<Properties>
<Property name='page' type='string' key='page'>
optionalpaginationdefault1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
optional records per page default20
</Property>
</Properties>
### Response
- `data` (List) return apps feedback list.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -765,10 +828,8 @@ Chat applications support session persistence, allowing previous chat history to
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1244,6 +1305,64 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'
@ -1432,8 +1551,8 @@ Chat applications support session persistence, allowing previous chat history to
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -487,6 +487,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
---
<Heading
url='/app/feedbacks'
method='GET'
title='アプリのメッセージの「いいね」とフィードバックを取得'
name='#app-feedbacks'
/>
<Row>
<Col>
アプリのエンドユーザーからのフィードバックや「いいね」を取得します。
### クエリ
<Properties>
<Property name='page' type='string' key='page'>
任意ページ番号。デフォルト値1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
任意1ページあたりの件数。デフォルト値20
</Property>
</Properties>
### レスポンス
- `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -764,10 +828,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"result": "success"
}
```text {{ title: '応答' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1241,3 +1303,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -493,6 +493,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
---
<Heading
url='/app/feedbacks'
method='GET'
title='获取APP的消息点赞和反馈'
name='#app-feedbacks'
/>
<Row>
<Col>
获取应用的终端用户反馈、点赞。
### Query
<Properties>
<Property name='page' type='string' key='page'>
选填分页默认值1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
选填每页数量默认值20
</Property>
</Properties>
### Response
- `data` (List) 返回该APP的点赞、反馈列表。
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -799,10 +864,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1266,7 +1329,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
@ -1456,8 +1575,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -450,6 +450,69 @@ Chat applications support session persistence, allowing previous chat history to
---
<Heading
url='/app/feedbacks'
method='GET'
title='Get feedbacks of application'
name='#app-feedbacks'
/>
<Row>
<Col>
Get application's feedbacks.
### Query
<Properties>
<Property name='page' type='string' key='page'>
optionalpaginationdefault1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
optional records per page default20
</Property>
</Properties>
### Response
- `data` (List) return apps feedback list.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -798,10 +861,8 @@ Chat applications support session persistence, allowing previous chat history to
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1280,6 +1341,64 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `chat_color_theme` (string) Chat color theme, in hex format
- `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
- `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___
<Heading
url='/apps/annotations'
method='GET'
@ -1472,8 +1591,8 @@ Chat applications support session persistence, allowing previous chat history to
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>

View File

@ -450,6 +450,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
---
<Heading
url='/app/feedbacks'
method='GET'
title='アプリのメッセージの「いいね」とフィードバックを取得'
name='#app-feedbacks'
/>
<Row>
<Col>
アプリのエンドユーザーからのフィードバックや「いいね」を取得します。
### クエリ
<Properties>
<Property name='page' type='string' key='page'>
任意ページ番号。デフォルト値1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
任意1ページあたりの件数。デフォルト値20
</Property>
</Properties>
### レスポンス
- `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -797,10 +861,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
<CodeGroup title="応答">
```json {{ title: '応答' }}
{
"result": "success"
}
```text {{ title: '応答' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1268,3 +1330,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `chat_color_theme` (string) チャットの色テーマ、16進数形式
- `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
- `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -464,6 +464,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
---
<Heading
url='/app/feedbacks'
method='GET'
title='获取APP的消息点赞和反馈'
name='#app-feedbacks'
/>
<Row>
<Col>
获取应用的终端用户反馈、点赞。
### Query
<Properties>
<Property name='page' type='string' key='page'>
选填分页默认值1
</Property>
</Properties>
<Properties>
<Property name='limit' type='string' key='limit'>
选填每页数量默认值20
</Property>
</Properties>
### Response
- `data` (List) 返回该APP的点赞、反馈列表。
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": [
{
"id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25",
"app_id": "f252d396-fe48-450e-94ec-e184218e7346",
"conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d",
"message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11",
"rating": "like",
"content": "message feedback information-3",
"from_source": "user",
"from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5",
"from_account_id": null,
"created_at": "2025-04-24T09:24:38",
"updated_at": "2025-04-24T09:24:38"
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/messages/{message_id}/suggested'
method='GET'
@ -811,10 +874,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"result": "success"
}
```text {{ title: 'Response' }}
204 No Content
```
</CodeGroup>
</Col>
@ -1271,3 +1332,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `chat_color_theme` (string) 聊天颜色主题, hex 格式
- `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
- `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"chat_color_theme": "#ff4a4a",
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
"use_icon_as_answer_icon": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -90,7 +90,7 @@ Workflow applications offers non-session support and is ideal for translation, a
Each streaming chunk starts with `data:`, separated by two newline characters `\n\n`, as shown below:
<CodeGroup>
```streaming {{ title: 'Response' }}
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n
```
</CodeGroup>
The structure of the streaming chunks varies depending on the `event`:
@ -116,6 +116,13 @@ Workflow applications offers non-session support and is ideal for translation, a
- `predecessor_node_id` (string) optional Prefix node ID, used for canvas display execution path
- `inputs` (object) Contents of all preceding node variables used in the node
- `created_at` (timestamp) timestamp of start, e.g., 1705395332
- `event: text_chunk` Text fragment
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
- `workflow_run_id` (string) Unique ID of workflow execution
- `event` (string) fixed to `text_chunk`
- `data` (object) detail
- `text` (string) Text content
- `from_variable_selector` (array) Text source path, helping developers understand which node and variable generated the text
- `event: node_finished` node execution ends, success or failure in different states in the same event
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
- `workflow_run_id` (string) Unique ID of workflow execution
@ -730,3 +737,56 @@ Workflow applications offers non-session support and is ideal for translation, a
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='Get Application WebApp Settings'
name='#site'
/>
<Row>
<Col>
Used to get the WebApp settings of the application.
### Response
- `title` (string) WebApp name
- `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture
- `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL.
- `icon_background` (string) Background color in hex format
- `icon_url` (string) Icon URL
- `description` (string) Description
- `copyright` (string) Copyright information
- `privacy_policy` (string) Privacy policy link
- `custom_disclaimer` (string) Custom disclaimer
- `default_language` (string) Default language
- `show_workflow_steps` (bool) Whether to show workflow details
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -93,7 +93,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます。以下のように表示されます:
<CodeGroup>
```streaming {{ title: '応答' }}
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n
```
</CodeGroup>
ストリーミングチャンクの構造は`event`に応じて異なります:
@ -119,6 +119,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `predecessor_node_id` (string) オプションのプレフィックスードID、キャンバス表示実行パスに使用
- `inputs` (object) ノードで使用されるすべての前のノード変数の内容
- `created_at` (timestamp) 開始のタイムスタンプ、例1705395332
- `event: text_chunk` テキストフラグメント
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
- `event` (string) `text_chunk`に固定
- `data` (object) 詳細
- `text` (string) テキスト内容
- `from_variable_selector` (array) テキスト生成元パス(開発者がどのノードのどの変数から生成されたかを理解するための情報)
- `event: node_finished` ノード実行終了、同じイベントで異なる状態で成功または失敗
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `workflow_run_id` (string) ワークフロー実行の一意のID
@ -733,3 +740,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</CodeGroup>
</Col>
</Row>
———
<Heading
url='/site'
method='GET'
title='アプリのWebApp設定を取得'
name='#site'
/>
<Row>
<Col>
アプリのWebApp設定を取得するために使用します。
### 応答
- `title` (string) WebApp名
- `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像
- `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL
- `icon_background` (string) 16進数形式の背景色
- `icon_url` (string) アイコンのURL
- `description` (string) 説明
- `copyright` (string) 著作権情報
- `privacy_policy` (string) プライバシーポリシーのリンク
- `custom_disclaimer` (string) カスタム免責事項
- `default_language` (string) デフォルト言語
- `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -87,7 +87,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
每个流式块均为 data: 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示:
<CodeGroup>
```streaming {{ title: 'Response' }}
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n
```
</CodeGroup>
流式块中根据 `event` 不同,结构也不同,包含以下类型:
@ -113,6 +113,13 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
- `predecessor_node_id` (string) 前置节点 ID用于画布展示执行路径
- `inputs` (object) 节点中所有使用到的前置节点变量内容
- `created_at` (timestamp) 开始时间
- `event: text_chunk` 文本片段
- `task_id` (string) 任务 ID用于请求跟踪和下方的停止响应接口
- `workflow_run_id` (string) workflow 执行 ID
- `event` (string) 固定为 `text_chunk`
- `data` (object) 详细内容
- `text` (string) 文本内容
- `from_variable_selector` (array) 文本来源路径,帮助开发者了解文本是由哪个节点的哪个变量生成的
- `event: node_finished` node 执行结束,成功失败同一事件中不同状态
- `task_id` (string) 任务 ID用于请求跟踪和下方的停止响应接口
- `workflow_run_id` (string) workflow 执行 ID
@ -720,3 +727,57 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/site'
method='GET'
title='获取应用 WebApp 设置'
name='#site'
/>
<Row>
<Col>
用于获取应用的 WebApp 设置
### Response
- `title` (string) WebApp 名称
- `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片
- `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL
- `icon_background` (string) hex 格式的背景色
- `icon_url` (string) 图标 URL
- `description` (string) 描述
- `copyright` (string) 版权信息
- `privacy_policy` (string) 隐私政策链接
- `custom_disclaimer` (string) 自定义免责声明
- `default_language` (string) 默认语言
- `show_workflow_steps` (bool) 是否显示工作流详情
</Col>
<Col>
<CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/site' \
-H 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"title": "My App",
"icon_type": "emoji",
"icon": "😄",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": "This is my app.",
"copyright": "all rights reserved",
"privacy_policy": "",
"custom_disclaimer": "All generated by AI",
"default_language": "en-US",
"show_workflow_steps": false,
}
```
</CodeGroup>
</Col>
</Row>
___

View File

@ -31,8 +31,8 @@ const Category: FC<ICategoryProps> = ({
const isAllCategories = !list.includes(value as AppCategory) || value === allCategoriesEn
const itemClassName = (isSelected: boolean) => cn(
'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-gray-700 hover:bg-gray-200',
isSelected && 'border-gray-200 bg-white text-primary-600 shadow-xs hover:bg-white',
'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-text-tertiary hover:bg-components-main-nav-nav-button-bg-active',
isSelected && 'border-components-main-nav-nav-button-border bg-components-main-nav-nav-button-bg-active text-components-main-nav-nav-button-text-active shadow-xs',
)
return (
@ -50,7 +50,7 @@ const Category: FC<ICategoryProps> = ({
className={itemClassName(name === value)}
onClick={() => onChange(name)}
>
{categoryI18n[name] ? t(`explore.category.${name}`) : name}
{(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name}
</div>
))}
</div>

View File

@ -13,6 +13,7 @@ import {
RiMap2Line,
RiSettings3Line,
RiStarLine,
RiTShirt2Line,
} from '@remixicon/react'
import Link from 'next/link'
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
@ -24,6 +25,7 @@ import Compliance from './compliance'
import PremiumBadge from '@/app/components/base/premium-badge'
import { useGetDocLanguage } from '@/context/i18n'
import Avatar from '@/app/components/base/avatar'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { logout } from '@/service/common'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
@ -81,8 +83,8 @@ export default function AppSelector() {
<MenuItems
className="
absolute right-0 mt-1.5 w-60 max-w-80
origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur
shadow-lg focus:outline-none
origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur shadow-lg
backdrop-blur-sm focus:outline-none
"
>
<MenuItem disabled>
@ -187,6 +189,15 @@ export default function AppSelector() {
}
</div>
</>}
<MenuItem disabled>
<div className='p-1'>
<div className={cn(itemClassName, 'hover:bg-transparent')}>
<RiTShirt2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.theme.theme')}</div>
<ThemeSwitcher />
</div>
</div>
</MenuItem>
<MenuItem>
<div className='p-1' onClick={() => handleLogout()}>
<div

View File

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react'
import { RiArrowDownSLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { WEB_PREFIX } from '@/config'
import PlanBadge from '@/app/components/header/plan-badge'
import { switchWorkspace } from '@/service/common'
import { useWorkspacesContext } from '@/context/workspace-context'
@ -23,7 +23,7 @@ const WorkplaceSelector = () => {
return
await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
location.assign(`${location.origin}${basePath}`)
location.assign(WEB_PREFIX)
}
catch {
notify({ type: 'error', message: t('common.provider.saveFailed') })

View File

@ -2,6 +2,7 @@
import cn from '@/utils/classnames'
import Modal from '@/app/components/base/modal'
import Input from '@/app/components/base/input'
import { WEB_PREFIX } from '@/config'
import { useTranslation } from 'react-i18next'
import { useState } from 'react'
import { useContext } from 'use-context-selector'
@ -33,7 +34,7 @@ const EditWorkspaceModal = ({
},
})
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
location.assign(`${location.origin}`)
location.assign(WEB_PREFIX)
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })

View File

@ -1,6 +1,5 @@
'use client'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { basePath } from '@/utils/var'
import { t } from 'i18next'
import copy from 'copy-to-clipboard'
import s from './index.module.css'
@ -19,7 +18,8 @@ const InvitationLink = ({
const selector = useRef(`invite-link-${randomString(4)}`)
const copyHandle = useCallback(() => {
copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${basePath}${value.url}`)
// No prefix is needed here because the backend has already processed it
copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${value.url}`)
setIsCopied(true)
}, [value])
@ -42,7 +42,7 @@ const InvitationLink = ({
<Tooltip
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
>
<div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2' onClick={copyHandle}>{basePath + value.url}</div>
<div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2' onClick={copyHandle}>{value.url}</div>
</Tooltip>
</div>
<div className="h-4 shrink-0 border bg-divider-regular" />

View File

@ -1,4 +1,5 @@
import { useCallback, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import {
@ -29,6 +30,7 @@ const InstallFromMarketplace = ({
searchText,
}: InstallFromMarketplaceProps) => {
const { t } = useTranslation()
const { theme } = useTheme()
const [collapse, setCollapse] = useState(false)
const locale = getLocaleOnClient()
const {
@ -53,7 +55,7 @@ const InstallFromMarketplace = ({
</div>
<div className='mb-2 flex items-center pt-2'>
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='system-sm-medium inline-flex items-center text-text-accent'>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}${theme ? `?theme=${theme}` : ''}`} className='system-sm-medium inline-flex items-center text-text-accent'>
{t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='h-4 w-4' />
</Link>

View File

@ -1,6 +1,5 @@
import type { FC } from 'react'
import type { ModelProvider } from '../declarations'
import { basePath } from '@/utils/var'
import { useLanguage } from '../hooks'
import { Openai } from '@/app/components/base/icons/src/vender/other'
import { AnthropicDark, AnthropicLight } from '@/app/components/base/icons/src/public/llm'
@ -41,7 +40,7 @@ const ProviderIcon: FC<ProviderIconProps> = ({
<div className={cn('inline-flex items-center gap-2', className)}>
<img
alt='provider-icon'
src={basePath + renderI18nObject(provider.icon_small, language)}
src={renderI18nObject(provider.icon_small, language)}
className='h-6 w-6'
/>
<div className='system-md-semibold text-text-primary'>

View File

@ -14,7 +14,6 @@ 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)
@ -57,7 +56,7 @@ const DatasetNav = () => {
icon_background: dataset.icon_background,
})) as NavItem[]}
createText={t('common.menus.newDataset')}
onCreate={() => router.push(`${basePath}/datasets/create`)}
onCreate={() => router.push('/datasets/create')}
onLoadmore={handleLoadmore}
/>
)

View File

@ -1,4 +1,5 @@
'use client'
import { useTheme } from 'next-themes'
import { RiArrowRightUpLine } from '@remixicon/react'
import { getPluginLinkInMarketplace } from '../utils'
import Card from '@/app/components/plugins/card'
@ -22,6 +23,7 @@ const CardWrapper = ({
locale,
}: CardWrapperProps) => {
const { t } = useMixedTranslation(locale)
const { theme } = useTheme()
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
@ -54,7 +56,7 @@ const CardWrapper = ({
>
{t('plugin.detailPanel.operation.install')}
</Button>
<a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
<a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
<Button
className='w-full gap-0.5'
>

View File

@ -1,4 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import {
@ -49,6 +50,7 @@ const DetailHeader = ({
onUpdate,
}: Props) => {
const { t } = useTranslation()
const { theme } = useTheme()
const locale = useGetLanguage()
const { checkForUpdates, fetchReleases } = useGitHubReleases()
const { setShowUpdatePluginModal } = useModalContext()
@ -85,9 +87,9 @@ const DetailHeader = ({
if (isFromGitHub)
return `https://github.com/${meta!.repo}`
if (isFromMarketplace)
return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`
return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`
return ''
}, [author, isFromGitHub, isFromMarketplace, meta, name])
}, [author, isFromGitHub, isFromMarketplace, meta, name, theme])
const [isShowUpdateModal, {
setTrue: showUpdateModal,

View File

@ -149,7 +149,7 @@ const EndpointCard = ({
</ActionButton>
</div>
</div>
{data.declaration.endpoints.map((endpoint, index) => (
{data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
<div key={index} className='flex h-6 items-center'>
<div className='system-xs-regular w-12 shrink-0 text-text-tertiary'>{endpoint.method}</div>
<div className='group/item system-xs-regular flex grow items-center truncate text-text-secondary'>

View File

@ -183,7 +183,7 @@ const ReasoningConfigForm: React.FC<Props> = ({
<>
{isString && (
<Input
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')}
className={cn(inputsIsFocus[variable] ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'rounded-lg border px-3 py-[6px]')}
value={varInput?.value as string || ''}
onChange={handleMixedTypeChange(variable)}
nodesOutputVars={nodeOutputVars}

View File

@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import React, { useMemo } from 'react'
import { useTheme } from 'next-themes'
import {
RiArrowRightUpLine,
RiBugLine,
@ -38,6 +39,7 @@ const PluginItem: FC<Props> = ({
plugin,
}) => {
const { t } = useTranslation()
const { theme } = useTheme()
const { categoriesMap } = useSingleCategories()
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
@ -164,7 +166,7 @@ const PluginItem: FC<Props> = ({
}
{source === PluginSource.marketplace
&& <>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='flex items-center gap-0.5'>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'>
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div>
<RiArrowRightUpLine className='h-3 w-3 text-text-tertiary' />
</a>

View File

@ -1,6 +1,7 @@
'use client'
import React from 'react'
import type { FC } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine } from '@remixicon/react'
import Badge from '../base/badge'
@ -28,6 +29,7 @@ const ProviderCard: FC<Props> = ({
}) => {
const getValueFromI18nObject = useRenderI18nObject()
const { t } = useTranslation()
const { theme } = useTheme()
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
@ -74,7 +76,7 @@ const ProviderCard: FC<Props> = ({
className='grow'
variant='secondary'
>
<a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}`} target='_blank' className='flex items-center gap-0.5'>
<a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'>
{t('plugin.detailPanel.operation.detail')}
<RiArrowRightUpLine className='h-4 w-4' />
</a>

View File

@ -36,6 +36,7 @@ export type PluginEndpointDeclaration = {
export type EndpointItem = {
path: string
method: string
hidden?: boolean
}
export type EndpointListItem = {

View File

@ -16,6 +16,7 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import type { SiteInfo } from '@/models/share'
import cn from '@/utils/classnames'
@ -70,6 +71,13 @@ const MenuDropdown: FC<Props> = ({
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-50'>
<div className='w-[224px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
<div className='p-1'>
<div className={cn('system-md-regular flex cursor-pointer items-center rounded-lg py-1.5 pl-3 pr-2 text-text-secondary')}>
<div className='grow'>{t('common.theme.theme')}</div>
<ThemeSwitcher />
</div>
</div>
<Divider type='horizontal' className='my-0' />
<div className='p-1'>
{data?.privacy_policy && (
<a href={data.privacy_policy} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
@ -83,17 +91,6 @@ const MenuDropdown: FC<Props> = ({
}}
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
>{t('common.userProfile.about')}</div>
{false && (
<>
<Divider />
<div
onClick={() => {
handleLogout()
}}
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-destructive hover:bg-state-base-hover'
>{t('common.userProfile.logout')}</div>
</>
)}
</div>
</div>
</PortalToFollowElemContent>

View File

@ -2,7 +2,7 @@ import {
memo,
useCallback,
} from 'react'
import { basePath } from '@/utils/var'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import {
RiAddLine,
@ -54,7 +54,7 @@ const Blocks = ({
>
<div className='flex h-[22px] w-full items-center justify-between pl-3 pr-1 text-xs font-medium text-gray-500'>
{toolWithProvider.label[language]}
<a className='hidden cursor-pointer items-center group-hover:flex' href={`${basePath}/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 h-3 w-3' /></a>
<Link className='hidden cursor-pointer items-center group-hover:flex' href={`/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 h-3 w-3' /></Link>
</div>
{list.map((tool) => {
const labelContent = (() => {

View File

@ -2,6 +2,7 @@ import {
useEffect,
useRef,
} from 'react'
import { useTheme } from 'next-themes'
import {
RiArrowRightUpLine,
RiArrowUpDoubleLine,
@ -25,7 +26,7 @@ const Marketplace = ({
}: MarketplaceProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation()
const { theme } = useTheme()
const {
isLoading,
marketplaceCollections,
@ -83,7 +84,7 @@ const Marketplace = ({
</span>
{t('common.operation.in')}
<a
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}`}
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}${theme ? `&theme=${theme}` : ''}`}
className='system-sm-medium ml-1 flex items-center text-text-accent'
target='_blank'
>

View File

@ -53,7 +53,10 @@ const ProviderList = () => {
})
}, [activeTab, tagFilterValue, keywords, collectionList])
const [currentProvider, setCurrentProvider] = useState<Collection | undefined>()
const [currentProviderId, setCurrentProviderId] = useState<string | undefined>()
const currentProvider = useMemo<Collection | undefined>(() => {
return filteredCollectionList.find(collection => collection.id === currentProviderId)
}, [currentProviderId, filteredCollectionList])
const { data: pluginList } = useInstalledPluginList()
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const currentPluginDetail = useMemo(() => {
@ -70,14 +73,14 @@ const ProviderList = () => {
>
<div className={cn(
'sticky top-0 z-20 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]',
currentProvider && 'pr-6',
currentProviderId && 'pr-6',
)}>
<TabSliderNew
value={activeTab}
onChange={(state) => {
setActiveTab(state)
if (state !== activeTab)
setCurrentProvider(undefined)
setCurrentProviderId(undefined)
}}
options={options}
/>
@ -93,47 +96,43 @@ const ProviderList = () => {
/>
</div>
</div>
{
(filteredCollectionList.length > 0 || activeTab !== 'builtin') && (
<div className={cn(
'relative grid shrink-0 grid-cols-1 content-start gap-4 px-12 pb-4 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4',
!filteredCollectionList.length && activeTab === 'workflow' && 'grow',
)}>
{activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
{filteredCollectionList.map(collection => (
<div
key={collection.id}
onClick={() => setCurrentProvider(collection)}
>
<Card
className={cn(
'cursor-pointer border-[1.5px] border-transparent',
currentProvider?.id === collection.id && 'border-components-option-card-option-selected-border',
)}
hideCornerMark
payload={{
...collection,
brief: collection.description,
org: collection.plugin_id ? collection.plugin_id.split('/')[0] : '',
name: collection.plugin_id ? collection.plugin_id.split('/')[1] : collection.name,
} as any}
footer={
<CardMoreInfo
tags={collection.labels}
/>
}
/>
</div>
))}
{!filteredCollectionList.length && activeTab === 'workflow' && <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'><WorkflowToolEmpty /></div>}
</div>
)
}
{
!filteredCollectionList.length && activeTab === 'builtin' && (
<Empty lightCard text={t('tools.noTools')} className='h-[224px] px-12' />
)
}
{(filteredCollectionList.length > 0 || activeTab !== 'builtin') && (
<div className={cn(
'relative grid shrink-0 grid-cols-1 content-start gap-4 px-12 pb-4 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4',
!filteredCollectionList.length && activeTab === 'workflow' && 'grow',
)}>
{activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
{filteredCollectionList.map(collection => (
<div
key={collection.id}
onClick={() => setCurrentProviderId(collection.id)}
>
<Card
className={cn(
'cursor-pointer border-[1.5px] border-transparent',
currentProviderId === collection.id && 'border-components-option-card-option-selected-border',
)}
hideCornerMark
payload={{
...collection,
brief: collection.description,
org: collection.plugin_id ? collection.plugin_id.split('/')[0] : '',
name: collection.plugin_id ? collection.plugin_id.split('/')[1] : collection.name,
} as any}
footer={
<CardMoreInfo
tags={collection.labels}
/>
}
/>
</div>
))}
{!filteredCollectionList.length && activeTab === 'workflow' && <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'><WorkflowToolEmpty /></div>}
</div>
)}
{!filteredCollectionList.length && activeTab === 'builtin' && (
<Empty lightCard text={t('tools.noTools')} className='h-[224px] px-12' />
)}
{
enable_marketplace && activeTab === 'builtin' && (
<Marketplace
@ -150,14 +149,14 @@ const ProviderList = () => {
{currentProvider && !currentProvider.plugin_id && (
<ProviderDetail
collection={currentProvider}
onHide={() => setCurrentProvider(undefined)}
onHide={() => setCurrentProviderId(undefined)}
onRefreshData={refetch}
/>
)}
<PluginDetailPanel
detail={currentPluginDetail}
onUpdate={() => invalidateInstalledPluginList()}
onHide={() => setCurrentProvider(undefined)}
onHide={() => setCurrentProviderId(undefined)}
/>
</>
)

View File

@ -28,7 +28,7 @@ const Contribute = ({ onRefreshData }: Props) => {
const linkUrl = useMemo(() => {
if (language.startsWith('zh_'))
return 'https://docs.dify.ai/zh-hans/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju'
return 'https://docs.dify.ai/guides/tools#how-to-create-custom-tools'
return 'https://docs.dify.ai/en/guides/tools#how-to-create-custom-tools'
}, [language])
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)

View File

@ -6,7 +6,7 @@ import {
RiCloseLine,
} from '@remixicon/react'
import { AuthHeaderPrefix, AuthType, CollectionType } from '../types'
import { basePath } from '@/utils/var'
import Link from 'next/link'
import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types'
import ToolItem from './tool-item'
import cn from '@/utils/classnames'
@ -279,10 +279,10 @@ const ProviderDetail = ({
variant='primary'
className={cn('my-3 w-[183px] shrink-0')}
>
<a className='flex items-center' href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
<Link className='flex items-center' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
<div className='system-sm-medium'>{t('tools.openInStudio')}</div>
<LinkExternal02 className='ml-1 h-4 w-4' />
</a>
</Link>
</Button>
<Button
className={cn('my-3 w-[183px] shrink-0')}

View File

@ -8,6 +8,7 @@ import type { WorkflowProps } from '@/app/components/workflow'
import WorkflowChildren from './workflow-children'
import {
useNodesSyncDraft,
useWorkflowRefreshDraft,
useWorkflowRun,
useWorkflowStartRun,
} from '../hooks'
@ -32,6 +33,7 @@ const WorkflowMain = ({
doSyncWorkflowDraft,
syncWorkflowDraftWhenPageClose,
} = useNodesSyncDraft()
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const {
handleBackupDraft,
handleLoadBackupDraft,
@ -49,6 +51,7 @@ const WorkflowMain = ({
return {
syncWorkflowDraftWhenPageClose,
doSyncWorkflowDraft,
handleRefreshWorkflowDraft,
handleBackupDraft,
handleLoadBackupDraft,
handleRestoreFromPublishedWorkflow,
@ -61,6 +64,7 @@ const WorkflowMain = ({
}, [
syncWorkflowDraftWhenPageClose,
doSyncWorkflowDraft,
handleRefreshWorkflowDraft,
handleBackupDraft,
handleLoadBackupDraft,
handleRestoreFromPublishedWorkflow,

View File

@ -4,3 +4,4 @@ export * from './use-nodes-sync-draft'
export * from './use-workflow-run'
export * from './use-workflow-start-run'
export * from './use-is-chat-mode'
export * from './use-workflow-refresh-draft'

View File

@ -6,20 +6,20 @@ import {
useWorkflowStore,
} from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
import {
useNodesReadOnly,
} from '@/app/components/workflow/hooks/use-workflow'
import { syncWorkflowDraft } from '@/service/workflow'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { API_PREFIX } from '@/config'
import { useWorkflowRefreshDraft } from '.'
export const useNodesSyncDraft = () => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const featuresStore = useFeaturesStore()
const { getNodesReadOnly } = useNodesReadOnly()
const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const params = useParams()
const getPostParams = useCallback(() => {

View File

@ -0,0 +1,36 @@
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { fetchWorkflowDraft } from '@/service/workflow'
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
export const useWorkflowRefreshDraft = () => {
const workflowStore = useWorkflowStore()
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
const handleRefreshWorkflowDraft = useCallback(() => {
const {
appId,
setSyncWorkflowDraftHash,
setIsSyncingWorkflowDraft,
setEnvironmentVariables,
setEnvSecrets,
setConversationVariables,
} = workflowStore.getState()
setIsSyncingWorkflowDraft(true)
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater)
setSyncWorkflowDraftHash(response.hash)
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
acc[env.id] = env.value
return acc
}, {} as Record<string, string>))
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
setConversationVariables(response.conversation_variables || [])
}).finally(() => setIsSyncingWorkflowDraft(false))
}, [handleUpdateWorkflowCanvas, workflowStore])
return {
handleRefreshWorkflowDraft,
}
}

View File

@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import ActionButton from '@/app/components/base/action-button'
@ -31,6 +32,7 @@ const OperationDropdown: FC<Props> = ({
version,
}) => {
const { t } = useTranslation()
const { theme } = useTheme()
const openRef = useRef(open)
const setOpen = useCallback((v: boolean) => {
onOpenChange(v)
@ -78,7 +80,7 @@ const OperationDropdown: FC<Props> = ({
<PortalToFollowElemContent className='z-[9999]'>
<div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>

View File

@ -31,6 +31,7 @@ type NodesExtraData = {
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
checkValid: any
checkVarValid?: any
}
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
[BlockEnum.Start]: {
@ -59,6 +60,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: AnswerDefault.getAvailablePrevNodes,
getAvailableNextNodes: AnswerDefault.getAvailableNextNodes,
checkValid: AnswerDefault.checkValid,
checkVarValid: AnswerDefault.checkVarValid,
},
[BlockEnum.LLM]: {
author: 'Dify',
@ -68,6 +70,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes,
getAvailableNextNodes: LLMDefault.getAvailableNextNodes,
checkValid: LLMDefault.checkValid,
checkVarValid: LLMDefault.checkVarValid,
},
[BlockEnum.KnowledgeRetrieval]: {
author: 'Dify',
@ -77,6 +80,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: KnowledgeRetrievalDefault.getAvailablePrevNodes,
getAvailableNextNodes: KnowledgeRetrievalDefault.getAvailableNextNodes,
checkValid: KnowledgeRetrievalDefault.checkValid,
checkVarValid: KnowledgeRetrievalDefault.checkVarValid,
},
[BlockEnum.IfElse]: {
author: 'Dify',
@ -86,6 +90,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: IfElseDefault.getAvailablePrevNodes,
getAvailableNextNodes: IfElseDefault.getAvailableNextNodes,
checkValid: IfElseDefault.checkValid,
checkVarValid: IfElseDefault.checkVarValid,
},
[BlockEnum.Iteration]: {
author: 'Dify',
@ -95,6 +100,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: IterationDefault.getAvailablePrevNodes,
getAvailableNextNodes: IterationDefault.getAvailableNextNodes,
checkValid: IterationDefault.checkValid,
checkVarValid: IterationDefault.checkVarValid,
},
[BlockEnum.IterationStart]: {
author: 'Dify',
@ -140,6 +146,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: CodeDefault.getAvailablePrevNodes,
getAvailableNextNodes: CodeDefault.getAvailableNextNodes,
checkValid: CodeDefault.checkValid,
checkVarValid: CodeDefault.checkVarValid,
},
[BlockEnum.TemplateTransform]: {
author: 'Dify',
@ -149,6 +156,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: TemplateTransformDefault.getAvailablePrevNodes,
getAvailableNextNodes: TemplateTransformDefault.getAvailableNextNodes,
checkValid: TemplateTransformDefault.checkValid,
checkVarValid: TemplateTransformDefault.checkVarValid,
},
[BlockEnum.QuestionClassifier]: {
author: 'Dify',
@ -158,6 +166,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: QuestionClassifierDefault.getAvailablePrevNodes,
getAvailableNextNodes: QuestionClassifierDefault.getAvailableNextNodes,
checkValid: QuestionClassifierDefault.checkValid,
checkVarValid: QuestionClassifierDefault.checkVarValid,
},
[BlockEnum.HttpRequest]: {
author: 'Dify',
@ -167,6 +176,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: HttpRequestDefault.getAvailablePrevNodes,
getAvailableNextNodes: HttpRequestDefault.getAvailableNextNodes,
checkValid: HttpRequestDefault.checkValid,
checkVarValid: HttpRequestDefault.checkVarValid,
},
[BlockEnum.VariableAssigner]: {
author: 'Dify',
@ -185,6 +195,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: AssignerDefault.getAvailablePrevNodes,
getAvailableNextNodes: AssignerDefault.getAvailableNextNodes,
checkValid: AssignerDefault.checkValid,
checkVarValid: AssignerDefault.checkVarValid,
},
[BlockEnum.VariableAggregator]: {
author: 'Dify',
@ -194,6 +205,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes,
getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes,
checkValid: VariableAssignerDefault.checkValid,
checkVarValid: VariableAssignerDefault.checkVarValid,
},
[BlockEnum.ParameterExtractor]: {
author: 'Dify',
@ -203,6 +215,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: ParameterExtractorDefault.getAvailablePrevNodes,
getAvailableNextNodes: ParameterExtractorDefault.getAvailableNextNodes,
checkValid: ParameterExtractorDefault.checkValid,
checkVarValid: ParameterExtractorDefault.checkVarValid,
},
[BlockEnum.Tool]: {
author: 'Dify',
@ -212,6 +225,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: ToolDefault.getAvailablePrevNodes,
getAvailableNextNodes: ToolDefault.getAvailableNextNodes,
checkValid: ToolDefault.checkValid,
checkVarValid: ToolDefault.checkVarValid,
},
[BlockEnum.DocExtractor]: {
author: 'Dify',
@ -221,6 +235,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: DocExtractorDefault.getAvailablePrevNodes,
getAvailableNextNodes: DocExtractorDefault.getAvailableNextNodes,
checkValid: DocExtractorDefault.checkValid,
checkVarValid: DocExtractorDefault.checkVarValid,
},
[BlockEnum.ListFilter]: {
author: 'Dify',
@ -230,6 +245,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes,
getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes,
checkValid: ListFilterDefault.checkValid,
checkVarValid: ListFilterDefault.checkVarValid,
},
[BlockEnum.Agent]: {
author: 'Dify',

View File

@ -140,6 +140,16 @@ const WorkflowChecklist = ({
</div>
)
}
{
node.varErrorMessage?.map((errorMessage: string) => (
<div className='rounded-b-lg bg-gray-25 px-3 py-2'>
<div className='flex text-xs leading-[18px] text-gray-500'>
<AlertTriangle className='mr-2 mt-[3px] h-3 w-3 text-[#F79009]' />
{errorMessage}
</div>
</div>
))
}
</div>
</div>
))

View File

@ -18,6 +18,7 @@ type CommonHooksFnMap = {
}
) => Promise<void>
syncWorkflowDraftWhenPageClose: () => void
handleRefreshWorkflowDraft: () => void
handleBackupDraft: () => void
handleLoadBackupDraft: () => void
handleRestoreFromPublishedWorkflow: (...args: any[]) => void
@ -35,6 +36,7 @@ export type Shape = {
export const createHooksStore = ({
doSyncWorkflowDraft = async () => noop(),
syncWorkflowDraftWhenPageClose = noop,
handleRefreshWorkflowDraft = noop,
handleBackupDraft = noop,
handleLoadBackupDraft = noop,
handleRestoreFromPublishedWorkflow = noop,
@ -48,6 +50,7 @@ export const createHooksStore = ({
refreshAll: props => set(state => ({ ...state, ...props })),
doSyncWorkflowDraft,
syncWorkflowDraftWhenPageClose,
handleRefreshWorkflowDraft,
handleBackupDraft,
handleLoadBackupDraft,
handleRestoreFromPublishedWorkflow,

View File

@ -16,3 +16,4 @@ export * from './use-shortcuts'
export * from './use-workflow-interactions'
export * from './use-workflow-mode'
export * from './use-format-time-from-now'
export * from './use-workflow-refresh-draft'

View File

@ -15,6 +15,7 @@ import { useStore } from '../store'
import {
getToolCheckParams,
getValidTreeNodes,
transformStartNodeVariables,
} from '../utils'
import {
CUSTOM_NODE,
@ -45,6 +46,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const { data: strategyProviders } = useStrategyProviders()
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
const chatVarList = useStore(s => s.conversationVariables)
const environmentVariables = useStore(s => s.environmentVariables)
const getCheckData = useCallback((data: CommonNodeType<{}>) => {
let checkData = data
if (data.type === BlockEnum.KnowledgeRetrieval) {
@ -64,7 +68,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const needWarningNodes = useMemo(() => {
const list = []
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true)
const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables)
const errMessageMap = new Map()
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
@ -110,8 +117,32 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage,
varErrorMessage: [],
})
}
errMessageMap.set(node.id, list[list.length - 1])
if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) {
const { errorMessage: varErrorMessages } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t)
if (varErrorMessages?.length) {
const errMessage = errMessageMap.get(node.id)
if (errMessage) {
errMessage.varErrorMessage = varErrorMessages
}
else {
list.push({
id: node.id,
type: node.data.type,
title: node.data.title,
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage: '',
varErrorMessage: varErrorMessages,
})
errMessageMap.set(node.id, list[list.length - 1])
}
}
}
}
}
@ -133,8 +164,13 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
})
}
for (let i = 0; i < validNodes.length; i++) {
const node = validNodes[i]
delete node._parentOutputVarMap
}
return list
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData])
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData, chatVarList, environmentVariables])
return needWarningNodes
}
@ -153,6 +189,9 @@ export const useChecklistBeforePublish = () => {
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
const updateTime = useRef(0)
const chatVarList = useStore(s => s.conversationVariables)
const environmentVariables = useStore(s => s.environmentVariables)
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
let checkData = data
if (data.type === BlockEnum.KnowledgeRetrieval) {
@ -183,12 +222,15 @@ export const useChecklistBeforePublish = () => {
const {
validNodes,
maxDepth,
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true)
if (maxDepth > MAX_TREE_DEPTH) {
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) })
return false
}
const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables)
// Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
@ -239,6 +281,14 @@ export const useChecklistBeforePublish = () => {
notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnectTip')}` })
return false
}
if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) {
const { errorMessage: varErrorMessage } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t)
if (varErrorMessage?.length) {
notify({ type: 'error', message: `[${node.data.title}] ${varErrorMessage[0]}` })
return false
}
}
}
if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) {
@ -252,7 +302,7 @@ export const useChecklistBeforePublish = () => {
}
return true
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData])
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, chatVarList, environmentVariables])
return {
handleCheckBeforePublish,

View File

@ -313,7 +313,6 @@ export const useWorkflowZoom = () => {
export const useWorkflowUpdate = () => {
const reactflow = useReactFlow()
const workflowStore = useWorkflowStore()
const { eventEmitter } = useEventEmitterContextContext()
const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdater) => {
@ -333,32 +332,8 @@ export const useWorkflowUpdate = () => {
setViewport(viewport)
}, [eventEmitter, reactflow])
const handleRefreshWorkflowDraft = useCallback(() => {
const {
appId,
setSyncWorkflowDraftHash,
setIsSyncingWorkflowDraft,
setEnvironmentVariables,
setEnvSecrets,
setConversationVariables,
} = workflowStore.getState()
setIsSyncingWorkflowDraft(true)
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater)
setSyncWorkflowDraftHash(response.hash)
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
acc[env.id] = env.value
return acc
}, {} as Record<string, string>))
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
// #TODO chatVar sync#
setConversationVariables(response.conversation_variables || [])
}).finally(() => setIsSyncingWorkflowDraft(false))
}, [handleUpdateWorkflowCanvas, workflowStore])
return {
handleUpdateWorkflowCanvas,
handleRefreshWorkflowDraft,
}
}

View File

@ -0,0 +1,9 @@
import { useHooksStore } from '@/app/components/workflow/hooks-store'
export const useWorkflowRefreshDraft = () => {
const handleRefreshWorkflowDraft = useHooksStore(s => s.handleRefreshWorkflowDraft)
return {
handleRefreshWorkflowDraft,
}
}

View File

@ -67,6 +67,9 @@ export const useWorkflowNodeStarted = () => {
incomeEdges.forEach((edge) => {
const incomeNode = nodes.find(node => node.id === edge.source)!
if (!incomeNode || !('data' in incomeNode))
return
if (
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)

View File

@ -44,7 +44,7 @@ import {
useShortcuts,
useWorkflow,
useWorkflowReadOnly,
useWorkflowUpdate,
useWorkflowRefreshDraft,
} from './hooks'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
@ -160,7 +160,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {
if (document.visibilityState === 'hidden')
syncWorkflowDraftWhenPageClose()

View File

@ -66,6 +66,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
case FormTypeEnum.textInput: {
const def = schema as CredentialFormSchemaTextInput
const value = props.value[schema.variable] || schema.default
const instanceId = schema.variable
const onChange = (value: string) => {
props.onChange({ ...props.value, [schema.variable]: value })
}
@ -77,6 +78,8 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
value={value}
onChange={onChange}
onGenerated={handleGenerated}
instanceId={instanceId}
key={instanceId}
title={renderI18nObject(schema.label)}
headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase'
containerBackgroundClassName='bg-transparent'
@ -223,7 +226,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
<Link href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/zh-hans/guides/workflow/node/agent#xuan-ze-agent-ce-le'
: 'https://docs.dify.ai/guides/workflow/node/agent#select-an-agent-strategy'
: 'https://docs.dify.ai/en/guides/workflow/node/agent#select-an-agent-strategy'
} className='text-text-accent-secondary' target='_blank'>
{t('workflow.nodes.agent.learnMore')}
</Link>

View File

@ -3,6 +3,7 @@ import type { FC } from 'react'
import Editor, { loader } from '@monaco-editor/react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import Base from '../base'
import { WEB_PREFIX } from '@/config'
import cn from '@/utils/classnames'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import {
@ -14,7 +15,7 @@ import './style.css'
import { noop } from 'lodash-es'
// load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482
loader.config({ paths: { vs: '/vs' } })
loader.config({ paths: { vs: `${WEB_PREFIX}/vs` } })
const CODE_EDITOR_LINE_HEIGHT = 18

View File

@ -34,7 +34,7 @@ const DefaultValue = ({
{t('workflow.nodes.common.errorHandle.defaultValue.desc')}
&nbsp;
<a
href='https://docs.dify.ai/guides/workflow/error-handling'
href='https://docs.dify.ai/en/guides/workflow/error-handling/README'
target='_blank'
className='text-text-accent'
>

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