mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
fix: resolve TypeScript errors in goto-anything tests and workflow (#32122)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -709,6 +709,17 @@ def parse_vibe_response(content: str) -> dict[str, Any]:
|
|||||||
"raw_content": content[:500], # First 500 chars for debugging
|
"raw_content": content[:500], # First 500 chars for debugging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Handle double-encoded JSON (when json.loads returns a string)
|
||||||
|
if isinstance(data, str):
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {
|
||||||
|
"intent": "error",
|
||||||
|
"error": "Failed to parse double-encoded JSON",
|
||||||
|
"raw_content": data[:500],
|
||||||
|
}
|
||||||
|
|
||||||
# Validate and normalize
|
# Validate and normalize
|
||||||
if "intent" not in data:
|
if "intent" not in data:
|
||||||
data["intent"] = "generate" # Default assumption
|
data["intent"] = "generate" # Default assumption
|
||||||
|
|||||||
@ -6,7 +6,11 @@ from collections.abc import Sequence
|
|||||||
import json_repair
|
import json_repair
|
||||||
|
|
||||||
from core.model_manager import ModelManager
|
from core.model_manager import ModelManager
|
||||||
from core.model_runtime.entities.message_entities import SystemPromptMessage, UserPromptMessage
|
from core.model_runtime.entities.message_entities import (
|
||||||
|
SystemPromptMessage,
|
||||||
|
TextPromptMessageContent,
|
||||||
|
UserPromptMessage,
|
||||||
|
)
|
||||||
from core.model_runtime.entities.model_entities import ModelType
|
from core.model_runtime.entities.model_entities import ModelType
|
||||||
from core.workflow.generator.prompts.builder_prompts import (
|
from core.workflow.generator.prompts.builder_prompts import (
|
||||||
BUILDER_SYSTEM_PROMPT,
|
BUILDER_SYSTEM_PROMPT,
|
||||||
@ -105,7 +109,31 @@ class WorkflowGenerator:
|
|||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
stream=False,
|
stream=False,
|
||||||
)
|
)
|
||||||
|
# Extract text content from response
|
||||||
plan_content = response.message.content
|
plan_content = response.message.content
|
||||||
|
if isinstance(plan_content, list):
|
||||||
|
# Extract text from content list
|
||||||
|
text_parts = []
|
||||||
|
for content in plan_content:
|
||||||
|
if isinstance(content, TextPromptMessageContent):
|
||||||
|
text_parts.append(content.data)
|
||||||
|
plan_content = "".join(text_parts)
|
||||||
|
elif plan_content is None:
|
||||||
|
plan_content = ""
|
||||||
|
|
||||||
|
# Check if LLM returned empty content
|
||||||
|
if not plan_content or not plan_content.strip():
|
||||||
|
usage = response.usage if hasattr(response, "usage") else "N/A"
|
||||||
|
logger.error("LLM returned empty content. Usage: %s", usage)
|
||||||
|
return {
|
||||||
|
"intent": "error",
|
||||||
|
"error": (
|
||||||
|
"LLM model returned empty response. This may indicate: "
|
||||||
|
"(1) Model refusal/content policy, (2) Model configuration issue, "
|
||||||
|
"(3) Plugin communication error. Try a different model or check model settings."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
# Reuse parse_vibe_response logic or simple load
|
# Reuse parse_vibe_response logic or simple load
|
||||||
plan_data = parse_vibe_response(plan_content)
|
plan_data = parse_vibe_response(plan_content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -212,13 +240,52 @@ class WorkflowGenerator:
|
|||||||
stream=False,
|
stream=False,
|
||||||
)
|
)
|
||||||
# Builder output is raw JSON nodes/edges
|
# Builder output is raw JSON nodes/edges
|
||||||
|
# Extract text content from response
|
||||||
build_content = build_res.message.content
|
build_content = build_res.message.content
|
||||||
|
if isinstance(build_content, list):
|
||||||
|
# Extract text from content list
|
||||||
|
text_parts = []
|
||||||
|
for content in build_content:
|
||||||
|
if isinstance(content, TextPromptMessageContent):
|
||||||
|
text_parts.append(content.data)
|
||||||
|
build_content = "".join(text_parts)
|
||||||
|
elif build_content is None:
|
||||||
|
build_content = ""
|
||||||
|
|
||||||
match = re.search(r"```(?:json)?\s*([\s\S]+?)```", build_content)
|
match = re.search(r"```(?:json)?\s*([\s\S]+?)```", build_content)
|
||||||
if match:
|
if match:
|
||||||
build_content = match.group(1)
|
build_content = match.group(1)
|
||||||
|
|
||||||
|
# Check if LLM returned empty content
|
||||||
|
if not build_content or not build_content.strip():
|
||||||
|
usage = build_res.usage if hasattr(build_res, "usage") else "N/A"
|
||||||
|
logger.error("Builder LLM returned empty content. Usage: %s", usage)
|
||||||
|
raise ValueError(
|
||||||
|
"LLM model returned empty response. This may indicate: "
|
||||||
|
"(1) Model refusal/content policy, (2) Model configuration issue, "
|
||||||
|
"(3) Plugin communication error. Try a different model or check model settings."
|
||||||
|
)
|
||||||
|
|
||||||
workflow_data = json_repair.loads(build_content)
|
workflow_data = json_repair.loads(build_content)
|
||||||
|
|
||||||
|
# Handle double-encoded JSON (when json_repair.loads returns a string)
|
||||||
|
# Keep decoding until we get a dict
|
||||||
|
max_decode_attempts = 3
|
||||||
|
decode_attempts = 0
|
||||||
|
while isinstance(workflow_data, str) and decode_attempts < max_decode_attempts:
|
||||||
|
workflow_data = json_repair.loads(workflow_data)
|
||||||
|
decode_attempts += 1
|
||||||
|
|
||||||
|
# If still a string, it's not valid JSON structure
|
||||||
|
if not isinstance(workflow_data, dict):
|
||||||
|
logger.error(
|
||||||
|
"workflow_data is not a dict after %s decode attempts. Type: %s, Value preview: %s",
|
||||||
|
decode_attempts,
|
||||||
|
type(workflow_data),
|
||||||
|
str(workflow_data)[:200],
|
||||||
|
)
|
||||||
|
raise ValueError(f"Expected dict, got {type(workflow_data).__name__}")
|
||||||
|
|
||||||
if "nodes" not in workflow_data:
|
if "nodes" not in workflow_data:
|
||||||
workflow_data["nodes"] = []
|
workflow_data["nodes"] = []
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ActionItem } from '../../app/components/goto-anything/actions/types'
|
import type { ScopeDescriptor } from '../../app/components/goto-anything/actions/types'
|
||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import CommandSelector from '../../app/components/goto-anything/command-selector'
|
import CommandSelector from '../../app/components/goto-anything/command-selector'
|
||||||
@ -20,36 +20,37 @@ vi.mock('cmdk', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
describe('CommandSelector', () => {
|
describe('CommandSelector', () => {
|
||||||
const mockActions: Record<string, ActionItem> = {
|
const mockScopes: ScopeDescriptor[] = [
|
||||||
app: {
|
{
|
||||||
key: '@app',
|
id: 'app',
|
||||||
shortcut: '@app',
|
shortcut: '@app',
|
||||||
title: 'Search Applications',
|
title: 'Search Applications',
|
||||||
description: 'Search apps',
|
description: 'Search apps',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
knowledge: {
|
{
|
||||||
key: '@knowledge',
|
id: 'knowledge',
|
||||||
shortcut: '@kb',
|
shortcut: '@kb',
|
||||||
|
aliases: ['@knowledge'],
|
||||||
title: 'Search Knowledge',
|
title: 'Search Knowledge',
|
||||||
description: 'Search knowledge bases',
|
description: 'Search knowledge bases',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
plugin: {
|
{
|
||||||
key: '@plugin',
|
id: 'plugin',
|
||||||
shortcut: '@plugin',
|
shortcut: '@plugin',
|
||||||
title: 'Search Plugins',
|
title: 'Search Plugins',
|
||||||
description: 'Search plugins',
|
description: 'Search plugins',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
node: {
|
{
|
||||||
key: '@node',
|
id: 'node',
|
||||||
shortcut: '@node',
|
shortcut: '@node',
|
||||||
title: 'Search Nodes',
|
title: 'Search Nodes',
|
||||||
description: 'Search workflow nodes',
|
description: 'Search workflow nodes',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
|
||||||
const mockOnCommandSelect = vi.fn()
|
const mockOnCommandSelect = vi.fn()
|
||||||
const mockOnCommandValueChange = vi.fn()
|
const mockOnCommandValueChange = vi.fn()
|
||||||
@ -62,7 +63,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should render all actions when no filter is provided', () => {
|
it('should render all actions when no filter is provided', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
@ -76,7 +77,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should render empty filter as showing all actions', () => {
|
it('should render empty filter as showing all actions', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
/>,
|
/>,
|
||||||
@ -93,7 +94,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should filter actions based on searchFilter - single match', () => {
|
it('should filter actions based on searchFilter - single match', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="k"
|
searchFilter="k"
|
||||||
/>,
|
/>,
|
||||||
@ -108,7 +109,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should filter actions with multiple matches', () => {
|
it('should filter actions with multiple matches', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="p"
|
searchFilter="p"
|
||||||
/>,
|
/>,
|
||||||
@ -123,7 +124,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should be case-insensitive when filtering', () => {
|
it('should be case-insensitive when filtering', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="APP"
|
searchFilter="APP"
|
||||||
/>,
|
/>,
|
||||||
@ -136,7 +137,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should match partial strings', () => {
|
it('should match partial strings', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="od"
|
searchFilter="od"
|
||||||
/>,
|
/>,
|
||||||
@ -153,7 +154,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should show empty state when no matches found', () => {
|
it('should show empty state when no matches found', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="xyz"
|
searchFilter="xyz"
|
||||||
/>,
|
/>,
|
||||||
@ -171,7 +172,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should not show empty state when filter is empty', () => {
|
it('should not show empty state when filter is empty', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
/>,
|
/>,
|
||||||
@ -185,7 +186,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should call onCommandValueChange when filter changes and first item differs', () => {
|
it('should call onCommandValueChange when filter changes and first item differs', () => {
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
commandValue="@app"
|
commandValue="@app"
|
||||||
@ -195,7 +196,7 @@ describe('CommandSelector', () => {
|
|||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="k"
|
searchFilter="k"
|
||||||
commandValue="@app"
|
commandValue="@app"
|
||||||
@ -209,7 +210,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should not call onCommandValueChange if current value still exists', () => {
|
it('should not call onCommandValueChange if current value still exists', () => {
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
commandValue="@app"
|
commandValue="@app"
|
||||||
@ -219,7 +220,7 @@ describe('CommandSelector', () => {
|
|||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="a"
|
searchFilter="a"
|
||||||
commandValue="@app"
|
commandValue="@app"
|
||||||
@ -233,7 +234,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should handle onCommandSelect callback correctly', () => {
|
it('should handle onCommandSelect callback correctly', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="k"
|
searchFilter="k"
|
||||||
/>,
|
/>,
|
||||||
@ -250,7 +251,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should handle empty actions object', () => {
|
it('should handle empty actions object', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={{}}
|
scopes={[]}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
/>,
|
/>,
|
||||||
@ -262,7 +263,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should handle special characters in filter', () => {
|
it('should handle special characters in filter', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="@"
|
searchFilter="@"
|
||||||
/>,
|
/>,
|
||||||
@ -277,7 +278,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should handle undefined onCommandValueChange gracefully', () => {
|
it('should handle undefined onCommandValueChange gracefully', () => {
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter=""
|
searchFilter=""
|
||||||
/>,
|
/>,
|
||||||
@ -286,7 +287,7 @@ describe('CommandSelector', () => {
|
|||||||
expect(() => {
|
expect(() => {
|
||||||
rerender(
|
rerender(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="k"
|
searchFilter="k"
|
||||||
/>,
|
/>,
|
||||||
@ -299,7 +300,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should work without searchFilter prop (backward compatible)', () => {
|
it('should work without searchFilter prop (backward compatible)', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
@ -313,7 +314,7 @@ describe('CommandSelector', () => {
|
|||||||
it('should work without commandValue and onCommandValueChange props', () => {
|
it('should work without commandValue and onCommandValueChange props', () => {
|
||||||
render(
|
render(
|
||||||
<CommandSelector
|
<CommandSelector
|
||||||
actions={mockActions}
|
scopes={mockScopes}
|
||||||
onCommandSelect={mockOnCommandSelect}
|
onCommandSelect={mockOnCommandSelect}
|
||||||
searchFilter="k"
|
searchFilter="k"
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { Mock } from 'vitest'
|
import type { Mock } from 'vitest'
|
||||||
import type { ActionItem } from '../../app/components/goto-anything/actions/types'
|
import type { ScopeDescriptor } from '../../app/components/goto-anything/actions/types'
|
||||||
|
|
||||||
// Import after mocking to get mocked version
|
// Import after mocking to get mocked version
|
||||||
import { matchAction } from '../../app/components/goto-anything/actions'
|
import { matchAction } from '../../app/components/goto-anything/actions'
|
||||||
@ -13,10 +13,11 @@ vi.mock('../../app/components/goto-anything/actions', () => ({
|
|||||||
vi.mock('../../app/components/goto-anything/actions/commands/registry')
|
vi.mock('../../app/components/goto-anything/actions/commands/registry')
|
||||||
|
|
||||||
// Implement the actual matchAction logic for testing
|
// Implement the actual matchAction logic for testing
|
||||||
const actualMatchAction = (query: string, actions: Record<string, ActionItem>) => {
|
const actualMatchAction = (query: string, scopes: ScopeDescriptor[]) => {
|
||||||
const result = Object.values(actions).find((action) => {
|
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
return scopes.find((scope) => {
|
||||||
// Special handling for slash commands
|
// Special handling for slash commands
|
||||||
if (action.key === '/') {
|
if (scope.id === 'slash' || scope.shortcut === '/') {
|
||||||
// Get all registered commands from the registry
|
// Get all registered commands from the registry
|
||||||
const allCommands = slashCommandRegistry.getAllCommands()
|
const allCommands = slashCommandRegistry.getAllCommands()
|
||||||
|
|
||||||
@ -33,39 +34,41 @@ const actualMatchAction = (query: string, actions: Record<string, ActionItem>) =
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const reg = new RegExp(`^(${action.key}|${action.shortcut})(?:\\s|$)`)
|
const shortcuts = [scope.shortcut, ...(scope.aliases || [])].map(escapeRegExp)
|
||||||
|
const reg = new RegExp(`^(${shortcuts.join('|')})(?:\\s|$)`)
|
||||||
return reg.test(query)
|
return reg.test(query)
|
||||||
})
|
})
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace mock with actual implementation
|
// Replace mock with actual implementation
|
||||||
;(matchAction as Mock).mockImplementation(actualMatchAction)
|
;(matchAction as Mock).mockImplementation(actualMatchAction)
|
||||||
|
|
||||||
describe('matchAction Logic', () => {
|
describe('matchAction Logic', () => {
|
||||||
const mockActions: Record<string, ActionItem> = {
|
const mockScopes: ScopeDescriptor[] = [
|
||||||
app: {
|
{
|
||||||
key: '@app',
|
id: 'app',
|
||||||
shortcut: '@a',
|
shortcut: '@app',
|
||||||
|
aliases: ['@a'],
|
||||||
title: 'Search Applications',
|
title: 'Search Applications',
|
||||||
description: 'Search apps',
|
description: 'Search apps',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
knowledge: {
|
{
|
||||||
key: '@knowledge',
|
id: 'knowledge',
|
||||||
shortcut: '@kb',
|
shortcut: '@kb',
|
||||||
|
aliases: ['@knowledge'],
|
||||||
title: 'Search Knowledge',
|
title: 'Search Knowledge',
|
||||||
description: 'Search knowledge bases',
|
description: 'Search knowledge bases',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
slash: {
|
{
|
||||||
key: '/',
|
id: 'slash',
|
||||||
shortcut: '/',
|
shortcut: '/',
|
||||||
title: 'Commands',
|
title: 'Commands',
|
||||||
description: 'Execute commands',
|
description: 'Execute commands',
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@ -81,32 +84,32 @@ describe('matchAction Logic', () => {
|
|||||||
|
|
||||||
describe('@ Actions Matching', () => {
|
describe('@ Actions Matching', () => {
|
||||||
it('should match @app with key', () => {
|
it('should match @app with key', () => {
|
||||||
const result = matchAction('@app', mockActions)
|
const result = matchAction('@app', mockScopes)
|
||||||
expect(result).toBe(mockActions.app)
|
expect(result).toBe(mockScopes[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match @app with shortcut', () => {
|
it('should match @app with shortcut', () => {
|
||||||
const result = matchAction('@a', mockActions)
|
const result = matchAction('@a', mockScopes)
|
||||||
expect(result).toBe(mockActions.app)
|
expect(result).toBe(mockScopes[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match @knowledge with key', () => {
|
it('should match @knowledge with key', () => {
|
||||||
const result = matchAction('@knowledge', mockActions)
|
const result = matchAction('@knowledge', mockScopes)
|
||||||
expect(result).toBe(mockActions.knowledge)
|
expect(result).toBe(mockScopes[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match @knowledge with shortcut @kb', () => {
|
it('should match @knowledge with shortcut @kb', () => {
|
||||||
const result = matchAction('@kb', mockActions)
|
const result = matchAction('@kb', mockScopes)
|
||||||
expect(result).toBe(mockActions.knowledge)
|
expect(result).toBe(mockScopes[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match with text after action', () => {
|
it('should match with text after action', () => {
|
||||||
const result = matchAction('@app search term', mockActions)
|
const result = matchAction('@app search term', mockScopes)
|
||||||
expect(result).toBe(mockActions.app)
|
expect(result).toBe(mockScopes[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not match partial @ actions', () => {
|
it('should not match partial @ actions', () => {
|
||||||
const result = matchAction('@ap', mockActions)
|
const result = matchAction('@ap', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -114,47 +117,47 @@ describe('matchAction Logic', () => {
|
|||||||
describe('Slash Commands Matching', () => {
|
describe('Slash Commands Matching', () => {
|
||||||
describe('Direct Mode Commands', () => {
|
describe('Direct Mode Commands', () => {
|
||||||
it('should not match direct mode commands', () => {
|
it('should not match direct mode commands', () => {
|
||||||
const result = matchAction('/docs', mockActions)
|
const result = matchAction('/docs', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not match direct mode with arguments', () => {
|
it('should not match direct mode with arguments', () => {
|
||||||
const result = matchAction('/docs something', mockActions)
|
const result = matchAction('/docs something', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not match any direct mode command', () => {
|
it('should not match any direct mode command', () => {
|
||||||
expect(matchAction('/community', mockActions)).toBeUndefined()
|
expect(matchAction('/community', mockScopes)).toBeUndefined()
|
||||||
expect(matchAction('/feedback', mockActions)).toBeUndefined()
|
expect(matchAction('/feedback', mockScopes)).toBeUndefined()
|
||||||
expect(matchAction('/account', mockActions)).toBeUndefined()
|
expect(matchAction('/account', mockScopes)).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Submenu Mode Commands', () => {
|
describe('Submenu Mode Commands', () => {
|
||||||
it('should match submenu mode commands exactly', () => {
|
it('should match submenu mode commands exactly', () => {
|
||||||
const result = matchAction('/theme', mockActions)
|
const result = matchAction('/theme', mockScopes)
|
||||||
expect(result).toBe(mockActions.slash)
|
expect(result).toBe(mockScopes[2])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match submenu mode with arguments', () => {
|
it('should match submenu mode with arguments', () => {
|
||||||
const result = matchAction('/theme dark', mockActions)
|
const result = matchAction('/theme dark', mockScopes)
|
||||||
expect(result).toBe(mockActions.slash)
|
expect(result).toBe(mockScopes[2])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match all submenu commands', () => {
|
it('should match all submenu commands', () => {
|
||||||
expect(matchAction('/language', mockActions)).toBe(mockActions.slash)
|
expect(matchAction('/language', mockScopes)).toBe(mockScopes[2])
|
||||||
expect(matchAction('/language en', mockActions)).toBe(mockActions.slash)
|
expect(matchAction('/language en', mockScopes)).toBe(mockScopes[2])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Slash Without Command', () => {
|
describe('Slash Without Command', () => {
|
||||||
it('should not match single slash', () => {
|
it('should not match single slash', () => {
|
||||||
const result = matchAction('/', mockActions)
|
const result = matchAction('/', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not match unregistered commands', () => {
|
it('should not match unregistered commands', () => {
|
||||||
const result = matchAction('/unknown', mockActions)
|
const result = matchAction('/unknown', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -162,28 +165,28 @@ describe('matchAction Logic', () => {
|
|||||||
|
|
||||||
describe('Edge Cases', () => {
|
describe('Edge Cases', () => {
|
||||||
it('should handle empty query', () => {
|
it('should handle empty query', () => {
|
||||||
const result = matchAction('', mockActions)
|
const result = matchAction('', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle whitespace only', () => {
|
it('should handle whitespace only', () => {
|
||||||
const result = matchAction(' ', mockActions)
|
const result = matchAction(' ', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle regular text without actions', () => {
|
it('should handle regular text without actions', () => {
|
||||||
const result = matchAction('search something', mockActions)
|
const result = matchAction('search something', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle special characters', () => {
|
it('should handle special characters', () => {
|
||||||
const result = matchAction('#tag', mockActions)
|
const result = matchAction('#tag', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle multiple @ or /', () => {
|
it('should handle multiple @ or /', () => {
|
||||||
expect(matchAction('@@app', mockActions)).toBeUndefined()
|
expect(matchAction('@@app', mockScopes)).toBeUndefined()
|
||||||
expect(matchAction('//theme', mockActions)).toBeUndefined()
|
expect(matchAction('//theme', mockScopes)).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -193,7 +196,7 @@ describe('matchAction Logic', () => {
|
|||||||
{ name: 'test', mode: 'direct' },
|
{ name: 'test', mode: 'direct' },
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = matchAction('/test', mockActions)
|
const result = matchAction('/test', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -202,8 +205,8 @@ describe('matchAction Logic', () => {
|
|||||||
{ name: 'test', mode: 'submenu' },
|
{ name: 'test', mode: 'submenu' },
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = matchAction('/test', mockActions)
|
const result = matchAction('/test', mockScopes)
|
||||||
expect(result).toBe(mockActions.slash)
|
expect(result).toBe(mockScopes[2])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should treat undefined mode as submenu', () => {
|
it('should treat undefined mode as submenu', () => {
|
||||||
@ -211,25 +214,25 @@ describe('matchAction Logic', () => {
|
|||||||
{ name: 'test' }, // No mode specified
|
{ name: 'test' }, // No mode specified
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = matchAction('/test', mockActions)
|
const result = matchAction('/test', mockScopes)
|
||||||
expect(result).toBe(mockActions.slash)
|
expect(result).toBe(mockScopes[2])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Registry Integration', () => {
|
describe('Registry Integration', () => {
|
||||||
it('should call getAllCommands when matching slash', () => {
|
it('should call getAllCommands when matching slash', () => {
|
||||||
matchAction('/theme', mockActions)
|
matchAction('/theme', mockScopes)
|
||||||
expect(slashCommandRegistry.getAllCommands).toHaveBeenCalled()
|
expect(slashCommandRegistry.getAllCommands).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not call getAllCommands for @ actions', () => {
|
it('should not call getAllCommands for @ actions', () => {
|
||||||
matchAction('@app', mockActions)
|
matchAction('@app', mockScopes)
|
||||||
expect(slashCommandRegistry.getAllCommands).not.toHaveBeenCalled()
|
expect(slashCommandRegistry.getAllCommands).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle empty command list', () => {
|
it('should handle empty command list', () => {
|
||||||
;(slashCommandRegistry.getAllCommands as Mock).mockReturnValue([])
|
;(slashCommandRegistry.getAllCommands as Mock).mockReturnValue([])
|
||||||
const result = matchAction('/anything', mockActions)
|
const result = matchAction('/anything', mockScopes)
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type { MockedFunction } from 'vitest'
|
|||||||
* 4. Ensure errors don't propagate to UI layer causing "search failed"
|
* 4. Ensure errors don't propagate to UI layer causing "search failed"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Actions, searchAnything } from '@/app/components/goto-anything/actions'
|
import { appScope, knowledgeScope, pluginScope, searchAnything } from '@/app/components/goto-anything/actions'
|
||||||
import { fetchAppList } from '@/service/apps'
|
import { fetchAppList } from '@/service/apps'
|
||||||
import { postMarketplace } from '@/service/base'
|
import { postMarketplace } from '@/service/base'
|
||||||
import { fetchDatasets } from '@/service/datasets'
|
import { fetchDatasets } from '@/service/datasets'
|
||||||
@ -57,10 +57,8 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
// Mock marketplace API failure (403 permission denied)
|
// Mock marketplace API failure (403 permission denied)
|
||||||
mockPostMarketplace.mockRejectedValue(new Error('HTTP 403: Forbidden'))
|
mockPostMarketplace.mockRejectedValue(new Error('HTTP 403: Forbidden'))
|
||||||
|
|
||||||
const pluginAction = Actions.plugin
|
|
||||||
|
|
||||||
// Directly call plugin action's search method
|
// Directly call plugin action's search method
|
||||||
const result = await pluginAction.search('@plugin', 'test', 'en')
|
const result = await pluginScope.search('@plugin', 'test', 'en')
|
||||||
|
|
||||||
// Should return empty array instead of throwing error
|
// Should return empty array instead of throwing error
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
@ -80,8 +78,7 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
data: { plugins: [] },
|
data: { plugins: [] },
|
||||||
})
|
})
|
||||||
|
|
||||||
const pluginAction = Actions.plugin
|
const result = await pluginScope.search('@plugin', '', 'en')
|
||||||
const result = await pluginAction.search('@plugin', '', 'en')
|
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
@ -92,8 +89,7 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const pluginAction = Actions.plugin
|
const result = await pluginScope.search('@plugin', 'test', 'en')
|
||||||
const result = await pluginAction.search('@plugin', 'test', 'en')
|
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
@ -104,8 +100,7 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
// Mock app API failure
|
// Mock app API failure
|
||||||
mockFetchAppList.mockRejectedValue(new Error('API Error'))
|
mockFetchAppList.mockRejectedValue(new Error('API Error'))
|
||||||
|
|
||||||
const appAction = Actions.app
|
const result = await appScope.search('@app', 'test', 'en')
|
||||||
const result = await appAction.search('@app', 'test', 'en')
|
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
@ -114,8 +109,7 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
// Mock knowledge API failure
|
// Mock knowledge API failure
|
||||||
mockFetchDatasets.mockRejectedValue(new Error('API Error'))
|
mockFetchDatasets.mockRejectedValue(new Error('API Error'))
|
||||||
|
|
||||||
const knowledgeAction = Actions.knowledge
|
const result = await knowledgeScope.search('@knowledge', 'test', 'en')
|
||||||
const result = await knowledgeAction.search('@knowledge', 'test', 'en')
|
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
@ -128,19 +122,20 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
mockFetchDatasets.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 })
|
mockFetchDatasets.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 })
|
||||||
mockPostMarketplace.mockRejectedValue(new Error('Plugin API failed'))
|
mockPostMarketplace.mockRejectedValue(new Error('Plugin API failed'))
|
||||||
|
|
||||||
const result = await searchAnything('en', 'test')
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
|
const result = await searchAnything('en', 'test', undefined, allScopes)
|
||||||
|
|
||||||
// Should return successful results even if plugin search fails
|
// Should return successful results even if plugin search fails
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
expect(console.warn).toHaveBeenCalledWith('Plugin search failed:', expect.any(Error))
|
expect(console.warn).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('@plugin dedicated search should return empty array when API fails', async () => {
|
it('@plugin dedicated search should return empty array when API fails', async () => {
|
||||||
// Mock plugin API failure
|
// Mock plugin API failure
|
||||||
mockPostMarketplace.mockRejectedValue(new Error('Plugin service unavailable'))
|
mockPostMarketplace.mockRejectedValue(new Error('Plugin service unavailable'))
|
||||||
|
|
||||||
const pluginAction = Actions.plugin
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
const result = await searchAnything('en', '@plugin test', pluginAction)
|
const result = await searchAnything('en', '@plugin test', pluginScope, allScopes)
|
||||||
|
|
||||||
// Should return empty array instead of throwing error
|
// Should return empty array instead of throwing error
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
@ -150,8 +145,8 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
// Mock app API failure
|
// Mock app API failure
|
||||||
mockFetchAppList.mockRejectedValue(new Error('App service unavailable'))
|
mockFetchAppList.mockRejectedValue(new Error('App service unavailable'))
|
||||||
|
|
||||||
const appAction = Actions.app
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
const result = await searchAnything('en', '@app test', appAction)
|
const result = await searchAnything('en', '@app test', appScope, allScopes)
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
@ -165,13 +160,13 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
mockFetchDatasets.mockRejectedValue(new Error('Dataset API failed'))
|
mockFetchDatasets.mockRejectedValue(new Error('Dataset API failed'))
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{ name: '@plugin', action: Actions.plugin },
|
{ name: '@plugin', scope: pluginScope },
|
||||||
{ name: '@app', action: Actions.app },
|
{ name: '@app', scope: appScope },
|
||||||
{ name: '@knowledge', action: Actions.knowledge },
|
{ name: '@knowledge', scope: knowledgeScope },
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const { name, action } of actions) {
|
for (const { name, scope } of actions) {
|
||||||
const result = await action.search(name, 'test', 'en')
|
const result = await scope.search(name, 'test', 'en')
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -181,7 +176,8 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
it('empty search term should be handled properly', async () => {
|
it('empty search term should be handled properly', async () => {
|
||||||
mockPostMarketplace.mockResolvedValue({ data: { plugins: [] } })
|
mockPostMarketplace.mockResolvedValue({ data: { plugins: [] } })
|
||||||
|
|
||||||
const result = await searchAnything('en', '@plugin ', Actions.plugin)
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
|
const result = await searchAnything('en', '@plugin ', pluginScope, allScopes)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -191,7 +187,8 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
|
|
||||||
mockPostMarketplace.mockRejectedValue(timeoutError)
|
mockPostMarketplace.mockRejectedValue(timeoutError)
|
||||||
|
|
||||||
const result = await searchAnything('en', '@plugin test', Actions.plugin)
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
|
const result = await searchAnything('en', '@plugin test', pluginScope, allScopes)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -199,7 +196,8 @@ describe('GotoAnything Search Error Handling', () => {
|
|||||||
const parseError = new SyntaxError('Unexpected token in JSON')
|
const parseError = new SyntaxError('Unexpected token in JSON')
|
||||||
mockPostMarketplace.mockRejectedValue(parseError)
|
mockPostMarketplace.mockRejectedValue(parseError)
|
||||||
|
|
||||||
const result = await searchAnything('en', '@plugin test', Actions.plugin)
|
const allScopes = [appScope, knowledgeScope, pluginScope]
|
||||||
|
const result = await searchAnything('en', '@plugin test', pluginScope, allScopes)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
import { isInWorkflowPage, VIBE_COMMAND_EVENT } from '@/app/components/workflow/constants'
|
import { isInWorkflowPage, VIBE_COMMAND_EVENT } from '@/app/components/workflow/constants'
|
||||||
import i18n from '@/i18n-config/i18next-config'
|
|
||||||
import { bananaCommand } from './banana'
|
import { bananaCommand } from './banana'
|
||||||
import { registerCommands, unregisterCommands } from './command-bus'
|
import { registerCommands, unregisterCommands } from './command-bus'
|
||||||
|
|
||||||
vi.mock('@/i18n-config/i18next-config', () => ({
|
// Mock i18n for testing
|
||||||
default: {
|
const mockI18n = {
|
||||||
t: vi.fn((key: string, options?: Record<string, unknown>) => {
|
t: vi.fn((key: string, options?: Record<string, unknown>) => {
|
||||||
if (!options)
|
if (!options)
|
||||||
return key
|
return key
|
||||||
return `${key}:${JSON.stringify(options)}`
|
return `${key}:${JSON.stringify(options)}`
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => ({
|
||||||
|
getI18n: () => mockI18n,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/workflow/constants', async () => {
|
vi.mock('@/app/components/workflow/constants', async () => {
|
||||||
@ -31,7 +33,7 @@ vi.mock('./command-bus', () => ({
|
|||||||
const mockedIsInWorkflowPage = vi.mocked(isInWorkflowPage)
|
const mockedIsInWorkflowPage = vi.mocked(isInWorkflowPage)
|
||||||
const mockedRegisterCommands = vi.mocked(registerCommands)
|
const mockedRegisterCommands = vi.mocked(registerCommands)
|
||||||
const mockedUnregisterCommands = vi.mocked(unregisterCommands)
|
const mockedUnregisterCommands = vi.mocked(unregisterCommands)
|
||||||
const mockedT = vi.mocked(i18n.t)
|
const mockedT = mockI18n.t
|
||||||
|
|
||||||
type CommandArgs = { dsl?: string }
|
type CommandArgs = { dsl?: string }
|
||||||
type CommandMap = Record<string, (args?: CommandArgs) => void | Promise<void>>
|
type CommandMap = Record<string, (args?: CommandArgs) => void | Promise<void>>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const nodeDefault: NodeDefault<CodeNodeType> = {
|
|||||||
const { code, variables } = payload
|
const { code, variables } = payload
|
||||||
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
|
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
||||||
if (!errorMessages && variables.filter(v => !v.value_selector.length).length > 0)
|
if (!errorMessages && variables.filter(v => !v.value_selector || !v.value_selector.length).length > 0)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
||||||
if (!errorMessages && !code)
|
if (!errorMessages && !code)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.code`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.code`, { ns: 'workflow' }) })
|
||||||
|
|||||||
@ -95,7 +95,7 @@ const nodeDefault: NodeDefault<LLMNodeType> = {
|
|||||||
payload.prompt_config?.jinja2_variables.forEach((i) => {
|
payload.prompt_config?.jinja2_variables.forEach((i) => {
|
||||||
if (!errorMessages && !i.variable)
|
if (!errorMessages && !i.variable)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
||||||
if (!errorMessages && !i.value_selector.length)
|
if (!errorMessages && (!i.value_selector || !i.value_selector.length))
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const nodeDefault: NodeDefault<TemplateTransformNodeType> = {
|
|||||||
|
|
||||||
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
|
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variable`, { ns: 'workflow' }) })
|
||||||
if (!errorMessages && variables.filter(v => !v.value_selector.length).length > 0)
|
if (!errorMessages && variables.filter(v => !v.value_selector || !v.value_selector.length).length > 0)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.variableValue`, { ns: 'workflow' }) })
|
||||||
if (!errorMessages && !template)
|
if (!errorMessages && !template)
|
||||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.templateTransform.code', { ns: 'workflow' }) })
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.templateTransform.code', { ns: 'workflow' }) })
|
||||||
|
|||||||
Reference in New Issue
Block a user