Compare commits

..

8 Commits

Author SHA1 Message Date
e93fdddb20 feat(config): add support for overriding database credentials with DIFY_DB_USER and DIFY_DB_PASS 2026-04-08 22:36:53 +08:00
ccfc8c6f15 chore: align prompt editor var checks with use-check-list checks (#34715)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-08 13:29:07 +00:00
4fb3fab82d fix: add backward-compatible query param for decode_plugin_from_ident… (#34720) 2026-04-08 13:28:37 +00:00
3cea0dfb07 fix: fix import error (#34728) 2026-04-08 13:27:53 +00:00
0d6db3a3f3 chore(i18n): sync translations with en-US (#34745)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-04-08 12:10:37 +00:00
3d5a81bd30 chore(i18n): sync translations with en-US (#34742)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-04-08 11:30:47 +00:00
yyh
208604a3a8 fix(ci): repair i18n bridge and translation workflow (#34738) 2026-04-08 11:05:13 +00:00
63bfba0bdb fix: update how ky handle error (#34735) 2026-04-08 10:38:33 +00:00
70 changed files with 442 additions and 179 deletions

View File

@ -134,6 +134,26 @@ class DatabaseConfig(BaseSettings):
default="",
)
DIFY_DB_USER: str | None = Field(
description="Override for DB_USERNAME. Takes precedence when set.",
default=None,
)
DIFY_DB_PASS: str | None = Field(
description="Override for DB_PASSWORD. Takes precedence when set.",
default=None,
)
@computed_field # type: ignore[prop-decorator]
@property
def effective_db_username(self) -> str:
return self.DIFY_DB_USER if self.DIFY_DB_USER is not None else self.DB_USERNAME
@computed_field # type: ignore[prop-decorator]
@property
def effective_db_password(self) -> str:
return self.DIFY_DB_PASS if self.DIFY_DB_PASS is not None else self.DB_PASSWORD
DB_DATABASE: str = Field(
description="Name of the database to connect to.",
default="dify",
@ -163,7 +183,7 @@ class DatabaseConfig(BaseSettings):
db_extras = f"?{db_extras}" if db_extras else ""
return (
f"{self.SQLALCHEMY_DATABASE_URI_SCHEME}://"
f"{quote_plus(self.DB_USERNAME)}:{quote_plus(self.DB_PASSWORD)}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_DATABASE}"
f"{quote_plus(self.effective_db_username)}:{quote_plus(self.effective_db_password)}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_DATABASE}"
f"{db_extras}"
)

View File

@ -209,7 +209,10 @@ class PluginInstaller(BasePluginClient):
"GET",
f"plugin/{tenant_id}/management/decode/from_identifier",
PluginDecodeResponse,
params={"plugin_unique_identifier": plugin_unique_identifier},
params={
"plugin_unique_identifier": plugin_unique_identifier,
"PluginUniqueIdentifier": plugin_unique_identifier, # compat with daemon <= 0.5.4
},
)
def fetch_plugin_installation_by_ids(

View File

@ -4,7 +4,7 @@ from graphon.entities.base_node_data import BaseNodeData
from graphon.enums import NodeType
from pydantic import BaseModel
from core.rag.entities import WeightedScoreConfig
from core.rag.entities.retrieval_settings import WeightedScoreConfig
from core.rag.index_processor.index_processor_base import SummaryIndexSettingDict
from core.rag.retrieval.retrieval_methods import RetrievalMethod
from core.workflow.nodes.knowledge_index import KNOWLEDGE_INDEX_NODE_TYPE

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
ROOT="$(dirname "$SCRIPT_DIR")"
cd "$ROOT/docker"
docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
ROOT="$(dirname "$SCRIPT_DIR")"
cd "$ROOT/docker"
docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d

View File

@ -0,0 +1,7 @@
@smoke @unauthenticated
Feature: Unauthenticated app console entry
Scenario: Redirect to the sign-in page when opening the apps console without logging in
Given I am not signed in
When I open the apps console
Then I should be redirected to the signin page
And I should see the "Sign in" button

View File

@ -9,3 +9,10 @@ Given('I am signed in as the default E2E admin', async function (this: DifyWorld
'text/plain',
)
})
Given('I am not signed in', async function (this: DifyWorld) {
this.attach(
'Using a clean browser context without the shared authenticated storage state.',
'text/plain',
)
})

View File

@ -10,6 +10,10 @@ Then('I should stay on the apps console', async function (this: DifyWorld) {
await expect(this.getPage()).toHaveURL(/\/apps(?:\?.*)?$/)
})
Then('I should be redirected to the signin page', async function (this: DifyWorld) {
await expect(this.getPage()).toHaveURL(/\/signin(?:\?.*)?$/)
})
Then('I should see the {string} button', async function (this: DifyWorld, label: string) {
await expect(this.getPage().getByRole('button', { name: label })).toBeVisible()
})

View File

@ -46,7 +46,11 @@ BeforeAll(async () => {
Before(async function (this: DifyWorld, { pickle }) {
if (!browser) throw new Error('Shared Playwright browser is not available.')
await this.startAuthenticatedSession(browser)
const isUnauthenticatedScenario = pickle.tags.some((tag) => tag.name === '@unauthenticated')
if (isUnauthenticatedScenario) await this.startUnauthenticatedSession(browser)
else await this.startAuthenticatedSession(browser)
this.scenarioStartedAt = Date.now()
const tags = pickle.tags.map((tag) => tag.name).join(' ')

View File

@ -25,12 +25,12 @@ export class DifyWorld extends World {
this.pageErrors = []
}
async startAuthenticatedSession(browser: Browser) {
async startSession(browser: Browser, authenticated: boolean) {
this.resetScenarioState()
this.context = await browser.newContext({
baseURL,
locale: defaultLocale,
storageState: authStatePath,
...(authenticated ? { storageState: authStatePath } : {}),
})
this.context.setDefaultTimeout(30_000)
this.page = await this.context.newPage()
@ -44,6 +44,14 @@ export class DifyWorld extends World {
})
}
async startAuthenticatedSession(browser: Browser) {
await this.startSession(browser, true)
}
async startUnauthenticatedSession(browser: Browser) {
await this.startSession(browser, false)
}
getPage() {
if (!this.page) throw new Error('Playwright page has not been initialized for this scenario.')

View File

@ -120,7 +120,10 @@ describe('HITLInputBlock', () => {
})
await waitFor(() => {
expect(onWorkflowMapUpdate).toHaveBeenCalledWith(workflowNodesMap)
expect(onWorkflowMapUpdate).toHaveBeenCalledWith({
workflowNodesMap,
availableVariables: [],
})
})
})
})

View File

@ -148,7 +148,10 @@ describe('HITLInputVariableBlockComponent', () => {
editor!.update(() => {
$getRoot().selectEnd()
})
handled = editor!.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, createWorkflowNodesMap())
handled = editor!.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, {
workflowNodesMap: createWorkflowNodesMap(),
availableVariables: [],
})
})
expect(handled).toBe(true)

View File

@ -22,7 +22,7 @@ const HITLInputReplacementBlock = ({
onFormInputsChange,
onFormInputItemRename,
onFormInputItemRemove,
workflowNodesMap,
workflowNodesMap = {},
getVarType,
variables,
readonly,

View File

@ -14,6 +14,7 @@ import {
useEffect,
} from 'react'
import { CustomTextNode } from '../custom-text/node'
import { UPDATE_WORKFLOW_NODES_MAP as WORKFLOW_UPDATE_WORKFLOW_NODES_MAP } from '../workflow-variable-block'
import {
$createHITLInputNode,
HITLInputNode,
@ -21,11 +22,13 @@ import {
export const INSERT_HITL_INPUT_BLOCK_COMMAND = createCommand('INSERT_HITL_INPUT_BLOCK_COMMAND')
export const DELETE_HITL_INPUT_BLOCK_COMMAND = createCommand('DELETE_HITL_INPUT_BLOCK_COMMAND')
export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP')
export const UPDATE_WORKFLOW_NODES_MAP = WORKFLOW_UPDATE_WORKFLOW_NODES_MAP
const HITLInputBlock = memo(({
onInsert,
onDelete,
workflowNodesMap,
workflowNodesMap = {},
variables: workflowAvailableVariables,
getVarType,
readonly,
}: HITLInputBlockType) => {
@ -33,9 +36,12 @@ const HITLInputBlock = memo(({
useEffect(() => {
editor.update(() => {
editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap)
editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, {
workflowNodesMap: workflowNodesMap || {},
availableVariables: workflowAvailableVariables || [],
})
})
}, [editor, workflowNodesMap])
}, [editor, workflowNodesMap, workflowAvailableVariables])
useEffect(() => {
if (!editor.hasNodes([HITLInputNode]))

View File

@ -1,3 +1,4 @@
import type { UpdateWorkflowNodesMapPayload } from '../workflow-variable-block'
import type { WorkflowNodesMap } from '../workflow-variable-block/node'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
@ -98,9 +99,8 @@ const HITLInputVariableBlockComponent = ({
return mergeRegister(
editor.registerCommand(
UPDATE_WORKFLOW_NODES_MAP,
(workflowNodesMap: WorkflowNodesMap) => {
setLocalWorkflowNodesMap(workflowNodesMap)
(payload: UpdateWorkflowNodesMapPayload) => {
setLocalWorkflowNodesMap(payload.workflowNodesMap)
return true
},
COMMAND_PRIORITY_EDITOR,

View File

@ -1,4 +1,5 @@
import type { LexicalEditor } from 'lexical'
import type { UpdateWorkflowNodesMapPayload } from '../index'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
@ -216,7 +217,7 @@ describe('WorkflowVariableBlockComponent', () => {
})
})
it('should mark env variable invalid when not found in environmentVariables', () => {
it('should treat env variable as valid regardless of environmentVariables contents', () => {
const environmentVariables: Var[] = [{ variable: 'env.valid_key', type: VarType.string }]
render(
@ -229,7 +230,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: expect.any(String),
errorMsg: undefined,
}))
})
@ -281,7 +282,7 @@ describe('WorkflowVariableBlockComponent', () => {
}))
})
it('should evaluate env fallback selector tokens when classifier is forced', () => {
it('should mark forced env branch invalid when selector prefix is missing', () => {
mockForcedVariableKind.value = 'env'
const environmentVariables: Var[] = [{ variable: '.', type: VarType.string }]
@ -295,7 +296,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: undefined,
errorMsg: expect.any(String),
}))
})
@ -330,7 +331,7 @@ describe('WorkflowVariableBlockComponent', () => {
}))
})
it('should mark conversation variable invalid when not found in conversationVariables', () => {
it('should treat conversation variable as valid regardless of conversationVariables contents', () => {
const conversationVariables: Var[] = [{ variable: 'conversation.other', type: VarType.string }]
render(
@ -343,7 +344,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: expect.any(String),
errorMsg: undefined,
}))
})
@ -364,7 +365,7 @@ describe('WorkflowVariableBlockComponent', () => {
}))
})
it('should evaluate conversation fallback selector tokens when classifier is forced', () => {
it('should mark forced conversation branch invalid when selector prefix is missing', () => {
mockForcedVariableKind.value = 'conversation'
const conversationVariables: Var[] = [{ variable: '.', type: VarType.string }]
@ -378,7 +379,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: undefined,
errorMsg: expect.any(String),
}))
})
@ -427,7 +428,7 @@ describe('WorkflowVariableBlockComponent', () => {
}))
})
it('should mark rag variable invalid when not found in ragVariables', () => {
it('should treat rag variable as valid regardless of ragVariables contents', () => {
const ragVariables: Var[] = [{ variable: 'rag.shared.other', type: VarType.string }]
render(
@ -440,7 +441,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: expect.any(String),
errorMsg: undefined,
}))
})
@ -461,7 +462,7 @@ describe('WorkflowVariableBlockComponent', () => {
}))
})
it('should evaluate rag fallback selector tokens when classifier is forced', () => {
it('should mark forced rag branch invalid when selector prefix is missing', () => {
mockForcedVariableKind.value = 'rag'
const ragVariables: Var[] = [{ variable: '..', type: VarType.string }]
@ -475,7 +476,7 @@ describe('WorkflowVariableBlockComponent', () => {
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: undefined,
errorMsg: expect.any(String),
}))
})
@ -488,20 +489,81 @@ describe('WorkflowVariableBlockComponent', () => {
/>,
)
const updateHandler = mockRegisterCommand.mock.calls[0][1] as (map: Record<string, unknown>) => boolean
const updateHandler = mockRegisterCommand.mock.calls[0][1] as (payload: UpdateWorkflowNodesMapPayload) => boolean
let result = false
act(() => {
result = updateHandler({
'node-1': {
title: 'Updated',
type: BlockEnum.LLM,
width: 100,
height: 50,
position: { x: 0, y: 0 },
workflowNodesMap: {
'node-1': {
title: 'Updated',
type: BlockEnum.LLM,
width: 100,
height: 50,
position: { x: 0, y: 0 },
},
},
availableVariables: [],
})
})
expect(result).toBe(true)
})
it('should mark non-special variable invalid when source key is missing in availableVariables', () => {
render(
<WorkflowVariableBlockComponent
nodeKey="k"
variables={['node-1', 'missing_key']}
workflowNodesMap={{
'node-1': {
title: 'Node A',
type: BlockEnum.LLM,
width: 200,
height: 100,
position: { x: 0, y: 0 },
},
}}
availableVariables={[
{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'existing_key', type: VarType.string }],
},
]}
/>,
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: expect.any(String),
}))
})
it('should keep non-special variable valid when source key exists in availableVariables', () => {
render(
<WorkflowVariableBlockComponent
nodeKey="k"
variables={['node-1', 'existing_key']}
workflowNodesMap={{
'node-1': {
title: 'Node A',
type: BlockEnum.LLM,
width: 200,
height: 100,
position: { x: 0, y: 0 },
},
}}
availableVariables={[
{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'existing_key', type: VarType.string }],
},
]}
/>,
)
expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({
errorMsg: undefined,
}))
})
})

