mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
feat(web): add Agent V2 frontend — app creation, node editor, sandbox settings
P0 — Agent App can be created and routed: - Add AppModeEnum.AGENT to types/app.ts - Add Agent card to create-app-modal (primary row, with RiRobot2Fill icon) - Route Agent apps to /workflow editor (same as workflow/advanced-chat) - Update layout-main.tsx mode guards P1 — Agent V2 workflow node: - Add BlockEnum.AgentV2 = 'agent-v2' to workflow types - Create agent-v2/node.tsx: displays model, strategy, tool count - Create agent-v2/panel.tsx: model selector, strategy picker, tool list, max iterations, memory config, vision toggle - Register in NodeComponentMap and PanelComponentMap P2 — Sandbox Provider settings: - Create sandbox-provider-page: list/configure/activate/delete providers (Docker, E2B, SSH, AWS CodeInterpreter) - Create service/sandbox.ts: API client for sandbox provider endpoints - Add "Sandbox Providers" to settings menu i18n: Add en-US and zh-Hans translations for agent V2 description. Made-with: Cursor
This commit is contained in:
parent
59b9221501
commit
f4e04fc872
@ -140,10 +140,10 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
router.replace(`/app/${appId}/overview`)
|
||||
return
|
||||
}
|
||||
if ((res.mode === AppModeEnum.WORKFLOW || res.mode === AppModeEnum.ADVANCED_CHAT) && (pathname).endsWith('configuration')) {
|
||||
if ((res.mode === AppModeEnum.WORKFLOW || res.mode === AppModeEnum.ADVANCED_CHAT || res.mode === AppModeEnum.AGENT) && (pathname).endsWith('configuration')) {
|
||||
router.replace(`/app/${appId}/workflow`)
|
||||
}
|
||||
else if ((res.mode !== AppModeEnum.WORKFLOW && res.mode !== AppModeEnum.ADVANCED_CHAT) && (pathname).endsWith('workflow')) {
|
||||
else if ((res.mode !== AppModeEnum.WORKFLOW && res.mode !== AppModeEnum.ADVANCED_CHAT && res.mode !== AppModeEnum.AGENT) && (pathname).endsWith('workflow')) {
|
||||
router.replace(`/app/${appId}/configuration`)
|
||||
}
|
||||
else {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { AppIconSelection } from '../../base/app-icon-picker'
|
||||
import { RiArrowRightLine, RiArrowRightSLine, RiExchange2Fill } from '@remixicon/react'
|
||||
import { RiArrowRightLine, RiArrowRightSLine, RiExchange2Fill, RiRobot2Fill } from '@remixicon/react'
|
||||
|
||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
@ -145,6 +145,19 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
setAppMode(AppModeEnum.ADVANCED_CHAT)
|
||||
}}
|
||||
/>
|
||||
<AppTypeCard
|
||||
active={appMode === AppModeEnum.AGENT}
|
||||
title={t('types.agent', { ns: 'app' })}
|
||||
description={t('newApp.agentV2ShortDescription', { ns: 'app' })}
|
||||
icon={(
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-violet-solid">
|
||||
<RiRobot2Fill className="h-4 w-4 text-components-avatar-shape-fill-stop-100" />
|
||||
</div>
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode(AppModeEnum.AGENT)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -8,6 +8,7 @@ export const ACCOUNT_SETTING_TAB = {
|
||||
API_BASED_EXTENSION: 'api-based-extension',
|
||||
CUSTOM: 'custom',
|
||||
LANGUAGE: 'language',
|
||||
SANDBOX_PROVIDER: 'sandbox-provider',
|
||||
} as const
|
||||
|
||||
export type AccountSettingTab = typeof ACCOUNT_SETTING_TAB[keyof typeof ACCOUNT_SETTING_TAB]
|
||||
|
||||
@ -21,6 +21,7 @@ import DataSourcePage from './data-source-page-new'
|
||||
import LanguagePage from './language-page'
|
||||
import MembersPage from './members-page'
|
||||
import ModelProviderPage from './model-provider-page'
|
||||
import SandboxProviderPage from './sandbox-provider-page'
|
||||
import { useResetModelProviderListExpanded } from './model-provider-page/atoms'
|
||||
|
||||
const iconClassName = `
|
||||
@ -94,6 +95,12 @@ export default function AccountSetting({
|
||||
icon: <span className={cn('i-ri-puzzle-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-puzzle-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.SANDBOX_PROVIDER,
|
||||
name: 'Sandbox Providers',
|
||||
icon: <span className={cn('i-ri-shield-keyhole-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-shield-keyhole-fill', iconClassName)} />,
|
||||
},
|
||||
)
|
||||
|
||||
if (enableReplaceWebAppLogo || enableBilling) {
|
||||
@ -233,6 +240,7 @@ export default function AccountSetting({
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.API_BASED_EXTENSION && <ApiBasedExtensionPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.CUSTOM && <CustomPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.LANGUAGE && <LanguagePage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.SANDBOX_PROVIDER && <SandboxProviderPage />}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCheckLine, RiDeleteBin7Line, RiSettings3Line } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import {
|
||||
activateSandboxProvider,
|
||||
deleteSandboxProviderConfig,
|
||||
listSandboxProviders,
|
||||
saveSandboxProviderConfig,
|
||||
} from '@/service/sandbox'
|
||||
import type { SandboxProvider } from '@/service/sandbox'
|
||||
|
||||
const providerConfigs: Record<string, { label: string, description: string, fields: Array<{ key: string, label: string, secret?: boolean }> }> = {
|
||||
docker: {
|
||||
label: 'Docker',
|
||||
description: 'Run agent tools in Docker containers on the local machine.',
|
||||
fields: [
|
||||
{ key: 'docker_sock', label: 'Docker Socket Path' },
|
||||
{ key: 'docker_image', label: 'Docker Image' },
|
||||
],
|
||||
},
|
||||
e2b: {
|
||||
label: 'E2B Cloud',
|
||||
description: 'Run agent tools in E2B cloud sandboxes.',
|
||||
fields: [
|
||||
{ key: 'api_key', label: 'E2B API Key', secret: true },
|
||||
{ key: 'e2b_default_template', label: 'Template' },
|
||||
],
|
||||
},
|
||||
ssh: {
|
||||
label: 'SSH Remote',
|
||||
description: 'Run agent tools on a remote server via SSH.',
|
||||
fields: [
|
||||
{ key: 'ssh_host', label: 'Host' },
|
||||
{ key: 'ssh_port', label: 'Port' },
|
||||
{ key: 'ssh_username', label: 'Username' },
|
||||
{ key: 'ssh_password', label: 'Password / Private Key', secret: true },
|
||||
],
|
||||
},
|
||||
aws_code_interpreter: {
|
||||
label: 'AWS CodeInterpreter',
|
||||
description: 'Run agent tools in Amazon Bedrock AgentCore Code Interpreter.',
|
||||
fields: [
|
||||
{ key: 'aws_access_key_id', label: 'Access Key ID', secret: true },
|
||||
{ key: 'aws_secret_access_key', label: 'Secret Access Key', secret: true },
|
||||
{ key: 'aws_region', label: 'Region' },
|
||||
{ key: 'code_interpreter_id', label: 'Code Interpreter ID' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function SandboxProviderPage() {
|
||||
const { t } = useTranslation()
|
||||
const [providers, setProviders] = useState<SandboxProvider[]>([])
|
||||
const [editingType, setEditingType] = useState<string | null>(null)
|
||||
const [editConfig, setEditConfig] = useState<Record<string, string>>({})
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const fetchProviders = useCallback(async () => {
|
||||
try {
|
||||
const data = await listSandboxProviders()
|
||||
setProviders(Array.isArray(data) ? data : [])
|
||||
}
|
||||
catch {
|
||||
setProviders([])
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => { fetchProviders() }, [fetchProviders])
|
||||
|
||||
const handleSave = async (providerType: string) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await saveSandboxProviderConfig(providerType, editConfig, true)
|
||||
toast({ type: 'success', message: 'Saved and activated' })
|
||||
setEditingType(null)
|
||||
fetchProviders()
|
||||
}
|
||||
catch (e: any) {
|
||||
toast({ type: 'error', message: e.message || 'Failed to save' })
|
||||
}
|
||||
finally { setLoading(false) }
|
||||
}
|
||||
|
||||
const handleDelete = async (providerType: string) => {
|
||||
try {
|
||||
await deleteSandboxProviderConfig(providerType)
|
||||
toast({ type: 'success', message: 'Deleted' })
|
||||
fetchProviders()
|
||||
}
|
||||
catch (e: any) {
|
||||
toast({ type: 'error', message: e.message || 'Failed to delete' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleActivate = async (providerType: string) => {
|
||||
try {
|
||||
await activateSandboxProvider(providerType)
|
||||
toast({ type: 'success', message: 'Activated' })
|
||||
fetchProviders()
|
||||
}
|
||||
catch (e: any) {
|
||||
toast({ type: 'error', message: e.message || 'Failed to activate' })
|
||||
}
|
||||
}
|
||||
|
||||
const activeProvider = providers.find(p => p.is_active)
|
||||
|
||||
return (
|
||||
<div className="pt-2 pb-7">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<RiSettings3Line className="h-5 w-5 text-text-secondary" />
|
||||
<h2 className="text-text-primary title-xl-semi-bold">Sandbox Providers</h2>
|
||||
</div>
|
||||
<p className="mb-6 text-text-tertiary system-sm-regular">
|
||||
Configure where agent tools execute in isolated environments.
|
||||
{activeProvider && (
|
||||
<span className="ml-2 text-text-accent">
|
||||
Active: <strong>{providerConfigs[activeProvider.provider_type]?.label || activeProvider.provider_type}</strong>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="grid gap-4">
|
||||
{Object.entries(providerConfigs).map(([type, cfg]) => {
|
||||
const existing = providers.find(p => p.provider_type === type)
|
||||
const isActive = existing?.is_active
|
||||
const isEditing = editingType === type
|
||||
|
||||
return (
|
||||
<div key={type} className={`rounded-xl border ${isActive ? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg' : 'border-divider-subtle'} p-4`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-text-primary system-md-semibold">{cfg.label}</h3>
|
||||
<p className="text-text-tertiary system-xs-regular mt-0.5">{cfg.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isActive && (
|
||||
<span className="flex items-center gap-1 rounded-full bg-util-colors-green-green-50 px-2 py-0.5 text-[11px] text-util-colors-green-green-600">
|
||||
<RiCheckLine className="h-3 w-3" /> Active
|
||||
</span>
|
||||
)}
|
||||
{existing && !isActive && (
|
||||
<Button size="small" onClick={() => handleActivate(type)}>Activate</Button>
|
||||
)}
|
||||
<Button size="small" variant="secondary" onClick={() => {
|
||||
setEditingType(isEditing ? null : type)
|
||||
setEditConfig(existing?.config || {})
|
||||
}}>
|
||||
{isEditing ? 'Cancel' : 'Configure'}
|
||||
</Button>
|
||||
{existing && (
|
||||
<Button size="small" variant="ghost" onClick={() => handleDelete(type)}>
|
||||
<RiDeleteBin7Line className="h-4 w-4 text-text-tertiary" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing && (
|
||||
<div className="mt-4 space-y-3 border-t border-divider-subtle pt-4">
|
||||
{cfg.fields.map(field => (
|
||||
<div key={field.key}>
|
||||
<label className="mb-1 block text-text-secondary system-xs-semibold">{field.label}</label>
|
||||
<Input
|
||||
type={field.secret ? 'password' : 'text'}
|
||||
value={editConfig[field.key] || ''}
|
||||
onChange={e => setEditConfig(prev => ({ ...prev, [field.key]: e.target.value }))}
|
||||
placeholder={field.label}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={loading}
|
||||
onClick={() => handleSave(type)}
|
||||
>
|
||||
{loading ? 'Saving...' : 'Save & Activate'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
web/app/components/workflow/nodes/agent-v2/node.tsx
Normal file
61
web/app/components/workflow/nodes/agent-v2/node.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import type { FC } from 'react'
|
||||
import type { NodeProps } from '../../types'
|
||||
import type { AgentV2NodeType } from './types'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiRobot2Line, RiToolsFill } from '@remixicon/react'
|
||||
import { Group, GroupLabel } from '../_base/components/group'
|
||||
import { SettingItem } from '../_base/components/setting-item'
|
||||
|
||||
const strategyLabels: Record<string, string> = {
|
||||
auto: 'Auto',
|
||||
'function-calling': 'Function Calling',
|
||||
'chain-of-thought': 'ReAct (Chain of Thought)',
|
||||
}
|
||||
|
||||
const AgentV2Node: FC<NodeProps<AgentV2NodeType>> = ({ id, data }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const modelName = data.model?.name || ''
|
||||
const modelProvider = data.model?.provider || ''
|
||||
const strategy = data.agent_strategy || 'auto'
|
||||
const enabledTools = useMemo(() => (data.tools || []).filter(t => t.enabled), [data.tools])
|
||||
const maxIter = data.max_iterations || 10
|
||||
|
||||
return (
|
||||
<div className="mb-1 space-y-1 px-3">
|
||||
<SettingItem label={t('workflow.nodes.llm.model')}>
|
||||
<span className="system-xs-medium text-text-secondary truncate">
|
||||
{modelName || 'Not configured'}
|
||||
</span>
|
||||
</SettingItem>
|
||||
<SettingItem label="Strategy">
|
||||
<span className="system-xs-medium text-text-secondary">
|
||||
{strategyLabels[strategy] || strategy}
|
||||
</span>
|
||||
</SettingItem>
|
||||
{enabledTools.length > 0 && (
|
||||
<Group label={<GroupLabel className="mt-1"><RiToolsFill className="mr-1 inline h-3 w-3" />Tools ({enabledTools.length})</GroupLabel>}>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{enabledTools.slice(0, 6).map((tool, i) => (
|
||||
<span key={i} className="inline-flex items-center rounded bg-components-badge-bg-gray px-1.5 py-0.5 text-[11px] text-text-tertiary">
|
||||
{tool.tool_name}
|
||||
</span>
|
||||
))}
|
||||
{enabledTools.length > 6 && (
|
||||
<span className="text-[11px] text-text-quaternary">+{enabledTools.length - 6}</span>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
)}
|
||||
{maxIter !== 10 && (
|
||||
<SettingItem label="Max Iterations">
|
||||
<span className="system-xs-medium text-text-secondary">{maxIter}</span>
|
||||
</SettingItem>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AgentV2Node.displayName = 'AgentV2Node'
|
||||
export default memo(AgentV2Node)
|
||||
145
web/app/components/workflow/nodes/agent-v2/panel.tsx
Normal file
145
web/app/components/workflow/nodes/agent-v2/panel.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import type { FC } from 'react'
|
||||
import type { AgentV2NodeType } from './types'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine, RiDeleteBin7Line } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Select from '@/app/components/base/select'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import ConfigVision from '../_base/components/config-vision'
|
||||
import MemoryConfig from '../_base/components/memory-config'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import ConfigPrompt from '../llm/components/config-prompt'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { useNodeDataUpdate } from '../../hooks/use-node-data-update'
|
||||
|
||||
const strategyOptions = [
|
||||
{ value: 'auto', name: 'Auto (based on model capability)' },
|
||||
{ value: 'function-calling', name: 'Function Calling' },
|
||||
{ value: 'chain-of-thought', name: 'ReAct (Chain of Thought)' },
|
||||
]
|
||||
|
||||
const Panel: FC<NodePanelProps<AgentV2NodeType>> = ({ id, data }) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
||||
|
||||
const updateData = useCallback((patch: Partial<AgentV2NodeType>) => {
|
||||
handleNodeDataUpdate({ id, data: patch as any })
|
||||
}, [id, handleNodeDataUpdate])
|
||||
|
||||
const inputs = data as AgentV2NodeType
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-4 pb-4 pt-2">
|
||||
{/* Model Selection */}
|
||||
<Field title={t('workflow.nodes.llm.model')}>
|
||||
<ModelParameterModal
|
||||
popupProps={{ disabled: false }}
|
||||
isInWorkflow
|
||||
isAdvancedMode
|
||||
mode={inputs.model?.mode || 'chat'}
|
||||
provider={inputs.model?.provider || ''}
|
||||
completionParams={inputs.model?.completion_params || {}}
|
||||
modelId={inputs.model?.name || ''}
|
||||
setModel={(model) => {
|
||||
updateData({
|
||||
model: {
|
||||
...inputs.model,
|
||||
provider: model.provider,
|
||||
name: model.modelId,
|
||||
mode: model.mode || 'chat',
|
||||
completion_params: model.completionParams || {},
|
||||
},
|
||||
})
|
||||
}}
|
||||
onCompletionParamsChange={(params) => {
|
||||
updateData({
|
||||
model: { ...inputs.model, completion_params: params },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Split />
|
||||
|
||||
{/* Agent Strategy */}
|
||||
<Field title="Agent Strategy">
|
||||
<Select
|
||||
items={strategyOptions}
|
||||
defaultValue={inputs.agent_strategy || 'auto'}
|
||||
onSelect={(item) => updateData({ agent_strategy: item.value as any })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{/* Max Iterations */}
|
||||
<Field title="Max Iterations">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={99}
|
||||
className="w-full rounded-lg border border-components-input-border-active px-3 py-1.5 text-[13px]"
|
||||
value={inputs.max_iterations || 10}
|
||||
onChange={(e) => updateData({ max_iterations: parseInt(e.target.value) || 10 })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Split />
|
||||
|
||||
{/* Tools */}
|
||||
<Field title={`Tools (${(inputs.tools || []).filter(t => t.enabled).length})`}>
|
||||
<div className="space-y-2">
|
||||
{(inputs.tools || []).map((tool, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between rounded-lg border border-divider-subtle px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
size="sm"
|
||||
defaultValue={tool.enabled}
|
||||
onChange={(v) => {
|
||||
const tools = [...(inputs.tools || [])]
|
||||
tools[idx] = { ...tools[idx], enabled: v }
|
||||
updateData({ tools })
|
||||
}}
|
||||
/>
|
||||
<span className="text-[13px] text-text-secondary">{tool.tool_name}</span>
|
||||
</div>
|
||||
<span className="text-[11px] text-text-quaternary">{tool.provider_name?.split('/').pop()}</span>
|
||||
</div>
|
||||
))}
|
||||
{(inputs.tools || []).length === 0 && (
|
||||
<div className="py-3 text-center text-[13px] text-text-quaternary">
|
||||
No tools configured. Add tools from the workflow toolbar.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Split />
|
||||
|
||||
{/* Memory */}
|
||||
<Field title="Memory">
|
||||
<MemoryConfig
|
||||
readonly={false}
|
||||
config={inputs.memory || { window: { enabled: true, size: 50 } }}
|
||||
onChange={(memory) => updateData({ memory })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Split />
|
||||
|
||||
{/* Vision */}
|
||||
<Field title="Vision">
|
||||
<ConfigVision
|
||||
payload={inputs.vision}
|
||||
onChange={(vision) => updateData({ vision })}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Panel.displayName = 'AgentV2Panel'
|
||||
export default memo(Panel)
|
||||
32
web/app/components/workflow/nodes/agent-v2/types.ts
Normal file
32
web/app/components/workflow/nodes/agent-v2/types.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, VisionSetting } from '@/app/components/workflow/types'
|
||||
|
||||
export type ToolMetadata = {
|
||||
enabled: boolean
|
||||
type: string
|
||||
provider_name: string
|
||||
tool_name: string
|
||||
plugin_unique_identifier?: string
|
||||
credential_id?: string
|
||||
parameters: Record<string, any>
|
||||
settings: Record<string, any>
|
||||
extra: Record<string, any>
|
||||
}
|
||||
|
||||
export type AgentV2NodeType = CommonNodeType & {
|
||||
model: ModelConfig
|
||||
prompt_template: PromptItem[] | PromptItem
|
||||
tools: ToolMetadata[]
|
||||
max_iterations: number
|
||||
agent_strategy: 'auto' | 'function-calling' | 'chain-of-thought'
|
||||
memory?: Memory
|
||||
context: {
|
||||
enabled: boolean
|
||||
variable_selector?: ValueSelector
|
||||
}
|
||||
vision: {
|
||||
enabled: boolean
|
||||
configs?: VisionSetting
|
||||
}
|
||||
structured_output_enabled?: boolean
|
||||
structured_output?: Record<string, any>
|
||||
}
|
||||
@ -2,6 +2,8 @@ import type { ComponentType } from 'react'
|
||||
import { BlockEnum } from '../types'
|
||||
import AgentNode from './agent/node'
|
||||
import AgentPanel from './agent/panel'
|
||||
import AgentV2Node from './agent-v2/node'
|
||||
import AgentV2Panel from './agent-v2/panel'
|
||||
import AnswerNode from './answer/node'
|
||||
import AnswerPanel from './answer/panel'
|
||||
import AssignerNode from './assigner/node'
|
||||
@ -72,6 +74,7 @@ export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.DocExtractor]: DocExtractorNode,
|
||||
[BlockEnum.ListFilter]: ListFilterNode,
|
||||
[BlockEnum.Agent]: AgentNode,
|
||||
[BlockEnum.AgentV2]: AgentV2Node,
|
||||
[BlockEnum.DataSource]: DataSourceNode,
|
||||
[BlockEnum.KnowledgeBase]: KnowledgeBaseNode,
|
||||
[BlockEnum.HumanInput]: HumanInputNode,
|
||||
@ -101,6 +104,7 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.DocExtractor]: DocExtractorPanel,
|
||||
[BlockEnum.ListFilter]: ListFilterPanel,
|
||||
[BlockEnum.Agent]: AgentPanel,
|
||||
[BlockEnum.AgentV2]: AgentV2Panel,
|
||||
[BlockEnum.DataSource]: DataSourcePanel,
|
||||
[BlockEnum.KnowledgeBase]: KnowledgeBasePanel,
|
||||
[BlockEnum.HumanInput]: HumanInputPanel,
|
||||
|
||||
@ -46,6 +46,7 @@ export enum BlockEnum {
|
||||
IterationStart = 'iteration-start',
|
||||
Assigner = 'assigner', // is now named as VariableAssigner
|
||||
Agent = 'agent',
|
||||
AgentV2 = 'agent-v2',
|
||||
Loop = 'loop',
|
||||
LoopStart = 'loop-start',
|
||||
LoopEnd = 'loop-end',
|
||||
|
||||
@ -135,6 +135,7 @@
|
||||
"newApp.advancedUserDescription": "Workflow with additional memory features and a chatbot interface.",
|
||||
"newApp.agentAssistant": "New Agent Assistant",
|
||||
"newApp.agentShortDescription": "Intelligent agent with reasoning and autonomous tool use",
|
||||
"newApp.agentV2ShortDescription": "Next-gen agent with tools, sandbox, and workflow integration",
|
||||
"newApp.agentUserDescription": "An intelligent agent capable of iterative reasoning and autonomous tool use to achieve task goals.",
|
||||
"newApp.appCreateDSLErrorPart1": "A significant difference in DSL versions has been detected. Forcing the import may cause the application to malfunction.",
|
||||
"newApp.appCreateDSLErrorPart2": "Do you want to continue?",
|
||||
|
||||
@ -135,6 +135,7 @@
|
||||
"newApp.advancedUserDescription": "基于工作流编排,适用于定义等复杂流程的多轮对话场景,具有记忆功能。",
|
||||
"newApp.agentAssistant": "新的智能助手",
|
||||
"newApp.agentShortDescription": "具备推理与自主工具调用的智能助手",
|
||||
"newApp.agentV2ShortDescription": "新一代 Agent,支持工具调用、沙箱执行和 Workflow 集成",
|
||||
"newApp.agentUserDescription": "能够迭代式的规划推理、自主工具调用,直至完成任务目标的智能助手。",
|
||||
"newApp.appCreateDSLErrorPart1": "检测到 DSL 版本差异较大,强制导入应用可能无法正常运行。",
|
||||
"newApp.appCreateDSLErrorPart2": "是否继续?",
|
||||
|
||||
40
web/service/sandbox.ts
Normal file
40
web/service/sandbox.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { del, get, post } from './base'
|
||||
|
||||
export type SandboxProvider = {
|
||||
provider_type: string
|
||||
is_active: boolean
|
||||
config?: Record<string, any>
|
||||
config_schema?: Array<{
|
||||
name: string
|
||||
type: string
|
||||
}>
|
||||
}
|
||||
|
||||
export const listSandboxProviders = (): Promise<SandboxProvider[]> => {
|
||||
return get<SandboxProvider[]>('workspaces/current/sandbox-providers')
|
||||
}
|
||||
|
||||
export const saveSandboxProviderConfig = (
|
||||
providerType: string,
|
||||
config: Record<string, any>,
|
||||
activate = false,
|
||||
): Promise<{ result: string }> => {
|
||||
return post<{ result: string }>(`workspaces/current/sandbox-provider/${providerType}/config`, {
|
||||
body: { config, activate },
|
||||
})
|
||||
}
|
||||
|
||||
export const activateSandboxProvider = (
|
||||
providerType: string,
|
||||
type = 'user',
|
||||
): Promise<{ result: string }> => {
|
||||
return post<{ result: string }>(`workspaces/current/sandbox-provider/${providerType}/activate`, {
|
||||
body: { type },
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteSandboxProviderConfig = (
|
||||
providerType: string,
|
||||
): Promise<{ result: string }> => {
|
||||
return del<{ result: string }>(`workspaces/current/sandbox-provider/${providerType}/config`)
|
||||
}
|
||||
@ -44,8 +44,9 @@ export enum AppModeEnum {
|
||||
CHAT = 'chat',
|
||||
ADVANCED_CHAT = 'advanced-chat',
|
||||
AGENT_CHAT = 'agent-chat',
|
||||
AGENT = 'agent',
|
||||
}
|
||||
export const AppModes = [AppModeEnum.COMPLETION, AppModeEnum.WORKFLOW, AppModeEnum.CHAT, AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT] as const
|
||||
export const AppModes = [AppModeEnum.COMPLETION, AppModeEnum.WORKFLOW, AppModeEnum.CHAT, AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT, AppModeEnum.AGENT] as const
|
||||
|
||||
/**
|
||||
* Variable type
|
||||
|
||||
@ -8,7 +8,7 @@ export const getRedirectionPath = (
|
||||
return `/app/${app.id}/overview`
|
||||
}
|
||||
else {
|
||||
if (app.mode === AppModeEnum.WORKFLOW || app.mode === AppModeEnum.ADVANCED_CHAT)
|
||||
if (app.mode === AppModeEnum.WORKFLOW || app.mode === AppModeEnum.ADVANCED_CHAT || app.mode === AppModeEnum.AGENT)
|
||||
return `/app/${app.id}/workflow`
|
||||
else
|
||||
return `/app/${app.id}/configuration`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user