-
+ {components?.title ?? }
@@ -77,11 +86,13 @@ const HeaderInNormal = ({
{components?.left}
-
- {components?.chatVariableTrigger}
-
-
-
+ {showContextButtons && (
+
+ {components?.chatVariableTrigger}
+ {showEnvButton && }
+ {showGlobalVariableButton && }
+
+ )}
{components?.middle}
diff --git a/web/app/components/workflow/hooks/__tests__/use-inspect-vars-crud.spec.ts b/web/app/components/workflow/hooks/__tests__/use-inspect-vars-crud.spec.ts
index 193e4307dea..90aa6290fa4 100644
--- a/web/app/components/workflow/hooks/__tests__/use-inspect-vars-crud.spec.ts
+++ b/web/app/components/workflow/hooks/__tests__/use-inspect-vars-crud.spec.ts
@@ -61,7 +61,22 @@ describe('useInspectVarsCrud', () => {
})
})
- it('appends query/files system vars to start-node inspect vars and filters them from the system list', () => {
+ it('should pass the flow id to shared variable queries for app flows', () => {
+ renderWorkflowHook(() => useInspectVarsCrud(), {
+ hooksStoreProps: {
+ configsMap: {
+ flowId: 'app-1',
+ flowType: FlowType.appFlow,
+ fileSettings: {} as never,
+ },
+ },
+ })
+
+ expect(mockUseConversationVarValues).toHaveBeenCalledWith(FlowType.appFlow, 'app-1')
+ expect(mockUseSysVarValues).toHaveBeenCalledWith(FlowType.appFlow, 'app-1')
+ })
+
+ it('should append query and files vars to the start node and keep other system vars separate', () => {
const hasNodeInspectVars = vi.fn(() => true)
const deleteAllInspectorVars = vi.fn()
const fetchInspectVarValue = vi.fn()
@@ -118,11 +133,11 @@ describe('useInspectVarsCrud', () => {
expect(result.current.deleteAllInspectorVars).toBe(deleteAllInspectorVars)
})
- it('uses an empty flow id for rag pipeline conversation and system value queries', () => {
+ it('should use an empty flow id for shared variable queries in rag pipelines', () => {
renderWorkflowHook(() => useInspectVarsCrud(), {
hooksStoreProps: {
configsMap: {
- flowId: 'rag-flow',
+ flowId: 'pipeline-1',
flowType: FlowType.ragPipeline,
fileSettings: {} as never,
},
@@ -132,4 +147,19 @@ describe('useInspectVarsCrud', () => {
expect(mockUseConversationVarValues).toHaveBeenCalledWith(FlowType.ragPipeline, '')
expect(mockUseSysVarValues).toHaveBeenCalledWith(FlowType.ragPipeline, '')
})
+
+ it('should use an empty flow id for shared variable queries in snippets', () => {
+ renderWorkflowHook(() => useInspectVarsCrud(), {
+ hooksStoreProps: {
+ configsMap: {
+ flowId: 'snippet-1',
+ flowType: FlowType.snippet,
+ fileSettings: {} as never,
+ },
+ },
+ })
+
+ expect(mockUseConversationVarValues).toHaveBeenCalledWith(FlowType.snippet, '')
+ expect(mockUseSysVarValues).toHaveBeenCalledWith(FlowType.snippet, '')
+ })
})
diff --git a/web/app/components/workflow/hooks/__tests__/use-nodes-available-var-list.spec.ts b/web/app/components/workflow/hooks/__tests__/use-nodes-available-var-list.spec.ts
index 55db395f2e3..1f5654ebda7 100644
--- a/web/app/components/workflow/hooks/__tests__/use-nodes-available-var-list.spec.ts
+++ b/web/app/components/workflow/hooks/__tests__/use-nodes-available-var-list.spec.ts
@@ -1,5 +1,7 @@
import type { Node, NodeOutPutVar, Var } from '../../types'
import { renderHook } from '@testing-library/react'
+import { useSnippetDetailStore } from '@/app/components/snippets/store'
+import { PipelineInputVarType } from '@/models/pipeline'
import { BlockEnum, VarType } from '../../types'
import useNodesAvailableVarList, { useGetNodesAvailableVarList } from '../use-nodes-available-var-list'
@@ -42,6 +44,8 @@ const outputVars: NodeOutPutVar[] = [{
describe('useNodesAvailableVarList', () => {
beforeEach(() => {
vi.clearAllMocks()
+ globalThis.history.pushState({}, '', '/')
+ useSnippetDetailStore.getState().reset()
mockGetBeforeNodesInSameBranchIncludeParent.mockImplementation((nodeId: string) => [createNode({ id: `before-${nodeId}` })])
mockGetTreeLeafNodes.mockImplementation((nodeId: string) => [createNode({ id: `leaf-${nodeId}` })])
mockGetNodeAvailableVars.mockReturnValue(outputVars)
@@ -76,7 +80,7 @@ describe('useNodesAvailableVarList', () => {
expect(mockGetBeforeNodesInSameBranchIncludeParent).toHaveBeenCalledWith('loop-1')
expect(mockGetBeforeNodesInSameBranchIncludeParent).toHaveBeenCalledWith('child-1')
expect(result.current['loop-1']?.availableNodes.map(node => node.id)).toEqual(['before-loop-1', 'loop-1'])
- expect(result.current['child-1']?.availableVars).toBe(outputVars)
+ expect(result.current['child-1']?.availableVars).toEqual(outputVars)
expect(mockGetNodeAvailableVars).toHaveBeenNthCalledWith(2, expect.objectContaining({
parentNode: loopNode,
isChatMode: true,
@@ -86,6 +90,37 @@ describe('useNodesAvailableVarList', () => {
}))
})
+ it('adds snippet input fields as virtual start variables on snippet canvases', () => {
+ globalThis.history.pushState({}, '', '/snippets/snippet-1/orchestrate')
+ useSnippetDetailStore.getState().setFields([{
+ type: PipelineInputVarType.textInput,
+ label: 'Topic',
+ variable: 'topic',
+ required: true,
+ }])
+
+ const currentNode = createNode({ id: 'node-a' })
+
+ const { result } = renderHook(() => useNodesAvailableVarList([currentNode], {
+ filterVar: () => true,
+ }))
+
+ expect(result.current['node-a']?.availableNodes[0]).toEqual(expect.objectContaining({
+ id: 'start',
+ data: expect.objectContaining({
+ type: BlockEnum.Start,
+ }),
+ }))
+ expect(result.current['node-a']?.availableVars[0]).toEqual(expect.objectContaining({
+ nodeId: 'start',
+ isStartNode: true,
+ vars: [expect.objectContaining({
+ variable: 'topic',
+ type: VarType.string,
+ })],
+ }))
+ })
+
it('returns a callback version that can use leaf nodes or caller-provided nodes', () => {
const firstNode = createNode({ id: 'node-a' })
const secondNode = createNode({ id: 'node-b' })
diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts
index 2a970193834..1bca16aea6a 100644
--- a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts
+++ b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts
@@ -12,9 +12,10 @@ const varsAppendStartNodeKeys = ['query', 'files']
const useInspectVarsCrud = () => {
const partOfNodesWithInspectVars = useStore(s => s.nodesWithInspectVars)
const configsMap = useHooksStore(s => s.configsMap)
- const isRagPipeline = configsMap?.flowType === FlowType.ragPipeline
- const { data: conversationVars } = useConversationVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '')
- const { data: allSystemVars } = useSysVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '')
+ const shouldSkipSharedVariableQueries = configsMap?.flowType === FlowType.ragPipeline || configsMap?.flowType === FlowType.snippet
+ const variableFlowId = shouldSkipSharedVariableQueries ? '' : configsMap?.flowId
+ const { data: conversationVars } = useConversationVarValues(configsMap?.flowType, variableFlowId)
+ const { data: allSystemVars } = useSysVarValues(configsMap?.flowType, variableFlowId)
const { varsAppendStartNode, systemVars } = (() => {
if (allSystemVars?.length === 0)
return { varsAppendStartNode: [], systemVars: [] }
diff --git a/web/app/components/workflow/hooks/use-nodes-available-var-list.ts b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts
index cb04b43002a..23a77ba8498 100644
--- a/web/app/components/workflow/hooks/use-nodes-available-var-list.ts
+++ b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts
@@ -1,10 +1,15 @@
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSnippetDetailStore } from '@/app/components/snippets/store'
import {
useIsChatMode,
useWorkflow,
useWorkflowVariables,
} from '@/app/components/workflow/hooks'
+import {
+ appendSnippetInputFieldVars,
+} from '@/app/components/workflow/nodes/_base/hooks/snippet-input-field-vars'
import { BlockEnum } from '@/app/components/workflow/types'
type Params = {
@@ -41,6 +46,8 @@ const useNodesAvailableVarList = (nodes: Node[], {
onlyLeafNodeVar: false,
filterVar: () => true,
}) => {
+ const { t } = useTranslation()
+ const snippetInputFields = useSnippetDetailStore(s => s.fields)
const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
const { getNodeAvailableVars } = useWorkflowVariables()
const isChatMode = useIsChatMode()
@@ -52,23 +59,31 @@ const useNodesAvailableVarList = (nodes: Node[], {
const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranchIncludeParent(nodeId))
if (node.data.type === BlockEnum.Loop)
availableNodes.push(node)
+ const snippetInputFieldAvailability = appendSnippetInputFieldVars({
+ availableNodes,
+ fields: snippetInputFields,
+ title: t('panelTitle', { ns: 'snippet' }),
+ })
const {
parentNode: iterationNode,
} = getNodeInfo(nodeId, nodes)
- const availableVars = getNodeAvailableVars({
- parentNode: iterationNode,
- beforeNodes: availableNodes,
- isChatMode,
- filterVar,
- hideEnv,
- hideChatVar,
- })
+ const availableVars = [
+ ...snippetInputFieldAvailability.availableVars,
+ ...getNodeAvailableVars({
+ parentNode: iterationNode,
+ beforeNodes: availableNodes,
+ isChatMode,
+ filterVar,
+ hideEnv,
+ hideChatVar,
+ }),
+ ]
const result = {
node,
availableVars,
- availableNodes,
+ availableNodes: snippetInputFieldAvailability.availableNodes,
}
nodeAvailabilityMap[nodeId] = result
})
@@ -76,6 +91,8 @@ const useNodesAvailableVarList = (nodes: Node[], {
}
export const useGetNodesAvailableVarList = () => {
+ const { t } = useTranslation()
+ const snippetInputFields = useSnippetDetailStore(s => s.fields)
const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
const { getNodeAvailableVars } = useWorkflowVariables()
const isChatMode = useIsChatMode()
@@ -96,28 +113,36 @@ export const useGetNodesAvailableVarList = () => {
const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranchIncludeParent(nodeId))
if (node.data.type === BlockEnum.Loop)
availableNodes.push(node)
+ const snippetInputFieldAvailability = appendSnippetInputFieldVars({
+ availableNodes,
+ fields: snippetInputFields,
+ title: t('panelTitle', { ns: 'snippet' }),
+ })
const {
parentNode: iterationNode,
} = getNodeInfo(nodeId, nodes)
- const availableVars = getNodeAvailableVars({
- parentNode: iterationNode,
- beforeNodes: availableNodes,
- isChatMode,
- filterVar,
- hideEnv,
- hideChatVar,
- })
+ const availableVars = [
+ ...snippetInputFieldAvailability.availableVars,
+ ...getNodeAvailableVars({
+ parentNode: iterationNode,
+ beforeNodes: availableNodes,
+ isChatMode,
+ filterVar,
+ hideEnv,
+ hideChatVar,
+ }),
+ ]
const result = {
node,
availableVars,
- availableNodes,
+ availableNodes: snippetInputFieldAvailability.availableNodes,
}
nodeAvailabilityMap[nodeId] = result
})
return nodeAvailabilityMap
- }, [getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode])
+ }, [getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode, snippetInputFields, t])
return {
getNodesAvailableVarList,
}
diff --git a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx
index 87e092402f9..c890aab83de 100644
--- a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx
+++ b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx
@@ -91,6 +91,10 @@ const Add = ({
onOpenChange={handleOpenChange}
disabled={nodesReadOnly}
onSelect={handleSelect}
+ snippetInsertPayload={{
+ prevNodeId: nodeId,
+ prevNodeSourceHandle: sourceHandle,
+ }}
placement="top"
offset={0}
trigger={renderTrigger}
diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx
index 568dfe20b87..1cfb6244837 100644
--- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx
+++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx
@@ -110,6 +110,10 @@ export const NodeTargetHandle = memo(({
open={open}
onOpenChange={handleOpenChange}
onSelect={handleSelect}
+ snippetInsertPayload={{
+ nextNodeId: id,
+ nextNodeTargetHandle: handleId,
+ }}
placement="left"
triggerClassName={open => `
absolute left-0 top-0 opacity-0 pointer-events-none transition-opacity duration-150
@@ -228,6 +232,10 @@ export const NodeSourceHandle = memo(({
open={open}
onOpenChange={handleOpenChange}
onSelect={handleSelect}
+ snippetInsertPayload={{
+ prevNodeId: id,
+ prevNodeSourceHandle: handleId,
+ }}
triggerClassName={open => `
absolute top-0 left-0 opacity-0 pointer-events-none transition-opacity duration-150
${nodeSelectorClassName}
diff --git a/web/app/components/workflow/nodes/_base/hooks/snippet-input-field-vars.ts b/web/app/components/workflow/nodes/_base/hooks/snippet-input-field-vars.ts
new file mode 100644
index 00000000000..a0c84b74955
--- /dev/null
+++ b/web/app/components/workflow/nodes/_base/hooks/snippet-input-field-vars.ts
@@ -0,0 +1,108 @@
+import type { InputVarType, Node, NodeOutPutVar } from '@/app/components/workflow/types'
+import type { SnippetInputField } from '@/models/snippet'
+import { NODE_WIDTH } from '@/app/components/workflow/constants'
+import { BlockEnum } from '@/app/components/workflow/types'
+import { PipelineInputVarType } from '@/models/pipeline'
+import { inputVarTypeToVarType } from '../../data-source/utils'
+
+export const SNIPPET_INPUT_FIELD_NODE_ID = 'start'
+
+export const isSnippetCanvas = () => {
+ if (typeof globalThis.location === 'undefined')
+ return false
+
+ return /^\/snippets\/[^/]+\/orchestrate/.test(globalThis.location.pathname)
+}
+
+const toWorkflowInputType = (type: SnippetInputField['type']) => type as unknown as InputVarType
+
+export const buildSnippetInputFieldNode = (
+ fields: SnippetInputField[],
+ title: string,
+): Node | undefined => {
+ const variables = fields.filter(field => !!field.variable)
+
+ if (!variables.length)
+ return undefined
+
+ return {
+ id: SNIPPET_INPUT_FIELD_NODE_ID,
+ type: 'custom',
+ position: { x: 0, y: 0 },
+ width: NODE_WIDTH,
+ height: 80,
+ data: {
+ title,
+ desc: '',
+ type: BlockEnum.Start,
+ variables: variables.map(field => ({
+ type: toWorkflowInputType(field.type),
+ label: field.label,
+ variable: field.variable,
+ max_length: field.max_length,
+ default: field.default_value,
+ required: field.required,
+ options: field.options,
+ placeholder: field.placeholder,
+ unit: field.unit,
+ allowed_file_upload_methods: field.allowed_file_upload_methods,
+ allowed_file_types: field.allowed_file_types,
+ allowed_file_extensions: field.allowed_file_extensions,
+ })),
+ },
+ } as Node
+}
+
+export const buildSnippetInputFieldVars = (
+ fields: SnippetInputField[],
+ title: string,
+): NodeOutPutVar | undefined => {
+ const vars = fields
+ .filter(field => !!field.variable)
+ .map(field => ({
+ variable: field.variable,
+ type: inputVarTypeToVarType(field.type as PipelineInputVarType),
+ isParagraph: field.type === PipelineInputVarType.paragraph,
+ isSelect: field.type === PipelineInputVarType.select,
+ options: field.options,
+ required: field.required,
+ des: field.label,
+ }))
+
+ if (!vars.length)
+ return undefined
+
+ return {
+ nodeId: SNIPPET_INPUT_FIELD_NODE_ID,
+ title,
+ vars,
+ isStartNode: true,
+ }
+}
+
+export const appendSnippetInputFieldVars = ({
+ availableNodes,
+ fields,
+ title,
+}: {
+ availableNodes: Node[]
+ fields: SnippetInputField[]
+ title: string
+}) => {
+ const shouldAppendSnippetInputFields = isSnippetCanvas()
+ && fields.length > 0
+ && !availableNodes.some(node => node.data.type === BlockEnum.Start)
+ const snippetInputFieldNode = shouldAppendSnippetInputFields
+ ? buildSnippetInputFieldNode(fields, title)
+ : undefined
+ const snippetInputFieldVars = shouldAppendSnippetInputFields
+ ? buildSnippetInputFieldVars(fields, title)
+ : undefined
+
+ return {
+ availableNodes: snippetInputFieldNode
+ ? [snippetInputFieldNode, ...availableNodes]
+ : availableNodes,
+ availableVars: snippetInputFieldVars ? [snippetInputFieldVars] : [],
+ }
+}
diff --git a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts
index f226900899a..e94f94916fc 100644
--- a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts
+++ b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts
@@ -1,4 +1,6 @@
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
+import { useTranslation } from 'react-i18next'
+import { useSnippetDetailStore } from '@/app/components/snippets/store'
import {
useIsChatMode,
useWorkflow,
@@ -7,6 +9,7 @@ import {
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { inputVarTypeToVarType } from '../../data-source/utils'
+import { appendSnippetInputFieldVars } from './snippet-input-field-vars'
import useNodeInfo from './use-node-info'
type Params = {
@@ -28,10 +31,17 @@ const useAvailableVarList = (nodeId: string, {
onlyLeafNodeVar: false,
filterVar: () => true,
}) => {
+ const { t } = useTranslation()
+ const snippetInputFields = useSnippetDetailStore(s => s.fields)
const { getTreeLeafNodes, getNodeById, getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
const { getNodeAvailableVars } = useWorkflowVariables()
const isChatMode = useIsChatMode()
const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranchIncludeParent(nodeId))
+ const snippetInputFieldAvailability = appendSnippetInputFieldVars({
+ availableNodes,
+ fields: snippetInputFields,
+ title: t('panelTitle', { ns: 'snippet' }),
+ })
const {
parentNode: iterationNode,
} = useNodeInfo(nodeId)
@@ -63,20 +73,24 @@ const useAvailableVarList = (nodeId: string, {
})
}
}
- const availableVars = [...getNodeAvailableVars({
- parentNode: iterationNode,
- beforeNodes: availableNodes,
- isChatMode,
- filterVar,
- hideEnv,
- hideChatVar,
- }), ...dataSourceRagVars]
+ const availableVars = [
+ ...snippetInputFieldAvailability.availableVars,
+ ...getNodeAvailableVars({
+ parentNode: iterationNode,
+ beforeNodes: availableNodes,
+ isChatMode,
+ filterVar,
+ hideEnv,
+ hideChatVar,
+ }),
+ ...dataSourceRagVars,
+ ]
return {
availableVars,
- availableNodes,
+ availableNodes: snippetInputFieldAvailability.availableNodes,
availableNodesWithParent: [
- ...availableNodes,
+ ...snippetInputFieldAvailability.availableNodes,
...(isDataSourceNode ? [currNode] : []),
],
}
diff --git a/web/app/components/workflow/nodes/iteration/add-block.tsx b/web/app/components/workflow/nodes/iteration/add-block.tsx
index 31787ebdad2..1b1fa488454 100644
--- a/web/app/components/workflow/nodes/iteration/add-block.tsx
+++ b/web/app/components/workflow/nodes/iteration/add-block.tsx
@@ -68,6 +68,10 @@ const AddBlock = ({
'hover:scale-125 transition-all'}
/>
diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx
index 6e444be2454..ad4de80b791 100644
--- a/web/app/components/workflow/selection-contextmenu.tsx
+++ b/web/app/components/workflow/selection-contextmenu.tsx
@@ -12,11 +12,13 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useReactFlowStore } from 'reactflow'
+import { useCreateSnippetFromSelection } from '@/app/components/snippets/hooks/use-create-snippet-from-selection'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks'
import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history'
import { ShortcutKbd } from './shortcuts/shortcut-kbd'
import { useStore, useWorkflowStore } from './store'
+import { BlockEnum } from './types'
const AlignType = {
Bottom: 'bottom',
@@ -71,6 +73,12 @@ const menuSections: MenuSection[] = [
},
]
+const unsupportedSnippetNodeTypes = new Set([
+ BlockEnum.Answer,
+ BlockEnum.End,
+ BlockEnum.Start,
+])
+
const getAlignableNodes = (nodes: Node[], selectedNodes: Node[]) => {
const selectedNodeIds = new Set(selectedNodes.map(node => node.id))
const childNodeIds = new Set()
@@ -234,8 +242,19 @@ export function SelectionContextmenu({
const selectedNodes = useReactFlowStore(state =>
state.getNodes().filter(node => node.selected),
)
+ const edges = useReactFlowStore(state => state.edges)
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { saveStateToHistory } = useWorkflowHistory()
+ const {
+ createSnippetDialog,
+ handleOpenCreateSnippet,
+ isCreateSnippetDialogOpen,
+ } = useCreateSnippetFromSelection({
+ edges,
+ selectedNodes,
+ onClose,
+ })
+ const canCreateSnippet = selectedNodes.every(node => !unsupportedSnippetNodeTypes.has(node.data.type))
const handleCopyNodes = useCallback(() => {
handleNodesCopy()
@@ -345,57 +364,73 @@ export function SelectionContextmenu({
}, [collaborativeWorkflow, workflowStore, selectedNodes, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory, onClose])
if (!isSelectionContextMenu || selectedNodes.length <= 1)
- return null
+ return isCreateSnippetDialogOpen ? createSnippetDialog : null
return (
-
-
-
- {t('common.copy', { defaultValue: 'common.copy', ns: 'workflow' })}
-
-
-
- {t('common.duplicate', { defaultValue: 'common.duplicate', ns: 'workflow' })}
-
-
-
-
-
-
- {t('operation.delete', { defaultValue: 'operation.delete', ns: 'common' })}
-
-
-
-
- {menuSections.map((section, sectionIndex) => (
-
- {sectionIndex > 0 && }
-
- {t(section.titleKey, { defaultValue: section.titleKey, ns: 'workflow' })}
-
- {section.items.map((item) => {
- return (
+ <>
+
+ {canCreateSnippet && (
+ <>
+
handleAlignNodes(item.alignType)}
+ className="px-3 text-text-secondary"
+ onClick={handleOpenCreateSnippet}
>
-
- {t(item.translationKey, { defaultValue: item.translationKey, ns: 'workflow' })}
+ {t('snippet.createDialogTitle', { defaultValue: 'Create Snippet', ns: 'workflow' })}
- )
- })}
+
+
+ >
+ )}
+
+
+ {t('common.copy', { defaultValue: 'common.copy', ns: 'workflow' })}
+
+
+
+ {t('common.duplicate', { defaultValue: 'common.duplicate', ns: 'workflow' })}
+
+
- ))}
-
+
+
+
+ {t('operation.delete', { defaultValue: 'operation.delete', ns: 'common' })}
+
+
+
+
+ {menuSections.map((section, sectionIndex) => (
+
+ {sectionIndex > 0 && }
+
+ {t(section.titleKey, { defaultValue: section.titleKey, ns: 'workflow' })}
+
+ {section.items.map((item) => {
+ return (
+ handleAlignNodes(item.alignType)}
+ >
+
+ {t(item.translationKey, { defaultValue: item.translationKey, ns: 'workflow' })}
+
+ )
+ })}
+
+ ))}
+
+ {createSnippetDialog}
+ >
)
}
diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts
index 77457b379f9..99ad3af49ea 100644
--- a/web/app/components/workflow/types.ts
+++ b/web/app/components/workflow/types.ts
@@ -498,7 +498,7 @@ export type ChildNodeTypeCount = {
[key: string]: number
}
-const TRIGGER_NODE_TYPES = [
+export const TRIGGER_NODE_TYPES = [
BlockEnum.TriggerSchedule,
BlockEnum.TriggerWebhook,
BlockEnum.TriggerPlugin,
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index 4eb392fb6d3..6c78ac82e75 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -10,7 +10,7 @@ import { getDatasetMap } from '@/env'
import { getLocaleOnServer } from '@/i18n-config/server'
import { headers } from '@/next/headers'
import PartnerStackCookieRecorder from './components/billing/partner-stack/cookie-recorder'
-import CreateAppAttributionBootstrap from './components/create-app-attribution-bootstrap'
+import { CreateAppAttributionBootstrap } from './components/create-app-attribution-bootstrap'
import { AgentationLoader } from './components/devtools/agentation-loader'
import { ReactScanLoader } from './components/devtools/react-scan/loader'
import { I18nServerProvider } from './components/provider/i18n-server'
@@ -51,11 +51,10 @@ const LocaleLayout = async ({
- {/* */}
diff --git a/web/contract/console/apps.ts b/web/contract/console/apps.ts
index bd9e8ed06b5..36233c0e7f1 100644
--- a/web/contract/console/apps.ts
+++ b/web/contract/console/apps.ts
@@ -9,7 +9,7 @@ export type AppListQuery = {
name?: string
mode?: AppModeEnum
tag_ids?: string[]
- is_created_by_me?: boolean
+ creator_id?: string
}
export const appListContract = base
diff --git a/web/contract/console/snippets.ts b/web/contract/console/snippets.ts
new file mode 100644
index 00000000000..ea7974b9175
--- /dev/null
+++ b/web/contract/console/snippets.ts
@@ -0,0 +1,344 @@
+import type {
+ CreateSnippetPayload,
+ IncrementSnippetUseCountResponse,
+ PublishSnippetWorkflowResponse,
+ Snippet,
+ SnippetDraftConfig,
+ SnippetDraftNodeRunPayload,
+ SnippetDraftRunPayload,
+ SnippetDraftSyncPayload,
+ SnippetDraftSyncResponse,
+ SnippetImportPayload,
+ SnippetIterationNodeRunPayload,
+ SnippetListResponse,
+ SnippetLoopNodeRunPayload,
+ SnippetWorkflow,
+ UpdateSnippetPayload,
+ WorkflowNodeExecution,
+ WorkflowNodeExecutionListResponse,
+ WorkflowRunDetail,
+ WorkflowRunPagination,
+} from '@/types/snippet'
+import { type } from '@orpc/contract'
+import { base } from '../base'
+
+export const listCustomizedSnippetsContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets',
+ method: 'GET',
+ })
+ .input(type<{
+ query: {
+ page: number
+ limit: number
+ keyword?: string
+ tag_ids?: string[]
+ creator_id?: string
+ is_published?: boolean
+ }
+ }>())
+ .output(type
())
+
+export const createCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets',
+ method: 'POST',
+ })
+ .input(type<{
+ body: CreateSnippetPayload
+ }>())
+ .output(type())
+
+export const getCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const updateCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}',
+ method: 'PATCH',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ body: UpdateSnippetPayload
+ }>())
+ .output(type())
+
+export const deleteCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}',
+ method: 'DELETE',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const exportCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}/export',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ query: {
+ include_secret?: 'true' | 'false'
+ }
+ }>())
+ .output(type())
+
+export const importCustomizedSnippetContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/imports',
+ method: 'POST',
+ })
+ .input(type<{
+ body: SnippetImportPayload
+ }>())
+ .output(type())
+
+export const confirmSnippetImportContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/imports/{importId}/confirm',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ importId: string
+ }
+ }>())
+ .output(type())
+
+export const checkSnippetDependenciesContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}/check-dependencies',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const incrementSnippetUseCountContract = base
+ .route({
+ path: '/workspaces/current/customized-snippets/{snippetId}/use-count/increment',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const getSnippetDraftWorkflowContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const syncSnippetDraftWorkflowContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ body: SnippetDraftSyncPayload
+ }>())
+ .output(type())
+
+export const getSnippetDraftConfigContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/config',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const getSnippetPublishedWorkflowContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/publish',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const publishSnippetWorkflowContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/publish',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const getSnippetDefaultBlockConfigsContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/default-workflow-block-configs',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ }>())
+ .output(type())
+
+export const listSnippetWorkflowRunsContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflow-runs',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ query: {
+ last_id?: string
+ limit?: number
+ }
+ }>())
+ .output(type())
+
+export const getSnippetWorkflowRunDetailContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflow-runs/{runId}',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ runId: string
+ }
+ }>())
+ .output(type())
+
+export const listSnippetWorkflowRunNodeExecutionsContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflow-runs/{runId}/node-executions',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ runId: string
+ }
+ }>())
+ .output(type())
+
+export const runSnippetDraftNodeContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/nodes/{nodeId}/run',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ nodeId: string
+ }
+ body: SnippetDraftNodeRunPayload
+ }>())
+ .output(type())
+
+export const getSnippetDraftNodeLastRunContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/nodes/{nodeId}/last-run',
+ method: 'GET',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ nodeId: string
+ }
+ }>())
+ .output(type())
+
+export const runSnippetDraftIterationNodeContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/iteration/nodes/{nodeId}/run',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ nodeId: string
+ }
+ body: SnippetIterationNodeRunPayload
+ }>())
+ .output(type())
+
+export const runSnippetDraftLoopNodeContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/loop/nodes/{nodeId}/run',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ nodeId: string
+ }
+ body: SnippetLoopNodeRunPayload
+ }>())
+ .output(type())
+
+export const runSnippetDraftWorkflowContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflows/draft/run',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ }
+ body: SnippetDraftRunPayload
+ }>())
+ .output(type())
+
+export const stopSnippetWorkflowTaskContract = base
+ .route({
+ path: '/snippets/{snippetId}/workflow-runs/tasks/{taskId}/stop',
+ method: 'POST',
+ })
+ .input(type<{
+ params: {
+ snippetId: string
+ taskId: string
+ }
+ }>())
+ .output(type())
diff --git a/web/contract/console/tags.ts b/web/contract/console/tags.ts
index 438da8a95ee..417ec2f9f07 100644
--- a/web/contract/console/tags.ts
+++ b/web/contract/console/tags.ts
@@ -1,7 +1,7 @@
import { type } from '@orpc/contract'
import { base } from '../base'
-export type TagType = 'knowledge' | 'app'
+export type TagType = 'knowledge' | 'app' | 'snippet'
export type Tag = {
id: string
diff --git a/web/contract/router.ts b/web/contract/router.ts
index 19870225514..5bad58e7322 100644
--- a/web/contract/router.ts
+++ b/web/contract/router.ts
@@ -18,6 +18,33 @@ import {
import { changePreferredProviderTypeContract, modelProvidersModelsContract } from './console/model-providers'
import { notificationContract, notificationDismissContract } from './console/notification'
import { pluginCheckInstalledContract, pluginLatestVersionsContract } from './console/plugins'
+import {
+ checkSnippetDependenciesContract,
+ confirmSnippetImportContract,
+ createCustomizedSnippetContract,
+ deleteCustomizedSnippetContract,
+ exportCustomizedSnippetContract,
+ getCustomizedSnippetContract,
+ getSnippetDefaultBlockConfigsContract,
+ getSnippetDraftConfigContract,
+ getSnippetDraftNodeLastRunContract,
+ getSnippetDraftWorkflowContract,
+ getSnippetPublishedWorkflowContract,
+ getSnippetWorkflowRunDetailContract,
+ importCustomizedSnippetContract,
+ incrementSnippetUseCountContract,
+ listCustomizedSnippetsContract,
+ listSnippetWorkflowRunNodeExecutionsContract,
+ listSnippetWorkflowRunsContract,
+ publishSnippetWorkflowContract,
+ runSnippetDraftIterationNodeContract,
+ runSnippetDraftLoopNodeContract,
+ runSnippetDraftNodeContract,
+ runSnippetDraftWorkflowContract,
+ stopSnippetWorkflowTaskContract,
+ syncSnippetDraftWorkflowContract,
+ updateCustomizedSnippetContract,
+} from './console/snippets'
import { systemFeaturesContract } from './console/system'
import {
tagBindingCreateContract,
@@ -110,6 +137,33 @@ export const consoleRouterContract = {
checkInstalled: pluginCheckInstalledContract,
latestVersions: pluginLatestVersionsContract,
},
+ snippets: {
+ list: listCustomizedSnippetsContract,
+ create: createCustomizedSnippetContract,
+ detail: getCustomizedSnippetContract,
+ update: updateCustomizedSnippetContract,
+ delete: deleteCustomizedSnippetContract,
+ export: exportCustomizedSnippetContract,
+ import: importCustomizedSnippetContract,
+ confirmImport: confirmSnippetImportContract,
+ checkDependencies: checkSnippetDependenciesContract,
+ incrementUseCount: incrementSnippetUseCountContract,
+ draftWorkflow: getSnippetDraftWorkflowContract,
+ syncDraftWorkflow: syncSnippetDraftWorkflowContract,
+ draftConfig: getSnippetDraftConfigContract,
+ publishedWorkflow: getSnippetPublishedWorkflowContract,
+ publishWorkflow: publishSnippetWorkflowContract,
+ defaultBlockConfigs: getSnippetDefaultBlockConfigsContract,
+ workflowRuns: listSnippetWorkflowRunsContract,
+ workflowRunDetail: getSnippetWorkflowRunDetailContract,
+ workflowRunNodeExecutions: listSnippetWorkflowRunNodeExecutionsContract,
+ runDraftNode: runSnippetDraftNodeContract,
+ lastDraftNodeRun: getSnippetDraftNodeLastRunContract,
+ runDraftIterationNode: runSnippetDraftIterationNodeContract,
+ runDraftLoopNode: runSnippetDraftLoopNodeContract,
+ runDraftWorkflow: runSnippetDraftWorkflowContract,
+ stopWorkflowTask: stopSnippetWorkflowTaskContract,
+ },
billing: {
...communityContract.billing,
invoices: invoicesContract,
diff --git a/web/features/tag-management/components/tag-filter.tsx b/web/features/tag-management/components/tag-filter.tsx
index 188a052f071..cbcb8e8550d 100644
--- a/web/features/tag-management/components/tag-filter.tsx
+++ b/web/features/tag-management/components/tag-filter.tsx
@@ -82,7 +82,7 @@ export const TagFilter = ({
-
+
{!value.length && t('tag.placeholder', { ns: 'common' })}
{!!value.length && currentTagName}
diff --git a/web/features/tag-management/components/tag-management-modal.tsx b/web/features/tag-management/components/tag-management-modal.tsx
index 2188fb5ec64..49d6b540d44 100644
--- a/web/features/tag-management/components/tag-management-modal.tsx
+++ b/web/features/tag-management/components/tag-management-modal.tsx
@@ -1,4 +1,5 @@
'use client'
+import type { TagType } from '@/contract/console/tags'
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import { useMutation, useQuery } from '@tanstack/react-query'
@@ -8,7 +9,7 @@ import { consoleQuery } from '@/service/client'
import { TagItemEditor } from './tag-item-editor'
type TagManagementModalProps = {
- type: 'knowledge' | 'app'
+ type: TagType
show: boolean
onClose: () => void
onTagsChange?: () => void
diff --git a/web/i18n-config/resources.ts b/web/i18n-config/resources.ts
index 857440a1eea..6cca6e97cba 100644
--- a/web/i18n-config/resources.ts
+++ b/web/i18n-config/resources.ts
@@ -25,6 +25,7 @@ import type plugin from '../i18n/en-US/plugin.json'
import type register from '../i18n/en-US/register.json'
import type runLog from '../i18n/en-US/run-log.json'
import type share from '../i18n/en-US/share.json'
+import type snippet from '../i18n/en-US/snippet.json'
import type time from '../i18n/en-US/time.json'
import type tools from '../i18n/en-US/tools.json'
import type workflow from '../i18n/en-US/workflow.json'
@@ -58,6 +59,7 @@ export type Resources = {
register: typeof register
runLog: typeof runLog
share: typeof share
+ snippet: typeof snippet
time: typeof time
tools: typeof tools
workflow: typeof workflow
@@ -91,6 +93,7 @@ export const namespaces = [
'register',
'runLog',
'share',
+ 'snippet',
'time',
'tools',
'workflow',
diff --git a/web/i18n/ar-TN/common.json b/web/i18n/ar-TN/common.json
index 0a97504080e..10dbbf7e7ee 100644
--- a/web/i18n/ar-TN/common.json
+++ b/web/i18n/ar-TN/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "تصفح",
"imageInput.dropImageHere": "أسقط صورتك هنا، أو",
"imageInput.supportedFormats": "يدعم PNG و JPG و JPEG و WEBP و GIF",
+ "imageUploader.imageList": "قائمة الصور",
"imageUploader.imageUpload": "تحميل الصورة",
"imageUploader.pasteImageLink": "لصق رابط الصورة",
"imageUploader.pasteImageLinkInputPlaceholder": "لصق رابط الصورة هنا",
@@ -512,6 +513,8 @@
"operation.ok": "موافق",
"operation.openInNewTab": "فتح في علامة تبويب جديدة",
"operation.params": "معلمات",
+ "operation.pause": "إيقاف مؤقت",
+ "operation.play": "تشغيل",
"operation.refresh": "إعادة تشغيل",
"operation.regenerate": "إعادة إنشاء",
"operation.reload": "إعادة تحميل",
@@ -519,6 +522,7 @@
"operation.rename": "إعادة تسمية",
"operation.reset": "إعادة تعيين",
"operation.resetKeywords": "إعادة تعيين الكلمات الرئيسية",
+ "operation.retry": "إعادة المحاولة",
"operation.save": "حفظ",
"operation.saveAndEnable": "حفظ وتمكين",
"operation.saveAndRegenerate": "حفظ وإعادة إنشاء القطع الفرعية",
@@ -533,13 +537,19 @@
"operation.skip": "تخطي",
"operation.submit": "إرسال",
"operation.sure": "أنا متأكد",
+ "operation.toggleFullscreen": "تبديل ملء الشاشة",
+ "operation.toggleMute": "تبديل كتم الصوت",
"operation.view": "عرض",
"operation.viewDetails": "عرض التفاصيل",
"operation.viewMore": "عرض المزيد",
"operation.yes": "نعم",
"operation.zoomIn": "تكبير",
"operation.zoomOut": "تصغير",
+ "pagination.editPageNumber": "تعديل رقم الصفحة، الصفحة الحالية {{page}} من {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "عناصر لكل صفحة",
+ "pagination.previous": "Previous page",
"placeholder.input": "يرجى الإدخال",
"placeholder.search": "بحث...",
"placeholder.select": "يرجى التحديد",
@@ -677,5 +687,6 @@
"voiceInput.converting": "التحويل إلى نص...",
"voiceInput.notAllow": "الميكروفون غير مصرح به",
"voiceInput.speaking": "تحدث الآن...",
+ "voiceInput.start": "إدخال صوتي",
"you": "أنت"
}
diff --git a/web/i18n/de-DE/common.json b/web/i18n/de-DE/common.json
index 94fd891d673..5b1e96b3a66 100644
--- a/web/i18n/de-DE/common.json
+++ b/web/i18n/de-DE/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "blättern",
"imageInput.dropImageHere": "Laden Sie Ihr Bild hierher hoch oder",
"imageInput.supportedFormats": "Unterstützt PNG, JPG, JPEG, WEBP und GIF",
+ "imageUploader.imageList": "Bilderliste",
"imageUploader.imageUpload": "Bild-Upload",
"imageUploader.pasteImageLink": "Bildlink einfügen",
"imageUploader.pasteImageLinkInputPlaceholder": "Bildlink hier einfügen",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "In neuem Tab öffnen",
"operation.params": "Parameter",
+ "operation.pause": "Pausieren",
+ "operation.play": "Abspielen",
"operation.refresh": "Neustart",
"operation.regenerate": "Erneuern",
"operation.reload": "Neu laden",
@@ -519,6 +522,7 @@
"operation.rename": "Umbenennen",
"operation.reset": "Zurücksetzen",
"operation.resetKeywords": "Schlüsselwörter zurücksetzen",
+ "operation.retry": "Erneut versuchen",
"operation.save": "Speichern",
"operation.saveAndEnable": "Speichern und Aktivieren",
"operation.saveAndRegenerate": "Speichern und Regenerieren von untergeordneten Chunks",
@@ -533,13 +537,19 @@
"operation.skip": "Schiff",
"operation.submit": "Senden",
"operation.sure": "Ich bin sicher",
+ "operation.toggleFullscreen": "Vollbild umschalten",
+ "operation.toggleMute": "Stummschaltung umschalten",
"operation.view": "Ansehen",
"operation.viewDetails": "Details anzeigen",
"operation.viewMore": "MEHR SEHEN",
"operation.yes": "Ja",
"operation.zoomIn": "Vergrößern",
"operation.zoomOut": "Verkleinern",
+ "pagination.editPageNumber": "Seitennummer bearbeiten, aktuelle Seite {{page}} von {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Artikel pro Seite",
+ "pagination.previous": "Previous page",
"placeholder.input": "Bitte eingeben",
"placeholder.search": "Suchen...",
"placeholder.select": "Bitte auswählen",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Umwandlung in Text...",
"voiceInput.notAllow": "Mikrofon nicht autorisiert",
"voiceInput.speaking": "Sprechen Sie jetzt...",
+ "voiceInput.start": "Spracheingabe",
"you": "Du"
}
diff --git a/web/i18n/en-US/app.json b/web/i18n/en-US/app.json
index 0efa33de071..e5e4c5ed1cf 100644
--- a/web/i18n/en-US/app.json
+++ b/web/i18n/en-US/app.json
@@ -228,6 +228,14 @@
"structOutput.required": "Required",
"structOutput.structured": "Structured",
"structOutput.structuredTip": "Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied JSON Schema",
+ "studio.apps": "Apps",
+ "studio.filters.allCreators": "All creators",
+ "studio.filters.creators": "Creators",
+ "studio.filters.reset": "Reset",
+ "studio.filters.searchCreators": "Search creator...",
+ "studio.filters.types": "Types",
+ "studio.filters.you": "You",
+ "studio.viewSnippets": "View Snippets",
"switch": "Switch to Workflow Orchestrate",
"switchLabel": "The app copy to be created",
"switchStart": "Start switch",
diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json
index 74a3649a4ce..899fa571889 100644
--- a/web/i18n/en-US/common.json
+++ b/web/i18n/en-US/common.json
@@ -545,7 +545,11 @@
"operation.yes": "Yes",
"operation.zoomIn": "Zoom In",
"operation.zoomOut": "Zoom Out",
+ "pagination.editPageNumber": "Edit page number, current page {{page}} of {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Items per page",
+ "pagination.previous": "Previous page",
"placeholder.input": "Please enter",
"placeholder.search": "Search...",
"placeholder.select": "Please select",
diff --git a/web/i18n/en-US/snippet.json b/web/i18n/en-US/snippet.json
new file mode 100644
index 00000000000..49a3f11af44
--- /dev/null
+++ b/web/i18n/en-US/snippet.json
@@ -0,0 +1,47 @@
+{
+ "cancel": "Cancel",
+ "continueEditing": "Continue Editing",
+ "create": "CREATE SNIPPET",
+ "createFailed": "Failed to create snippet",
+ "createFromBlank": "Create from blank",
+ "defaultName": "Untitled Snippet",
+ "deleteConfirmContent": "Once deleted, it cannot be recovered, and Workflows that reference this Snippet will not update automatically.",
+ "deleteConfirmTitle": "Delete Snippet?",
+ "deleteFailed": "Failed to delete snippet",
+ "deleted": "Snippet deleted",
+ "discardChanges": "Discard Changes",
+ "discardChangesDescription": "Your current changes won’t be saved to this snippet.",
+ "discardChangesTitle": "Discard current changes?",
+ "draft": "Draft",
+ "editDialogTitle": "Edit Snippet Info",
+ "editDone": "Snippet info updated",
+ "editFailed": "Failed to update snippet info",
+ "exportFailed": "Export snippet failed.",
+ "importFailed": "Failed to import snippet DSL",
+ "importSuccess": "Snippet imported",
+ "inputFieldButton": "Input Field",
+ "inputVariables": "Input Variables",
+ "management": "SNIPPET MANAGEMENT",
+ "menu.deleteSnippet": "Delete",
+ "menu.editInfo": "Edit Info",
+ "menu.exportSnippet": "Export Snippet",
+ "notFoundDescription": "The requested snippet mock was not found.",
+ "notFoundTitle": "Snippet not found",
+ "panelDescription": "Defines the input fields that allow the snippet to receive data from other nodes.",
+ "panelPrimaryGroup": "Core inputs",
+ "panelSecondaryGroup": "Optional inputs",
+ "panelTitle": "Input Field",
+ "publishButton": "Publish",
+ "publishFailed": "Failed to publish snippet",
+ "publishMenuCurrentDraft": "Current draft unpublished",
+ "publishSuccess": "Snippet published",
+ "save": "Save",
+ "sectionOrchestrate": "Orchestrate",
+ "testRunButton": "Test run",
+ "typeLabel": "Snippet",
+ "unknownUser": "User",
+ "unsavedChanges": "Current changes are not saved.",
+ "updatedBy": "{{name}} updated {{time}}",
+ "usageCount": "Used {{count}} times",
+ "variableInspect": "Variable Inspect"
+}
diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json
index 774713476b6..331ce97526c 100644
--- a/web/i18n/en-US/workflow.json
+++ b/web/i18n/en-US/workflow.json
@@ -247,6 +247,12 @@
"common.searchVar": "Search variable",
"common.setVarValuePlaceholder": "Set variable",
"common.showRunHistory": "Show Run History",
+ "common.switchToStandardWorkflowConfirm.switch": "Switch",
+ "common.switchToStandardWorkflowConfirm.targetTypes.app": "Workflow",
+ "common.switchToStandardWorkflowConfirm.targetTypes.knowledge_base": "Knowledge Base",
+ "common.switchToStandardWorkflowConfirm.targetTypes.snippets": "Snippet",
+ "common.switchToStandardWorkflowConfirm.title": "Switch to Standard Workflow?",
+ "common.switchToStandardWorkflowTip": "Turns this evaluator back into a standard workflow and restores public Web App access.",
"common.syncingData": "Syncing data, just a few seconds.",
"common.tagBound": "Number of apps using this tag",
"common.undo": "Undo",
@@ -1151,6 +1157,16 @@
"singleRun.testRun": "Test Run",
"singleRun.testRunIteration": "Test Run Iteration",
"singleRun.testRunLoop": "Test Run Loop",
+ "snippet.addToSnippet": "Add to snippet",
+ "snippet.confirm": "Confirm",
+ "snippet.createDialogTitle": "Create Snippet",
+ "snippet.createSuccess": "Snippet created",
+ "snippet.descriptionLabel": "Description (Optional)",
+ "snippet.descriptionPlaceholder": "Briefly describe your snippet",
+ "snippet.nameLabel": "Snippet Name",
+ "snippet.namePlaceholder": "Snippet name",
+ "snippet.shortcuts.press": "Press",
+ "snippet.shortcuts.toConfirm": "to confirm",
"tabs.-": "Default",
"tabs.addAll": "Add all",
"tabs.agent": "Agent Strategy",
@@ -1158,6 +1174,7 @@
"tabs.allTool": "All",
"tabs.allTriggers": "All triggers",
"tabs.blocks": "Nodes",
+ "tabs.createSnippet": "Create a snippet",
"tabs.customTool": "Custom",
"tabs.featuredTools": "Featured",
"tabs.hideActions": "Hide tools",
@@ -1167,19 +1184,23 @@
"tabs.noFeaturedTriggers": "Discover more triggers in Marketplace",
"tabs.noPluginsFound": "No plugins were found",
"tabs.noResult": "No match found",
+ "tabs.noSnippetsFound": "No snippets were found",
"tabs.plugin": "Plugin",
"tabs.pluginByAuthor": "By {{author}}",
"tabs.question-understand": "Question Understand",
"tabs.requestToCommunity": "Requests to the community",
"tabs.searchBlock": "Search node",
"tabs.searchDataSource": "Search Data Source",
+ "tabs.searchSnippets": "Search snippets...",
"tabs.searchTool": "Search tool",
"tabs.searchTrigger": "Search triggers...",
"tabs.showLessFeatured": "Show less",
"tabs.showMoreFeatured": "Show more",
+ "tabs.snippets": "Snippets",
"tabs.sources": "Sources",
"tabs.start": "Start",
"tabs.startDisabledTip": "Trigger node and user input node are mutually exclusive.",
+ "tabs.startNotSupportedTip": "The Start tab is not supported in snippets.",
"tabs.tools": "Tools",
"tabs.transform": "Transform",
"tabs.usePlugin": "Select tool",
diff --git a/web/i18n/es-ES/common.json b/web/i18n/es-ES/common.json
index 844bc57d03f..8e768bac51e 100644
--- a/web/i18n/es-ES/common.json
+++ b/web/i18n/es-ES/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "navegar",
"imageInput.dropImageHere": "Deja tu imagen aquí, o",
"imageInput.supportedFormats": "Soporta PNG, JPG, JPEG, WEBP y GIF",
+ "imageUploader.imageList": "Lista de imágenes",
"imageUploader.imageUpload": "Carga de Imagen",
"imageUploader.pasteImageLink": "Pegar enlace de imagen",
"imageUploader.pasteImageLinkInputPlaceholder": "Pega el enlace de imagen aquí",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Abrir en una nueva pestaña",
"operation.params": "Parámetros",
+ "operation.pause": "Pausar",
+ "operation.play": "Reproducir",
"operation.refresh": "Reiniciar",
"operation.regenerate": "Regenerar",
"operation.reload": "Recargar",
@@ -519,6 +522,7 @@
"operation.rename": "Renombrar",
"operation.reset": "Restablecer",
"operation.resetKeywords": "Restablecer palabras clave",
+ "operation.retry": "Reintentar",
"operation.save": "Guardar",
"operation.saveAndEnable": "Guardar y habilitar",
"operation.saveAndRegenerate": "Guardar y regenerar fragmentos secundarios",
@@ -533,13 +537,19 @@
"operation.skip": "Navío",
"operation.submit": "Enviar",
"operation.sure": "Estoy seguro",
+ "operation.toggleFullscreen": "Alternar pantalla completa",
+ "operation.toggleMute": "Alternar silencio",
"operation.view": "Vista",
"operation.viewDetails": "Ver detalles",
"operation.viewMore": "VER MÁS",
"operation.yes": "Sí",
"operation.zoomIn": "Acercar",
"operation.zoomOut": "Alejar",
+ "pagination.editPageNumber": "Editar número de página, página actual {{page}} de {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Elementos por página",
+ "pagination.previous": "Previous page",
"placeholder.input": "Por favor ingresa",
"placeholder.search": "Buscar...",
"placeholder.select": "Por favor selecciona",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Convirtiendo a texto...",
"voiceInput.notAllow": "micrófono no autorizado",
"voiceInput.speaking": "Habla ahora...",
+ "voiceInput.start": "Entrada de voz",
"you": "Tú"
}
diff --git a/web/i18n/fa-IR/common.json b/web/i18n/fa-IR/common.json
index c09f4827a8b..9a163793e3a 100644
--- a/web/i18n/fa-IR/common.json
+++ b/web/i18n/fa-IR/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "مرورگر",
"imageInput.dropImageHere": "عکس خود را اینجا رها کنید، یا",
"imageInput.supportedFormats": "از فرمتهای PNG، JPG، JPEG، WEBP و GIF پشتیبانی میکند",
+ "imageUploader.imageList": "فهرست تصاویر",
"imageUploader.imageUpload": "بارگذاری تصویر",
"imageUploader.pasteImageLink": "پیوند تصویر را بچسبانید",
"imageUploader.pasteImageLinkInputPlaceholder": "پیوند تصویر را اینجا بچسبانید",
@@ -512,6 +513,8 @@
"operation.ok": "تایید",
"operation.openInNewTab": "باز کردن در برگه جدید",
"operation.params": "پارامترها",
+ "operation.pause": "مکث",
+ "operation.play": "پخش",
"operation.refresh": "شروع مجدد",
"operation.regenerate": "بازسازی",
"operation.reload": "بارگذاری مجدد",
@@ -519,6 +522,7 @@
"operation.rename": "تغییر نام",
"operation.reset": "بازنشانی",
"operation.resetKeywords": "بازنشانی کلمات کلیدی",
+ "operation.retry": "تلاش دوباره",
"operation.save": "ذخیره",
"operation.saveAndEnable": "ذخیره و فعال سازی",
"operation.saveAndRegenerate": "ذخیره و بازسازی تکه های فرزند",
@@ -533,13 +537,19 @@
"operation.skip": "کشتی",
"operation.submit": "ارسال",
"operation.sure": "مطمئن هستم",
+ "operation.toggleFullscreen": "تغییر حالت تمامصفحه",
+ "operation.toggleMute": "تغییر حالت بیصدا",
"operation.view": "مشاهده",
"operation.viewDetails": "دیدن جزئیات",
"operation.viewMore": "بیشتر ببینید",
"operation.yes": "بله",
"operation.zoomIn": "بزرگنمایی",
"operation.zoomOut": "کوچک نمایی",
+ "pagination.editPageNumber": "ویرایش شماره صفحه، صفحه فعلی {{page}} از {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "موارد در هر صفحه",
+ "pagination.previous": "Previous page",
"placeholder.input": "لطفا وارد کنید",
"placeholder.search": "جستجو...",
"placeholder.select": "لطفا انتخاب کنید",
@@ -677,5 +687,6 @@
"voiceInput.converting": "در حال تبدیل به متن...",
"voiceInput.notAllow": "میکروفون مجاز نیست",
"voiceInput.speaking": "اکنون صحبت کنید...",
+ "voiceInput.start": "ورودی صوتی",
"you": "تو"
}
diff --git a/web/i18n/fr-FR/common.json b/web/i18n/fr-FR/common.json
index 48f8a9e7a73..e861cb535ef 100644
--- a/web/i18n/fr-FR/common.json
+++ b/web/i18n/fr-FR/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "naviguer",
"imageInput.dropImageHere": "Déposez votre image ici, ou",
"imageInput.supportedFormats": "Prend en charge PNG, JPG, JPEG, WEBP et GIF",
+ "imageUploader.imageList": "Liste des images",
"imageUploader.imageUpload": "Téléchargement d'image",
"imageUploader.pasteImageLink": "Collez le lien de l'image",
"imageUploader.pasteImageLinkInputPlaceholder": "Collez le lien de l'image ici",
@@ -512,6 +513,8 @@
"operation.ok": "D'accord",
"operation.openInNewTab": "Ouvrir dans un nouvel onglet",
"operation.params": "Paramètres",
+ "operation.pause": "Pause",
+ "operation.play": "Lire",
"operation.refresh": "Redémarrer",
"operation.regenerate": "Régénérer",
"operation.reload": "Recharger",
@@ -519,6 +522,7 @@
"operation.rename": "Renommer",
"operation.reset": "Réinitialiser",
"operation.resetKeywords": "Réinitialiser les mots-clés",
+ "operation.retry": "Réessayer",
"operation.save": "Enregistrer",
"operation.saveAndEnable": "Enregistrer et Activer",
"operation.saveAndRegenerate": "Enregistrer et régénérer des morceaux enfants",
@@ -533,13 +537,19 @@
"operation.skip": "Bateau",
"operation.submit": "Envoyer",
"operation.sure": "Je suis sûr",
+ "operation.toggleFullscreen": "Basculer en plein écran",
+ "operation.toggleMute": "Activer/désactiver le son",
"operation.view": "Vue",
"operation.viewDetails": "Voir les détails",
"operation.viewMore": "VOIR PLUS",
"operation.yes": "Oui",
"operation.zoomIn": "Zoom avant",
"operation.zoomOut": "Zoom arrière",
+ "pagination.editPageNumber": "Modifier le numéro de page, page actuelle {{page}} sur {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Articles par page",
+ "pagination.previous": "Previous page",
"placeholder.input": "Veuillez entrer",
"placeholder.search": "Rechercher...",
"placeholder.select": "Veuillez sélectionner",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Conversion en texte...",
"voiceInput.notAllow": "microphone non autorisé",
"voiceInput.speaking": "Parle maintenant...",
+ "voiceInput.start": "Saisie vocale",
"you": "Vous"
}
diff --git a/web/i18n/hi-IN/common.json b/web/i18n/hi-IN/common.json
index 5be5861c00f..3a03f1ece76 100644
--- a/web/i18n/hi-IN/common.json
+++ b/web/i18n/hi-IN/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "ब्राउज़ करें",
"imageInput.dropImageHere": "अपनी छवि यहाँ छोड़ें, या",
"imageInput.supportedFormats": "PNG, JPG, JPEG, WEBP और GIF का समर्थन करता है",
+ "imageUploader.imageList": "छवि सूची",
"imageUploader.imageUpload": "छवि अपलोड",
"imageUploader.pasteImageLink": "छवि लिंक पेस्ट करें",
"imageUploader.pasteImageLinkInputPlaceholder": "छवि लिंक यहाँ पेस्ट करें",
@@ -512,6 +513,8 @@
"operation.ok": "ठीक है",
"operation.openInNewTab": "नए टैब में खोलें",
"operation.params": "पैरामीटर",
+ "operation.pause": "रोकें",
+ "operation.play": "चलाएं",
"operation.refresh": "पुनः प्रारंभ करें",
"operation.regenerate": "पुनर्जन्म",
"operation.reload": "पुनः लोड करें",
@@ -519,6 +522,7 @@
"operation.rename": "नाम बदलें",
"operation.reset": "रीसेट करें",
"operation.resetKeywords": "कीवर्ड रीसेट करें",
+ "operation.retry": "पुनः प्रयास करें",
"operation.save": "सहेजें",
"operation.saveAndEnable": "सहेजें और सक्षम करें",
"operation.saveAndRegenerate": "सहेजें और पुन: उत्पन्न करें बाल विखंडू",
@@ -533,13 +537,19 @@
"operation.skip": "जहाज़",
"operation.submit": "जमा करें",
"operation.sure": "मुझे यकीन है",
+ "operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें",
+ "operation.toggleMute": "म्यूट टॉगल करें",
"operation.view": "देखना",
"operation.viewDetails": "विवरण देखें",
"operation.viewMore": "और देखें",
"operation.yes": "हाँ",
"operation.zoomIn": "ज़ूम इन करें",
"operation.zoomOut": "ज़ूम आउट करें",
+ "pagination.editPageNumber": "पृष्ठ संख्या संपादित करें, वर्तमान पृष्ठ {{page}} / {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "प्रति पृष्ठ आइटम",
+ "pagination.previous": "Previous page",
"placeholder.input": "कृपया दर्ज करें",
"placeholder.search": "खोजें...",
"placeholder.select": "कृपया चयन करें",
@@ -677,5 +687,6 @@
"voiceInput.converting": "पाठ में परिवर्तित हो रहा है...",
"voiceInput.notAllow": "माइक्रोफोन अधिकृत नहीं है",
"voiceInput.speaking": "अब बोलें...",
+ "voiceInput.start": "वॉइस इनपुट",
"you": "आप"
}
diff --git a/web/i18n/id-ID/common.json b/web/i18n/id-ID/common.json
index f3bbbc9ac4d..488354d0bb4 100644
--- a/web/i18n/id-ID/common.json
+++ b/web/i18n/id-ID/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "Telusuri",
"imageInput.dropImageHere": "Letakkan gambar Anda di sini, atau",
"imageInput.supportedFormats": "Mendukung PNG, JPG, JPEG, WEBP dan GIF",
+ "imageUploader.imageList": "Daftar gambar",
"imageUploader.imageUpload": "Unggah Gambar",
"imageUploader.pasteImageLink": "Tempel tautan gambar",
"imageUploader.pasteImageLinkInputPlaceholder": "Tempel tautan gambar di sini",
@@ -512,6 +513,8 @@
"operation.ok": "OKE",
"operation.openInNewTab": "Buka di tab baru",
"operation.params": "Parameter",
+ "operation.pause": "Jeda",
+ "operation.play": "Putar",
"operation.refresh": "Segarkan",
"operation.regenerate": "Regenerasi",
"operation.reload": "Muat Ulang",
@@ -519,6 +522,7 @@
"operation.rename": "Ubah nama",
"operation.reset": "Reset",
"operation.resetKeywords": "Atur ulang kata kunci",
+ "operation.retry": "Coba lagi",
"operation.save": "Simpan",
"operation.saveAndEnable": "Simpan & Aktifkan",
"operation.saveAndRegenerate": "Simpan & Buat Ulang Potongan Anak",
@@ -533,13 +537,19 @@
"operation.skip": "Lewat",
"operation.submit": "Kirim",
"operation.sure": "Saya yakin",
+ "operation.toggleFullscreen": "Alihkan layar penuh",
+ "operation.toggleMute": "Alihkan bisu",
"operation.view": "Lihat",
"operation.viewDetails": "Lihat Detail",
"operation.viewMore": "LIHAT LEBIH BANYAK",
"operation.yes": "Ya",
"operation.zoomIn": "Perbesar",
"operation.zoomOut": "Perkecil",
+ "pagination.editPageNumber": "Edit nomor halaman, halaman saat ini {{page}} dari {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Item per halaman",
+ "pagination.previous": "Previous page",
"placeholder.input": "Silakan masuk",
"placeholder.search": "Cari...",
"placeholder.select": "Silakan pilih",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Mengonversi ke teks...",
"voiceInput.notAllow": "mikrofon tidak diizinkan",
"voiceInput.speaking": "Bicaralah sekarang...",
+ "voiceInput.start": "Input suara",
"you": "Kamu"
}
diff --git a/web/i18n/it-IT/common.json b/web/i18n/it-IT/common.json
index ba416c470c5..99a0489f938 100644
--- a/web/i18n/it-IT/common.json
+++ b/web/i18n/it-IT/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "sfogliare",
"imageInput.dropImageHere": "Trascina la tua immagine qui, oppure",
"imageInput.supportedFormats": "Supporta PNG, JPG, JPEG, WEBP e GIF",
+ "imageUploader.imageList": "Elenco immagini",
"imageUploader.imageUpload": "Caricamento Immagine",
"imageUploader.pasteImageLink": "Incolla link immagine",
"imageUploader.pasteImageLinkInputPlaceholder": "Incolla qui il link immagine",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Apri in una nuova scheda",
"operation.params": "Parametri",
+ "operation.pause": "Pausa",
+ "operation.play": "Riproduci",
"operation.refresh": "Riavvia",
"operation.regenerate": "Rigenerare",
"operation.reload": "Ricarica",
@@ -519,6 +522,7 @@
"operation.rename": "Rinomina",
"operation.reset": "Reimposta",
"operation.resetKeywords": "Reimposta parole chiave",
+ "operation.retry": "Riprova",
"operation.save": "Salva",
"operation.saveAndEnable": "Salva & Abilita",
"operation.saveAndRegenerate": "Salva e rigenera i blocchi figlio",
@@ -533,13 +537,19 @@
"operation.skip": "Nave",
"operation.submit": "Invia",
"operation.sure": "Sono sicuro",
+ "operation.toggleFullscreen": "Attiva/disattiva schermo intero",
+ "operation.toggleMute": "Attiva/disattiva muto",
"operation.view": "Vista",
"operation.viewDetails": "Visualizza dettagli",
"operation.viewMore": "SCOPRI DI PIÙ",
"operation.yes": "Sì",
"operation.zoomIn": "Ingrandisci",
"operation.zoomOut": "Zoom indietro",
+ "pagination.editPageNumber": "Modifica numero pagina, pagina corrente {{page}} di {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Articoli per pagina",
+ "pagination.previous": "Previous page",
"placeholder.input": "Per favore inserisci",
"placeholder.search": "Cerca...",
"placeholder.select": "Per favore seleziona",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Conversione in testo...",
"voiceInput.notAllow": "microfono non autorizzato",
"voiceInput.speaking": "Parla ora...",
+ "voiceInput.start": "Input vocale",
"you": "Tu"
}
diff --git a/web/i18n/ja-JP/common.json b/web/i18n/ja-JP/common.json
index 6e488ba3016..0cbd11539b9 100644
--- a/web/i18n/ja-JP/common.json
+++ b/web/i18n/ja-JP/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "ブラウズする",
"imageInput.dropImageHere": "ここに画像をドロップするか、",
"imageInput.supportedFormats": "PNG、JPG、JPEG、WEBP、および GIF をサポートしています。",
+ "imageUploader.imageList": "画像リスト",
"imageUploader.imageUpload": "画像アップロード",
"imageUploader.pasteImageLink": "画像リンクを貼り付ける",
"imageUploader.pasteImageLinkInputPlaceholder": "ここに画像リンクを貼り付けてください",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "新しいタブで開く",
"operation.params": "パラメータ",
+ "operation.pause": "一時停止",
+ "operation.play": "再生",
"operation.refresh": "リフレッシュ",
"operation.regenerate": "再生成",
"operation.reload": "再読み込み",
@@ -519,6 +522,7 @@
"operation.rename": "名前の変更",
"operation.reset": "リセット",
"operation.resetKeywords": "キーワードをリセット",
+ "operation.retry": "再試行",
"operation.save": "保存",
"operation.saveAndEnable": "保存 & 有効に",
"operation.saveAndRegenerate": "保存して子チャンクを再生成",
@@ -533,13 +537,19 @@
"operation.skip": "スキップ",
"operation.submit": "送信",
"operation.sure": "確認済み",
+ "operation.toggleFullscreen": "全画面表示を切り替え",
+ "operation.toggleMute": "ミュートを切り替え",
"operation.view": "表示",
"operation.viewDetails": "詳細を見る",
"operation.viewMore": "さらに表示",
"operation.yes": "はい",
"operation.zoomIn": "ズームインする",
"operation.zoomOut": "ズームアウト",
+ "pagination.editPageNumber": "ページ番号を編集、現在のページ {{page}} / {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "ページあたりのアイテム数",
+ "pagination.previous": "Previous page",
"placeholder.input": "入力してください",
"placeholder.search": "検索...",
"placeholder.select": "選択してください",
@@ -677,5 +687,6 @@
"voiceInput.converting": "テキストに変換中...",
"voiceInput.notAllow": "マイクが許可されていません",
"voiceInput.speaking": "今話しています...",
+ "voiceInput.start": "音声入力",
"you": "あなた"
}
diff --git a/web/i18n/ko-KR/common.json b/web/i18n/ko-KR/common.json
index 3faf2527079..1130cc8a78c 100644
--- a/web/i18n/ko-KR/common.json
+++ b/web/i18n/ko-KR/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "찾아보기",
"imageInput.dropImageHere": "여기에 이미지를 드롭하거나",
"imageInput.supportedFormats": "PNG, JPG, JPEG, WEBP 및 GIF 를 지원합니다.",
+ "imageUploader.imageList": "이미지 목록",
"imageUploader.imageUpload": "이미지 업로드",
"imageUploader.pasteImageLink": "이미지 링크 붙여넣기",
"imageUploader.pasteImageLinkInputPlaceholder": "여기에 이미지 링크를 붙여넣으세요",
@@ -512,6 +513,8 @@
"operation.ok": "확인",
"operation.openInNewTab": "새 탭에서 열기",
"operation.params": "매개변수",
+ "operation.pause": "일시 중지",
+ "operation.play": "재생",
"operation.refresh": "새로 고침",
"operation.regenerate": "재생성",
"operation.reload": "다시 불러오기",
@@ -519,6 +522,7 @@
"operation.rename": "이름 바꾸기",
"operation.reset": "초기화",
"operation.resetKeywords": "키워드 재설정",
+ "operation.retry": "다시 시도",
"operation.save": "저장",
"operation.saveAndEnable": "저장 및 활성화",
"operation.saveAndRegenerate": "저장 및 자식 청크 재생성",
@@ -533,13 +537,19 @@
"operation.skip": "건너뛰기",
"operation.submit": "전송",
"operation.sure": "확인",
+ "operation.toggleFullscreen": "전체 화면 전환",
+ "operation.toggleMute": "음소거 전환",
"operation.view": "보기",
"operation.viewDetails": "세부 정보보기",
"operation.viewMore": "더보기",
"operation.yes": "네",
"operation.zoomIn": "확대",
"operation.zoomOut": "축소",
+ "pagination.editPageNumber": "페이지 번호 편집, 현재 {{page}} / {{totalPages}} 페이지",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "페이지당 항목 수",
+ "pagination.previous": "Previous page",
"placeholder.input": "입력해주세요",
"placeholder.search": "검색...",
"placeholder.select": "선택해주세요",
@@ -677,5 +687,6 @@
"voiceInput.converting": "텍스트로 변환 중...",
"voiceInput.notAllow": "마이크가 허용되지 않았습니다",
"voiceInput.speaking": "지금 말하고 있습니다...",
+ "voiceInput.start": "음성 입력",
"you": "나"
}
diff --git a/web/i18n/nl-NL/common.json b/web/i18n/nl-NL/common.json
index 968a80ac08c..e28346255ee 100644
--- a/web/i18n/nl-NL/common.json
+++ b/web/i18n/nl-NL/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "browse",
"imageInput.dropImageHere": "Drop your image here, or",
"imageInput.supportedFormats": "Supports PNG, JPG, JPEG, WEBP and GIF",
+ "imageUploader.imageList": "Afbeeldingenlijst",
"imageUploader.imageUpload": "Image Upload",
"imageUploader.pasteImageLink": "Paste image link",
"imageUploader.pasteImageLinkInputPlaceholder": "Paste image link here",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Open in new tab",
"operation.params": "Params",
+ "operation.pause": "Pauzeren",
+ "operation.play": "Afspelen",
"operation.refresh": "Restart",
"operation.regenerate": "Regenerate",
"operation.reload": "Reload",
@@ -519,6 +522,7 @@
"operation.rename": "Rename",
"operation.reset": "Reset",
"operation.resetKeywords": "Reset keywords",
+ "operation.retry": "Opnieuw proberen",
"operation.save": "Save",
"operation.saveAndEnable": "Save & Enable",
"operation.saveAndRegenerate": "Save & Regenerate Child Chunks",
@@ -533,13 +537,19 @@
"operation.skip": "Skip",
"operation.submit": "Submit",
"operation.sure": "I'm sure",
+ "operation.toggleFullscreen": "Volledig scherm schakelen",
+ "operation.toggleMute": "Dempen schakelen",
"operation.view": "View",
"operation.viewDetails": "View Details",
"operation.viewMore": "VIEW MORE",
"operation.yes": "Yes",
"operation.zoomIn": "Zoom In",
"operation.zoomOut": "Zoom Out",
+ "pagination.editPageNumber": "Paginanummer bewerken, huidige pagina {{page}} van {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Items per page",
+ "pagination.previous": "Previous page",
"placeholder.input": "Please enter",
"placeholder.search": "Search...",
"placeholder.select": "Please select",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Converting to text...",
"voiceInput.notAllow": "microphone not authorized",
"voiceInput.speaking": "Speak now...",
+ "voiceInput.start": "Spraakinvoer",
"you": "You"
}
diff --git a/web/i18n/pl-PL/common.json b/web/i18n/pl-PL/common.json
index 9f2df3f7321..c599122788d 100644
--- a/web/i18n/pl-PL/common.json
+++ b/web/i18n/pl-PL/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "przeglądaj",
"imageInput.dropImageHere": "Upuść swój obraz tutaj, lub",
"imageInput.supportedFormats": "Obsługuje PNG, JPG, JPEG, WEBP i GIF",
+ "imageUploader.imageList": "Lista obrazów",
"imageUploader.imageUpload": "Przesyłanie obrazu",
"imageUploader.pasteImageLink": "Wklej link do obrazu",
"imageUploader.pasteImageLinkInputPlaceholder": "Wklej tutaj link do obrazu",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Otwórz w nowej karcie",
"operation.params": "Parametry",
+ "operation.pause": "Pauza",
+ "operation.play": "Odtwórz",
"operation.refresh": "Odśwież",
"operation.regenerate": "Ponownie wygenerować",
"operation.reload": "Przeładuj",
@@ -519,6 +522,7 @@
"operation.rename": "Zmień nazwę",
"operation.reset": "Resetuj",
"operation.resetKeywords": "Resetuj słowa kluczowe",
+ "operation.retry": "Spróbuj ponownie",
"operation.save": "Zapisz",
"operation.saveAndEnable": "Zapisz i Włącz",
"operation.saveAndRegenerate": "Zapisywanie i regeneracja fragmentów podrzędnych",
@@ -533,13 +537,19 @@
"operation.skip": "Statek",
"operation.submit": "Prześlij",
"operation.sure": "Jestem pewien",
+ "operation.toggleFullscreen": "Przełącz pełny ekran",
+ "operation.toggleMute": "Przełącz wyciszenie",
"operation.view": "Widok",
"operation.viewDetails": "Wyświetl szczegóły",
"operation.viewMore": "ZOBACZ WIĘCEJ",
"operation.yes": "Tak",
"operation.zoomIn": "Powiększenie",
"operation.zoomOut": "Pomniejszanie",
+ "pagination.editPageNumber": "Edytuj numer strony, bieżąca strona {{page}} z {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Ilość elementów na stronie",
+ "pagination.previous": "Previous page",
"placeholder.input": "Proszę wprowadzić",
"placeholder.search": "Szukaj...",
"placeholder.select": "Proszę wybrać",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Konwertowanie na tekst...",
"voiceInput.notAllow": "mikrofon nieautoryzowany",
"voiceInput.speaking": "Mów teraz...",
+ "voiceInput.start": "Wprowadzanie głosowe",
"you": "Ty"
}
diff --git a/web/i18n/pt-BR/common.json b/web/i18n/pt-BR/common.json
index 28f9ecab25d..b7e0925ef9e 100644
--- a/web/i18n/pt-BR/common.json
+++ b/web/i18n/pt-BR/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "navegar",
"imageInput.dropImageHere": "Arraste sua imagem aqui, ou",
"imageInput.supportedFormats": "Suporta PNG, JPG, JPEG, WEBP e GIF",
+ "imageUploader.imageList": "Lista de imagens",
"imageUploader.imageUpload": "Enviar Imagem",
"imageUploader.pasteImageLink": "Colar link da imagem",
"imageUploader.pasteImageLinkInputPlaceholder": "Cole o link da imagem aqui",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Abrir em nova guia",
"operation.params": "Parâmetros",
+ "operation.pause": "Pausar",
+ "operation.play": "Reproduzir",
"operation.refresh": "Reiniciar",
"operation.regenerate": "Regenerar",
"operation.reload": "Recarregar",
@@ -519,6 +522,7 @@
"operation.rename": "Renomear",
"operation.reset": "Redefinir",
"operation.resetKeywords": "Redefinir palavras-chave",
+ "operation.retry": "Tentar novamente",
"operation.save": "Salvar",
"operation.saveAndEnable": "Salvar e Ativar",
"operation.saveAndRegenerate": "Salvar e regenerar pedaços filhos",
@@ -533,13 +537,19 @@
"operation.skip": "Navio",
"operation.submit": "Enviar",
"operation.sure": "Tenho certeza",
+ "operation.toggleFullscreen": "Alternar tela cheia",
+ "operation.toggleMute": "Alternar mudo",
"operation.view": "Vista",
"operation.viewDetails": "Ver detalhes",
"operation.viewMore": "VER MAIS",
"operation.yes": "Sim",
"operation.zoomIn": "Ampliar",
"operation.zoomOut": "Diminuir o zoom",
+ "pagination.editPageNumber": "Editar número da página, página atual {{page}} de {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Itens por página",
+ "pagination.previous": "Previous page",
"placeholder.input": "Por favor, insira",
"placeholder.search": "Pesquisar...",
"placeholder.select": "Por favor, selecione",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Convertendo para texto...",
"voiceInput.notAllow": "microfone não autorizado",
"voiceInput.speaking": "Fale agora...",
+ "voiceInput.start": "Entrada por voz",
"you": "Você"
}
diff --git a/web/i18n/ro-RO/common.json b/web/i18n/ro-RO/common.json
index 8a0167edeb1..d7845a2c7c7 100644
--- a/web/i18n/ro-RO/common.json
+++ b/web/i18n/ro-RO/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "naviga",
"imageInput.dropImageHere": "Trageți imaginea aici sau",
"imageInput.supportedFormats": "Suportă PNG, JPG, JPEG, WEBP și GIF",
+ "imageUploader.imageList": "Listă de imagini",
"imageUploader.imageUpload": "Încărcare imagine",
"imageUploader.pasteImageLink": "Inserați link-ul imaginii",
"imageUploader.pasteImageLinkInputPlaceholder": "Inserați link-ul imaginii aici",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Deschide într-o filă nouă",
"operation.params": "Parametri",
+ "operation.pause": "Pauză",
+ "operation.play": "Redare",
"operation.refresh": "Reîncarcă",
"operation.regenerate": "Regenera",
"operation.reload": "Reîncarcă",
@@ -519,6 +522,7 @@
"operation.rename": "Redenumește",
"operation.reset": "Resetează",
"operation.resetKeywords": "Resetează cuvintele cheie",
+ "operation.retry": "Reîncercați",
"operation.save": "Salvează",
"operation.saveAndEnable": "Salvează și Activează",
"operation.saveAndRegenerate": "Salvați și regenerați bucățile secundare",
@@ -533,13 +537,19 @@
"operation.skip": "Navă",
"operation.submit": "Prezinte",
"operation.sure": "Sunt sigur",
+ "operation.toggleFullscreen": "Comută ecran complet",
+ "operation.toggleMute": "Comută sunetul",
"operation.view": "Vedere",
"operation.viewDetails": "Vezi detalii",
"operation.viewMore": "VEZI MAI MULT",
"operation.yes": "Da",
"operation.zoomIn": "Măriți",
"operation.zoomOut": "Micșorare",
+ "pagination.editPageNumber": "Editați numărul paginii, pagina curentă {{page}} din {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Articole pe pagină",
+ "pagination.previous": "Previous page",
"placeholder.input": "Vă rugăm să introduceți",
"placeholder.search": "Caută...",
"placeholder.select": "Vă rugăm să selectați",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Se convertește la text...",
"voiceInput.notAllow": "microfonul nu este autorizat",
"voiceInput.speaking": "Vorbiți acum...",
+ "voiceInput.start": "Introducere vocală",
"you": "Tu"
}
diff --git a/web/i18n/ru-RU/common.json b/web/i18n/ru-RU/common.json
index abeaecacd9d..a571d8bb1c8 100644
--- a/web/i18n/ru-RU/common.json
+++ b/web/i18n/ru-RU/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "просмотр",
"imageInput.dropImageHere": "Перетащите ваше изображение сюда или",
"imageInput.supportedFormats": "Поддерживает PNG, JPG, JPEG, WEBP и GIF",
+ "imageUploader.imageList": "Список изображений",
"imageUploader.imageUpload": "Загрузка изображения",
"imageUploader.pasteImageLink": "Вставить ссылку на изображение",
"imageUploader.pasteImageLinkInputPlaceholder": "Вставьте ссылку на изображение здесь",
@@ -512,6 +513,8 @@
"operation.ok": "ОК",
"operation.openInNewTab": "Открыть в новой вкладке",
"operation.params": "Параметры",
+ "operation.pause": "Пауза",
+ "operation.play": "Воспроизвести",
"operation.refresh": "Перезапустить",
"operation.regenerate": "Регенерировать",
"operation.reload": "Перезагрузить",
@@ -519,6 +522,7 @@
"operation.rename": "Переименовать",
"operation.reset": "Сбросить",
"operation.resetKeywords": "Сбросить ключевые слова",
+ "operation.retry": "Повторить",
"operation.save": "Сохранить",
"operation.saveAndEnable": "Сохранить и включить",
"operation.saveAndRegenerate": "Сохранение и повторное создание дочерних блоков",
@@ -533,13 +537,19 @@
"operation.skip": "Корабль",
"operation.submit": "Отправить",
"operation.sure": "Я уверен",
+ "operation.toggleFullscreen": "Переключить полноэкранный режим",
+ "operation.toggleMute": "Переключить звук",
"operation.view": "Вид",
"operation.viewDetails": "Подробнее",
"operation.viewMore": "ПОДРОБНЕЕ",
"operation.yes": "Да",
"operation.zoomIn": "Увеличить",
"operation.zoomOut": "Уменьшение масштаба",
+ "pagination.editPageNumber": "Изменить номер страницы, текущая страница {{page}} из {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Элементов на странице",
+ "pagination.previous": "Previous page",
"placeholder.input": "Пожалуйста, введите",
"placeholder.search": "Поиск...",
"placeholder.select": "Пожалуйста, выберите",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Преобразование в текст...",
"voiceInput.notAllow": "микрофон не авторизован",
"voiceInput.speaking": "Говорите сейчас...",
+ "voiceInput.start": "Голосовой ввод",
"you": "Ты"
}
diff --git a/web/i18n/sl-SI/common.json b/web/i18n/sl-SI/common.json
index f34c4c34592..eca279f7629 100644
--- a/web/i18n/sl-SI/common.json
+++ b/web/i18n/sl-SI/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "brskati",
"imageInput.dropImageHere": "Tukaj spustite svojo sliko ali",
"imageInput.supportedFormats": "Podpira PNG, JPG, JPEG, WEBP in GIF",
+ "imageUploader.imageList": "Seznam slik",
"imageUploader.imageUpload": "Nalaganje slik",
"imageUploader.pasteImageLink": "Prilepi povezavo do slike",
"imageUploader.pasteImageLinkInputPlaceholder": "Tukaj prilepi povezavo do slike",
@@ -512,6 +513,8 @@
"operation.ok": "V redu",
"operation.openInNewTab": "Odpri v novem zavihku",
"operation.params": "Parametri",
+ "operation.pause": "Premor",
+ "operation.play": "Predvajaj",
"operation.refresh": "Osveži",
"operation.regenerate": "Regeneracijo",
"operation.reload": "Ponovno naloži",
@@ -519,6 +522,7 @@
"operation.rename": "Preimenuj",
"operation.reset": "Ponastavi",
"operation.resetKeywords": "Ponastavi ključne besede",
+ "operation.retry": "Poskusi znova",
"operation.save": "Shrani",
"operation.saveAndEnable": "Shrani in omogoči",
"operation.saveAndRegenerate": "Shranite in regenerirajte otroške koščke",
@@ -533,13 +537,19 @@
"operation.skip": "Ladja",
"operation.submit": "Predložiti",
"operation.sure": "Prepričan sem",
+ "operation.toggleFullscreen": "Preklopi celozaslonski način",
+ "operation.toggleMute": "Preklopi utišanje",
"operation.view": "Pogled",
"operation.viewDetails": "Poglej podrobnosti",
"operation.viewMore": "POGLEJ VEČ",
"operation.yes": "Da",
"operation.zoomIn": "Povečava",
"operation.zoomOut": "Pomanjšanje",
+ "pagination.editPageNumber": "Uredi številko strani, trenutna stran {{page}} od {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Elementi na stran",
+ "pagination.previous": "Previous page",
"placeholder.input": "Vnesite prosim",
"placeholder.search": "Išči...",
"placeholder.select": "Izberite prosim",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Pretvorba v besedilo ...",
"voiceInput.notAllow": "Mikrofon ni pooblaščen",
"voiceInput.speaking": "Spregovorite zdaj ...",
+ "voiceInput.start": "Glasovni vnos",
"you": "Ti"
}
diff --git a/web/i18n/th-TH/common.json b/web/i18n/th-TH/common.json
index 2c4ecb80b50..e5407dfb17c 100644
--- a/web/i18n/th-TH/common.json
+++ b/web/i18n/th-TH/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "ท่องเว็บ",
"imageInput.dropImageHere": "วางภาพของคุณที่นี่ หรือ",
"imageInput.supportedFormats": "รองรับ PNG, JPG, JPEG, WEBP และ GIF",
+ "imageUploader.imageList": "รายการรูปภาพ",
"imageUploader.imageUpload": "อัปโหลดรูปภาพ",
"imageUploader.pasteImageLink": "วางลิงก์รูปภาพ",
"imageUploader.pasteImageLinkInputPlaceholder": "วางลิงค์รูปภาพที่นี่",
@@ -512,6 +513,8 @@
"operation.ok": "ตกลง, ได้",
"operation.openInNewTab": "เปิดในแท็บใหม่",
"operation.params": "พารามิเตอร์",
+ "operation.pause": "หยุดชั่วคราว",
+ "operation.play": "เล่น",
"operation.refresh": "เริ่มใหม่",
"operation.regenerate": "สร้างใหม่",
"operation.reload": "โหลด",
@@ -519,6 +522,7 @@
"operation.rename": "ตั้งชื่อใหม่",
"operation.reset": "รี เซ็ต",
"operation.resetKeywords": "รีเซ็ตคำสำคัญ",
+ "operation.retry": "ลองอีกครั้ง",
"operation.save": "ประหยัด",
"operation.saveAndEnable": "บันทึกและเปิดใช้งาน",
"operation.saveAndRegenerate": "บันทึกและสร้างก้อนย่อยใหม่",
@@ -533,13 +537,19 @@
"operation.skip": "เรือ",
"operation.submit": "ส่ง",
"operation.sure": "ฉันแน่ใจ",
+ "operation.toggleFullscreen": "สลับเต็มหน้าจอ",
+ "operation.toggleMute": "สลับปิดเสียง",
"operation.view": "ทิวทัศน์",
"operation.viewDetails": "ดูรายละเอียด",
"operation.viewMore": "ดูเพิ่มเติม",
"operation.yes": "ใช่",
"operation.zoomIn": "ซูมเข้า",
"operation.zoomOut": "ซูมออก",
+ "pagination.editPageNumber": "แก้ไขหมายเลขหน้า หน้าปัจจุบัน {{page}} จาก {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "รายการต่อหน้า",
+ "pagination.previous": "Previous page",
"placeholder.input": "กรุณากรอก",
"placeholder.search": "ค้นหา...",
"placeholder.select": "กรุณาเลือก",
@@ -677,5 +687,6 @@
"voiceInput.converting": "กําลังแปลงเป็นข้อความ...",
"voiceInput.notAllow": "ไม่ได้รับอนุญาตไมโครโฟน",
"voiceInput.speaking": "พูดเดี๋ยวนี้...",
+ "voiceInput.start": "ป้อนข้อมูลด้วยเสียง",
"you": "คุณ"
}
diff --git a/web/i18n/tr-TR/common.json b/web/i18n/tr-TR/common.json
index d87a889ced9..fb33b572024 100644
--- a/web/i18n/tr-TR/common.json
+++ b/web/i18n/tr-TR/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "göz atın",
"imageInput.dropImageHere": "Görüntünüzü buraya bırakın veya",
"imageInput.supportedFormats": "PNG, JPG, JPEG, WEBP ve GIF'i destekler",
+ "imageUploader.imageList": "Görsel listesi",
"imageUploader.imageUpload": "Görüntü Yükleme",
"imageUploader.pasteImageLink": "Görüntü bağlantısını yapıştır",
"imageUploader.pasteImageLinkInputPlaceholder": "Görüntü bağlantısını buraya yapıştırın",
@@ -512,6 +513,8 @@
"operation.ok": "Tamam",
"operation.openInNewTab": "Yeni sekmede aç",
"operation.params": "Parametreler",
+ "operation.pause": "Duraklat",
+ "operation.play": "Oynat",
"operation.refresh": "Yeniden Başlat",
"operation.regenerate": "Yeniden Oluştur",
"operation.reload": "Yeniden Yükle",
@@ -519,6 +522,7 @@
"operation.rename": "Yeniden Adlandır",
"operation.reset": "Sıfırla",
"operation.resetKeywords": "Anahtar kelimeleri sıfırla",
+ "operation.retry": "Tekrar dene",
"operation.save": "Kaydet",
"operation.saveAndEnable": "Kaydet ve Etkinleştir",
"operation.saveAndRegenerate": "Alt Parçaları Kaydetme ve Yeniden Oluşturma",
@@ -533,13 +537,19 @@
"operation.skip": "Atla",
"operation.submit": "Gönder",
"operation.sure": "Eminim",
+ "operation.toggleFullscreen": "Tam ekranı aç/kapat",
+ "operation.toggleMute": "Sessize al/aç",
"operation.view": "Görüntüle",
"operation.viewDetails": "Detayları Görüntüle",
"operation.viewMore": "DAHA FAZLA GÖSTER",
"operation.yes": "Evet",
"operation.zoomIn": "Yakınlaştırma",
"operation.zoomOut": "Uzaklaştırma",
+ "pagination.editPageNumber": "Sayfa numarasını düzenle, geçerli sayfa {{page}} / {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Sayfa başına öğe sayısı",
+ "pagination.previous": "Previous page",
"placeholder.input": "Lütfen girin",
"placeholder.search": "Ara...",
"placeholder.select": "Lütfen seçin",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Metne dönüştürülüyor...",
"voiceInput.notAllow": "mikrofon yetkilendirilmedi",
"voiceInput.speaking": "Şimdi konuş...",
+ "voiceInput.start": "Sesli giriş",
"you": "Sen"
}
diff --git a/web/i18n/uk-UA/common.json b/web/i18n/uk-UA/common.json
index e2e76c0ab66..eca81346ab7 100644
--- a/web/i18n/uk-UA/common.json
+++ b/web/i18n/uk-UA/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "перегляд",
"imageInput.dropImageHere": "Перетягніть зображення сюди або",
"imageInput.supportedFormats": "Підтримує PNG, JPG, JPEG, WEBP і GIF",
+ "imageUploader.imageList": "Список зображень",
"imageUploader.imageUpload": "Завантаження зображення",
"imageUploader.pasteImageLink": "Вставити посилання на зображення",
"imageUploader.pasteImageLinkInputPlaceholder": "Вставте посилання на зображення тут",
@@ -512,6 +513,8 @@
"operation.ok": "ОК",
"operation.openInNewTab": "Відкрити в новій вкладці",
"operation.params": "Параметри",
+ "operation.pause": "Пауза",
+ "operation.play": "Відтворити",
"operation.refresh": "Перезапустити",
"operation.regenerate": "Відновити",
"operation.reload": "Перезавантажити",
@@ -519,6 +522,7 @@
"operation.rename": "Перейменувати",
"operation.reset": "Скинути",
"operation.resetKeywords": "Скинути ключові слова",
+ "operation.retry": "Повторити",
"operation.save": "Зберегти",
"operation.saveAndEnable": "Зберегти та Увімкнути",
"operation.saveAndRegenerate": "Збереження та регенерація дочірніх фрагментів",
@@ -533,13 +537,19 @@
"operation.skip": "Корабель",
"operation.submit": "Представити",
"operation.sure": "Я впевнений",
+ "operation.toggleFullscreen": "Перемкнути повноекранний режим",
+ "operation.toggleMute": "Перемкнути звук",
"operation.view": "Вид",
"operation.viewDetails": "Перегляд докладних відомостей",
"operation.viewMore": "ДИВИТИСЬ БІЛЬШЕ",
"operation.yes": "Так",
"operation.zoomIn": "Збільшити масштаб",
"operation.zoomOut": "Зменшити масштаб",
+ "pagination.editPageNumber": "Редагувати номер сторінки, поточна сторінка {{page}} з {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Елементів на сторінці",
+ "pagination.previous": "Previous page",
"placeholder.input": "Будь ласка, введіть текст",
"placeholder.search": "Пошук...",
"placeholder.select": "Будь ласка, оберіть параметр",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Перетворення на текст...",
"voiceInput.notAllow": "мікрофон не авторизований",
"voiceInput.speaking": "Говоріть зараз...",
+ "voiceInput.start": "Голосове введення",
"you": "Ти"
}
diff --git a/web/i18n/vi-VN/common.json b/web/i18n/vi-VN/common.json
index 1bf257018e0..f35c7323c29 100644
--- a/web/i18n/vi-VN/common.json
+++ b/web/i18n/vi-VN/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "duyệt",
"imageInput.dropImageHere": "Kéo hình ảnh của bạn vào đây, hoặc",
"imageInput.supportedFormats": "Hỗ trợ PNG, JPG, JPEG, WEBP và GIF",
+ "imageUploader.imageList": "Danh sách hình ảnh",
"imageUploader.imageUpload": "Tải ảnh lên",
"imageUploader.pasteImageLink": "Dán liên kết ảnh",
"imageUploader.pasteImageLinkInputPlaceholder": "Dán liên kết ảnh ở đây",
@@ -512,6 +513,8 @@
"operation.ok": "OK",
"operation.openInNewTab": "Mở trong tab mới",
"operation.params": "Tham số",
+ "operation.pause": "Tạm dừng",
+ "operation.play": "Phát",
"operation.refresh": "Làm mới",
"operation.regenerate": "Tái tạo",
"operation.reload": "Tải lại",
@@ -519,6 +522,7 @@
"operation.rename": "Đổi tên",
"operation.reset": "Đặt lại",
"operation.resetKeywords": "Đặt lại từ khóa",
+ "operation.retry": "Thử lại",
"operation.save": "Lưu",
"operation.saveAndEnable": "Lưu & Kích hoạt",
"operation.saveAndRegenerate": "Lưu và tạo lại các phần con",
@@ -533,13 +537,19 @@
"operation.skip": "Tàu",
"operation.submit": "Trình",
"operation.sure": "Tôi chắc chắn",
+ "operation.toggleFullscreen": "Chuyển đổi toàn màn hình",
+ "operation.toggleMute": "Bật/tắt tiếng",
"operation.view": "Cảnh",
"operation.viewDetails": "Xem chi tiết",
"operation.viewMore": "XEM THÊM",
"operation.yes": "Vâng",
"operation.zoomIn": "Phóng to",
"operation.zoomOut": "Thu nhỏ",
+ "pagination.editPageNumber": "Chỉnh sửa số trang, trang hiện tại {{page}} trên {{totalPages}}",
+ "pagination.next": "Next page",
+ "pagination.pageNumber": "Page number",
"pagination.perPage": "Mục trên mỗi trang",
+ "pagination.previous": "Previous page",
"placeholder.input": "Vui lòng nhập",
"placeholder.search": "Tìm kiếm...",
"placeholder.select": "Vui lòng chọn",
@@ -677,5 +687,6 @@
"voiceInput.converting": "Chuyển đổi thành văn bản...",
"voiceInput.notAllow": "micro không được ủy quyền",
"voiceInput.speaking": "Hãy nói...",
+ "voiceInput.start": "Nhập bằng giọng nói",
"you": "Bạn"
}
diff --git a/web/i18n/zh-Hans/app.json b/web/i18n/zh-Hans/app.json
index 8f46a4433ea..f30113b4149 100644
--- a/web/i18n/zh-Hans/app.json
+++ b/web/i18n/zh-Hans/app.json
@@ -228,6 +228,7 @@
"structOutput.required": "必填",
"structOutput.structured": "结构化输出",
"structOutput.structuredTip": "结构化输出是一项功能,可确保模型始终生成符合您提供的 JSON 模式的响应",
+ "studio.viewSnippets": "查看 Snippets",
"switch": "迁移为工作流编排",
"switchLabel": "新应用创建为",
"switchStart": "开始迁移",
diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/zh-Hans/common.json
index e20652373c1..98b6cd02d78 100644
--- a/web/i18n/zh-Hans/common.json
+++ b/web/i18n/zh-Hans/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "浏览",
"imageInput.dropImageHere": "将图片拖放到此处,或",
"imageInput.supportedFormats": "支持 PNG、JPG、JPEG、WEBP 和 GIF 格式",
+ "imageUploader.imageList": "图片列表",
"imageUploader.imageUpload": "图片上传",
"imageUploader.pasteImageLink": "粘贴图片链接",
"imageUploader.pasteImageLinkInputPlaceholder": "将图像链接粘贴到此处",
@@ -512,6 +513,8 @@
"operation.ok": "好的",
"operation.openInNewTab": "在新标签页打开",
"operation.params": "参数设置",
+ "operation.pause": "暂停",
+ "operation.play": "播放",
"operation.refresh": "重新开始",
"operation.regenerate": "重新生成",
"operation.reload": "刷新",
@@ -519,6 +522,7 @@
"operation.rename": "重命名",
"operation.reset": "重置",
"operation.resetKeywords": "重置关键词",
+ "operation.retry": "重试",
"operation.save": "保存",
"operation.saveAndEnable": "保存并启用",
"operation.saveAndRegenerate": "保存并重新生成子分段",
@@ -533,13 +537,19 @@
"operation.skip": "跳过",
"operation.submit": "提交",
"operation.sure": "我确定",
+ "operation.toggleFullscreen": "切换全屏",
+ "operation.toggleMute": "切换静音",
"operation.view": "查看",
"operation.viewDetails": "查看详情",
"operation.viewMore": "查看更多",
"operation.yes": "是",
"operation.zoomIn": "放大",
"operation.zoomOut": "缩小",
+ "pagination.editPageNumber": "编辑页码,当前第 {{page}} 页,共 {{totalPages}} 页",
+ "pagination.next": "下一页",
+ "pagination.pageNumber": "页码",
"pagination.perPage": "每页显示",
+ "pagination.previous": "上一页",
"placeholder.input": "请输入",
"placeholder.search": "搜索...",
"placeholder.select": "请选择",
@@ -677,5 +687,6 @@
"voiceInput.converting": "正在转换为文本...",
"voiceInput.notAllow": "麦克风未授权",
"voiceInput.speaking": "现在讲...",
+ "voiceInput.start": "语音输入",
"you": "你"
}
diff --git a/web/i18n/zh-Hans/snippet.json b/web/i18n/zh-Hans/snippet.json
new file mode 100644
index 00000000000..7ceb44b483e
--- /dev/null
+++ b/web/i18n/zh-Hans/snippet.json
@@ -0,0 +1,47 @@
+{
+ "cancel": "取消",
+ "continueEditing": "继续编辑",
+ "create": "创建 Snippet",
+ "createFailed": "创建 Snippet 失败",
+ "createFromBlank": "创建空白 Snippet",
+ "defaultName": "未命名 Snippet",
+ "deleteConfirmContent": "删除后不可恢复,引用此 Snippet 的工作流不会自动更新。",
+ "deleteConfirmTitle": "删除 Snippet?",
+ "deleteFailed": "删除 Snippet 失败",
+ "deleted": "Snippet 已删除",
+ "discardChanges": "放弃更改",
+ "discardChangesDescription": "当前更改不会保存到此 Snippet。",
+ "discardChangesTitle": "放弃当前更改?",
+ "draft": "草稿",
+ "editDialogTitle": "编辑 Snippet 信息",
+ "editDone": "Snippet 信息已更新",
+ "editFailed": "更新 Snippet 信息失败",
+ "exportFailed": "导出 Snippet 失败。",
+ "importFailed": "导入 Snippet DSL 失败",
+ "importSuccess": "Snippet 导入成功",
+ "inputFieldButton": "输入字段",
+ "inputVariables": "输入变量",
+ "management": "SNIPPET 管理",
+ "menu.deleteSnippet": "删除",
+ "menu.editInfo": "编辑信息",
+ "menu.exportSnippet": "导出 Snippet",
+ "notFoundDescription": "未找到对应的 snippet 静态数据。",
+ "notFoundTitle": "未找到 Snippet",
+ "panelDescription": "定义允许 snippet 从其他节点接收数据的输入字段。",
+ "panelPrimaryGroup": "核心输入",
+ "panelSecondaryGroup": "可选输入",
+ "panelTitle": "输入字段",
+ "publishButton": "发布",
+ "publishFailed": "发布 Snippet 失败",
+ "publishMenuCurrentDraft": "当前草稿未发布",
+ "publishSuccess": "Snippet 已发布",
+ "save": "保存",
+ "sectionOrchestrate": "编排",
+ "testRunButton": "测试运行",
+ "typeLabel": "Snippet",
+ "unknownUser": "用户",
+ "unsavedChanges": "当前更改未保存。",
+ "updatedBy": "{{name}} 更新于 {{time}}",
+ "usageCount": "已使用 {{count}} 次",
+ "variableInspect": "变量查看"
+}
diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json
index 951d60bfd83..22e3ae18e39 100644
--- a/web/i18n/zh-Hans/workflow.json
+++ b/web/i18n/zh-Hans/workflow.json
@@ -247,6 +247,12 @@
"common.searchVar": "搜索变量",
"common.setVarValuePlaceholder": "设置变量值",
"common.showRunHistory": "显示运行历史",
+ "common.switchToStandardWorkflowConfirm.switch": "切换",
+ "common.switchToStandardWorkflowConfirm.targetTypes.app": "工作流",
+ "common.switchToStandardWorkflowConfirm.targetTypes.knowledge_base": "知识库",
+ "common.switchToStandardWorkflowConfirm.targetTypes.snippets": "片段",
+ "common.switchToStandardWorkflowConfirm.title": "切换为标准工作流?",
+ "common.switchToStandardWorkflowTip": "将当前评测器转换回标准工作流,并恢复公开 Web App 访问。",
"common.syncingData": "同步数据中,只需几秒钟。",
"common.tagBound": "使用此标签的应用数量",
"common.undo": "撤销",
@@ -1151,6 +1157,16 @@
"singleRun.testRun": "测试运行",
"singleRun.testRunIteration": "测试运行迭代",
"singleRun.testRunLoop": "测试运行循环",
+ "snippet.addToSnippet": "添加到 snippet",
+ "snippet.confirm": "确认",
+ "snippet.createDialogTitle": "创建 Snippet",
+ "snippet.createSuccess": "Snippet 已创建",
+ "snippet.descriptionLabel": "描述(可选)",
+ "snippet.descriptionPlaceholder": "简要描述你的 snippet",
+ "snippet.nameLabel": "Snippet 名称",
+ "snippet.namePlaceholder": "Snippet 名称",
+ "snippet.shortcuts.press": "按下",
+ "snippet.shortcuts.toConfirm": "确认",
"tabs.-": "默认",
"tabs.addAll": "添加全部",
"tabs.agent": "Agent 策略",
@@ -1158,6 +1174,7 @@
"tabs.allTool": "全部",
"tabs.allTriggers": "全部触发器",
"tabs.blocks": "节点",
+ "tabs.createSnippet": "创建 snippet",
"tabs.customTool": "自定义",
"tabs.featuredTools": "精选推荐",
"tabs.hideActions": "收起工具",
@@ -1167,19 +1184,23 @@
"tabs.noFeaturedTriggers": "前往插件市场查看更多触发器",
"tabs.noPluginsFound": "未找到插件",
"tabs.noResult": "未找到匹配项",
+ "tabs.noSnippetsFound": "未找到 snippets",
"tabs.plugin": "插件",
"tabs.pluginByAuthor": "来自 {{author}}",
"tabs.question-understand": "问题理解",
"tabs.requestToCommunity": "向社区反馈",
"tabs.searchBlock": "搜索节点",
"tabs.searchDataSource": "搜索数据源",
+ "tabs.searchSnippets": "搜索 snippets...",
"tabs.searchTool": "搜索工具",
"tabs.searchTrigger": "搜索触发器...",
"tabs.showLessFeatured": "收起",
"tabs.showMoreFeatured": "查看更多",
+ "tabs.snippets": "Snippets",
"tabs.sources": "数据源",
"tabs.start": "开始",
"tabs.startDisabledTip": "触发节点与用户输入节点互斥。",
+ "tabs.startNotSupportedTip": "Snippet 暂不支持 Start 标签。",
"tabs.tools": "工具",
"tabs.transform": "转换",
"tabs.usePlugin": "选择工具",
diff --git a/web/i18n/zh-Hant/common.json b/web/i18n/zh-Hant/common.json
index 021319be1ea..8bcdb17a9c6 100644
--- a/web/i18n/zh-Hant/common.json
+++ b/web/i18n/zh-Hant/common.json
@@ -194,6 +194,7 @@
"imageInput.browse": "瀏覽",
"imageInput.dropImageHere": "將您的圖片放在這裡,或",
"imageInput.supportedFormats": "支援 PNG、JPG、JPEG、WEBP 和 GIF",
+ "imageUploader.imageList": "圖片列表",
"imageUploader.imageUpload": "圖片上傳",
"imageUploader.pasteImageLink": "貼上圖片連結",
"imageUploader.pasteImageLinkInputPlaceholder": "將影象連結貼上到此處",
@@ -512,6 +513,8 @@
"operation.ok": "好的",
"operation.openInNewTab": "在新選項卡中打開",
"operation.params": "引數設定",
+ "operation.pause": "暫停",
+ "operation.play": "播放",
"operation.refresh": "重新開始",
"operation.regenerate": "再生",
"operation.reload": "重新整理",
@@ -519,6 +522,7 @@
"operation.rename": "重新命名",
"operation.reset": "重置",
"operation.resetKeywords": "重置關鍵字",
+ "operation.retry": "重試",
"operation.save": "儲存",
"operation.saveAndEnable": "儲存並啟用",
"operation.saveAndRegenerate": "保存並重新生成子塊",
@@ -533,13 +537,19 @@
"operation.skip": "船",
"operation.submit": "提交",
"operation.sure": "我確定",
+ "operation.toggleFullscreen": "切換全螢幕",
+ "operation.toggleMute": "切換靜音",
"operation.view": "視圖",
"operation.viewDetails": "查看詳情",
"operation.viewMore": "查看更多",
"operation.yes": "是",
"operation.zoomIn": "放大",
"operation.zoomOut": "縮小",
+ "pagination.editPageNumber": "編輯頁碼,目前第 {{page}} 頁,共 {{totalPages}} 頁",
+ "pagination.next": "下一頁",
+ "pagination.pageNumber": "頁碼",
"pagination.perPage": "每頁項目數",
+ "pagination.previous": "上一頁",
"placeholder.input": "請輸入",
"placeholder.search": "搜尋...",
"placeholder.select": "請選擇",
@@ -677,5 +687,6 @@
"voiceInput.converting": "正在轉換為文字...",
"voiceInput.notAllow": "麥克風未授權",
"voiceInput.speaking": "現在講...",
+ "voiceInput.start": "語音輸入",
"you": "你"
}
diff --git a/web/models/snippet.ts b/web/models/snippet.ts
new file mode 100644
index 00000000000..dbb1496b50c
--- /dev/null
+++ b/web/models/snippet.ts
@@ -0,0 +1,49 @@
+import type { Viewport } from 'reactflow'
+import type { Edge, Node } from '@/app/components/workflow/types'
+import type { Tag } from '@/contract/console/tags'
+import type { InputVar } from '@/models/pipeline'
+
+export type SnippetSection = 'orchestrate'
+
+export type SnippetListItem = {
+ id: string
+ name: string
+ description: string
+ updatedAt: string
+ usage: string
+ tags: Tag[]
+ is_published?: boolean
+ status?: string
+}
+
+export type SnippetDetail = {
+ id: string
+ name: string
+ description: string
+ updatedAt: string
+ usage: string
+ tags: Tag[]
+ is_published?: boolean
+ status?: string
+}
+
+export type SnippetCanvasData = {
+ nodes: Node[]
+ edges: Edge[]
+ viewport: Viewport
+}
+
+export type SnippetInputField = InputVar
+
+export type SnippetDetailUIModel = {
+ inputFieldCount: number
+ checklistCount: number
+ autoSavedAt: string
+}
+
+export type SnippetDetailPayload = {
+ snippet: SnippetDetail
+ graph: SnippetCanvasData
+ inputFields: SnippetInputField[]
+ uiMeta: SnippetDetailUIModel
+}
diff --git a/web/service/__tests__/use-snippet-workflows.spec.tsx b/web/service/__tests__/use-snippet-workflows.spec.tsx
new file mode 100644
index 00000000000..d39f27390b1
--- /dev/null
+++ b/web/service/__tests__/use-snippet-workflows.spec.tsx
@@ -0,0 +1,78 @@
+import type { ReactNode } from 'react'
+import type { SnippetWorkflow } from '@/types/snippet'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { renderHook, waitFor } from '@testing-library/react'
+import { get } from '../base'
+import { useSnippetDraftWorkflow } from '../use-snippet-workflows'
+
+const { draftWorkflowQueryOptions } = vi.hoisted(() => ({
+ draftWorkflowQueryOptions: vi.fn(),
+}))
+
+vi.mock('../base', () => ({
+ get: vi.fn(),
+}))
+
+vi.mock('@/service/client', () => ({
+ consoleQuery: {
+ snippets: {
+ draftWorkflow: {
+ queryOptions: draftWorkflowQueryOptions,
+ },
+ },
+ },
+}))
+
+const mockGet = vi.mocked(get)
+
+const createWrapper = () => {
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ })
+
+ return ({ children }: { children: ReactNode }) => (
+ {children}
+ )
+}
+
+describe('useSnippetDraftWorkflow', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ draftWorkflowQueryOptions.mockReturnValue({
+ queryKey: ['console', 'snippets', 'draft-workflow', 'snippet-1'],
+ enabled: true,
+ queryFn: vi.fn(),
+ })
+ })
+
+ it('should fetch the draft workflow silently during initialization', async () => {
+ const onSuccess = vi.fn()
+ const draftWorkflow = {
+ hash: 'draft-hash',
+ updated_at: 1_712_345_678,
+ } as SnippetWorkflow
+
+ mockGet.mockResolvedValueOnce(draftWorkflow)
+
+ const { result } = renderHook(() => useSnippetDraftWorkflow('snippet-1', onSuccess), {
+ wrapper: createWrapper(),
+ })
+
+ await waitFor(() => {
+ expect(result.current.data).toEqual(draftWorkflow)
+ })
+
+ expect(draftWorkflowQueryOptions).toHaveBeenCalledWith({
+ input: {
+ params: { snippetId: 'snippet-1' },
+ },
+ enabled: true,
+ })
+ expect(mockGet).toHaveBeenCalledWith('/snippets/snippet-1/workflows/draft', {}, { silent: true })
+ expect(onSuccess).toHaveBeenCalledWith(draftWorkflow)
+ })
+})
diff --git a/web/service/use-snippet-workflows.ts b/web/service/use-snippet-workflows.ts
new file mode 100644
index 00000000000..e049179ab9e
--- /dev/null
+++ b/web/service/use-snippet-workflows.ts
@@ -0,0 +1,132 @@
+import type { SnippetWorkflow } from '@/types/snippet'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import { consoleQuery } from '@/service/client'
+import { get } from './base'
+
+const isNotFoundError = (error: unknown) => {
+ return !!error && typeof error === 'object' && 'status' in error && error.status === 404
+}
+
+export const fetchSnippetDraftWorkflow = async (snippetId: string) => {
+ try {
+ return await get(`/snippets/${snippetId}/workflows/draft`, {}, { silent: true })
+ }
+ catch (error) {
+ if (isNotFoundError(error))
+ return undefined
+
+ throw error
+ }
+}
+
+const invalidateSnippetWorkflowQueries = async (
+ queryClient: ReturnType,
+ snippetId: string,
+) => {
+ await Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.draftWorkflow.queryKey({
+ input: {
+ params: { snippetId },
+ },
+ }),
+ }),
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.publishedWorkflow.queryKey({
+ input: {
+ params: { snippetId },
+ },
+ }),
+ }),
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.workflowRuns.key(),
+ }),
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.lastDraftNodeRun.key(),
+ }),
+ ])
+}
+
+export const useSnippetDraftWorkflow = (
+ snippetId: string,
+ onSuccess?: (draftWorkflow: SnippetWorkflow) => void,
+) => {
+ const queryOptions = consoleQuery.snippets.draftWorkflow.queryOptions({
+ input: {
+ params: { snippetId },
+ },
+ enabled: !!snippetId,
+ })
+
+ return useQuery({
+ ...queryOptions,
+ queryFn: async () => {
+ const draftWorkflow = await fetchSnippetDraftWorkflow(snippetId)
+ if (draftWorkflow)
+ onSuccess?.(draftWorkflow)
+ return draftWorkflow
+ },
+ })
+}
+
+export const useSnippetPublishedWorkflow = (
+ snippetId: string,
+ onSuccess?: (publishedWorkflow: SnippetWorkflow) => void,
+) => {
+ const queryOptions = consoleQuery.snippets.publishedWorkflow.queryOptions({
+ input: {
+ params: { snippetId },
+ },
+ enabled: !!snippetId,
+ })
+
+ return useQuery({
+ ...queryOptions,
+ queryFn: async (context) => {
+ try {
+ const publishedWorkflow = await queryOptions.queryFn(context)
+ onSuccess?.(publishedWorkflow)
+ return publishedWorkflow
+ }
+ catch (error) {
+ if (isNotFoundError(error))
+ return undefined
+
+ throw error
+ }
+ },
+ })
+}
+
+export const useSnippetDefaultBlockConfigs = (
+ snippetId: string,
+ onSuccess?: (nodesDefaultConfigs: unknown) => void,
+) => {
+ const queryOptions = consoleQuery.snippets.defaultBlockConfigs.queryOptions({
+ input: {
+ params: { snippetId },
+ },
+ enabled: !!snippetId,
+ })
+
+ return useQuery({
+ ...queryOptions,
+ queryFn: async (context) => {
+ const nodesDefaultConfigs = await queryOptions.queryFn(context)
+ onSuccess?.(nodesDefaultConfigs)
+ return nodesDefaultConfigs
+ },
+ })
+}
+
+export const usePublishSnippetWorkflowMutation = (snippetId: string) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ ...consoleQuery.snippets.publishWorkflow.mutationOptions({
+ onSuccess: async () => {
+ await invalidateSnippetWorkflowQueries(queryClient, snippetId)
+ },
+ }),
+ })
+}
diff --git a/web/service/use-snippets.ts b/web/service/use-snippets.ts
new file mode 100644
index 00000000000..6eb067a60b3
--- /dev/null
+++ b/web/service/use-snippets.ts
@@ -0,0 +1,259 @@
+import type {
+ SnippetCanvasData,
+ SnippetDetailPayload,
+ SnippetDetail as SnippetDetailUIModel,
+ SnippetInputField as SnippetInputFieldUIModel,
+ SnippetListItem as SnippetListItemUIModel,
+} from '@/models/snippet'
+import type {
+ Snippet as SnippetContract,
+ SnippetDSLImportResponse,
+ SnippetListResponse,
+ SnippetWorkflow,
+} from '@/types/snippet'
+import {
+ keepPreviousData,
+ useInfiniteQuery,
+ useMutation,
+ useQuery,
+ useQueryClient,
+} from '@tanstack/react-query'
+import dayjs from 'dayjs'
+import { consoleClient, consoleQuery } from '@/service/client'
+
+type SnippetListParams = {
+ page?: number
+ limit?: number
+ keyword?: string
+ tag_ids?: string[]
+ creator_id?: string
+ is_published?: boolean
+}
+
+type SnippetSummary = Pick
+
+const DEFAULT_SNIPPET_LIST_PARAMS = {
+ page: 1,
+ limit: 30,
+} satisfies Required>
+
+const DEFAULT_GRAPH: SnippetCanvasData = {
+ nodes: [],
+ edges: [],
+ viewport: { x: 0, y: 0, zoom: 1 },
+}
+
+const toMilliseconds = (timestamp?: number) => {
+ if (!timestamp)
+ return undefined
+
+ return timestamp > 1_000_000_000_000 ? timestamp : timestamp * 1000
+}
+
+const formatTimestamp = (timestamp?: number) => {
+ const milliseconds = toMilliseconds(timestamp)
+ if (!milliseconds)
+ return ''
+
+ return dayjs(milliseconds).format('YYYY-MM-DD HH:mm')
+}
+
+const toSnippetListItem = (snippet: SnippetSummary): SnippetListItemUIModel => {
+ return {
+ id: snippet.id,
+ name: snippet.name,
+ description: snippet.description,
+ updatedAt: formatTimestamp(snippet.updated_at),
+ usage: String(snippet.use_count ?? 0),
+ tags: snippet.tags,
+ is_published: snippet.is_published,
+ status: undefined,
+ }
+}
+
+const toSnippetDetail = (snippet: SnippetContract): SnippetDetailUIModel => {
+ return {
+ ...toSnippetListItem(snippet),
+ }
+}
+
+const toSnippetCanvasData = (workflow?: SnippetWorkflow): SnippetCanvasData => {
+ const graph = workflow?.graph
+
+ if (!graph || typeof graph !== 'object')
+ return DEFAULT_GRAPH
+
+ const graphRecord = graph as Record
+
+ return {
+ nodes: Array.isArray(graphRecord.nodes) ? graphRecord.nodes as SnippetCanvasData['nodes'] : DEFAULT_GRAPH.nodes,
+ edges: Array.isArray(graphRecord.edges) ? graphRecord.edges as SnippetCanvasData['edges'] : DEFAULT_GRAPH.edges,
+ viewport: graphRecord.viewport && typeof graphRecord.viewport === 'object'
+ ? graphRecord.viewport as SnippetCanvasData['viewport']
+ : DEFAULT_GRAPH.viewport,
+ }
+}
+
+export const buildSnippetDetailPayload = (snippet: SnippetContract, workflow?: SnippetWorkflow): SnippetDetailPayload => {
+ const inputFields = Array.isArray(workflow?.input_fields)
+ ? workflow.input_fields as SnippetInputFieldUIModel[]
+ : []
+
+ return {
+ snippet: toSnippetDetail(snippet),
+ graph: toSnippetCanvasData(workflow),
+ inputFields,
+ uiMeta: {
+ inputFieldCount: inputFields.length,
+ checklistCount: 0,
+ autoSavedAt: formatTimestamp(workflow?.updated_at ?? snippet.updated_at),
+ },
+ }
+}
+
+const normalizeSnippetListParams = (params: SnippetListParams) => {
+ return {
+ page: params.page ?? DEFAULT_SNIPPET_LIST_PARAMS.page,
+ limit: params.limit ?? DEFAULT_SNIPPET_LIST_PARAMS.limit,
+ ...(params.keyword ? { keyword: params.keyword } : {}),
+ ...(params.tag_ids?.length ? { tag_ids: params.tag_ids } : {}),
+ ...(params.creator_id ? { creator_id: params.creator_id } : {}),
+ ...(typeof params.is_published === 'boolean' ? { is_published: params.is_published } : {}),
+ }
+}
+
+const snippetListKey = (params: SnippetListParams) => ['snippets', 'list', params]
+
+export const useInfiniteSnippetList = (params: SnippetListParams = {}, options?: { enabled?: boolean }) => {
+ const normalizedParams = normalizeSnippetListParams(params)
+
+ return useInfiniteQuery({
+ queryKey: snippetListKey(normalizedParams),
+ queryFn: ({ pageParam = normalizedParams.page }) => {
+ return consoleClient.snippets.list({
+ query: {
+ ...normalizedParams,
+ page: pageParam as number,
+ },
+ })
+ },
+ getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : undefined,
+ initialPageParam: normalizedParams.page,
+ placeholderData: keepPreviousData,
+ ...options,
+ })
+}
+
+export const useSnippetApiDetail = (snippetId: string) => {
+ return useQuery(consoleQuery.snippets.detail.queryOptions({
+ input: {
+ params: { snippetId },
+ },
+ enabled: !!snippetId,
+ }))
+}
+
+export const useCreateSnippetMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation(consoleQuery.snippets.create.mutationOptions({
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ }))
+}
+
+export const useUpdateSnippetMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ ...consoleQuery.snippets.update.mutationOptions({
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ }),
+ })
+}
+
+export const useDeleteSnippetMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ ...consoleQuery.snippets.delete.mutationOptions({
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ }),
+ })
+}
+
+export const useIncrementSnippetUseCountMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ ...consoleQuery.snippets.incrementUseCount.mutationOptions({
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ }),
+ })
+}
+
+export const useExportSnippetMutation = () => {
+ return useMutation({
+ mutationFn: ({ snippetId, include = false }) => {
+ return consoleClient.snippets.export({
+ params: { snippetId },
+ query: { include_secret: include ? 'true' : 'false' },
+ })
+ },
+ })
+}
+
+export const useImportSnippetDSLMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ mode, yamlContent, yamlUrl }) => {
+ return consoleClient.snippets.import({
+ body: {
+ mode,
+ yaml_content: yamlContent,
+ yaml_url: yamlUrl,
+ },
+ }) as Promise
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ })
+}
+
+export const useConfirmSnippetImportMutation = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ importId }) => {
+ return consoleClient.snippets.confirmImport({
+ params: {
+ importId,
+ },
+ }) as Promise
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: consoleQuery.snippets.key(),
+ })
+ },
+ })
+}
diff --git a/web/service/utils.ts b/web/service/utils.ts
index 6d0c3ca88e0..7691ea4e71d 100644
--- a/web/service/utils.ts
+++ b/web/service/utils.ts
@@ -3,6 +3,7 @@ import { FlowType } from '@/types/common'
export const flowPrefixMap = {
[FlowType.appFlow]: 'apps',
[FlowType.ragPipeline]: 'rag/pipelines',
+ [FlowType.snippet]: 'snippets',
}
export const getFlowPrefix = (type?: FlowType) => {
diff --git a/web/types/app.ts b/web/types/app.ts
index 6361efa85fc..8b7286b2db8 100644
--- a/web/types/app.ts
+++ b/web/types/app.ts
@@ -9,44 +9,72 @@ import type {
WeightedScoreEnum,
} from '@/models/datasets'
import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug'
+import type { WorkflowKind } from '@/types/workflow'
-export enum Theme {
- light = 'light',
- dark = 'dark',
- system = 'system',
-}
+export type Theme = 'light' | 'dark' | 'system'
+export const Theme = {
+ light: 'light' as Theme,
+ dark: 'dark' as Theme,
+ system: 'system' as Theme,
+} as const
-export enum ModelModeType {
- chat = 'chat',
- completion = 'completion',
- unset = '',
-}
+export type ModelModeType = 'chat' | 'completion' | ''
+export const ModelModeType = {
+ chat: 'chat' as ModelModeType,
+ completion: 'completion' as ModelModeType,
+ unset: '' as ModelModeType,
+} as const
-export enum RETRIEVE_TYPE {
- oneWay = 'single',
- multiWay = 'multiple',
-}
+export type RETRIEVE_TYPE = 'single' | 'multiple'
+export const RETRIEVE_TYPE = {
+ oneWay: 'single' as RETRIEVE_TYPE,
+ multiWay: 'multiple' as RETRIEVE_TYPE,
+} as const
-export enum RETRIEVE_METHOD {
- semantic = 'semantic_search',
- fullText = 'full_text_search',
- hybrid = 'hybrid_search',
- invertedIndex = 'invertedIndex',
- keywordSearch = 'keyword_search',
-}
+export type RETRIEVE_METHOD
+ = | 'semantic_search'
+ | 'full_text_search'
+ | 'hybrid_search'
+ | 'invertedIndex'
+ | 'keyword_search'
+export const RETRIEVE_METHOD = {
+ semantic: 'semantic_search' as RETRIEVE_METHOD,
+ fullText: 'full_text_search' as RETRIEVE_METHOD,
+ hybrid: 'hybrid_search' as RETRIEVE_METHOD,
+ invertedIndex: 'invertedIndex' as RETRIEVE_METHOD,
+ keywordSearch: 'keyword_search' as RETRIEVE_METHOD,
+} as const
/**
* App modes
*/
-export enum AppModeEnum {
- COMPLETION = 'completion',
- WORKFLOW = 'workflow',
- CHAT = 'chat',
- ADVANCED_CHAT = 'advanced-chat',
- AGENT_CHAT = 'agent-chat',
-}
+export type AppModeEnum
+ = | 'completion'
+ | 'workflow'
+ | 'chat'
+ | 'advanced-chat'
+ | 'agent-chat'
+export const AppModeEnum = {
+ COMPLETION: 'completion' as AppModeEnum,
+ WORKFLOW: 'workflow' as AppModeEnum,
+ CHAT: 'chat' as AppModeEnum,
+ ADVANCED_CHAT: 'advanced-chat' as AppModeEnum,
+ AGENT_CHAT: 'agent-chat' as AppModeEnum,
+} as const
export const AppModes = [AppModeEnum.COMPLETION, AppModeEnum.WORKFLOW, AppModeEnum.CHAT, AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT] as const
+export type AppTypeEnum
+ = | 'workflow'
+ | 'chat'
+ | 'rag-pipeline'
+ | 'snippet'
+export const AppTypeEnum = {
+ WORKFLOW: 'workflow' as AppTypeEnum,
+ CHAT: 'chat' as AppTypeEnum,
+ RAG_PIPELINE: 'rag-pipeline' as AppTypeEnum,
+ SNIPPET: 'snippet' as AppTypeEnum,
+} as const
+
/**
* Variable type
*/
@@ -122,10 +150,11 @@ export type ToolItem = {
}
} | AgentTool
-export enum AgentStrategy {
- functionCall = 'function_call',
- react = 'react',
-}
+export type AgentStrategy = 'function_call' | 'react'
+export const AgentStrategy = {
+ functionCall: 'function_call' as AgentStrategy,
+ react: 'react' as AgentStrategy,
+} as const
export type CompletionParams = {
/** Maximum number of tokens in the answer message returned by Completion */
@@ -361,27 +390,32 @@ export type App = {
max_active_requests?: number | null
/** whether workflow trigger has un-published draft */
has_draft_trigger?: boolean
+ /** Type */
+ workflow_kind?: WorkflowKind | null
}
export type AppSSO = {
enable_sso: boolean
}
-export enum Resolution {
- low = 'low',
- high = 'high',
-}
+export type Resolution = 'low' | 'high'
+export const Resolution = {
+ low: 'low' as Resolution,
+ high: 'high' as Resolution,
+} as const
-export enum TransferMethod {
- all = 'all',
- local_file = 'local_file',
- remote_url = 'remote_url',
-}
+export type TransferMethod = 'all' | 'local_file' | 'remote_url'
+export const TransferMethod = {
+ all: 'all' as TransferMethod,
+ local_file: 'local_file' as TransferMethod,
+ remote_url: 'remote_url' as TransferMethod,
+} as const
-export enum TtsAutoPlay {
- enabled = 'enabled',
- disabled = 'disabled',
-}
+export type TtsAutoPlay = 'enabled' | 'disabled'
+export const TtsAutoPlay = {
+ enabled: 'enabled' as TtsAutoPlay,
+ disabled: 'disabled' as TtsAutoPlay,
+} as const
export const ALLOW_FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif']
diff --git a/web/types/common.ts b/web/types/common.ts
index 19bc8acc8d4..6c7cbebf20a 100644
--- a/web/types/common.ts
+++ b/web/types/common.ts
@@ -1,4 +1,5 @@
export enum FlowType {
appFlow = 'appFlow',
ragPipeline = 'ragPipeline',
+ snippet = 'snippet',
}
diff --git a/web/types/snippet.ts b/web/types/snippet.ts
new file mode 100644
index 00000000000..8e022d42275
--- /dev/null
+++ b/web/types/snippet.ts
@@ -0,0 +1,156 @@
+import type { Tag } from '@/contract/console/tags'
+
+export type SnippetType = 'node' | 'group'
+
+export type SnippetInputField = Record
+
+export type Snippet = {
+ id: string
+ name: string
+ description: string
+ type: SnippetType
+ is_published: boolean
+ version: string
+ use_count: number
+ tags: Tag[]
+ input_fields: SnippetInputField[]
+ created_at: number
+ created_by: string
+ updated_at: number
+ updated_by: string
+}
+
+export type SnippetListItem = Omit
+
+export type SnippetListResponse = {
+ data: SnippetListItem[]
+ page: number
+ limit: number
+ total: number
+ has_more: boolean
+}
+
+export type CreateSnippetPayload = {
+ name: string
+ description?: string
+ type?: SnippetType
+ input_fields?: SnippetInputField[]
+}
+
+export type UpdateSnippetPayload = {
+ name?: string
+ description?: string
+}
+
+export type SnippetImportPayload = {
+ mode?: string
+ yaml_content?: string
+ yaml_url?: string
+ snippet_id?: string
+ name?: string
+ description?: string
+}
+
+export type SnippetDSLImportResponse = {
+ id: string
+ status: string
+ snippet_id?: string
+ current_dsl_version?: string
+ imported_dsl_version?: string
+ error: string
+}
+
+export type IncrementSnippetUseCountResponse = {
+ result: string
+ use_count: number
+}
+
+export type SnippetWorkflow = {
+ id: string
+ graph: Record
+ features: Record
+ input_fields?: SnippetInputField[]
+ hash: string
+ created_at: number
+ updated_at: number
+}
+
+export type SnippetDraftSyncPayload = {
+ graph?: Record
+ hash?: string
+ environment_variables?: Record[]
+ conversation_variables?: Record[]
+ input_fields?: SnippetInputField[]
+}
+
+export type SnippetDraftSyncResponse = {
+ result: string
+ hash: string
+ updated_at: number
+}
+
+export type SnippetDraftConfig = {
+ parallel_depth_limit: number
+}
+
+export type PublishSnippetWorkflowResponse = {
+ result: string
+ created_at: number
+}
+
+export type WorkflowRunDetail = {
+ id: string
+ version: string
+ status: 'running' | 'succeeded' | 'failed' | 'stopped' | 'partial-succeeded'
+ elapsed_time: number
+ total_tokens: number
+ total_steps: number
+ created_at: number
+ finished_at: number
+ exceptions_count: number
+}
+
+export type WorkflowRunPagination = {
+ limit: number
+ has_more: boolean
+ data: WorkflowRunDetail[]
+}
+
+export type WorkflowNodeExecution = {
+ id: string
+ index: number
+ node_id: string
+ node_type: string
+ title: string
+ inputs: Record
+ process_data: Record
+ outputs: Record
+ status: string
+ error: string
+ elapsed_time: number
+ created_at: number
+ finished_at: number
+}
+
+export type WorkflowNodeExecutionListResponse = {
+ data: WorkflowNodeExecution[]
+}
+
+export type SnippetDraftNodeRunPayload = {
+ inputs?: Record
+ query?: string
+ files?: Record[]
+}
+
+export type SnippetDraftRunPayload = {
+ inputs?: Record
+ files?: Record[]
+}
+
+export type SnippetIterationNodeRunPayload = {
+ inputs?: Record
+}
+
+export type SnippetLoopNodeRunPayload = {
+ inputs?: Record
+}
diff --git a/web/types/workflow.ts b/web/types/workflow.ts
index 95d8e47fdbf..76e61c0f7a9 100644
--- a/web/types/workflow.ts
+++ b/web/types/workflow.ts
@@ -427,6 +427,8 @@ export type PublishWorkflowParams = {
releaseNotes: string
}
+export type WorkflowKind = 'standard'
+
export type UpdateWorkflowParams = {
url: string
title: string