mirror of
https://github.com/langgenius/dify.git
synced 2026-02-07 11:31:17 +08:00
feat: new runtime options
This commit is contained in:
parent
1c7c475c43
commit
3902929d9f
@ -24,6 +24,7 @@ from core.app.apps.message_based_app_generator import MessageBasedAppGenerator
|
||||
from core.app.apps.message_based_app_queue_manager import MessageBasedAppQueueManager
|
||||
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom
|
||||
from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotAppStreamResponse
|
||||
from core.app.layers.sandbox_layer import SandboxLayer
|
||||
from core.helper.trace_id_helper import extract_external_trace_id_from_args
|
||||
from core.model_runtime.errors.invoke import InvokeAuthorizationError
|
||||
from core.ops.ops_trace_manager import TraceQueueManager
|
||||
@ -512,6 +513,11 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
||||
if workflow is None:
|
||||
raise ValueError("Workflow not found")
|
||||
|
||||
runtime = workflow.features_dict.get("runtime")
|
||||
graph_engine_layers = ()
|
||||
if isinstance(runtime, dict) and runtime.get("enabled"):
|
||||
graph_engine_layers = (SandboxLayer(),)
|
||||
|
||||
# Determine system_user_id based on invocation source
|
||||
is_external_api_call = application_generate_entity.invoke_from in {
|
||||
InvokeFrom.WEB_APP,
|
||||
@ -542,6 +548,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
||||
app=app,
|
||||
workflow_execution_repository=workflow_execution_repository,
|
||||
workflow_node_execution_repository=workflow_node_execution_repository,
|
||||
graph_engine_layers=graph_engine_layers,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@ -23,6 +23,7 @@ from core.app.apps.workflow.generate_response_converter import WorkflowAppGenera
|
||||
from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity
|
||||
from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse
|
||||
from core.app.layers.sandbox_layer import SandboxLayer
|
||||
from core.helper.trace_id_helper import extract_external_trace_id_from_args
|
||||
from core.model_runtime.errors.invoke import InvokeAuthorizationError
|
||||
from core.ops.ops_trace_manager import TraceQueueManager
|
||||
@ -487,6 +488,10 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
if workflow is None:
|
||||
raise ValueError("Workflow not found")
|
||||
|
||||
runtime = workflow.features_dict.get("runtime")
|
||||
if isinstance(runtime, dict) and runtime.get("enabled"):
|
||||
graph_engine_layers = (*graph_engine_layers, SandboxLayer())
|
||||
|
||||
# Determine system_user_id based on invocation source
|
||||
is_external_api_call = application_generate_entity.invoke_from in {
|
||||
InvokeFrom.WEB_APP,
|
||||
|
||||
@ -126,10 +126,6 @@ class CommandNode(Node[CommandNodeData]):
|
||||
connection_handle, command
|
||||
)
|
||||
|
||||
# This node currently does not support interactive stdin.
|
||||
with contextlib.suppress(Exception):
|
||||
stdin_transport.close()
|
||||
|
||||
is_combined_stream = stdout_transport is stderr_transport
|
||||
|
||||
stdout_thread = threading.Thread(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
|
||||
from core.virtual_environment.__base.entities import Arch, CommandStatus, ConnectionHandle, FileState, Metadata
|
||||
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
|
||||
@ -139,12 +140,16 @@ def test_command_node_nonzero_exit_code_returns_failed_result():
|
||||
assert "exited with code" in result.error
|
||||
|
||||
|
||||
def test_command_node_timeout_returns_failed_result_and_closes_transports():
|
||||
def test_command_node_timeout_returns_failed_result_and_closes_transports(monkeypatch: Any):
|
||||
from core.workflow.nodes.command import node as command_node_module
|
||||
|
||||
monkeypatch.setattr(command_node_module, "COMMAND_NODE_TIMEOUT_SECONDS", 1)
|
||||
|
||||
node = _make_node(command="sleep 10")
|
||||
sandbox = FakeSandbox(
|
||||
stdout=b"",
|
||||
stderr=b"",
|
||||
statuses=[CommandStatus(status=CommandStatus.Status.RUNNING, exit_code=None)] * 100,
|
||||
statuses=[CommandStatus(status=CommandStatus.Status.RUNNING, exit_code=None)] * 1000,
|
||||
close_streams=False,
|
||||
)
|
||||
node.sandbox = sandbox
|
||||
|
||||
@ -39,6 +39,10 @@ type CreateAppProps = {
|
||||
defaultAppMode?: AppModeEnum
|
||||
}
|
||||
|
||||
type RuntimeMode = 'classical' | 'new'
|
||||
|
||||
const WORKFLOW_RUNTIME_STORAGE_KEY_PREFIX = 'workflow:sandbox-runtime:'
|
||||
|
||||
function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: CreateAppProps) {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
@ -50,6 +54,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
const [name, setName] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [isAppTypeExpanded, setIsAppTypeExpanded] = useState(false)
|
||||
const [runtimeMode, setRuntimeMode] = useState<RuntimeMode>('classical')
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
@ -60,6 +65,9 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
useEffect(() => {
|
||||
if (appMode === AppModeEnum.CHAT || appMode === AppModeEnum.AGENT_CHAT || appMode === AppModeEnum.COMPLETION)
|
||||
setIsAppTypeExpanded(true)
|
||||
|
||||
if (appMode !== AppModeEnum.WORKFLOW && appMode !== AppModeEnum.ADVANCED_CHAT)
|
||||
setRuntimeMode('classical')
|
||||
}, [appMode])
|
||||
|
||||
const onCreate = useCallback(async () => {
|
||||
@ -84,6 +92,9 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
mode: appMode,
|
||||
})
|
||||
|
||||
if (runtimeMode === 'new' && (appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT))
|
||||
localStorage.setItem(`${WORKFLOW_RUNTIME_STORAGE_KEY_PREFIX}${app.id}`, '1')
|
||||
|
||||
// Track app creation success
|
||||
trackEvent('create_app', {
|
||||
app_mode: appMode,
|
||||
@ -103,7 +114,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
})
|
||||
}
|
||||
isCreatingRef.current = false
|
||||
}, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor])
|
||||
}, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor, runtimeMode])
|
||||
|
||||
const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
|
||||
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
|
||||
@ -258,6 +269,54 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT) && (
|
||||
<div>
|
||||
<div className="system-sm-semibold mb-2 text-text-secondary">
|
||||
{t('newApp.runtimeLabel', { ns: 'app' })}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-10 flex-1 items-center gap-2 rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg px-3',
|
||||
runtimeMode === 'new' && 'border-components-option-card-option-border-selected bg-components-option-card-option-selected-bg',
|
||||
)}
|
||||
onClick={() => setRuntimeMode('new')}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'h-4 w-4 rounded-full border border-components-radio-border bg-components-radio-bg shadow-xs',
|
||||
runtimeMode === 'new' && 'border-[5px] border-components-radio-border-checked',
|
||||
)}
|
||||
/>
|
||||
<div className="system-sm-medium text-text-secondary">
|
||||
{t('newApp.runtimeOptionNew', { ns: 'app' })}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-10 flex-1 items-center gap-2 rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg px-3',
|
||||
runtimeMode === 'classical' && 'border-components-option-card-option-border-selected bg-components-option-card-option-selected-bg',
|
||||
)}
|
||||
onClick={() => setRuntimeMode('classical')}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'h-4 w-4 rounded-full border border-components-radio-border bg-components-radio-bg shadow-xs',
|
||||
runtimeMode === 'classical' && 'border-[5px] border-components-radio-border-checked',
|
||||
)}
|
||||
/>
|
||||
<div className="system-sm-medium text-text-secondary">
|
||||
{t('newApp.runtimeOptionClassical', { ns: 'app' })}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{isAppsFull && <AppsFull className="mt-4" loc="app-create" />}
|
||||
<div className="flex items-center justify-between pb-10 pt-5">
|
||||
|
||||
@ -54,6 +54,9 @@ export const createFeaturesStore = (initProps?: Partial<FeaturesState>) => {
|
||||
annotationReply: {
|
||||
enabled: false,
|
||||
},
|
||||
runtime: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
return createStore<FeatureStoreState>()(set => ({
|
||||
|
||||
@ -77,6 +77,10 @@ export type AnnotationReplyConfig = {
|
||||
}
|
||||
}
|
||||
|
||||
export type Runtime = {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export enum FeatureEnum {
|
||||
moreLikeThis = 'moreLikeThis',
|
||||
opening = 'opening',
|
||||
@ -87,6 +91,7 @@ export enum FeatureEnum {
|
||||
moderation = 'moderation',
|
||||
file = 'file',
|
||||
annotationReply = 'annotationReply',
|
||||
runtime = 'runtime',
|
||||
}
|
||||
|
||||
export type Features = {
|
||||
@ -99,6 +104,7 @@ export type Features = {
|
||||
[FeatureEnum.moderation]?: SensitiveWordAvoidance
|
||||
[FeatureEnum.file]?: FileUpload
|
||||
[FeatureEnum.annotationReply]?: AnnotationReplyConfig
|
||||
[FeatureEnum.runtime]?: Runtime
|
||||
}
|
||||
|
||||
export type OnFeaturesChange = (features?: Features) => void
|
||||
|
||||
@ -71,6 +71,7 @@ export const useNodesSyncDraft = () => {
|
||||
retriever_resource: features.citation,
|
||||
sensitive_word_avoidance: features.moderation,
|
||||
file_upload: features.file,
|
||||
runtime: features.runtime,
|
||||
},
|
||||
environment_variables: environmentVariables,
|
||||
conversation_variables: conversationVariables,
|
||||
|
||||
@ -85,6 +85,11 @@ export const useWorkflowInit = () => {
|
||||
const nodesData = isAdvancedChat ? nodesTemplate : []
|
||||
const edgesData = isAdvancedChat ? edgesTemplate : []
|
||||
|
||||
const runtimeStorageKey = `workflow:sandbox-runtime:${appDetail.id}`
|
||||
const enableSandboxRuntime = localStorage.getItem(runtimeStorageKey) === '1'
|
||||
if (enableSandboxRuntime)
|
||||
localStorage.removeItem(runtimeStorageKey)
|
||||
|
||||
syncWorkflowDraft({
|
||||
url: `/apps/${appDetail.id}/workflows/draft`,
|
||||
params: {
|
||||
@ -94,6 +99,7 @@ export const useWorkflowInit = () => {
|
||||
},
|
||||
features: {
|
||||
retriever_resource: { enabled: true },
|
||||
runtime: { enabled: enableSandboxRuntime },
|
||||
},
|
||||
environment_variables: [],
|
||||
conversation_variables: [],
|
||||
|
||||
@ -748,6 +748,7 @@ export const useWorkflowRun = () => {
|
||||
citation: publishedWorkflow.features.retriever_resource,
|
||||
moderation: publishedWorkflow.features.sensitive_word_avoidance,
|
||||
file: publishedWorkflow.features.file_upload,
|
||||
runtime: publishedWorkflow.features.runtime || { enabled: false },
|
||||
}
|
||||
|
||||
featuresStore?.setState({ features: mappedFeatures })
|
||||
|
||||
@ -192,6 +192,7 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
text2speech: features.text_to_speech || { enabled: false },
|
||||
citation: features.retriever_resource || { enabled: false },
|
||||
moderation: features.sensitive_word_avoidance || { enabled: false },
|
||||
runtime: features.runtime || { enabled: false },
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -121,6 +121,7 @@ const UpdateDSLModal = ({
|
||||
text2speech: features.text_to_speech || { enabled: false },
|
||||
citation: features.retriever_resource || { enabled: false },
|
||||
moderation: features.sensitive_word_avoidance || { enabled: false },
|
||||
runtime: features.runtime || { enabled: false },
|
||||
}
|
||||
|
||||
eventEmitter?.emit({
|
||||
|
||||
@ -159,6 +159,7 @@
|
||||
"newApp.completionShortDescription": "AI assistant for text generation tasks",
|
||||
"newApp.completionUserDescription": "Quickly build an AI assistant for text generation tasks with simple configuration.",
|
||||
"newApp.dropDSLToCreateApp": "Drop DSL file here to create app",
|
||||
"newApp.enableSandboxRuntime": "Enable sandbox runtime (supports Command node)",
|
||||
"newApp.forAdvanced": "FOR ADVANCED USERS",
|
||||
"newApp.forBeginners": "More basic app types",
|
||||
"newApp.foundResult": "{{count}} Result",
|
||||
@ -173,6 +174,9 @@
|
||||
"newApp.noTemplateFoundTip": "Try searching using different keywords.",
|
||||
"newApp.optional": "Optional",
|
||||
"newApp.previewDemo": "Preview demo",
|
||||
"newApp.runtimeLabel": "Runtime",
|
||||
"newApp.runtimeOptionClassical": "Classical",
|
||||
"newApp.runtimeOptionNew": "Recommend",
|
||||
"newApp.showTemplates": "I want to choose from a template",
|
||||
"newApp.startFromBlank": "Create from Blank",
|
||||
"newApp.startFromTemplate": "Create from Template",
|
||||
|
||||
@ -173,6 +173,9 @@
|
||||
"newApp.noTemplateFoundTip": "別のキーワードを使用して検索してみてください。",
|
||||
"newApp.optional": "任意",
|
||||
"newApp.previewDemo": "デモをプレビュー",
|
||||
"newApp.runtimeLabel": "Runtime",
|
||||
"newApp.runtimeOptionClassical": "Classical",
|
||||
"newApp.runtimeOptionNew": "推奨",
|
||||
"newApp.showTemplates": "テンプレートから選択したい",
|
||||
"newApp.startFromBlank": "最初から作成",
|
||||
"newApp.startFromTemplate": "テンプレートから作成",
|
||||
|
||||
@ -173,6 +173,9 @@
|
||||
"newApp.noTemplateFoundTip": "请尝试使用不同的关键字进行搜索。",
|
||||
"newApp.optional": "可选",
|
||||
"newApp.previewDemo": "预览 Demo",
|
||||
"newApp.runtimeLabel": "Runtime",
|
||||
"newApp.runtimeOptionClassical": "Classical",
|
||||
"newApp.runtimeOptionNew": "推荐",
|
||||
"newApp.showTemplates": "我想从范例模板中选择",
|
||||
"newApp.startFromBlank": "创建空白应用",
|
||||
"newApp.startFromTemplate": "从应用模板创建",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user