mirror of
https://github.com/langgenius/dify.git
synced 2026-03-09 17:25:10 +00:00
Compare commits
6 Commits
main
...
fix/draft-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed55b16a37 | ||
|
|
afeb8d3e68 | ||
|
|
2ce39ce1d5 | ||
|
|
0b7a2c3a80 | ||
|
|
f20cff9158 | ||
|
|
f848274413 |
@@ -121,3 +121,9 @@ class NeedAddIdsError(BaseHTTPException):
|
||||
error_code = "need_add_ids"
|
||||
description = "Need to add ids."
|
||||
code = 400
|
||||
|
||||
|
||||
class VariableValidationError(BaseHTTPException):
|
||||
error_code = "variable_validation_error"
|
||||
description = "Variable validation failed."
|
||||
code = 400
|
||||
|
||||
@@ -11,7 +11,12 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||
|
||||
import services
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
|
||||
from controllers.console.app.error import (
|
||||
ConversationCompletedError,
|
||||
DraftWorkflowNotExist,
|
||||
DraftWorkflowNotSync,
|
||||
VariableValidationError,
|
||||
)
|
||||
from controllers.console.app.workflow_run import workflow_run_node_execution_model
|
||||
from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
||||
@@ -32,6 +37,7 @@ from dify_graph.enums import NodeType
|
||||
from dify_graph.file.models import File
|
||||
from dify_graph.graph_engine.manager import GraphEngineManager
|
||||
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
|
||||
from dify_graph.variables.exc import VariableError
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from factories import file_factory, variable_factory
|
||||
@@ -302,6 +308,8 @@ class DraftWorkflowApi(Resource):
|
||||
)
|
||||
except WorkflowHashNotEqualError:
|
||||
raise DraftWorkflowNotSync()
|
||||
except VariableError as e:
|
||||
raise VariableValidationError(description=str(e))
|
||||
|
||||
return {
|
||||
"result": "success",
|
||||
|
||||
@@ -72,9 +72,18 @@ SEGMENT_TO_VARIABLE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
_MAX_VARIABLE_DESCRIPTION_LENGTH = 255
|
||||
|
||||
|
||||
def build_conversation_variable_from_mapping(mapping: Mapping[str, Any], /) -> VariableBase:
|
||||
if not mapping.get("name"):
|
||||
raise VariableError("missing name")
|
||||
description = mapping.get("description", "")
|
||||
if len(description) > _MAX_VARIABLE_DESCRIPTION_LENGTH:
|
||||
raise VariableError(
|
||||
f"description of variable '{mapping['name']}' is too long"
|
||||
f" (max {_MAX_VARIABLE_DESCRIPTION_LENGTH} characters)"
|
||||
)
|
||||
return _build_variable_from_mapping(mapping=mapping, selector=[CONVERSATION_VARIABLE_NODE_ID, mapping["name"]])
|
||||
|
||||
|
||||
|
||||
@@ -1589,6 +1589,8 @@ class WorkflowDraftVariable(Base):
|
||||
variable.file_id = file_id
|
||||
variable._set_selector(list(variable_utils.to_selector(node_id, name)))
|
||||
variable.node_execution_id = node_execution_id
|
||||
variable.visible = True
|
||||
variable.is_default_value = False
|
||||
return variable
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -700,6 +700,8 @@ def _model_to_insertion_dict(model: WorkflowDraftVariable) -> dict[str, Any]:
|
||||
d["updated_at"] = model.updated_at
|
||||
if model.description is not None:
|
||||
d["description"] = model.description
|
||||
if model.is_default_value is not None:
|
||||
d["is_default_value"] = model.is_default_value
|
||||
return d
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from services.workflow_draft_variable_service import (
|
||||
DraftVariableSaver,
|
||||
VariableResetError,
|
||||
WorkflowDraftVariableService,
|
||||
_model_to_insertion_dict,
|
||||
)
|
||||
|
||||
|
||||
@@ -475,3 +476,41 @@ class TestWorkflowDraftVariableService:
|
||||
assert node_var.visible == True
|
||||
assert node_var.editable == True
|
||||
assert node_var.node_execution_id == "exec-id"
|
||||
|
||||
|
||||
class TestModelToInsertionDict:
|
||||
"""Reproduce two production errors in _model_to_insertion_dict / _new()."""
|
||||
|
||||
def test_visible_and_is_default_value_always_present(self):
|
||||
"""Problem 1: _new() did not set visible/is_default_value, causing
|
||||
inconsistent dict keys across rows in multi-row INSERT and missing
|
||||
is_default_value in the insertion dict entirely.
|
||||
"""
|
||||
conv_var = WorkflowDraftVariable.new_conversation_variable(
|
||||
app_id="app-1",
|
||||
name="counter",
|
||||
value=StringSegment(value="0"),
|
||||
)
|
||||
# _new() should explicitly set these fields so they are not None
|
||||
assert conv_var.visible is not None
|
||||
assert conv_var.is_default_value is not None
|
||||
|
||||
d = _model_to_insertion_dict(conv_var)
|
||||
# visible must appear in every row's dict
|
||||
assert "visible" in d
|
||||
# is_default_value must always be present
|
||||
assert "is_default_value" in d
|
||||
|
||||
def test_description_passthrough(self):
|
||||
"""_model_to_insertion_dict passes description as-is;
|
||||
length validation is enforced earlier in build_conversation_variable_from_mapping.
|
||||
"""
|
||||
desc = "a" * 200
|
||||
conv_var = WorkflowDraftVariable.new_conversation_variable(
|
||||
app_id="app-1",
|
||||
name="counter",
|
||||
value=StringSegment(value="0"),
|
||||
description=desc,
|
||||
)
|
||||
d = _model_to_insertion_dict(conv_var)
|
||||
assert d["description"] == desc
|
||||
|
||||
@@ -231,6 +231,8 @@ const ChatVariableModal = ({
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_DESCRIPTION_LENGTH = 255
|
||||
|
||||
const handleSave = () => {
|
||||
if (!checkVariableName(name))
|
||||
return
|
||||
@@ -241,6 +243,8 @@ const ChatVariableModal = ({
|
||||
// return notify({ type: 'error', message: 'value can not be empty' })
|
||||
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
|
||||
return notify({ type: 'error', message: 'object key can not be empty' })
|
||||
if (description.length > MAX_DESCRIPTION_LENGTH)
|
||||
return notify({ type: 'error', message: t('chatVariable.modal.descriptionTooLong', { ns: 'workflow', maxLength: MAX_DESCRIPTION_LENGTH }) })
|
||||
|
||||
onSave({
|
||||
id: chatVar ? chatVar.id : uuid4(),
|
||||
@@ -273,7 +277,7 @@ const ChatVariableModal = ({
|
||||
<div
|
||||
className={cn('flex h-full w-[360px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl', type === ChatVarType.Object && 'w-[480px]')}
|
||||
>
|
||||
<div className="system-xl-semibold mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary">
|
||||
<div className="mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary system-xl-semibold">
|
||||
{!chatVar ? t('chatVariable.modal.title', { ns: 'workflow' }) : t('chatVariable.modal.editTitle', { ns: 'workflow' })}
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
@@ -287,7 +291,7 @@ const ChatVariableModal = ({
|
||||
<div className="max-h-[480px] overflow-y-auto px-4 py-2">
|
||||
{/* name */}
|
||||
<div className="mb-4">
|
||||
<div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('chatVariable.modal.name', { ns: 'workflow' })}</div>
|
||||
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-semibold">{t('chatVariable.modal.name', { ns: 'workflow' })}</div>
|
||||
<div className="flex">
|
||||
<Input
|
||||
placeholder={t('chatVariable.modal.namePlaceholder', { ns: 'workflow' }) || ''}
|
||||
@@ -300,7 +304,7 @@ const ChatVariableModal = ({
|
||||
</div>
|
||||
{/* type */}
|
||||
<div className="mb-4">
|
||||
<div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('chatVariable.modal.type', { ns: 'workflow' })}</div>
|
||||
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-semibold">{t('chatVariable.modal.type', { ns: 'workflow' })}</div>
|
||||
<div className="flex">
|
||||
<VariableTypeSelector
|
||||
value={type}
|
||||
@@ -312,7 +316,7 @@ const ChatVariableModal = ({
|
||||
</div>
|
||||
{/* default value */}
|
||||
<div className="mb-4">
|
||||
<div className="system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary">
|
||||
<div className="mb-1 flex h-6 items-center justify-between text-text-secondary system-sm-semibold">
|
||||
<div>{t('chatVariable.modal.value', { ns: 'workflow' })}</div>
|
||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber || type === ChatVarType.ArrayBoolean) && (
|
||||
<Button
|
||||
@@ -341,7 +345,7 @@ const ChatVariableModal = ({
|
||||
{type === ChatVarType.String && (
|
||||
// Input will remove \n\r, so use Textarea just like description area
|
||||
<textarea
|
||||
className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
|
||||
className="block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none system-sm-regular placeholder:text-components-input-text-placeholder placeholder:system-sm-regular hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
|
||||
value={value}
|
||||
placeholder={t('chatVariable.modal.valuePlaceholder', { ns: 'workflow' }) || ''}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
@@ -404,15 +408,20 @@ const ChatVariableModal = ({
|
||||
</div>
|
||||
{/* description */}
|
||||
<div className="">
|
||||
<div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('chatVariable.modal.description', { ns: 'workflow' })}</div>
|
||||
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-semibold">{t('chatVariable.modal.description', { ns: 'workflow' })}</div>
|
||||
<div className="flex">
|
||||
<textarea
|
||||
className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
|
||||
className="block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none system-sm-regular placeholder:text-components-input-text-placeholder placeholder:system-sm-regular hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
|
||||
value={description}
|
||||
placeholder={t('chatVariable.modal.descriptionPlaceholder', { ns: 'workflow' }) || ''}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn('mt-1 text-right system-xs-regular', description.length > MAX_DESCRIPTION_LENGTH ? 'text-text-destructive' : 'text-text-quaternary')}>
|
||||
{description.length}
|
||||
/
|
||||
{MAX_DESCRIPTION_LENGTH}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row-reverse rounded-b-2xl p-4 pt-2">
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"chatVariable.modal.arrayValue": "Value",
|
||||
"chatVariable.modal.description": "Description",
|
||||
"chatVariable.modal.descriptionPlaceholder": "Describe the variable",
|
||||
"chatVariable.modal.descriptionTooLong": "Description must be {{maxLength}} characters or less",
|
||||
"chatVariable.modal.editInForm": "Edit in Form",
|
||||
"chatVariable.modal.editInJSON": "Edit in JSON",
|
||||
"chatVariable.modal.editTitle": "Edit Conversation Variable",
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"chatVariable.modal.arrayValue": "值",
|
||||
"chatVariable.modal.description": "描述",
|
||||
"chatVariable.modal.descriptionPlaceholder": "变量的描述",
|
||||
"chatVariable.modal.descriptionTooLong": "描述不能超过 {{maxLength}} 个字符",
|
||||
"chatVariable.modal.editInForm": "在表单中编辑",
|
||||
"chatVariable.modal.editInJSON": "在 JSON 中编辑",
|
||||
"chatVariable.modal.editTitle": "编辑会话变量",
|
||||
|
||||
Reference in New Issue
Block a user