View File

@ -105,7 +105,10 @@ describe('WorkflowVariableBlock', () => {
)
expect(mockUpdate).toHaveBeenCalled()
expect(mockDispatchCommand).toHaveBeenCalledWith(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap)
expect(mockDispatchCommand).toHaveBeenCalledWith(UPDATE_WORKFLOW_NODES_MAP, {
workflowNodesMap,
availableVariables: [],
})
})
it('should throw when WorkflowVariableBlockNode is not registered', () => {
@ -137,6 +140,7 @@ describe('WorkflowVariableBlock', () => {
['node-1', 'answer'],
workflowNodesMap,
getVarType,
[],
)
expect($insertNodes).toHaveBeenCalledWith([{ id: 'workflow-node' }])
expect(onInsert).toHaveBeenCalledTimes(1)

View File

@ -1,5 +1,5 @@
import type { Klass, LexicalEditor, LexicalNode } from 'lexical'
import type { Var } from '@/app/components/workflow/types'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { createEditor } from 'lexical'
import { Type } from '@/app/components/workflow/nodes/llm/types'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
@ -57,45 +57,43 @@ describe('WorkflowVariableBlockNode', () => {
it('should decorate with component props from node state', () => {
runInEditor(() => {
const getVarType = vi.fn(() => Type.number)
const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }]
const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }]
const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }]
const availableVariables: NodeOutPutVar[] = [{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'answer', type: VarType.string }],
}]
const node = new WorkflowVariableBlockNode(
['node-1', 'answer'],
{ 'node-1': { title: 'A', type: BlockEnum.LLM } },
getVarType,
'decorator-key',
environmentVariables,
conversationVariables,
ragVariables,
availableVariables,
)
const decorated = node.decorate()
expect(decorated.props.nodeKey).toBe('decorator-key')
expect(decorated.props.variables).toEqual(['node-1', 'answer'])
expect(decorated.props.workflowNodesMap).toEqual({ 'node-1': { title: 'A', type: BlockEnum.LLM } })
expect(decorated.props.environmentVariables).toEqual(environmentVariables)
expect(decorated.props.conversationVariables).toEqual(conversationVariables)
expect(decorated.props.ragVariables).toEqual(ragVariables)
expect(decorated.props.availableVariables).toEqual(availableVariables)
})
})
it('should export and import json with full payload', () => {
it('should export and import json with available variables payload', () => {
runInEditor(() => {
const getVarType = vi.fn(() => Type.string)
const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }]
const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }]
const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }]
const availableVariables: NodeOutPutVar[] = [{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'answer', type: VarType.string }],
}]
const node = new WorkflowVariableBlockNode(
['node-1', 'answer'],
{ 'node-1': { title: 'A', type: BlockEnum.LLM } },
getVarType,
undefined,
environmentVariables,
conversationVariables,
ragVariables,
availableVariables,
)
expect(node.exportJSON()).toEqual({
@ -104,9 +102,7 @@ describe('WorkflowVariableBlockNode', () => {
variables: ['node-1', 'answer'],
workflowNodesMap: { 'node-1': { title: 'A', type: BlockEnum.LLM } },
getVarType,
environmentVariables,
conversationVariables,
ragVariables,
availableVariables,
})
const imported = WorkflowVariableBlockNode.importJSON({
@ -115,48 +111,51 @@ describe('WorkflowVariableBlockNode', () => {
variables: ['node-2', 'result'],
workflowNodesMap: { 'node-2': { title: 'B', type: BlockEnum.Tool } },
getVarType,
environmentVariables,
conversationVariables,
ragVariables,
availableVariables,
})
expect(imported).toBeInstanceOf(WorkflowVariableBlockNode)
expect(imported.getVariables()).toEqual(['node-2', 'result'])
expect(imported.getWorkflowNodesMap()).toEqual({ 'node-2': { title: 'B', type: BlockEnum.Tool } })
expect(imported.getAvailableVariables()).toEqual(availableVariables)
})
})
it('should return getters and text content in expected format', () => {
runInEditor(() => {
const getVarType = vi.fn(() => Type.string)
const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }]
const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }]
const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }]
const availableVariables: NodeOutPutVar[] = [{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'answer', type: VarType.string }],
}]
const node = new WorkflowVariableBlockNode(
['node-1', 'answer'],
{ 'node-1': { title: 'A', type: BlockEnum.LLM } },
getVarType,
undefined,
environmentVariables,
conversationVariables,
ragVariables,
availableVariables,
)
expect(node.getVariables()).toEqual(['node-1', 'answer'])
expect(node.getWorkflowNodesMap()).toEqual({ 'node-1': { title: 'A', type: BlockEnum.LLM } })
expect(node.getVarType()).toBe(getVarType)
expect(node.getEnvironmentVariables()).toEqual(environmentVariables)
expect(node.getConversationVariables()).toEqual(conversationVariables)
expect(node.getRagVariables()).toEqual(ragVariables)
expect(node.getAvailableVariables()).toEqual(availableVariables)
expect(node.getTextContent()).toBe('{{#node-1.answer#}}')
})
})
it('should create node helper and type guard checks', () => {
runInEditor(() => {
const node = $createWorkflowVariableBlockNode(['node-1', 'answer'], {}, undefined)
const availableVariables: NodeOutPutVar[] = [{
nodeId: 'node-1',
title: 'Node A',
vars: [{ variable: 'answer', type: VarType.string }],
}]
const node = $createWorkflowVariableBlockNode(['node-1', 'answer'], {}, undefined, availableVariables)
expect(node).toBeInstanceOf(WorkflowVariableBlockNode)
expect(node.getAvailableVariables()).toEqual(availableVariables)
expect($isWorkflowVariableBlockNode(node)).toBe(true)
expect($isWorkflowVariableBlockNode(null)).toBe(false)
expect($isWorkflowVariableBlockNode(undefined)).toBe(false)

View File

@ -183,12 +183,7 @@ describe('WorkflowVariableBlockReplacementBlock', () => {
['node-1', 'output'],
workflowNodesMap,
getVarType,
variables[0].vars,
variables[1].vars,
[
{ variable: 'ragVarA', type: VarType.string, isRagVariable: true },
{ variable: 'rag.shared.answer', type: VarType.string, isRagVariable: true },
],
variables,
)
expect($applyNodeReplacement).toHaveBeenCalledWith({ type: 'workflow-node' })
expect(created).toEqual({ type: 'workflow-node' })
@ -214,8 +209,6 @@ describe('WorkflowVariableBlockReplacementBlock', () => {
workflowNodesMap,
undefined,
[],
[],
undefined,
)
})
})

View File

@ -1,5 +1,8 @@
import type {
UpdateWorkflowNodesMapPayload,
} from './index'
import type { WorkflowNodesMap } from './node'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
import {
@ -15,7 +18,7 @@ import {
import { useTranslation } from 'react-i18next'
import { useReactFlow, useStoreApi } from 'reactflow'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isRagVariableVar, isSpecialVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel'
import {
VariableLabelInEditor,
@ -34,6 +37,7 @@ type WorkflowVariableBlockComponentProps = {
nodeKey: string
variables: string[]
workflowNodesMap: WorkflowNodesMap
availableVariables?: NodeOutPutVar[]
environmentVariables?: Var[]
conversationVariables?: Var[]
ragVariables?: Var[]
@ -47,10 +51,8 @@ const WorkflowVariableBlockComponent = ({
nodeKey,
variables,
workflowNodesMap = {},
availableVariables,
getVarType,
environmentVariables,
conversationVariables,
ragVariables,
}: WorkflowVariableBlockComponentProps) => {
const { t } = useTranslation()
const [editor] = useLexicalComposerContext()
@ -66,36 +68,25 @@ const WorkflowVariableBlockComponent = ({
}
)()
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
const [localAvailableVariables, setLocalAvailableVariables] = useState<NodeOutPutVar[]>(availableVariables || [])
const node = localWorkflowNodesMap![variables[isRagVar ? 1 : 0]]
const isException = isExceptionVariable(varName, node?.type)
const sourceNodeId = variables[isRagVar ? 1 : 0]
const isLlmModelInstalled = useLlmModelPluginInstalled(sourceNodeId, localWorkflowNodesMap)
const variableValid = useMemo(() => {
let variableValid = true
const isEnv = isENV(variables)
const isChatVar = isConversationVar(variables)
const isGlobal = isGlobalVar(variables)
if (isGlobal)
if (isSpecialVar(variables[0] ?? ''))
return true
if (isEnv) {
if (environmentVariables)
variableValid = environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
}
else if (isChatVar) {
if (conversationVariables)
variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
}
else if (isRagVar) {
if (ragVariables)
variableValid = ragVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}.${variables?.[2] ?? ''}`)
}
else {
variableValid = !!node
}
return variableValid
}, [variables, node, environmentVariables, conversationVariables, isRagVar, ragVariables])
if (!variables[1])
return false
const sourceNode = localAvailableVariables.find(v => v.nodeId === variables[0])
if (!sourceNode)
return false
return sourceNode.vars.some(v => v.variable === variables[1])
}, [localAvailableVariables, variables])
const reactflow = useReactFlow()
const store = useStoreApi()
@ -107,9 +98,9 @@ const WorkflowVariableBlockComponent = ({
return mergeRegister(
editor.registerCommand(
UPDATE_WORKFLOW_NODES_MAP,
(workflowNodesMap: WorkflowNodesMap) => {
setLocalWorkflowNodesMap(workflowNodesMap)
(payload: UpdateWorkflowNodesMapPayload) => {
setLocalWorkflowNodesMap(payload.workflowNodesMap)
setLocalAvailableVariables(payload.availableVariables)
return true
},
COMMAND_PRIORITY_EDITOR,

View File

@ -17,9 +17,14 @@ import {
export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND')
export const DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND')
export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP')
export type UpdateWorkflowNodesMapPayload = {
workflowNodesMap: NonNullable<WorkflowVariableBlockType['workflowNodesMap']>
availableVariables: NonNullable<WorkflowVariableBlockType['variables']>
}
export const UPDATE_WORKFLOW_NODES_MAP = createCommand<UpdateWorkflowNodesMapPayload>('UPDATE_WORKFLOW_NODES_MAP')
const WorkflowVariableBlock = memo(({
workflowNodesMap,
workflowNodesMap = {},
variables: workflowAvailableVariables,
onInsert,
onDelete,
getVarType,
@ -28,9 +33,12 @@ const WorkflowVariableBlock = memo(({
useEffect(() => {
editor.update(() => {
editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap)
editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, {
workflowNodesMap: workflowNodesMap || {},
availableVariables: workflowAvailableVariables || [],
})
})
}, [editor, workflowNodesMap])
}, [editor, workflowNodesMap, workflowAvailableVariables])
useEffect(() => {
if (!editor.hasNodes([WorkflowVariableBlockNode]))
@ -40,7 +48,12 @@ const WorkflowVariableBlock = memo(({
editor.registerCommand(
INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND,
(variables: string[]) => {
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap, getVarType)
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(
variables,
workflowNodesMap,
getVarType,
workflowAvailableVariables || [],
)
$insertNodes([workflowVariableBlockNode])
if (onInsert)
@ -61,7 +74,7 @@ const WorkflowVariableBlock = memo(({
COMMAND_PRIORITY_EDITOR,
),
)
}, [editor, onInsert, onDelete, workflowNodesMap, getVarType])
}, [editor, onInsert, onDelete, workflowNodesMap, getVarType, workflowAvailableVariables])
return null
})

View File

@ -1,49 +1,55 @@
import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
import type { GetVarType, WorkflowVariableBlockType } from '../../types'
import type { Var } from '@/app/components/workflow/types'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { DecoratorNode } from 'lexical'
import WorkflowVariableBlockComponent from './component'
export type WorkflowNodesMap = WorkflowVariableBlockType['workflowNodesMap']
export type WorkflowNodesMap = NonNullable<WorkflowVariableBlockType['workflowNodesMap']>
type SerializedNode = SerializedLexicalNode & {
variables: string[]
workflowNodesMap: WorkflowNodesMap
getVarType?: GetVarType
environmentVariables?: Var[]
conversationVariables?: Var[]
ragVariables?: Var[]
availableVariables?: NodeOutPutVar[]
}
export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element> {
__variables: string[]
__workflowNodesMap: WorkflowNodesMap
__getVarType?: GetVarType
__environmentVariables?: Var[]
__conversationVariables?: Var[]
__ragVariables?: Var[]
__availableVariables?: NodeOutPutVar[]
static getType(): string {
return 'workflow-variable-block'
}
static clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode {
return new WorkflowVariableBlockNode(node.__variables, node.__workflowNodesMap, node.__getVarType, node.__key, node.__environmentVariables, node.__conversationVariables, node.__ragVariables)
return new WorkflowVariableBlockNode(
node.__variables,
node.__workflowNodesMap,
node.__getVarType,
node.__key,
node.__availableVariables,
)
}
isInline(): boolean {
return true
}
constructor(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType: any, key?: NodeKey, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]) {
constructor(
variables: string[],
workflowNodesMap: WorkflowNodesMap,
getVarType: any,
key?: NodeKey,
availableVariables?: NodeOutPutVar[],
) {
super(key)
this.__variables = variables
this.__workflowNodesMap = workflowNodesMap
this.__getVarType = getVarType
this.__environmentVariables = environmentVariables
this.__conversationVariables = conversationVariables
this.__ragVariables = ragVariables
this.__availableVariables = availableVariables
}
createDOM(): HTMLElement {
@ -63,30 +69,34 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
variables={this.__variables}
workflowNodesMap={this.__workflowNodesMap}
getVarType={this.__getVarType!}
environmentVariables={this.__environmentVariables}
conversationVariables={this.__conversationVariables}
ragVariables={this.__ragVariables}
availableVariables={this.__availableVariables}
/>
)
}
static importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode {
const node = $createWorkflowVariableBlockNode(serializedNode.variables, serializedNode.workflowNodesMap, serializedNode.getVarType, serializedNode.environmentVariables, serializedNode.conversationVariables, serializedNode.ragVariables)
const node = $createWorkflowVariableBlockNode(
serializedNode.variables,
serializedNode.workflowNodesMap,
serializedNode.getVarType,
serializedNode.availableVariables,
)
return node
}
exportJSON(): SerializedNode {
return {
const json: SerializedNode = {
type: 'workflow-variable-block',
version: 1,
variables: this.getVariables(),
workflowNodesMap: this.getWorkflowNodesMap(),
getVarType: this.getVarType(),
environmentVariables: this.getEnvironmentVariables(),
conversationVariables: this.getConversationVariables(),
ragVariables: this.getRagVariables(),
}
if (this.getAvailableVariables())
json.availableVariables = this.getAvailableVariables()
return json
}
getVariables(): string[] {
@ -104,27 +114,28 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
return self.__getVarType
}
getEnvironmentVariables(): any {
getAvailableVariables(): NodeOutPutVar[] | undefined {
const self = this.getLatest()
return self.__environmentVariables
}
getConversationVariables(): any {
const self = this.getLatest()
return self.__conversationVariables
}
getRagVariables(): any {
const self = this.getLatest()
return self.__ragVariables
return self.__availableVariables
}
getTextContent(): string {
return `{{#${this.getVariables().join('.')}#}}`
}
}
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode {
return new WorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, undefined, environmentVariables, conversationVariables, ragVariables)
export function $createWorkflowVariableBlockNode(
variables: string[],
workflowNodesMap: WorkflowNodesMap,
getVarType?: GetVarType,
availableVariables?: NodeOutPutVar[],
): WorkflowVariableBlockNode {
return new WorkflowVariableBlockNode(
variables,
workflowNodesMap,
getVarType,
undefined,
availableVariables,
)
}
export function $isWorkflowVariableBlockNode(

View File

@ -15,19 +15,12 @@ import { WorkflowVariableBlockNode } from './index'
import { $createWorkflowVariableBlockNode } from './node'
const WorkflowVariableBlockReplacementBlock = ({
workflowNodesMap,
workflowNodesMap = {},
getVarType,
onInsert,
variables,
}: WorkflowVariableBlockType) => {
const [editor] = useLexicalComposerContext()
const ragVariables = variables?.reduce<any[]>((acc, curr) => {
if (curr.nodeId === 'rag')
acc.push(...curr.vars)
else
acc.push(...curr.vars.filter(v => v.isRagVariable))
return acc
}, [])
useEffect(() => {
if (!editor.hasNodes([WorkflowVariableBlockNode]))
@ -39,8 +32,13 @@ const WorkflowVariableBlockReplacementBlock = ({
onInsert()
const nodePathString = textNode.getTextContent().slice(3, -3)
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables?.find(o => o.nodeId === 'env')?.vars || [], variables?.find(o => o.nodeId === 'conversation')?.vars || [], ragVariables))
}, [onInsert, workflowNodesMap, getVarType, variables, ragVariables])
return $applyNodeReplacement($createWorkflowVariableBlockNode(
nodePathString.split('.'),
workflowNodesMap,
getVarType,
variables || [],
))
}, [onInsert, workflowNodesMap, getVarType, variables])
const getMatch = useCallback((text: string) => {
const matchArr = REGEX.exec(text)

View File

@ -22,6 +22,9 @@ import Panel from '../panel'
import { ParamType, ReasoningModeType } from '../types'
import useConfig from '../use-config'
const reasoningModeFunctionToolCallingLabel = 'workflow.nodes.parameterExtractor.reasoningModeFunctionToolCalling'
const reasoningModePromptLabel = 'workflow.nodes.parameterExtractor.reasoningModePrompt'
type MockToolCollection = {
id: string
tools: Array<{
@ -735,8 +738,8 @@ describe('parameter-extractor path', () => {
/>,
)
await user.click(screen.getByRole('button', { name: 'Function/Tool Calling' }))
await user.click(screen.getByRole('button', { name: 'Prompt' }))
await user.click(screen.getByRole('button', { name: reasoningModeFunctionToolCallingLabel }))
await user.click(screen.getByRole('button', { name: reasoningModePromptLabel }))
expect(onChange).toHaveBeenNthCalledWith(1, ReasoningModeType.functionCall)
expect(onChange).toHaveBeenNthCalledWith(2, ReasoningModeType.prompt)
@ -826,7 +829,7 @@ describe('parameter-extractor path', () => {
target: { value: 'Extract city, budget, and due date' },
})
await user.click(screen.getByRole('button', { name: 'memory-config' }))
await user.click(screen.getByRole('button', { name: 'Function/Tool Calling' }))
await user.click(screen.getByRole('button', { name: reasoningModeFunctionToolCallingLabel }))
expect(handleModelChanged).toHaveBeenCalledWith({
provider: 'anthropic',

View File

@ -33,12 +33,12 @@ const ReasoningModePicker: FC<Props> = ({
>
<div className="grid grid-cols-2 gap-x-1">
<OptionCard
title="Function/Tool Calling"
title={t(`${i18nPrefix}.reasoningModeFunctionToolCalling`, { ns: 'workflow' })}
onSelect={handleChange(ReasoningModeType.functionCall)}
selected={type === ReasoningModeType.functionCall}
/>
<OptionCard
title="Prompt"
title={t(`${i18nPrefix}.reasoningModePrompt`, { ns: 'workflow' })}
selected={type === ReasoningModeType.prompt}
onSelect={handleChange(ReasoningModeType.prompt)}
/>

View File

@ -3385,7 +3385,7 @@
"count": 3
},
"react-refresh/only-export-components": {
"count": 3
"count": 2
}
},
"app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx": {
@ -3485,12 +3485,7 @@
},
"app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx": {
"ts/no-explicit-any": {
"count": 5
}
},
"app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx": {
"ts/no-explicit-any": {
"count": 1
"count": 2
}
},
"app/components/base/prompt-log-modal/card.tsx": {

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "الصفحات المصرح بها",
"dataSource.notion.remove": "إزالة",
"dataSource.notion.selector.addPages": "إضافة صفحات",
"dataSource.notion.selector.configure": "تكوين Notion",
"dataSource.notion.selector.docs": "وثائق Notion",
"dataSource.notion.selector.headerTitle": "اختر صفحات Notion",
"dataSource.notion.selector.noSearchResult": "لا توجد نتائج بحث",
"dataSource.notion.selector.pageSelected": "الصفحات المحددة",
"dataSource.notion.selector.preview": "معاينة",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "هو نجاح. عند النجاح تكون القيمة 1، عند الفشل تكون القيمة 0.",
"nodes.parameterExtractor.outputVars.usage": "معلومات استخدام النموذج",
"nodes.parameterExtractor.reasoningMode": "وضع التفكير",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "استدعاء دالة/أداة",
"nodes.parameterExtractor.reasoningModePrompt": "موجّه",
"nodes.parameterExtractor.reasoningModeTip": "يمكنك اختيار وضع التفكير المناسب بناءً على قدرة النموذج على الاستجابة للتعليمات لاستدعاء الوظيفة أو المطالبات.",
"nodes.questionClassifiers.addClass": "إضافة فئة",
"nodes.questionClassifiers.advancedSetting": "إعدادات متقدمة",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Autorisierte Seiten",
"dataSource.notion.remove": "Entfernen",
"dataSource.notion.selector.addPages": "Seiten hinzufügen",
"dataSource.notion.selector.configure": "Notion konfigurieren",
"dataSource.notion.selector.docs": "Notion-Dokumentation",
"dataSource.notion.selector.headerTitle": "Notion-Seiten auswählen",
"dataSource.notion.selector.noSearchResult": "Keine Suchergebnisse",
"dataSource.notion.selector.pageSelected": "Ausgewählte Seiten",
"dataSource.notion.selector.preview": "VORSCHAU",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Ist Erfolg. Bei Erfolg beträgt der Wert 1, bei Misserfolg beträgt der Wert 0.",
"nodes.parameterExtractor.outputVars.usage": "Nutzungsinformationen des Modells",
"nodes.parameterExtractor.reasoningMode": "Schlussfolgerungsmodus",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Funktion/Tool-Aufruf",
"nodes.parameterExtractor.reasoningModePrompt": "Eingabeaufforderung",
"nodes.parameterExtractor.reasoningModeTip": "Sie können den entsprechenden Schlussfolgerungsmodus basierend auf der Fähigkeit des Modells wählen, auf Anweisungen zur Funktionsaufruf- oder Eingabeaufforderungen zu reagieren.",
"nodes.questionClassifiers.addClass": "Klasse hinzufügen",
"nodes.questionClassifiers.advancedSetting": "Erweiterte Einstellung",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Is Success.On success the value is 1, on failure the value is 0.",
"nodes.parameterExtractor.outputVars.usage": "Model Usage Information",
"nodes.parameterExtractor.reasoningMode": "Reasoning Mode",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Function/Tool Calling",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "You can choose the appropriate reasoning mode based on the model's ability to respond to instructions for function calling or prompts.",
"nodes.questionClassifiers.addClass": "Add Class",
"nodes.questionClassifiers.advancedSetting": "Advanced Setting",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Páginas autorizadas",
"dataSource.notion.remove": "Eliminar",
"dataSource.notion.selector.addPages": "Agregar páginas",
"dataSource.notion.selector.configure": "Configurar Notion",
"dataSource.notion.selector.docs": "Documentación de Notion",
"dataSource.notion.selector.headerTitle": "Elegir páginas de Notion",
"dataSource.notion.selector.noSearchResult": "No hay resultados de búsqueda",
"dataSource.notion.selector.pageSelected": "Páginas seleccionadas",
"dataSource.notion.selector.preview": "VISTA PREVIA",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Es éxito. En caso de éxito el valor es 1, en caso de fallo el valor es 0.",
"nodes.parameterExtractor.outputVars.usage": "Información de uso del modelo",
"nodes.parameterExtractor.reasoningMode": "Modo de razonamiento",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Función/Llamada de herramienta",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Puede elegir el modo de razonamiento apropiado basado en la capacidad del modelo para responder a instrucciones para llamadas de funciones o indicaciones.",
"nodes.questionClassifiers.addClass": "Agregar clase",
"nodes.questionClassifiers.advancedSetting": "Configuración avanzada",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "صفحات مجاز",
"dataSource.notion.remove": "حذف",
"dataSource.notion.selector.addPages": "افزودن صفحات",
"dataSource.notion.selector.configure": "پیکربندی Notion",
"dataSource.notion.selector.docs": "مستندات Notion",
"dataSource.notion.selector.headerTitle": "صفحات Notion را انتخاب کنید",
"dataSource.notion.selector.noSearchResult": "نتیجه جستجویی یافت نشد",
"dataSource.notion.selector.pageSelected": "صفحات انتخاب شده",
"dataSource.notion.selector.preview": "پیش‌نمایش",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "موفقیت (۱=موفق، ۰=ناموفق)",
"nodes.parameterExtractor.outputVars.usage": "اطلاعات مصرف مدل",
"nodes.parameterExtractor.reasoningMode": "حالت استدلال",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "فراخوانی تابع/ابزار",
"nodes.parameterExtractor.reasoningModePrompt": "پیام",
"nodes.parameterExtractor.reasoningModeTip": "حالت استدلال مناسب را بر اساس توانایی مدل (Function Call یا Prompt) انتخاب کنید.",
"nodes.questionClassifiers.addClass": "افزودن کلاس",
"nodes.questionClassifiers.advancedSetting": "تنظیمات پیشرفته",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Pages autorisées",
"dataSource.notion.remove": "Supprimer",
"dataSource.notion.selector.addPages": "Ajouter des pages",
"dataSource.notion.selector.configure": "Configurer Notion",
"dataSource.notion.selector.docs": "Documentation Notion",
"dataSource.notion.selector.headerTitle": "Choisir des pages Notion",
"dataSource.notion.selector.noSearchResult": "Aucun résultat de recherche",
"dataSource.notion.selector.pageSelected": "Pages Sélectionnées",
"dataSource.notion.selector.preview": "APERÇU",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Est réussi. En cas de succès, la valeur est 1, en cas d'échec, la valeur est 0.",
"nodes.parameterExtractor.outputVars.usage": "Informations sur l'utilisation du modèle",
"nodes.parameterExtractor.reasoningMode": "Mode de raisonnement",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fonction/Appel d'outil",
"nodes.parameterExtractor.reasoningModePrompt": "Invite",
"nodes.parameterExtractor.reasoningModeTip": "Vous pouvez choisir le mode de raisonnement approprié en fonction de la capacité du modèle à répondre aux instructions pour les appels de fonction ou les invites.",
"nodes.questionClassifiers.addClass": "Ajouter une classe",
"nodes.questionClassifiers.advancedSetting": "Paramètre avancé",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "अधिकृत पृष्ठ",
"dataSource.notion.remove": "हटाएं",
"dataSource.notion.selector.addPages": "पृष्ठ जोड़ें",
"dataSource.notion.selector.configure": "Notion कॉन्फ़िगर करें",
"dataSource.notion.selector.docs": "Notion दस्तावेज़",
"dataSource.notion.selector.headerTitle": "Notion पृष्ठ चुनें",
"dataSource.notion.selector.noSearchResult": "कोई खोज परिणाम नहीं",
"dataSource.notion.selector.pageSelected": "चयनित पृष्ठ",
"dataSource.notion.selector.preview": "पूर्वावलोकन",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "सफलता है। सफलता पर मान 1 है, असफलता पर मान 0 है।",
"nodes.parameterExtractor.outputVars.usage": "मॉडल उपयोग जानकारी",
"nodes.parameterExtractor.reasoningMode": "रीज़निंग मोड",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "फ़ंक्शन/टूल कॉलिंग",
"nodes.parameterExtractor.reasoningModePrompt": "प्रॉम्प्ट",
"nodes.parameterExtractor.reasoningModeTip": "फ़ंक्शन कॉलिंग या प्रॉम्प्ट्स के लिए निर्देशों का जवाब देने की मॉडल की क्षमता के आधार पर उपयुक्त रीज़निंग मोड चुन सकते हैं।",
"nodes.questionClassifiers.addClass": "क्लास जोड़ें",
"nodes.questionClassifiers.advancedSetting": "उन्नत सेटिंग",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Halaman yang disahkan",
"dataSource.notion.remove": "Hapus",
"dataSource.notion.selector.addPages": "Tambahkan halaman",
"dataSource.notion.selector.configure": "Konfigurasi Notion",
"dataSource.notion.selector.docs": "Dokumentasi Notion",
"dataSource.notion.selector.headerTitle": "Pilih halaman Notion",
"dataSource.notion.selector.noSearchResult": "Tidak ada hasil pencarian",
"dataSource.notion.selector.pageSelected": "Halaman yang Dipilih",
"dataSource.notion.selector.preview": "PRATAYANG",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Apakah Success.Pada keberhasilan nilainya adalah 1, pada kegagalan nilainya adalah 0.",
"nodes.parameterExtractor.outputVars.usage": "Informasi Penggunaan Model",
"nodes.parameterExtractor.reasoningMode": "Mode Penalaran",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fungsi/Pemanggilan Alat",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Anda dapat memilih mode penalaran yang sesuai berdasarkan kemampuan model untuk menanggapi instruksi untuk pemanggilan fungsi atau perintah.",
"nodes.questionClassifiers.addClass": "Tambahkan Kelas",
"nodes.questionClassifiers.advancedSetting": "Pengaturan Lanjutan",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Pagine autorizzate",
"dataSource.notion.remove": "Rimuovi",
"dataSource.notion.selector.addPages": "Aggiungi pagine",
"dataSource.notion.selector.configure": "Configura Notion",
"dataSource.notion.selector.docs": "Documentazione Notion",
"dataSource.notion.selector.headerTitle": "Scegli le pagine Notion",
"dataSource.notion.selector.noSearchResult": "Nessun risultato di ricerca",
"dataSource.notion.selector.pageSelected": "Pagine selezionate",
"dataSource.notion.selector.preview": "ANTEPRIMA",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "È successo. In caso di successo il valore è 1, in caso di fallimento il valore è 0.",
"nodes.parameterExtractor.outputVars.usage": "Informazioni sull'utilizzo del modello",
"nodes.parameterExtractor.reasoningMode": "Modalità di ragionamento",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Funzione/Chiamata strumento",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Puoi scegliere la modalità di ragionamento appropriata in base alla capacità del modello di rispondere alle istruzioni per la chiamata delle funzioni o i prompt.",
"nodes.questionClassifiers.addClass": "Aggiungi Classe",
"nodes.questionClassifiers.advancedSetting": "Impostazione Avanzata",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "認証済みページ",
"dataSource.notion.remove": "削除",
"dataSource.notion.selector.addPages": "ページの追加",
"dataSource.notion.selector.configure": "Notionを設定する",
"dataSource.notion.selector.docs": "Notionドキュメント",
"dataSource.notion.selector.headerTitle": "Notionページを選択",
"dataSource.notion.selector.noSearchResult": "検索結果なし",
"dataSource.notion.selector.pageSelected": "選択済みページ",
"dataSource.notion.selector.preview": "プレビュー",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "成功。成功した場合の値は 1、失敗した場合の値は 0 です。",
"nodes.parameterExtractor.outputVars.usage": "モデル使用量",
"nodes.parameterExtractor.reasoningMode": "推論モード",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "関数/ツール呼び出し",
"nodes.parameterExtractor.reasoningModePrompt": "プロンプト",
"nodes.parameterExtractor.reasoningModeTip": "関数呼び出しやプロンプトの指示に応答するモデルの能力に基づいて、適切な推論モードを選択できます。",
"nodes.questionClassifiers.addClass": "クラスを追加",
"nodes.questionClassifiers.advancedSetting": "高度な設定",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "페이지가 허가됨",
"dataSource.notion.remove": "제거하기",
"dataSource.notion.selector.addPages": "페이지 추가하기",
"dataSource.notion.selector.configure": "Notion 구성",
"dataSource.notion.selector.docs": "Notion 문서",
"dataSource.notion.selector.headerTitle": "Notion 페이지 선택",
"dataSource.notion.selector.noSearchResult": "검색 결과 없음",
"dataSource.notion.selector.pageSelected": "페이지 선택됨",
"dataSource.notion.selector.preview": "미리보기",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "성공 여부. 성공 시 값은 1 이고, 실패 시 값은 0 입니다.",
"nodes.parameterExtractor.outputVars.usage": "모델 사용 정보",
"nodes.parameterExtractor.reasoningMode": "추론 모드",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "함수/도구 호출",
"nodes.parameterExtractor.reasoningModePrompt": "프롬프트",
"nodes.parameterExtractor.reasoningModeTip": "모델의 함수 호출 또는 프롬프트에 대한 지시 응답 능력을 기반으로 적절한 추론 모드를 선택할 수 있습니다.",
"nodes.questionClassifiers.addClass": "클래스 추가",
"nodes.questionClassifiers.advancedSetting": "고급 설정",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Pages authorized",
"dataSource.notion.remove": "Remove",
"dataSource.notion.selector.addPages": "Add pages",
"dataSource.notion.selector.configure": "Notion configureren",
"dataSource.notion.selector.docs": "Notion-documentatie",
"dataSource.notion.selector.headerTitle": "Notion-pagina's kiezen",
"dataSource.notion.selector.noSearchResult": "No search results",
"dataSource.notion.selector.pageSelected": "Pages Selected",
"dataSource.notion.selector.preview": "PREVIEW",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Is Success.On success the value is 1, on failure the value is 0.",
"nodes.parameterExtractor.outputVars.usage": "Model Usage Information",
"nodes.parameterExtractor.reasoningMode": "Reasoning Mode",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Functie/Tool-aanroep",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "You can choose the appropriate reasoning mode based on the model's ability to respond to instructions for function calling or prompts.",
"nodes.questionClassifiers.addClass": "Add Class",
"nodes.questionClassifiers.advancedSetting": "Advanced Setting",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Strony autoryzowane",
"dataSource.notion.remove": "Usuń",
"dataSource.notion.selector.addPages": "Dodaj strony",
"dataSource.notion.selector.configure": "Skonfiguruj Notion",
"dataSource.notion.selector.docs": "Dokumentacja Notion",
"dataSource.notion.selector.headerTitle": "Wybierz strony Notion",
"dataSource.notion.selector.noSearchResult": "Brak wyników wyszukiwania",
"dataSource.notion.selector.pageSelected": "Zaznaczone strony",
"dataSource.notion.selector.preview": "PODGLĄD",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Czy się udało. W przypadku sukcesu wartość wynosi 1, w przypadku niepowodzenia wartość wynosi 0.",
"nodes.parameterExtractor.outputVars.usage": "Informacje o użyciu modelu",
"nodes.parameterExtractor.reasoningMode": "Tryb wnioskowania",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Funkcja/Wywołanie narzędzia",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Możesz wybrać odpowiedni tryb wnioskowania w zależności od zdolności modelu do reagowania na instrukcje dotyczące wywoływania funkcji lub zapytań.",
"nodes.questionClassifiers.addClass": "Dodaj klasę",
"nodes.questionClassifiers.advancedSetting": "Zaawansowane ustawienia",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Páginas autorizadas",
"dataSource.notion.remove": "Remover",
"dataSource.notion.selector.addPages": "Adicionar páginas",
"dataSource.notion.selector.configure": "Configurar Notion",
"dataSource.notion.selector.docs": "Documentação do Notion",
"dataSource.notion.selector.headerTitle": "Escolher páginas do Notion",
"dataSource.notion.selector.noSearchResult": "Nenhum resultado de pesquisa",
"dataSource.notion.selector.pageSelected": "Páginas Selecionadas",
"dataSource.notion.selector.preview": "PRÉ-VISUALIZAÇÃO",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "É sucesso. Em caso de sucesso, o valor é 1, em caso de falha, o valor é 0.",
"nodes.parameterExtractor.outputVars.usage": "Informações de uso do modelo",
"nodes.parameterExtractor.reasoningMode": "Modo de raciocínio",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Função/Chamada de Ferramenta",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Você pode escolher o modo de raciocínio apropriado com base na capacidade do modelo de responder a instruções para chamadas de função ou prompts.",
"nodes.questionClassifiers.addClass": "Adicionar classe",
"nodes.questionClassifiers.advancedSetting": "Configuração avançada",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Pagini autorizate",
"dataSource.notion.remove": "Elimină",
"dataSource.notion.selector.addPages": "Adăugați pagini",
"dataSource.notion.selector.configure": "Configurare Notion",
"dataSource.notion.selector.docs": "Documentație Notion",
"dataSource.notion.selector.headerTitle": "Alegeți paginile Notion",
"dataSource.notion.selector.noSearchResult": "Niciun rezultat la căutare",
"dataSource.notion.selector.pageSelected": "Pagini selectate",
"dataSource.notion.selector.preview": "PREVIZUALIZARE",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Este succes. În caz de succes valoarea este 1, în caz de eșec valoarea este 0.",
"nodes.parameterExtractor.outputVars.usage": "Informații de utilizare a modelului",
"nodes.parameterExtractor.reasoningMode": "Mod de raționament",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Funcție/Apel instrument",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Puteți alege modul de raționament potrivit în funcție de capacitatea modelului de a răspunde la instrucțiuni pentru apelarea funcțiilor sau prompturi.",
"nodes.questionClassifiers.addClass": "Adăugați clasă",
"nodes.questionClassifiers.advancedSetting": "Setare avansată",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Авторизованные страницы",
"dataSource.notion.remove": "Удалить",
"dataSource.notion.selector.addPages": "Добавить страницы",
"dataSource.notion.selector.configure": "Настроить Notion",
"dataSource.notion.selector.docs": "Документация Notion",
"dataSource.notion.selector.headerTitle": "Выберите страницы Notion",
"dataSource.notion.selector.noSearchResult": "Нет результатов поиска",
"dataSource.notion.selector.pageSelected": "Выбранные страницы",
"dataSource.notion.selector.preview": "ПРЕДПРОСМОТР",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Успешно. В случае успеха значение равно 1, в случае сбоя - 0.",
"nodes.parameterExtractor.outputVars.usage": "Информация об использовании модели",
"nodes.parameterExtractor.reasoningMode": "Режим рассуждения",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Вызов функции/инструмента",
"nodes.parameterExtractor.reasoningModePrompt": "Подсказка",
"nodes.parameterExtractor.reasoningModeTip": "Вы можете выбрать соответствующий режим рассуждения, основываясь на способности модели реагировать на инструкции для вызова функций или подсказки.",
"nodes.questionClassifiers.addClass": "Добавить класс",
"nodes.questionClassifiers.advancedSetting": "Расширенные настройки",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Dovoljene strani",
"dataSource.notion.remove": "Odstrani",
"dataSource.notion.selector.addPages": "Dodajanje strani",
"dataSource.notion.selector.configure": "Konfiguriraj Notion",
"dataSource.notion.selector.docs": "Dokumentacija Notion",
"dataSource.notion.selector.headerTitle": "Izberite strani Notion",
"dataSource.notion.selector.noSearchResult": "Ni rezultatov iskanja",
"dataSource.notion.selector.pageSelected": "Izbrane strani",
"dataSource.notion.selector.preview": "PREDOGLED",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Ali je uspeh. Na uspehu je vrednost 1, na neuspehu je vrednost 0.",
"nodes.parameterExtractor.outputVars.usage": "Informacije o uporabi modela",
"nodes.parameterExtractor.reasoningMode": "Način razmišljanja",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Klic funkcije/orodja",
"nodes.parameterExtractor.reasoningModePrompt": "Poziv",
"nodes.parameterExtractor.reasoningModeTip": "Lahko izberete ustrezen način razmišljanja glede na sposobnost modela, da se odzove na navodila za klic funkcij ali pozive.",
"nodes.questionClassifiers.addClass": "Dodaj razred",
"nodes.questionClassifiers.advancedSetting": "Napredno nastavitev",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "เพจที่ได้รับอนุญาต",
"dataSource.notion.remove": "ถอด",
"dataSource.notion.selector.addPages": "เพิ่มหน้า",
"dataSource.notion.selector.configure": "กำหนดค่า Notion",
"dataSource.notion.selector.docs": "เอกสาร Notion",
"dataSource.notion.selector.headerTitle": "เลือกหน้า Notion",
"dataSource.notion.selector.noSearchResult": "ไม่มีผลการค้นหา",
"dataSource.notion.selector.pageSelected": "หน้าที่เลือก",
"dataSource.notion.selector.preview": "ดูตัวอย่าง",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "คือ Success เมื่อสําเร็จค่าคือ 1 เมื่อล้มเหลวค่าเป็น 0",
"nodes.parameterExtractor.outputVars.usage": "ข้อมูลการใช้งานรุ่น",
"nodes.parameterExtractor.reasoningMode": "โหมดการให้เหตุผล",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "ฟังก์ชัน/การเรียกใช้เครื่องมือ",
"nodes.parameterExtractor.reasoningModePrompt": "พรอมต์",
"nodes.parameterExtractor.reasoningModeTip": "คุณสามารถเลือกโหมดการให้เหตุผลที่เหมาะสมตามความสามารถของโมเดลในการตอบสนองต่อคําแนะนําสําหรับการเรียกใช้ฟังก์ชันหรือข้อความแจ้ง",
"nodes.questionClassifiers.addClass": "เพิ่มชั้นเรียน",
"nodes.questionClassifiers.advancedSetting": "การตั้งค่าขั้นสูง",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Yetkilendirilen sayfalar",
"dataSource.notion.remove": "Kaldır",
"dataSource.notion.selector.addPages": "Sayfa ekle",
"dataSource.notion.selector.configure": "Notion'ı Yapılandır",
"dataSource.notion.selector.docs": "Notion belgeleri",
"dataSource.notion.selector.headerTitle": "Notion sayfalarını seçin",
"dataSource.notion.selector.noSearchResult": "Arama sonucu yok",
"dataSource.notion.selector.pageSelected": "Seçilen Sayfalar",
"dataSource.notion.selector.preview": "ÖNİZLEME",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Başarılı mı. Başarılı olduğunda değer 1, başarısız olduğunda değer 0'dır.",
"nodes.parameterExtractor.outputVars.usage": "Model Kullanım Bilgileri",
"nodes.parameterExtractor.reasoningMode": "Akıl Yürütme Modu",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fonksiyon/Araç Çağrısı",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Modelin fonksiyon çağırma veya istemler için talimatlara yanıt verme yeteneğine bağlı olarak uygun akıl yürütme modunu seçebilirsiniz.",
"nodes.questionClassifiers.addClass": "Sınıf Ekle",
"nodes.questionClassifiers.advancedSetting": "Gelişmiş Ayarlar",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Авторизовані сторінки",
"dataSource.notion.remove": "Видалити",
"dataSource.notion.selector.addPages": "Додати сторінки",
"dataSource.notion.selector.configure": "Налаштувати Notion",
"dataSource.notion.selector.docs": "Документація Notion",
"dataSource.notion.selector.headerTitle": "Виберіть сторінки Notion",
"dataSource.notion.selector.noSearchResult": "Результатів пошуку немає",
"dataSource.notion.selector.pageSelected": "Сторінки вибрано",
"dataSource.notion.selector.preview": "ПЕРЕДПЕРЕГЛЯД",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Є успіх. У разі успіху значення 1, у разі невдачі значення 0.",
"nodes.parameterExtractor.outputVars.usage": "Інформація про використання моделі",
"nodes.parameterExtractor.reasoningMode": "Режим інференції",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Виклик функції/інструменту",
"nodes.parameterExtractor.reasoningModePrompt": "Підказка",
"nodes.parameterExtractor.reasoningModeTip": "Ви можете вибрати відповідний режим інференції залежно від здатності моделі реагувати на інструкції щодо викликів функцій або запитів.",
"nodes.questionClassifiers.addClass": "Додати клас",
"nodes.questionClassifiers.advancedSetting": "Розширене налаштування",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "Các trang được ủy quyền",
"dataSource.notion.remove": "Xóa",
"dataSource.notion.selector.addPages": "Thêm trang",
"dataSource.notion.selector.configure": "Cấu hình Notion",
"dataSource.notion.selector.docs": "Tài liệu Notion",
"dataSource.notion.selector.headerTitle": "Chọn các trang Notion",
"dataSource.notion.selector.noSearchResult": "Không có kết quả tìm kiếm",
"dataSource.notion.selector.pageSelected": "Các trang đã chọn",
"dataSource.notion.selector.preview": "Xem trước",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "Thành công. Khi thành công giá trị là 1, khi thất bại giá trị là 0.",
"nodes.parameterExtractor.outputVars.usage": "Thông tin sử dụng mô hình",
"nodes.parameterExtractor.reasoningMode": "Chế độ suy luận",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Hàm/Gọi công cụ",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt",
"nodes.parameterExtractor.reasoningModeTip": "Bạn có thể chọn chế độ suy luận phù hợp dựa trên khả năng của mô hình để phản hồi các hướng dẫn về việc gọi hàm hoặc prompt.",
"nodes.questionClassifiers.addClass": "Thêm lớp",
"nodes.questionClassifiers.advancedSetting": "Cài đặt nâng cao",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "是否成功。成功时值为 1失败时值为 0。",
"nodes.parameterExtractor.outputVars.usage": "模型用量信息",
"nodes.parameterExtractor.reasoningMode": "推理模式",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "函数/工具调用",
"nodes.parameterExtractor.reasoningModePrompt": "提示词",
"nodes.parameterExtractor.reasoningModeTip": "你可以根据模型对于 Function calling 或 Prompt 的指令响应能力选择合适的推理模式",
"nodes.questionClassifiers.addClass": "添加分类",
"nodes.questionClassifiers.advancedSetting": "高级设置",

View File

@ -135,6 +135,9 @@
"dataSource.notion.pagesAuthorized": "已授權頁面",
"dataSource.notion.remove": "刪除",
"dataSource.notion.selector.addPages": "新增頁面",
"dataSource.notion.selector.configure": "配置 Notion",
"dataSource.notion.selector.docs": "Notion 文件",
"dataSource.notion.selector.headerTitle": "選擇 Notion 頁面",
"dataSource.notion.selector.noSearchResult": "無搜尋結果",
"dataSource.notion.selector.pageSelected": "已選頁面",
"dataSource.notion.selector.preview": "預覽",

View File

@ -856,6 +856,8 @@
"nodes.parameterExtractor.outputVars.isSuccess": "是否成功。成功時值為 1失敗時值為 0。",
"nodes.parameterExtractor.outputVars.usage": "模型用量信息",
"nodes.parameterExtractor.reasoningMode": "推理模式",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "函數/工具調用",
"nodes.parameterExtractor.reasoningModePrompt": "提示詞",
"nodes.parameterExtractor.reasoningModeTip": "你可以根據模型對於 Function calling 或 Prompt 的指令響應能力選擇合適的推理模式",
"nodes.questionClassifiers.addClass": "新增分類",
"nodes.questionClassifiers.advancedSetting": "高級設置",

View File

@ -38,6 +38,26 @@ export type ResponseError = {
status: number
}
const createResponseFromHTTPError = (error: HTTPError): Response => {
const headers = new Headers(error.response.headers)
headers.delete('content-length')
let body: BodyInit | null = null
if (typeof error.data === 'string')
body = error.data
else if (error.data !== undefined)
body = JSON.stringify(error.data)
if (body !== null && !headers.has('content-type'))
headers.set('content-type', ContentType.json)
return new Response(body, {
status: error.response.status,
statusText: error.response.statusText,
headers,
})
}
const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook => {
return async ({ response }) => {
if (!/^([23])\d{2}$/.test(String(response.status))) {
@ -209,7 +229,7 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions:
}
catch (error) {
if (error instanceof HTTPError)
throw error.response.clone()
throw createResponseFromHTTPError(error)
throw error
}