Merge branch 'main' into feat/rag-pipeline

This commit is contained in:
twwu
2025-05-30 15:10:16 +08:00
265 changed files with 3761 additions and 1208 deletions

View File

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useMemo, useState } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import copy from 'copy-to-clipboard'
@ -32,6 +32,7 @@ import cn from '@/utils/classnames'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import { canFindTool } from '@/utils'
import { useMittContextSelector } from '@/context/mitt-context'
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
const AgentTools: FC = () => {
@ -39,7 +40,6 @@ const AgentTools: FC = () => {
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
const currentCollection = useMemo(() => {
if (!currentTool) return null
@ -61,6 +61,17 @@ const AgentTools: FC = () => {
collection,
}
})
const useSubscribe = useMittContextSelector(s => s.useSubscribe)
const handleUpdateToolsWhenInstallToolSuccess = useCallback((installedPluginNames: string[]) => {
const newModelConfig = produce(modelConfig, (draft) => {
draft.agentConfig.tools.forEach((item: any) => {
if (item.isDeleted && installedPluginNames.includes(item.provider_id))
item.isDeleted = false
})
})
setModelConfig(newModelConfig)
}, [modelConfig, setModelConfig])
useSubscribe('plugin:install:success', handleUpdateToolsWhenInstallToolSuccess as any)
const handleToolSettingChange = (value: Record<string, any>) => {
const newModelConfig = produce(modelConfig, (draft) => {
@ -132,7 +143,7 @@ const AgentTools: FC = () => {
disabled={false}
supportAddCustomTool
onSelect={handleSelectTool}
selectedTools={tools}
selectedTools={tools as any}
/>
</>
)}

View File

@ -99,7 +99,15 @@ const DebugWithMultipleModel = () => {
}, [twoLine, threeLine, fourLine])
const setShowAppConfigureFeaturesModal = useAppStore(s => s.setShowAppConfigureFeaturesModal)
const inputsForm = modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({ ...item, label: item.name, variable: item.key })) as InputForm[]
const inputsForm = modelConfig.configs.prompt_variables
.filter(item => item.type !== 'api')
.map(item => ({
...item,
label: item.name,
variable: item.key,
hide: item.hide ?? false,
required: item.required ?? false,
})) as InputForm[]
return (
<div className='flex h-full flex-col'>
@ -133,6 +141,7 @@ const DebugWithMultipleModel = () => {
{isChatMode && (
<div className='shrink-0 px-6 pb-0'>
<ChatInputArea
botName='Bot'
showFeatureBar
showFileUpload={false}
onFeatureBarClick={setShowAppConfigureFeaturesModal}

View File

@ -79,6 +79,7 @@ import {
} from '@/utils'
import PluginDependency from '@/app/components/workflow/plugin-dependency'
import { supportFunctionCall } from '@/utils/tool-call'
import { MittProvider } from '@/context/mitt-context'
type PublishConfig = {
modelConfig: ModelConfig
@ -908,7 +909,7 @@ const Configuration: FC = () => {
}}
>
<FeaturesProvider features={featuresData}>
<>
<MittProvider>
<div className="flex h-full flex-col">
<div className='relative flex h-[200px] grow pt-14'>
{/* Header */}
@ -1060,7 +1061,7 @@ const Configuration: FC = () => {
/>
)}
<PluginDependency />
</>
</MittProvider>
</FeaturesProvider>
</ConfigContext.Provider>
)

View File

@ -29,6 +29,7 @@ import type { FileUpload } from '@/app/components/base/features/types'
import { TransferMethod } from '@/types/app'
type ChatInputAreaProps = {
botName?: string
showFeatureBar?: boolean
showFileUpload?: boolean
featureBarDisabled?: boolean
@ -43,6 +44,7 @@ type ChatInputAreaProps = {
disabled?: boolean
}
const ChatInputArea = ({
botName,
showFeatureBar,
showFileUpload,
featureBarDisabled,
@ -192,7 +194,7 @@ const ChatInputArea = ({
className={cn(
'body-lg-regular w-full resize-none bg-transparent p-1 leading-6 text-text-tertiary outline-none',
)}
placeholder={t('common.chat.inputPlaceholder') || ''}
placeholder={t('common.chat.inputPlaceholder', { botName }) || ''}
autoFocus
minRows={1}
onResize={handleTextareaResize}

View File

@ -303,6 +303,7 @@ const Chat: FC<ChatProps> = ({
{
!noChatInput && (
<ChatInputArea
botName={appData?.site.title || ''}
disabled={inputDisabled}
showFeatureBar={showFeatureBar}
showFileUpload={showFileUpload}

View File

@ -117,7 +117,7 @@ const Question: FC<QuestionProps> = ({
</div>
<div
ref={contentRef}
className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900'
className='w-full rounded-2xl bg-background-gradient-bg-fill-chat-bubble-bg-3 px-4 py-3 text-sm text-text-primary'
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
>
{

View File

@ -12,8 +12,7 @@ export class Theme {
public colorPathOnHeader = 'text-text-primary-on-surface'
public backgroundButtonDefaultColorStyle = 'backgroundColor: #1C64F2'
public roundedBackgroundColorStyle = 'backgroundColor: rgb(245 248 255)'
public chatBubbleColorStyle = 'backgroundColor: rgb(225 239 254)'
public chatBubbleColor = 'rgb(225 239 254)'
public chatBubbleColorStyle = ''
constructor(chatColorTheme: string | null = null, chatColorThemeInverted = false) {
this.chatColorTheme = chatColorTheme
@ -29,7 +28,6 @@ export class Theme {
this.backgroundButtonDefaultColorStyle = `backgroundColor: ${this.primaryColor}; color: ${this.colorFontOnHeaderStyle};`
this.roundedBackgroundColorStyle = `backgroundColor: ${hexToRGBA(this.primaryColor, 0.05)}`
this.chatBubbleColorStyle = `backgroundColor: ${hexToRGBA(this.primaryColor, 0.15)}`
this.chatBubbleColor = `${hexToRGBA(this.primaryColor, 0.15)}`
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './OpenaiTale.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 = 'OpenaiTale'
export default Icon

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './OpenaiYellow.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 = 'OpenaiYellow'
export default Icon

View File

@ -30,7 +30,9 @@ export { default as OpenaiBlue } from './OpenaiBlue'
export { default as OpenaiGreen } from './OpenaiGreen'
export { default as OpenaiText } from './OpenaiText'
export { default as OpenaiTransparent } from './OpenaiTransparent'
export { default as OpenaiTale } from './OpenaiTale'
export { default as OpenaiViolet } from './OpenaiViolet'
export { default as OpenaiYellow } from './OpenaiYellow'
export { default as OpenllmText } from './OpenllmText'
export { default as Openllm } from './Openllm'
export { default as ReplicateText } from './ReplicateText'

View File

@ -0,0 +1,21 @@
/**
* @fileoverview AudioBlock component for rendering audio elements in Markdown.
* Extracted from the main markdown renderer for modularity.
* Uses the AudioGallery component to display audio players.
*/
import React, { memo } from 'react'
import AudioGallery from '@/app/components/base/audio-gallery'
const AudioBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <AudioGallery key={src} srcs={[src]} />
return null
}
return <AudioGallery key={srcs.join()} srcs={srcs} />
})
AudioBlock.displayName = 'AudioBlock'
export default AudioBlock

View File

@ -1,34 +1,19 @@
import ReactMarkdown from 'react-markdown'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import ReactEcharts from 'echarts-for-react'
import 'katex/dist/katex.min.css'
import RemarkMath from 'remark-math'
import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex'
import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import SyntaxHighlighter from 'react-syntax-highlighter'
import {
atelierHeathDark,
atelierHeathLight,
} from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Component, memo, useEffect, useMemo, useRef, useState } from 'react'
import { flow } from 'lodash-es'
import ActionButton from '@/app/components/base/action-button'
import CopyIcon from '@/app/components/base/copy-icon'
import SVGBtn from '@/app/components/base/svg'
import Flowchart from '@/app/components/base/mermaid'
import ImageGallery from '@/app/components/base/image-gallery'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import VideoGallery from '@/app/components/base/video-gallery'
import AudioGallery from '@/app/components/base/audio-gallery'
import MarkdownButton from '@/app/components/base/markdown-blocks/button'
import MarkdownForm from '@/app/components/base/markdown-blocks/form'
import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
import { Theme } from '@/types/app'
import useTheme from '@/hooks/use-theme'
import cn from '@/utils/classnames'
import SVGRenderer from './svg-gallery'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
import ErrorBoundary from '@/app/components/base/markdown/error-boundary'
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
@ -64,50 +49,6 @@ const getCorrectCapitalizationLanguageName = (language: string) => {
return language.charAt(0).toUpperCase() + language.substring(1)
}
const preprocessLaTeX = (content: string) => {
if (typeof content !== 'string')
return content
const codeBlockRegex = /```[\s\S]*?```/g
const codeBlocks = content.match(codeBlockRegex) || []
let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER')
processedContent = flow([
(str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`),
])(processedContent)
codeBlocks.forEach((block) => {
processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block)
})
return processedContent
}
const preprocessThinkTag = (content: string) => {
const thinkOpenTagRegex = /<think>\n/g
const thinkCloseTagRegex = /\n<\/think>/g
return flow([
(str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'),
(str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'),
])(content)
}
export function PreCode(props: { children: any }) {
const ref = useRef<HTMLPreElement>(null)
return (
<pre ref={ref}>
<span
className="copy-code-button"
></span>
{props.children}
</pre>
)
}
// **Add code block
// Avoid error #185 (Maximum update depth exceeded.
// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
@ -444,150 +385,4 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
})
CodeBlock.displayName = 'CodeBlock'
const VideoBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <VideoGallery key={src} srcs={[src]} />
return null
}
return <VideoGallery key={srcs.join()} srcs={srcs} />
})
VideoBlock.displayName = 'VideoBlock'
const AudioBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <AudioGallery key={src} srcs={[src]} />
return null
}
return <AudioGallery key={srcs.join()} srcs={srcs} />
})
AudioBlock.displayName = 'AudioBlock'
const ScriptBlock = memo(({ node }: any) => {
const scriptContent = node.children[0]?.value || ''
return `<script>${scriptContent}</script>`
})
ScriptBlock.displayName = 'ScriptBlock'
const Paragraph = (paragraph: any) => {
const { node }: any = paragraph
const children_node = node.children
if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') {
return (
<div className="markdown-img-wrapper">
<ImageGallery srcs={[children_node[0].properties.src]} />
{
Array.isArray(paragraph.children) && paragraph.children.length > 1 && (
<div className="mt-2">{paragraph.children.slice(1)}</div>
)
}
</div>
)
}
return <p>{paragraph.children}</p>
}
const Img = ({ src }: any) => {
return <div className="markdown-img-wrapper"><ImageGallery srcs={[src]} /></div>
}
const Link = ({ node, children, ...props }: any) => {
if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { onSend } = useChatContext()
const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1])
return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value || ''}>{node.children[0]?.value || ''}</abbr>
}
else {
return <a {...props} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a>
}
}
export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
const latexContent = flow([
preprocessThinkTag,
preprocessLaTeX,
])(props.content)
return (
<div className={cn('markdown-body', '!text-text-primary', props.className)}>
<ReactMarkdown
remarkPlugins={[
RemarkGfm,
[RemarkMath, { singleDollarTextMath: false }],
RemarkBreaks,
]}
rehypePlugins={[
RehypeKatex,
RehypeRaw as any,
// The Rehype plug-in is used to remove the ref attribute of an element
() => {
return (tree) => {
const iterate = (node: any) => {
if (node.type === 'element' && node.properties?.ref)
delete node.properties.ref
if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
node.type = 'text'
node.value = `<${node.tagName}`
}
if (node.children)
node.children.forEach(iterate)
}
tree.children.forEach(iterate)
}
},
]}
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
components={{
code: CodeBlock,
img: Img,
video: VideoBlock,
audio: AudioBlock,
a: Link,
p: Paragraph,
button: MarkdownButton,
form: MarkdownForm,
script: ScriptBlock as any,
details: ThinkBlock,
}}
>
{/* Markdown detect has problem. */}
{latexContent}
</ReactMarkdown>
</div>
)
}
// **Add an ECharts runtime error handler
// Avoid error #7832 (Crash when ECharts accesses undefined objects)
// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash.
export default class ErrorBoundary extends Component {
constructor(props: any) {
super(props)
this.state = { hasError: false }
}
componentDidCatch(error: any, errorInfo: any) {
this.setState({ hasError: true })
console.error(error, errorInfo)
}
render() {
// eslint-disable-next-line ts/ban-ts-comment
// @ts-expect-error
if (this.state.hasError)
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
// eslint-disable-next-line ts/ban-ts-comment
// @ts-expect-error
return this.props.children
}
}
export default CodeBlock

View File

@ -0,0 +1,13 @@
/**
* @fileoverview Img component for rendering <img> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* Uses the ImageGallery component to display images.
*/
import React from 'react'
import ImageGallery from '@/app/components/base/image-gallery'
const Img = ({ src }: any) => {
return <div className="markdown-img-wrapper"><ImageGallery srcs={[src]} /></div>
}
export default Img

View File

@ -0,0 +1,18 @@
/**
* @fileoverview Barrel file for all markdown block components.
* This allows for cleaner imports in other parts of the application.
*/
export { default as AudioBlock } from './audio-block'
export { default as CodeBlock } from './code-block'
export { default as Img } from './img'
export { default as Link } from './link'
export { default as Paragraph } from './paragraph'
export { default as PreCode } from './pre-code'
export { default as ScriptBlock } from './script-block'
export { default as VideoBlock } from './video-block'
// Assuming these are also standalone components in this directory intended for Markdown rendering
export { default as MarkdownButton } from './button'
export { default as MarkdownForm } from './form'
export { default as ThinkBlock } from './think-block'

View File

@ -0,0 +1,21 @@
/**
* @fileoverview Link component for rendering <a> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* Handles special rendering for "abbr:" type links for interactive chat actions.
*/
import React from 'react'
import { useChatContext } from '@/app/components/base/chat/chat/context'
const Link = ({ node, children, ...props }: any) => {
const { onSend } = useChatContext()
if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) {
const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1])
return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value || ''}>{node.children[0]?.value || ''}</abbr>
}
else {
return <a {...props} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a>
}
}
export default Link

View File

@ -0,0 +1,27 @@
/**
* @fileoverview Paragraph component for rendering <p> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* Handles special rendering for paragraphs that directly contain an image.
*/
import React from 'react'
import ImageGallery from '@/app/components/base/image-gallery'
const Paragraph = (paragraph: any) => {
const { node }: any = paragraph
const children_node = node.children
if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') {
return (
<div className="markdown-img-wrapper">
<ImageGallery srcs={[children_node[0].properties.src]} />
{
Array.isArray(paragraph.children) && paragraph.children.length > 1 && (
<div className="mt-2">{paragraph.children.slice(1)}</div>
)
}
</div>
)
}
return <p>{paragraph.children}</p>
}
export default Paragraph

View File

@ -0,0 +1,21 @@
/**
* @fileoverview PreCode component for rendering <pre> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* This is a simple wrapper around the HTML <pre> element.
*/
import React, { useRef } from 'react'
function PreCode(props: { children: any }) {
const ref = useRef<HTMLPreElement>(null)
return (
<pre ref={ref}>
<span
className="copy-code-button"
></span>
{props.children}
</pre>
)
}
export default PreCode

View File

@ -0,0 +1,15 @@
/**
* @fileoverview ScriptBlock component for handling <script> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* Note: Current implementation returns the script tag as a string, which might not execute as expected in React.
* This behavior is preserved from the original implementation and may need review for security and functionality.
*/
import { memo } from 'react'
const ScriptBlock = memo(({ node }: any) => {
const scriptContent = node.children[0]?.value || ''
return `<script>${scriptContent}</script>`
})
ScriptBlock.displayName = 'ScriptBlock'
export default ScriptBlock

View File

@ -0,0 +1,21 @@
/**
* @fileoverview VideoBlock component for rendering video elements in Markdown.
* Extracted from the main markdown renderer for modularity.
* Uses the VideoGallery component to display videos.
*/
import React, { memo } from 'react'
import VideoGallery from '@/app/components/base/video-gallery'
const VideoBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <VideoGallery key={src} srcs={[src]} />
return null
}
return <VideoGallery key={srcs.join()} srcs={srcs} />
})
VideoBlock.displayName = 'VideoBlock'
export default VideoBlock

View File

@ -0,0 +1,33 @@
/**
* @fileoverview ErrorBoundary component for React.
* This component was extracted from the main markdown renderer.
* It catches JavaScript errors anywhere in its child component tree,
* logs those errors, and displays a fallback UI instead of the crashed component tree.
* Primarily used around complex rendering logic like ECharts or SVG within Markdown.
*/
import React, { Component } from 'react'
// **Add an ECharts runtime error handler
// Avoid error #7832 (Crash when ECharts accesses undefined objects)
// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash.
export default class ErrorBoundary extends Component {
constructor(props: any) {
super(props)
this.state = { hasError: false }
}
componentDidCatch(error: any, errorInfo: any) {
this.setState({ hasError: true })
console.error(error, errorInfo)
}
render() {
// eslint-disable-next-line ts/ban-ts-comment
// @ts-expect-error
if (this.state.hasError)
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
// eslint-disable-next-line ts/ban-ts-comment
// @ts-expect-error
return this.props.children
}
}

View File

@ -0,0 +1,87 @@
import ReactMarkdown from 'react-markdown'
import 'katex/dist/katex.min.css'
import RemarkMath from 'remark-math'
import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex'
import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import { flow } from 'lodash-es'
import cn from '@/utils/classnames'
import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
import {
AudioBlock,
CodeBlock,
Img,
Link,
MarkdownButton,
MarkdownForm,
Paragraph,
ScriptBlock,
ThinkBlock,
VideoBlock,
} from '@/app/components/base/markdown-blocks'
/**
* @fileoverview Main Markdown rendering component.
* This file was refactored to extract individual block renderers and utility functions
* into separate modules for better organization and maintainability as of [Date of refactor].
* Further refactoring candidates (custom block components not fitting general categories)
* are noted in their respective files if applicable.
*/
export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
const latexContent = flow([
preprocessThinkTag,
preprocessLaTeX,
])(props.content)
return (
<div className={cn('markdown-body', '!text-text-primary', props.className)}>
<ReactMarkdown
remarkPlugins={[
RemarkGfm,
[RemarkMath, { singleDollarTextMath: false }],
RemarkBreaks,
]}
rehypePlugins={[
RehypeKatex,
RehypeRaw as any,
// The Rehype plug-in is used to remove the ref attribute of an element
() => {
return (tree: any) => {
const iterate = (node: any) => {
if (node.type === 'element' && node.properties?.ref)
delete node.properties.ref
if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
node.type = 'text'
node.value = `<${node.tagName}`
}
if (node.children)
node.children.forEach(iterate)
}
tree.children.forEach(iterate)
}
},
]}
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
components={{
code: CodeBlock,
img: Img,
video: VideoBlock,
audio: AudioBlock,
a: Link,
p: Paragraph,
button: MarkdownButton,
form: MarkdownForm,
script: ScriptBlock as any,
details: ThinkBlock,
}}
>
{/* Markdown detect has problem. */}
{latexContent}
</ReactMarkdown>
</div>
)
}

View File

@ -0,0 +1,37 @@
/**
* @fileoverview Utility functions for preprocessing Markdown content.
* These functions were extracted from the main markdown renderer for better separation of concerns.
* Includes preprocessing for LaTeX and custom "think" tags.
*/
import { flow } from 'lodash-es'
export const preprocessLaTeX = (content: string) => {
if (typeof content !== 'string')
return content
const codeBlockRegex = /```[\s\S]*?```/g
const codeBlocks = content.match(codeBlockRegex) || []
let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER')
processedContent = flow([
(str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`),
(str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`),
])(processedContent)
codeBlocks.forEach((block) => {
processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block)
})
return processedContent
}
export const preprocessThinkTag = (content: string) => {
const thinkOpenTagRegex = /<think>\n/g
const thinkCloseTagRegex = /\n<\/think>/g
return flow([
(str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'),
(str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'),
])(content)
}

View File

@ -130,7 +130,7 @@ const CustomWebAppBrand = () => {
<div className='system-xs-regular text-text-tertiary'>{t('custom.webapp.changeLogoTip')}</div>
</div>
<div className='flex items-center'>
{(uploadDisabled || (!webappLogo && !webappBrandRemoved)) && (
{(!uploadDisabled && webappLogo && !webappBrandRemoved) && (
<>
<Button
variant='ghost'

View File

@ -866,10 +866,10 @@ const StepTwo = ({
<>
<CustomDialog show={isQAConfirmDialogOpen} onClose={() => setIsQAConfirmDialogOpen(false)} className='w-[432px]'>
<header className='mb-4 pt-6'>
<h2 className='text-lg font-semibold'>
<h2 className='text-lg font-semibold text-text-primary'>
{t('datasetCreation.stepTwo.qaSwitchHighQualityTipTitle')}
</h2>
<p className='mt-2 text-sm font-normal'>
<p className='mt-2 text-sm font-normal text-text-secondary'>
{t('datasetCreation.stepTwo.qaSwitchHighQualityTipContent')}
</p>
</header>
@ -929,7 +929,7 @@ const StepTwo = ({
</div>
)}
{hasSetIndexType && indexType === IndexingType.ECONOMICAL && (
<div className='system-xs-medium mt-2'>
<div className='system-xs-medium mt-2 text-text-tertiary'>
{t('datasetCreation.stepTwo.indexSettingTip')}
<Link className='text-text-accent' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
</div>

View File

@ -50,20 +50,20 @@ const CSVDownload: FC<{ docForm: ChunkingMode }> = ({ docForm }) => {
return (
<div className='mt-6'>
<div className='text-sm font-medium text-gray-900'>{t('share.generation.csvStructureTitle')}</div>
<div className='text-sm font-medium text-text-primary'>{t('share.generation.csvStructureTitle')}</div>
<div className='mt-2 max-h-[500px] overflow-auto'>
{docForm === ChunkingMode.qa && (
<table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-gray-200 text-xs'>
<thead className='text-gray-500'>
<table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-subtle text-xs'>
<thead className='text-text-secondary'>
<tr>
<td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.question')}</td>
<td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.answer')}</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.question')}</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.answer')}</td>
</tr>
</thead>
<tbody className='text-gray-700'>
<tbody className='text-text-tertiary'>
<tr>
<td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 1</td>
<td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.answer')} 1</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 1</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.answer')} 1</td>
</tr>
<tr>
<td className='h-9 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 2</td>
@ -73,15 +73,15 @@ const CSVDownload: FC<{ docForm: ChunkingMode }> = ({ docForm }) => {
</table>
)}
{docForm === ChunkingMode.text && (
<table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-gray-200 text-xs'>
<thead className='text-gray-500'>
<table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-subtle text-xs'>
<thead className='text-text-secondary'>
<tr>
<td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.contentTitle')}</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.contentTitle')}</td>
</tr>
</thead>
<tbody className='text-gray-700'>
<tbody className='text-text-tertiary'>
<tr>
<td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 1</td>
<td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 1</td>
</tr>
<tr>
<td className='h-9 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 2</td>

View File

@ -93,29 +93,29 @@ const CSVUploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex h-20 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-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}>
<div className='flex w-full items-center justify-center space-x-2'>
<CSVIcon className="shrink-0" />
<div className='text-gray-500'>
<div className='text-text-secondary'>
{t('datasetDocuments.list.batchModal.csvUploadTitle')}
<span className='cursor-pointer text-primary-400' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span>
<span className='cursor-pointer 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 h-20 items-center rounded-xl border border-gray-200 bg-gray-50 px-6 text-sm font-normal', 'hover:border-[#B2CCFF] hover:bg-[#F5F8FF]')}>
<div className={cn('group flex h-20 items-center rounded-xl border border-components-panel-border bg-components-panel-bg-blur px-6 text-sm font-normal', 'hover:border-divider-subtle hover:bg-components-panel-on-panel-item-bg-hover')}>
<CSVIcon className="shrink-0" />
<div className='ml-2 flex w-0 grow'>
<span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-gray-800'>{file.name.replace(/.csv$/, '')}</span>
<span className='shrink-0 text-gray-500'>.csv</span>
<span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-text-primary'>{file.name.replace(/.csv$/, '')}</span>
<span className='shrink-0 text-text-secondary'>.csv</span>
</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='mx-2 h-4 w-px bg-text-secondary' />
<div className='cursor-pointer p-2' onClick={removeFile}>
<RiDeleteBinLine className='h-4 w-4 text-gray-500' />
<RiDeleteBinLine className='h-4 w-4 text-text-secondary' />
</div>
</div>
</div>

View File

@ -41,9 +41,9 @@ const BatchModal: FC<IBatchModalProps> = ({
return (
<Modal isShow={isShow} onClose={noop} className='!max-w-[520px] !rounded-xl px-8 py-6'>
<div className='relative pb-1 text-xl font-medium leading-[30px] text-gray-900'>{t('datasetDocuments.list.batchModal.title')}</div>
<div className='relative pb-1 text-xl font-medium leading-[30px] text-text-primary'>{t('datasetDocuments.list.batchModal.title')}</div>
<div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onCancel}>
<RiCloseLine className='h-4 w-4 text-gray-500' />
<RiCloseLine className='h-4 w-4 text-text-secondary' />
</div>
<CSVUploader
file={currentCSV}

View File

@ -1298,6 +1298,76 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
method='GET'
title='Get a Chunk Details in a Document'
name='#view_document_chunk'
/>
<Row>
<Col>
Get details of a specific document segment in the specified knowledge base
### Path
<Properties>
<Property name='dataset_id' type='string' key='dataset_id'>
Knowledge Base ID
</Property>
<Property name='document_id' type='string' key='document_id'>
Document ID
</Property>
<Property name='segment_id' type='string' key='segment_id'>
Segment ID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": {
"id": "chunk_id",
"position": 2,
"document_id": "document_id",
"content": "Segment content text",
"sign_content": "Signature content text",
"answer": "Answer content (if in Q&A mode)",
"word_count": 470,
"tokens": 382,
"keywords": ["keyword1", "keyword2"],
"index_node_id": "index_node_id",
"index_node_hash": "index_node_hash",
"hit_count": 0,
"enabled": true,
"status": "completed",
"created_by": "creator_id",
"created_at": creation_timestamp,
"updated_at": update_timestamp,
"indexing_at": indexing_timestamp,
"completed_at": completion_timestamp,
"error": null,
"child_chunks": []
},
"doc_form": "text_model"
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
method='DELETE'
@ -1771,20 +1841,45 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
Query keyword
</Property>
<Property name='retrieval_model' type='object' key='retrieval_model'>
Retrieval model (optional, if not filled, it will be recalled according to the default method)
- <code>search_method</code> (text) Search method: One of the following four keywords is required
- <code>keyword_search</code> Keyword search
- <code>semantic_search</code> Semantic search
- <code>full_text_search</code> Full-text search
- <code>hybrid_search</code> Hybrid search
- <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional)
- <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled
- <code>reranking_provider_name</code> (string) Rerank model provider
- <code>reranking_model_name</code> (string) Rerank model name
- <code>weights</code> (float) Semantic search weight setting in hybrid search mode
- <code>top_k</code> (integer) Number of results to return (optional)
- <code>score_threshold_enabled</code> (bool) Whether to enable score threshold
- <code>score_threshold</code> (float) Score threshold
Retrieval parameters (optional, if not filled, it will be recalled according to the default method)
- <code>search_method</code> (text) Search method: One of the following four keywords is required
- <code>keyword_search</code> Keyword search
- <code>semantic_search</code> Semantic search
- <code>full_text_search</code> Full-text search
- <code>hybrid_search</code> Hybrid search
- <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional)
- <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled
- <code>reranking_provider_name</code> (string) Rerank model provider
- <code>reranking_model_name</code> (string) Rerank model name
- <code>weights</code> (float) Semantic search weight setting in hybrid search mode
- <code>top_k</code> (integer) Number of results to return (optional)
- <code>score_threshold_enabled</code> (bool) Whether to enable score threshold
- <code>score_threshold</code> (float) Score threshold
- <code>metadata_filtering_conditions</code> (object) Metadata filtering conditions
- <code>logical_operator</code> (string) Logical operator: <code>and</code> | <code>or</code>
- <code>conditions</code> (array[object]) Conditions list
- <code>name</code> (string) Metadata field name
- <code>comparison_operator</code> (string) Comparison operator, allowed values:
- String comparison:
- <code>contains</code>: Contains
- <code>not contains</code>: Does not contain
- <code>start with</code>: Starts with
- <code>end with</code>: Ends with
- <code>is</code>: Equals
- <code>is not</code>: Does not equal
- <code>empty</code>: Is empty
- <code>not empty</code>: Is not empty
- Numeric comparison:
- <code>=</code>: Equals
- <code>≠</code>: Does not equal
- <code>></code>: Greater than
- <code>< </code>: Less than
- <code>≥</code>: Greater than or equal
- <code>≤</code>: Less than or equal
- Time comparison:
- <code>before</code>: Before
- <code>after</code>: After
- <code>value</code> (string|number|null) Comparison value
</Property>
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
Unused field
@ -1809,7 +1904,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
"weights": null,
"top_k": 1,
"score_threshold_enabled": false,
"score_threshold": null
"score_threshold": null,
"metadata_filtering_conditions": {
"logical_operator": "and",
"conditions": [
{
"name": "document_name",
"comparison_operator": "contains",
"value": "test"
}
]
}
}
}'`}
>
@ -2089,9 +2194,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
label="/datasets/{dataset_id}/documents/metadata"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"operation_data": [{"document_id": "document_id", "metadata_list": [{"id": "id", "value": "value", "name": "name"}]}]}'`}
>
```bash {{ title: 'cURL' }}
```
</CodeGroup>
```bash {{ title: 'cURL' }} </CodeGroup>
</Col>
</Row>
@ -2246,6 +2349,316 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
</Row>
<hr className='ml-0 mr-0' />
Okay, I will translate the Chinese text in your document while keeping all formatting and code content unchanged.
<Heading
url='/datasets/tags'
method='POST'
title='Create New Knowledge Base Type Tag'
name='#create_new_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) New tag name, required, maximum length 50
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag1"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "testtag1",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='GET'
title='Get Knowledge Base Type Tags'
name='#get_knowledge_type_tags'
/>
<Row>
<Col>
### Request Body
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/datasets/tags"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
[
{
"id": "39d6934c-ed36-463d-b4a7-377fa1503dc0",
"name": "testtag1",
"type": "knowledge",
"binding_count": "0"
},
...
]
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='PATCH'
title='Modify Knowledge Base Type Tag Name'
name='#modify_knowledge_tag_name'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) Modified tag name, required, maximum length 50
</Property>
<Property name='tag_id' type='string'>
(text) Tag ID, required
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="PATCH"
label="/datasets/tags"
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "tag-renamed",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='DELETE'
title='Delete Knowledge Base Type Tag'
name='#delete_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) Tag ID, required
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="DELETE"
label="/datasets/tags"
targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/binding'
method='POST'
title='Bind Dataset to Knowledge Base Type Tag'
name='#bind_dataset_to_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_ids' type='list'>
(list) List of Tag IDs, required
</Property>
<Property name='target_id' type='string'>
(text) Dataset ID, required
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/binding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/unbinding'
method='POST'
title='Unbind Dataset and Knowledge Base Type Tag'
name='#unbind_dataset_and_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) Tag ID, required
</Property>
<Property name='target_id' type='string'>
(text) Dataset ID, required
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/unbinding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/<uuid:dataset_id>/tags'
method='POST'
title='Query Tags Bound to a Dataset'
name='#query_dataset_tags'
/>
<Row>
<Col>
### Path
<Properties>
<Property name='dataset_id' type='string'>
(text) Dataset ID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/<uuid:dataset_id>/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data":
[
{"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524",
"name": "123"
},
...
],
"total": 3
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Row>
<Col>

View File

@ -1057,6 +1057,75 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Heading
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
method='GET'
title='ドキュメントセグメントの詳細を表示'
name='#view_document_segment'
/>
<Row>
<Col>
指定されたナレッジベース内の特定のドキュメントセグメントの詳細を表示します
### パス
<Properties>
<Property name='dataset_id' type='string' key='dataset_id'>
ナレッジベースID
</Property>
<Property name='document_id' type='string' key='document_id'>
ドキュメントID
</Property>
<Property name='segment_id' type='string' key='segment_id'>
セグメントID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="リクエスト"
tag="GET"
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="レスポンス">
```json {{ title: 'Response' }}
{
"data": {
"id": "セグメントID",
"position": 2,
"document_id": "ドキュメントID",
"content": "セグメント内容テキスト",
"sign_content": "署名内容テキスト",
"answer": "回答内容(Q&Aモードの場合)",
"word_count": 470,
"tokens": 382,
"keywords": ["キーワード1", "キーワード2"],
"index_node_id": "インデックスードID",
"index_node_hash": "インデックスノードハッシュ",
"hit_count": 0,
"enabled": true,
"status": "completed",
"created_by": "作成者ID",
"created_at": 作成タイムスタンプ,
"updated_at": 更新タイムスタンプ,
"indexing_at": インデックス作成タイムスタンプ,
"completed_at": 完了タイムスタンプ,
"error": null,
"child_chunks": []
},
"doc_form": "text_model"
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
method='DELETE'
title='ドキュメント内のチャンクを削除'
name='#delete_segment'
@ -1100,7 +1169,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
method='POST'
title='ドキュメント内のチャンクを更新'
name='#update_segment'
@ -1528,20 +1596,45 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
クエリキーワード
</Property>
<Property name='retrieval_model' type='object' key='retrieval_model'>
検索モデル (オプション、入力されない場合はデフォルトの方法でリコールされます)
- <code>search_method</code> (text) 検索方法: 以下の 4 つのキーワードのいずれかが必要です
- <code>keyword_search</code> キーワード検索
- <code>semantic_search</code> セマンティック検索
- <code>full_text_search</code> 全文検索
- <code>hybrid_search</code> ハイブリッド検索
- <code>reranking_enable</code> (bool) 再ランキングを有効にするかどうか、検索モードが semantic_search または hybrid_search の場合に必須 (オプション)
- <code>reranking_mode</code> (object) 再ランキングモデル構成、再ランキングが有効な場合に必須
- <code>reranking_provider_name</code> (string) 再ランキングモデルプロバイダー
- <code>reranking_model_name</code> (string) 再ランキングモデル名
- <code>weights</code> (float) ハイブリッド検索モードでのセマンティック検索の重み設定
- <code>top_k</code> (integer) 返される結果の数 (オプション)
- <code>score_threshold_enabled</code> (bool) スコア閾値を有効にするかどうか
- <code>score_threshold</code> (float) スコア閾値
検索パラメータ(オプション、入力されない場合はデフォルトの方法でリコールされます
- <code>search_method</code> (text) 検索方法: 以下の4つのキーワードのいずれかが必要です
- <code>keyword_search</code> キーワード検索
- <code>semantic_search</code> セマンティック検索
- <code>full_text_search</code> 全文検索
- <code>hybrid_search</code> ハイブリッド検索
- <code>reranking_enable</code> (bool) 再ランキングを有効にするかどうか、検索モードがsemantic_searchまたはhybrid_searchの場合に必須オプション
- <code>reranking_mode</code> (object) 再ランキングモデル構成、再ランキングが有効な場合に必須
- <code>reranking_provider_name</code> (string) 再ランキングモデルプロバイダー
- <code>reranking_model_name</code> (string) 再ランキングモデル名
- <code>weights</code> (float) ハイブリッド検索モードでのセマンティック検索の重み設定
- <code>top_k</code> (integer) 返される結果の数オプション
- <code>score_threshold_enabled</code> (bool) スコア閾値を有効にするかどうか
- <code>score_threshold</code> (float) スコア閾値
- <code>metadata_filtering_conditions</code> (object) メタデータフィルタリング条件
- <code>logical_operator</code> (string) 論理演算子: <code>and</code> | <code>or</code>
- <code>conditions</code> (array[object]) 条件リスト
- <code>name</code> (string) メタデータフィールド名
- <code>comparison_operator</code> (string) 比較演算子、許可される値:
- 文字列比較:
- <code>contains</code>: 含む
- <code>not contains</code>: 含まない
- <code>start with</code>: で始まる
- <code>end with</code>: で終わる
- <code>is</code>: 等しい
- <code>is not</code>: 等しくない
- <code>empty</code>: 空
- <code>not empty</code>: 空でない
- 数値比較:
- <code>=</code>: 等しい
- <code>≠</code>: 等しくない
- <code>></code>: より大きい
- <code>< </code>: より小さい
- <code>≥</code>: 以上
- <code>≤</code>: 以下
- 時間比較:
- <code>before</code>: より前
- <code>after</code>: より後
- <code>value</code> (string|number|null) 比較値
</Property>
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
未使用フィールド
@ -1566,7 +1659,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
"weights": null,
"top_k": 1,
"score_threshold_enabled": false,
"score_threshold": null
"score_threshold": null,
"metadata_filtering_conditions": {
"logical_operator": "and",
"conditions": [
{
"name": "document_name",
"comparison_operator": "contains",
"value": "test"
}
]
}
}
}'`}
>
@ -1898,6 +2001,313 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='POST'
title='ナレッジベースタイプタグの新規作成'
name='#create_new_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) 新しいタグ名、必須、最大長50文字
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag1"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "testtag1",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='GET'
title='ナレッジベースタイプタグの取得'
name='#get_knowledge_type_tags'
/>
<Row>
<Col>
### Request Body
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/datasets/tags"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
[
{
"id": "39d6934c-ed36-463d-b4a7-377fa1503dc0",
"name": "testtag1",
"type": "knowledge",
"binding_count": "0"
},
...
]
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='PATCH'
title='ナレッジベースタイプタグ名の変更'
name='#modify_knowledge_tag_name'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) 変更後のタグ名、必須、最大長50文字
</Property>
<Property name='tag_id' type='string'>
(text) タグID、必須
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="PATCH"
label="/datasets/tags"
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "tag-renamed",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='DELETE'
title='ナレッジベースタイプタグの削除'
name='#delete_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) タグID、必須
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="DELETE"
label="/datasets/tags"
targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/binding'
method='POST'
title='ナレッジベースをナレッジベースタイプタグに紐付け'
name='#bind_dataset_to_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_ids' type='list'>
(list) タグIDリスト、必須
</Property>
<Property name='target_id' type='string'>
(text) ナレッジベースID、必須
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/binding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/unbinding'
method='POST'
title='ナレッジベースとナレッジベースタイプタグの紐付け解除'
name='#unbind_dataset_and_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) タグID、必須
</Property>
<Property name='target_id' type='string'>
(text) ナレッジベースID、必須
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/unbinding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/<uuid:dataset_id>/tags'
method='POST'
title='ナレッジベースに紐付けられたタグの照会'
name='#query_dataset_tags'
/>
<Row>
<Col>
### Path
<Properties>
<Property name='dataset_id' type='string'>
(text) ナレッジベースID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/<uuid:dataset_id>/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data":
[
{"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524",
"name": "123"
},
...
],
"total": 3
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Row>

View File

@ -1351,6 +1351,75 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Heading
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
method='GET'
title='查看文档分段详情'
name='#view_document_segment'
/>
<Row>
<Col>
查看指定知识库中特定文档的分段详情
### Path
<Properties>
<Property name='dataset_id' type='string' key='dataset_id'>
知识库 ID
</Property>
<Property name='document_id' type='string' key='document_id'>
文档 ID
</Property>
<Property name='segment_id' type='string' key='segment_id'>
分段 ID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data": {
"id": "分段唯一ID",
"position": 2,
"document_id": "所属文档ID",
"content": "分段内容文本",
"sign_content": "签名内容文本",
"answer": "答案内容(如果有)",
"word_count": 470,
"tokens": 382,
"keywords": ["关键词1", "关键词2"],
"index_node_id": "索引节点ID",
"index_node_hash": "索引节点哈希值",
"hit_count": 0,
"enabled": true,
"status": "completed",
"created_by": "创建者ID",
"created_at": 创建时间戳,
"updated_at": 更新时间戳,
"indexing_at": 索引时间戳,
"completed_at": 完成时间戳,
"error": null,
"child_chunks": []
},
"doc_form": "text_model"
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
method='POST'
title='更新文档分段'
name='#update_segment'
@ -1827,6 +1896,31 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>top_k</code> (integer) 返回结果数量,非必填
- <code>score_threshold_enabled</code> (bool) 是否开启 score 阈值
- <code>score_threshold</code> (float) Score 阈值
- <code>metadata_filtering_conditions</code> (object) 元数据过滤条件
- <code>logical_operator</code> (string) 逻辑运算符: <code>and</code> | <code>or</code>
- <code>conditions</code> (array[object]) 条件列表
- <code>name</code> (string) 元数据字段名
- <code>comparison_operator</code> (string) 比较运算符,可选值:
- 字符串比较:
- <code>contains</code>: 包含
- <code>not contains</code>: 不包含
- <code>start with</code>: 以...开头
- <code>end with</code>: 以...结尾
- <code>is</code>: 等于
- <code>is not</code>: 不等于
- <code>empty</code>: 为空
- <code>not empty</code>: 不为空
- 数值比较:
- <code>=</code>: 等于
- <code>≠</code>: 不等于
- <code>></code>: 大于
- <code> < </code>: 小于
- <code>≥</code>: 大于等于
- <code>≤</code>: 小于等于
- 时间比较:
- <code>before</code>: 早于
- <code>after</code>: 晚于
- <code>value</code> (string|number|null) 比较值
</Property>
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
未启用字段
@ -1851,7 +1945,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
"weights": null,
"top_k": 1,
"score_threshold_enabled": false,
"score_threshold": null
"score_threshold": null,
"metadata_filtering_conditions": {
"logical_operator": "and",
"conditions": [
{
"name": "document_name",
"comparison_operator": "contains",
"value": "test"
}
]
}
}
}'`}
>
@ -2287,6 +2391,314 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='POST'
title='新增知识库类型标签'
name='#create_new_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) 新标签名称必填最大长度为50
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag1"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "testtag1",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='GET'
title='获取知识库类型标签'
name='#get_knowledge_type_tags'
/>
<Row>
<Col>
### Request Body
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/datasets/tags"
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`}
>
```bash {{ title: 'cURL' }}
curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
[
{
"id": "39d6934c-ed36-463d-b4a7-377fa1503dc0",
"name": "testtag1",
"type": "knowledge",
"binding_count": "0"
},
...
]
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='PATCH'
title='修改知识库类型标签名称'
name='#modify_knowledge_tag_name'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='name' type='string'>
(text) 修改后的标签名称必填最大长度为50
</Property>
<Property name='tag_id' type='string'>
(text) 标签ID必填
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="PATCH"
label="/datasets/tags"
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6",
"name": "tag-renamed",
"type": "knowledge",
"binding_count": 0
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags'
method='DELETE'
title='删除知识库类型标签'
name='#delete_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) 标签ID必填
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="DELETE"
label="/datasets/tags"
targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`}
>
```bash {{ title: 'cURL' }}
curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/binding'
method='POST'
title='绑定知识库到知识库类型标签'
name='#bind_dataset_to_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_ids' type='list'>
(list) 标签ID列表必填
</Property>
<Property name='target_id' type='string'>
(text) 知识库ID必填
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/binding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/tags/unbinding'
method='POST'
title='解绑知识库和知识库类型标签'
name='#unbind_dataset_and_knowledge_tag'
/>
<Row>
<Col>
### Request Body
<Properties>
<Property name='tag_id' type='string'>
(text) 标签ID必填
</Property>
<Property name='target_id' type='string'>
(text) 知识库ID必填
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/tags/unbinding"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{"result": "success"}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Heading
url='/datasets/<uuid:dataset_id>/tags'
method='POST'
title='查询知识库已绑定的标签'
name='#query_dataset_tags'
/>
<Row>
<Col>
### Path
<Properties>
<Property name='dataset_id' type='string'>
(text) 知识库ID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="POST"
label="/datasets/<uuid:dataset_id>/tags"
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`}
>
```bash {{ title: 'cURL' }}
curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: 'Response' }}
{
"data":
[
{"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524",
"name": "123"
},
...
],
"total": 3
}
```
</CodeGroup>
</Col>
</Row>
<hr className='ml-0 mr-0' />
<Row>

View File

@ -5,7 +5,7 @@ import type {
} from '../declarations'
import { useLanguage } from '../hooks'
import { Group } from '@/app/components/base/icons/src/vender/other'
import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
import { OpenaiBlue, OpenaiTale, OpenaiViolet, OpenaiYellow } from '@/app/components/base/icons/src/public/llm'
import cn from '@/utils/classnames'
import { renderI18nObject } from '@/i18n'
@ -22,6 +22,10 @@ const ModelIcon: FC<ModelIconProps> = ({
isDeprecated = false,
}) => {
const language = useLanguage()
if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('o'))
return <div className='flex items-center justify-center'><OpenaiYellow className={cn('h-5 w-5', className)} /></div>
if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.includes('gpt-4.1'))
return <div className='flex items-center justify-center'><OpenaiTale className={cn('h-5 w-5', className)} /></div>
if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.includes('gpt-4o'))
return <div className='flex items-center justify-center'><OpenaiBlue className={cn('h-5 w-5', className)} /></div>
if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('gpt-4'))

View File

@ -9,6 +9,7 @@ import InstallMulti from './install-multi'
import { useInstallOrUpdate } from '@/service/use-plugins'
import useRefreshPluginList from '../../hooks/use-refresh-plugin-list'
import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission'
import { useMittContextSelector } from '@/context/mitt-context'
const i18nPrefix = 'plugin.installModal'
type Props = {
@ -29,6 +30,7 @@ const Install: FC<Props> = ({
isHideButton,
}) => {
const { t } = useTranslation()
const emit = useMittContextSelector(s => s.emit)
const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([])
const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([])
const selectedPluginsNum = selectedPlugins.length
@ -63,8 +65,12 @@ const Install: FC<Props> = ({
})
}))
const hasInstallSuccess = res.some(r => r.success)
if (hasInstallSuccess)
if (hasInstallSuccess) {
refreshPluginList(undefined, true)
emit('plugin:install:success', selectedPlugins.map((p) => {
return `${p.plugin_id}/${p.name}`
}))
}
},
})
const handleInstall = () => {

View File

@ -2,7 +2,7 @@
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BodyType, type HttpNodeType, Method } from '../types'
import { BodyPayloadValueType, BodyType, type HttpNodeType, Method } from '../types'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Textarea from '@/app/components/base/textarea'
@ -51,11 +51,16 @@ const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: str
case '-d':
case '--data':
case '--data-raw':
case '--data-binary':
case '--data-binary': {
if (i + 1 >= args.length)
return { node: null, error: 'Missing data value after -d, --data, --data-raw, or --data-binary.' }
node.body = { type: BodyType.rawText, data: args[++i].replace(/^['"]|['"]$/g, '') }
const bodyPayload = [{
type: BodyPayloadValueType.text,
value: args[++i].replace(/^['"]|['"]$/g, ''),
}]
node.body = { type: BodyType.rawText, data: bodyPayload }
break
}
case '-F':
case '--form': {
if (i + 1 >= args.length)

View File

@ -28,6 +28,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
const { theme } = useTheme()
const monacoRef = useRef<any>(null)
const editorRef = useRef<any>(null)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (monacoRef.current) {
@ -74,6 +75,19 @@ const CodeEditor: FC<CodeEditorProps> = ({
onUpdate?.(value)
}, [onUpdate])
useEffect(() => {
const resizeObserver = new ResizeObserver(() => {
editorRef.current?.layout()
})
if (containerRef.current)
resizeObserver.observe(containerRef.current)
return () => {
resizeObserver.disconnect()
}
}, [])
return (
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', className)}>
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
@ -102,9 +116,11 @@ const CodeEditor: FC<CodeEditorProps> = ({
</Tooltip>
</div>
</div>
<div className={classNames('relative', editorWrapperClassName)}>
<div
ref={containerRef}
className={classNames('relative overflow-hidden', editorWrapperClassName)}
>
<Editor
height='100%'
defaultLanguage='json'
value={value}
onChange={handleEditorChange}
@ -117,7 +133,6 @@ const CodeEditor: FC<CodeEditorProps> = ({
scrollBeyondLastLine: false,
wordWrap: 'on',
wrappingIndent: 'same',
// Add these options
overviewRulerBorder: false,
hideCursorInOverviewRuler: true,
renderLineHighlightOnlyWhenFocus: false,

View File

@ -21,7 +21,7 @@ import { MittProvider, VisualEditorContextProvider, useMittContext } from './vis
import ErrorMessage from './error-message'
import { useVisualEditorStore } from './visual-editor/store'
import Toast from '@/app/components/base/toast'
import { useGetLanguage } from '@/context/i18n'
import { useGetDocLanguage } from '@/context/i18n'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
type JsonSchemaConfigProps = {
@ -47,21 +47,13 @@ const DEFAULT_SCHEMA: SchemaRoot = {
additionalProperties: false,
}
const HELP_DOC_URL = {
zh_Hans: 'https://docs.dify.ai/zh-hans/guides/workflow/structured-outputs',
en_US: 'https://docs.dify.ai/en/guides/workflow/structured-outputs',
ja_JP: 'https://docs.dify.ai/ja-jp/guides/workflow/structured-outputs',
}
type LocaleKey = keyof typeof HELP_DOC_URL
const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
defaultSchema,
onSave,
onClose,
}) => {
const { t } = useTranslation()
const locale = useGetLanguage() as LocaleKey
const docLanguage = useGetDocLanguage()
const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor)
const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA)
const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2))
@ -260,7 +252,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
<div className='flex items-center gap-x-2 p-6 pt-5'>
<a
className='flex grow items-center gap-x-1 text-text-accent'
href={HELP_DOC_URL[locale]}
href={`https://docs.dify.ai/${docLanguage}/guides/workflow/structured-outputs`}
target='_blank'
rel='noopener noreferrer'
>

View File

@ -12,7 +12,7 @@ const SchemaEditor: FC<SchemaEditorProps> = ({
}) => {
return (
<CodeEditor
className='rounded-xl'
className='grow rounded-xl'
editorWrapperClassName='grow'
value={schema}
onUpdate={onUpdate}

View File

@ -34,7 +34,7 @@ const UserInput = () => {
return null
return (
<div className={cn('sticky top-0 z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}>
<div className={cn('relative z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}>
<div className='px-4 pb-4 pt-3'>
{visibleVariables.map((variable, index) => (
<div