diff --git a/web/app/components/workflow/nodes/agent-v2/components/agent-task-field.tsx b/web/app/components/workflow/nodes/agent-v2/components/agent-task-field.tsx index 7ae37e81dea..01a208be2d1 100644 --- a/web/app/components/workflow/nodes/agent-v2/components/agent-task-field.tsx +++ b/web/app/components/workflow/nodes/agent-v2/components/agent-task-field.tsx @@ -103,7 +103,10 @@ export function AgentTaskField({ {t(`${i18nPrefix}.task.label`, { ns: 'workflow' })} - + {t(`${i18nPrefix}.task.tooltip`, { ns: 'workflow' })} diff --git a/web/features/agent-v2/agent-composer/__tests__/store.spec.ts b/web/features/agent-v2/agent-composer/__tests__/store.spec.ts index 5e0867f5761..17acef94a85 100644 --- a/web/features/agent-v2/agent-composer/__tests__/store.spec.ts +++ b/web/features/agent-v2/agent-composer/__tests__/store.spec.ts @@ -1,11 +1,12 @@ import type { AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen' +import type { AgentSoulConfigWithFiles } from '../conversions' import { describe, expect, it } from 'vitest' import { agentSoulConfigToFormState, formStateToAgentSoulConfig } from '../conversions' import { defaultAgentSoulConfigFormState } from '../form-state' describe('agent composer store conversions', () => { it('should hydrate editable form state from an AgentSoulConfig and preserve it in the config snapshot', () => { - const baseConfig: AgentSoulConfig = { + const baseConfig: AgentSoulConfigWithFiles = { app_features: { opening_statement: 'Hello', suggested_questions: ['What changed?'], @@ -52,6 +53,26 @@ describe('agent composer store conversions', () => { }, ], }, + files: { + skills: [ + { + id: 'tender-analyzer', + name: 'Tender Analyzer', + description: 'Parses RFPs.', + path: 'tender-analyzer', + skill_md_key: 'tender-analyzer/SKILL.md', + full_archive_key: 'tender-analyzer/.DIFY-SKILL-FULL.zip', + }, + ], + files: [ + { + id: 'files/sample.pdf', + file_id: 'drive-file-1', + name: 'sample.pdf', + drive_key: 'files/sample.pdf', + }, + ], + }, model: { model: 'gpt-4.1', model_provider: 'openai', @@ -129,6 +150,20 @@ describe('agent composer store conversions', () => { value: 'credential-1', }), ], + skills: [ + expect.objectContaining({ + name: 'Tender Analyzer', + skillMdKey: 'tender-analyzer/SKILL.md', + archiveKey: 'tender-analyzer/.DIFY-SKILL-FULL.zip', + }), + ], + files: [ + expect.objectContaining({ + name: 'sample.pdf', + fileId: 'drive-file-1', + driveKey: 'files/sample.pdf', + }), + ], }) expect(formState.tools).toEqual(expect.arrayContaining([ expect.objectContaining({ @@ -152,6 +187,26 @@ describe('agent composer store conversions', () => { }) expect(publishConfig).not.toHaveProperty('skills_files') + expect(publishConfig.files).toEqual({ + skills: [ + { + id: 'tender-analyzer', + name: 'Tender Analyzer', + description: 'Parses RFPs.', + path: 'tender-analyzer', + skill_md_key: 'tender-analyzer/SKILL.md', + full_archive_key: 'tender-analyzer/.DIFY-SKILL-FULL.zip', + }, + ], + files: [ + { + id: 'files/sample.pdf', + file_id: 'drive-file-1', + name: 'sample.pdf', + drive_key: 'files/sample.pdf', + }, + ], + }) expect(publishConfig.tools?.dify_tools).toEqual([ expect.objectContaining({ provider: 'DuckDuckGo', diff --git a/web/features/agent-v2/agent-composer/conversions.ts b/web/features/agent-v2/agent-composer/conversions.ts index 949b8f17e91..6aa2339730b 100644 --- a/web/features/agent-v2/agent-composer/conversions.ts +++ b/web/features/agent-v2/agent-composer/conversions.ts @@ -7,8 +7,10 @@ import type { } from '@dify/contracts/api/console/agent/types.gen' import type { AgentCliTool, + AgentFileNode, AgentKnowledgeRetrievalItem, AgentProviderTool, + AgentSkill, AgentSoulConfigFormState, AgentTool, EnvVariable, @@ -27,6 +29,32 @@ import { checkKey } from '@/utils/var' import { defaultAgentSoulConfigFormState } from './form-state' import { getKnowledgeRetrievalSetName } from './knowledge-validation' +type AgentSoulFileRefConfig = { + id?: string | null + file_id?: string | null + name?: string | null + type?: string | null + drive_key?: string | null +} + +type AgentSoulSkillRefConfig = { + id?: string | null + name?: string | null + description?: string | null + path?: string | null + skill_md_key?: string | null + full_archive_key?: string | null +} + +type AgentSoulFilesConfig = { + skills?: AgentSoulSkillRefConfig[] + files?: AgentSoulFileRefConfig[] +} + +export type AgentSoulConfigWithFiles = AgentSoulConfig & { + files?: AgentSoulFilesConfig +} + type AgentSoulDifyToolConfig = NonNullable['dify_tools']>[number] type AgentSoulCliToolConfig = NonNullable['cli_tools']>[number] type AgentSoulToolRuntimeParameterValue = NonNullable[string] @@ -396,6 +424,79 @@ const toEnvConfig = (variables: EnvVariable[]): AgentSoulConfig['env'] => ({ })), }) +const toSkillConfigs = (skills: AgentSkill[]): AgentSoulSkillRefConfig[] => skills.map(skill => ({ + id: skill.path ?? skill.id, + name: skill.name, + description: skill.description, + path: skill.path, + skill_md_key: skill.skillMdKey, + full_archive_key: skill.archiveKey, +})) + +const toFileConfigs = (files: AgentFileNode[]): AgentSoulFileRefConfig[] => files.flatMap((file) => { + if (file.children?.length) + return toFileConfigs(file.children) + + return [{ + id: file.id, + file_id: file.fileId, + name: file.name, + drive_key: file.driveKey, + }] +}) + +const toFilesConfig = (formState: AgentSoulConfigFormState): AgentSoulFilesConfig => ({ + skills: toSkillConfigs(formState.skills), + files: toFileConfigs(formState.files), +}) + +const getAgentFileName = (file: AgentSoulFileRefConfig) => { + if (file.name) + return file.name + + const driveKey = file.drive_key ?? file.id ?? file.file_id ?? '' + return driveKey.split('/').pop() || driveKey +} + +const toSkillFormState = (config?: AgentSoulConfig): AgentSkill[] => { + const filesConfig = (config as AgentSoulConfigWithFiles | undefined)?.files + + return (filesConfig?.skills ?? []).flatMap((skill) => { + const id = skill.skill_md_key ?? skill.path ?? skill.id + const name = skill.name ?? skill.path ?? id + if (!id || !name) + return [] + + return [{ + id, + name, + description: skill.description ?? undefined, + path: skill.path ?? undefined, + skillMdKey: skill.skill_md_key ?? undefined, + archiveKey: skill.full_archive_key ?? undefined, + }] + }) +} + +const toFileFormState = (config?: AgentSoulConfig): AgentFileNode[] => { + const filesConfig = (config as AgentSoulConfigWithFiles | undefined)?.files + + return (filesConfig?.files ?? []).flatMap((file) => { + const id = file.drive_key ?? file.file_id ?? file.id + const name = getAgentFileName(file) + if (!id || !name) + return [] + + return [{ + id, + name, + icon: 'file' as const, + fileId: file.file_id ?? undefined, + driveKey: file.drive_key ?? undefined, + }] + }) +} + const toDraftModel = (config?: AgentSoulConfig): DefaultModel | undefined => { const modelProvider = config?.model?.model_provider const model = config?.model?.model @@ -433,7 +534,7 @@ export const formStateToAgentSoulConfig = ({ baseConfig?: AgentSoulConfig formState: AgentSoulConfigFormState currentModel?: DefaultModel -}): AgentSoulConfig => { +}): AgentSoulConfigWithFiles => { return { ...baseConfig, prompt: { @@ -456,6 +557,7 @@ export const formStateToAgentSoulConfig = ({ app_features: formState.appFeatures ?? baseConfig?.app_features, knowledge: toKnowledgeConfig(formState.knowledgeRetrievals), env: toEnvConfig(formState.envVariables), + files: toFilesConfig(formState), } } @@ -470,6 +572,8 @@ export const agentSoulConfigToFormState = ( prompt: config?.prompt?.system_prompt ?? '', model: toDraftModel(config), appFeatures: config?.app_features, + skills: toSkillFormState(config), + files: toFileFormState(config), tools: [ ...providerToolState.tools, ...toCliToolFormState(config), diff --git a/web/features/agent-v2/agent-composer/form-state.ts b/web/features/agent-v2/agent-composer/form-state.ts index 2818351524b..42598a0d214 100644 --- a/web/features/agent-v2/agent-composer/form-state.ts +++ b/web/features/agent-v2/agent-composer/form-state.ts @@ -36,6 +36,7 @@ export type AgentFileNode = { id: string name: string icon: FileTreeIconType + fileId?: string driveKey?: string children?: AgentFileNode[] } @@ -99,6 +100,8 @@ export type AgentSoulConfigFormState = { prompt: string model?: DefaultModel appFeatures?: AgentSoulAppFeaturesConfig + skills: AgentSkill[] + files: AgentFileNode[] tools: AgentTool[] knowledgeRetrievals: AgentKnowledgeRetrievalItem[] envVariables: EnvVariable[] @@ -107,6 +110,8 @@ export type AgentSoulConfigFormState = { export const defaultAgentSoulConfigFormState: AgentSoulConfigFormState = { prompt: '', + skills: [], + files: [], tools: [], knowledgeRetrievals: [], envVariables: [], diff --git a/web/features/agent-v2/agent-composer/reference-labels.ts b/web/features/agent-v2/agent-composer/reference-labels.ts index 25eaa9c7063..0616129fd7c 100644 --- a/web/features/agent-v2/agent-composer/reference-labels.ts +++ b/web/features/agent-v2/agent-composer/reference-labels.ts @@ -1,4 +1,4 @@ -import type { AgentKnowledgeRetrievalItem, AgentTool } from './form-state' +import type { AgentFileNode, AgentKnowledgeRetrievalItem, AgentSkill, AgentTool } from './form-state' const getKnowledgeRetrievalName = (item: AgentKnowledgeRetrievalItem) => item.name ?? item.nameKey ?? item.id @@ -73,3 +73,37 @@ export const syncCliToolReferenceLabels = ({ currentItems: toReferenceLabelItems(currentTools.filter(tool => tool.kind === 'cli'), tool => tool.name), nextItems: toReferenceLabelItems(nextTools.filter(tool => tool.kind === 'cli'), tool => tool.name), }) + +export const syncSkillReferenceLabels = ({ + prompt, + currentSkills, + nextSkills, +}: { + prompt: string + currentSkills: AgentSkill[] + nextSkills: AgentSkill[] +}) => syncReferenceLabels({ + prompt, + kind: 'skill', + currentItems: toReferenceLabelItems(currentSkills, skill => skill.name), + nextItems: toReferenceLabelItems(nextSkills, skill => skill.name), +}) + +const flattenFileNodes = (files: AgentFileNode[]): AgentFileNode[] => files.flatMap(file => ( + file.children?.length ? flattenFileNodes(file.children) : [file] +)) + +export const syncFileReferenceLabels = ({ + prompt, + currentFiles, + nextFiles, +}: { + prompt: string + currentFiles: AgentFileNode[] + nextFiles: AgentFileNode[] +}) => syncReferenceLabels({ + prompt, + kind: 'file', + currentItems: toReferenceLabelItems(flattenFileNodes(currentFiles), file => file.name), + nextItems: toReferenceLabelItems(flattenFileNodes(nextFiles), file => file.name), +}) diff --git a/web/features/agent-v2/agent-composer/store-modules/files.ts b/web/features/agent-v2/agent-composer/store-modules/files.ts new file mode 100644 index 00000000000..e686116a8ff --- /dev/null +++ b/web/features/agent-v2/agent-composer/store-modules/files.ts @@ -0,0 +1,24 @@ +import type { AgentFileNode } from '../form-state' +import type { DraftFieldUpdate } from './utils' +import { atom } from 'jotai' +import { syncFileReferenceLabels } from '../reference-labels' +import { agentComposerDraftAtom } from '../store' +import { resolveDraftFieldUpdate } from './utils' + +export const agentComposerFilesAtom = atom], void>( + get => get(agentComposerDraftAtom).files, + (get, set, filesUpdate: DraftFieldUpdate) => { + const draft = get(agentComposerDraftAtom) + const files = resolveDraftFieldUpdate(draft.files, filesUpdate) + + set(agentComposerDraftAtom, { + ...draft, + prompt: syncFileReferenceLabels({ + prompt: draft.prompt, + currentFiles: draft.files, + nextFiles: files, + }), + files, + }) + }, +) diff --git a/web/features/agent-v2/agent-composer/store-modules/skills.ts b/web/features/agent-v2/agent-composer/store-modules/skills.ts new file mode 100644 index 00000000000..2fa8bd47740 --- /dev/null +++ b/web/features/agent-v2/agent-composer/store-modules/skills.ts @@ -0,0 +1,24 @@ +import type { AgentSkill } from '../form-state' +import type { DraftFieldUpdate } from './utils' +import { atom } from 'jotai' +import { syncSkillReferenceLabels } from '../reference-labels' +import { agentComposerDraftAtom } from '../store' +import { resolveDraftFieldUpdate } from './utils' + +export const agentComposerSkillsAtom = atom], void>( + get => get(agentComposerDraftAtom).skills, + (get, set, skillsUpdate: DraftFieldUpdate) => { + const draft = get(agentComposerDraftAtom) + const skills = resolveDraftFieldUpdate(draft.skills, skillsUpdate) + + set(agentComposerDraftAtom, { + ...draft, + prompt: syncSkillReferenceLabels({ + prompt: draft.prompt, + currentSkills: draft.skills, + nextSkills: skills, + }), + skills, + }) + }, +) diff --git a/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx b/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx index 1400f6b7ca5..26c9c8da641 100644 --- a/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx @@ -4,6 +4,8 @@ import { act, renderHook } from '@testing-library/react' import { createStore, Provider as JotaiProvider } from 'jotai' import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store' +import { agentComposerFilesAtom } from '@/features/agent-v2/agent-composer/store-modules/files' +import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt' import { useAgentConfigureSync } from '../use-agent-configure-sync' const composerPutMutationFn = vi.hoisted(() => vi.fn(async (variables: { @@ -145,6 +147,89 @@ describe('useAgentConfigureSync', () => { expect(result.current.draftSavedAt).toBe(1710000105000) }) + it('should include Agent Soul files when autosaving file changes', async () => { + const { store } = renderUseAgentConfigureSync() + + act(() => { + store.set(agentComposerDraftAtom, { + ...defaultAgentSoulConfigFormState, + files: [ + { + id: 'files/uploaded.md', + name: 'uploaded.md', + icon: 'markdown', + fileId: 'drive-file-1', + driveKey: 'files/uploaded.md', + }, + ], + }) + }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(5000) + }) + + expect(composerPutMutationFn).toHaveBeenCalledWith(expect.objectContaining({ + body: expect.objectContaining({ + agent_soul: expect.objectContaining({ + files: { + skills: [], + files: [ + { + id: 'files/uploaded.md', + file_id: 'drive-file-1', + name: 'uploaded.md', + drive_key: 'files/uploaded.md', + }, + ], + }, + }), + }), + })) + }) + + it('should preserve uploaded files when prompt is updated immediately after upload', async () => { + const { store } = renderUseAgentConfigureSync() + + act(() => { + store.set(agentComposerFilesAtom, [ + { + id: 'files/uploaded.md', + name: 'uploaded.md', + icon: 'markdown', + fileId: 'drive-file-1', + driveKey: 'files/uploaded.md', + }, + ]) + store.set(agentComposerPromptAtom, 'Use [§file:files%2Fuploaded.md:uploaded.md§]') + }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(5000) + }) + + expect(composerPutMutationFn).toHaveBeenCalledWith(expect.objectContaining({ + body: expect.objectContaining({ + agent_soul: expect.objectContaining({ + prompt: expect.objectContaining({ + system_prompt: 'Use [§file:files%2Fuploaded.md:uploaded.md§]', + }), + files: { + skills: [], + files: [ + { + id: 'files/uploaded.md', + file_id: 'drive-file-1', + name: 'uploaded.md', + drive_key: 'files/uploaded.md', + }, + ], + }, + }), + }), + })) + }) + it('should skip autosave when knowledge retrieval validation fails', async () => { const { result, store } = renderUseAgentConfigureSync() diff --git a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx index 7de47e23451..3822c991fc0 100644 --- a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx @@ -78,6 +78,7 @@ vi.mock('foxact/use-clipboard', () => ({ vi.mock('@/context/i18n', () => ({ useGetLanguage: () => 'en_US', + useDocLink: () => 'https://docs.example.com', })) vi.mock('@/service/use-tools', () => ({ diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/drive-context.ts b/web/features/agent-v2/agent-detail/configure/components/orchestrate/drive-context.ts index d94a0632471..b9443c6b59d 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/drive-context.ts +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/drive-context.ts @@ -1,11 +1,9 @@ 'use client' -import type { AgentDriveItemResponse } from '@dify/contracts/api/console/agent/types.gen' -import type { AgentFileNode, AgentSkill } from '@/features/agent-v2/agent-composer/form-state' -import { useQuery } from '@tanstack/react-query' +import { useAtomValue } from 'jotai' import { createContext, use, useMemo } from 'react' -import { consoleQuery } from '@/service/client' -import { getDriveFileIconType } from './files/file-icon' +import { agentComposerFilesAtom } from '@/features/agent-v2/agent-composer/store-modules/files' +import { agentComposerSkillsAtom } from '@/features/agent-v2/agent-composer/store-modules/skills' export type AgentDriveApiContext = { agentId: string @@ -21,41 +19,6 @@ const AgentDriveApiContext = createContext(null) export const AgentDriveApiContextProvider = AgentDriveApiContext.Provider -const getAgentDriveFileName = (key: string) => { - const normalizedKey = key.endsWith('/') ? key.slice(0, -1) : key - return normalizedKey.split('/').pop() || normalizedKey -} - -const toAgentSkill = (item: { - archive_key?: string | null - description: string - name: string - path: string - skill_md_key: string -}): AgentSkill => ({ - id: item.skill_md_key, - name: item.name, - description: item.description || undefined, - path: item.path, - skillMdKey: item.skill_md_key, - archiveKey: item.archive_key ?? undefined, -}) - -const toAgentFileNodeFromDriveItem = (item: { - file_kind: string - key: string - mime_type?: string | null -}): AgentFileNode => ({ - id: item.key, - name: getAgentDriveFileName(item.key), - icon: getDriveFileIconType({ - fileKind: item.file_kind, - fileName: getAgentDriveFileName(item.key), - mimeType: item.mime_type, - }), - driveKey: item.key, -}) - export const useAgentDriveApiContext = () => { const context = use(AgentDriveApiContext) if (!context) @@ -66,35 +29,10 @@ export const useAgentDriveApiContext = () => { export const useAgentDriveSkills = () => { const apiContext = useAgentDriveApiContext() - const agentSkillsQuery = useQuery({ - ...consoleQuery.agent.byAgentId.drive.skills.get.queryOptions({ - input: { - params: { - agent_id: apiContext.agentId, - }, - }, - }), - enabled: !apiContext.workflow, - }) - const workflowSkillsQuery = useQuery({ - ...consoleQuery.apps.byAppId.agent.drive.skills.get.queryOptions({ - input: { - params: { - app_id: apiContext.workflow?.appId ?? '', - }, - query: { - node_id: apiContext.workflow?.nodeId, - }, - }, - }), - enabled: !!apiContext.workflow, - }) - const query = apiContext.workflow ? workflowSkillsQuery : agentSkillsQuery - const skills = useMemo(() => (query.data?.items ?? []).map(toAgentSkill), [query.data?.items]) + const skills = useAtomValue(agentComposerSkillsAtom) return { apiContext, - query, skills, } } @@ -105,42 +43,14 @@ export const useAgentDriveFiles = ({ prefix?: string } = {}) => { const apiContext = useAgentDriveApiContext() - const agentFilesQuery = useQuery({ - ...consoleQuery.agent.byAgentId.drive.files.get.queryOptions({ - input: { - params: { - agent_id: apiContext.agentId, - }, - query: { - prefix, - }, - }, - }), - enabled: !apiContext.workflow, - }) - const workflowFilesQuery = useQuery({ - ...consoleQuery.apps.byAppId.agent.drive.files.get.queryOptions({ - input: { - params: { - app_id: apiContext.workflow?.appId ?? '', - }, - query: { - node_id: apiContext.workflow?.nodeId, - prefix, - }, - }, - }), - enabled: !!apiContext.workflow, - }) - const query = apiContext.workflow ? workflowFilesQuery : agentFilesQuery + const draftFiles = useAtomValue(agentComposerFilesAtom) const files = useMemo( - () => (query.data?.items ?? []).map((item: AgentDriveItemResponse) => toAgentFileNodeFromDriveItem(item)), - [query.data?.items], + () => draftFiles.filter(file => !prefix || file.driveKey?.startsWith(prefix)), + [draftFiles, prefix], ) return { apiContext, - query, files, } } diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx index b8728fe9572..5468720a2cf 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx @@ -1,9 +1,12 @@ import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { useAtomValue } from 'jotai' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { formStateToAgentSoulConfig } from '@/features/agent-v2/agent-composer/conversions' import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider' +import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store' import { AgentDriveApiContextProvider } from '../../drive-context' import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import { AgentFiles } from '../index' @@ -101,10 +104,38 @@ vi.mock('@/service/client', () => ({ const agentFilesDraft = { ...defaultAgentSoulConfigFormState, + files: [ + { + id: 'files/agent-roster-skill-detail-dialog-preview-image.png', + name: 'agent-roster-skill-detail-dialog-preview-image.png', + icon: 'image', + driveKey: 'files/agent-roster-skill-detail-dialog-preview-image.png', + }, + { + id: 'files/brief.md', + name: 'brief.md', + icon: 'markdown', + driveKey: 'files/brief.md', + }, + ], } satisfies AgentSoulConfigFormState const agentSkillFilesDraft = { ...defaultAgentSoulConfigFormState, + files: [ + { + id: 'files/run.py', + name: 'run.py', + icon: 'code', + driveKey: 'files/run.py', + }, + { + id: 'files/SKILL.md', + name: 'SKILL.md', + icon: 'markdown', + driveKey: 'files/SKILL.md', + }, + ], } satisfies AgentSoulConfigFormState function renderAgentFiles(initialDraft: AgentSoulConfigFormState = agentFilesDraft) { @@ -169,6 +200,17 @@ function renderWorkflowAgentFiles(initialDraft: AgentSoulConfigFormState = agent ) } +function ConfigSnapshotProbe() { + const draft = useAtomValue(agentComposerDraftAtom) + const configSnapshot = formStateToAgentSoulConfig({ formState: draft }) + + return ( +
+      {JSON.stringify(configSnapshot)}
+    
+ ) +} + describe('AgentFiles', () => { beforeEach(() => { vi.clearAllMocks() @@ -264,35 +306,18 @@ describe('AgentFiles', () => { }) }) - it('should list Agent App drive files under the files prefix', () => { + it('should list Agent Soul files under the files prefix', () => { renderAgentFiles() - expect(mocks.agentDriveFilesQueryOptions).toHaveBeenCalledWith({ - input: { - params: { - agent_id: 'agent-1', - }, - query: { - prefix: 'files/', - }, - }, - }) + expect(screen.getByRole('button', { name: 'brief.md' })).toBeInTheDocument() + expect(mocks.agentDriveFilesQueryOptions).not.toHaveBeenCalled() }) - it('should list workflow-node drive files under the files prefix', () => { + it('should list workflow-node Agent Soul files under the files prefix', () => { renderWorkflowAgentFiles() - expect(mocks.workflowAgentDriveFilesQueryOptions).toHaveBeenCalledWith({ - input: { - params: { - app_id: 'app-1', - }, - query: { - node_id: 'node-1', - prefix: 'files/', - }, - }, - }) + expect(screen.getByRole('button', { name: 'brief.md' })).toBeInTheDocument() + expect(mocks.workflowAgentDriveFilesQueryOptions).not.toHaveBeenCalled() }) it('should keep the file preview trigger focus ring inside the row bounds', () => { @@ -547,10 +572,25 @@ describe('AgentFiles', () => { }), mutationKey: ['commit-agent-file'], }) - renderAgentFiles({ - ...defaultAgentSoulConfigFormState, + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, }) + render( + + + + + + + + , + ) + fireEvent.click(screen.getByRole('button', { name: 'agentV2.agentDetail.configure.files.add' })) const input = document.querySelector('input[type="file"]') expect(input).toBeInstanceOf(HTMLInputElement) @@ -576,6 +616,17 @@ describe('AgentFiles', () => { ) }) expect(await screen.findByRole('button', { name: 'uploaded.md' })).toBeInTheDocument() + await waitFor(() => { + const serializedConfig = JSON.parse(screen.getByTestId('config-snapshot-probe').textContent ?? '{}') + expect(serializedConfig.files.files).toEqual([ + { + id: 'drive-file-1', + file_id: 'drive-file-1', + name: 'uploaded.md', + drive_key: 'files/uploaded.md', + }, + ]) + }) }) it('should commit an uploaded file through workflow-node drive endpoints and refresh the list', async () => { @@ -750,13 +801,10 @@ describe('AgentFiles', () => { expect(screen.getByRole('button', { name: 'brief.md' })).toBeInTheDocument() }) - it('should render the empty state when the drive file query returns no items', () => { - mocks.agentDriveFilesQueryOptions.mockImplementation(({ input }) => ({ - queryKey: ['agent-drive-files', input], - initialData: { items: [] }, - queryFn: async () => ({ items: [] }), - })) - renderAgentFiles() + it('should render the empty state when Agent Soul has no files', () => { + renderAgentFiles({ + ...defaultAgentSoulConfigFormState, + }) expect(screen.getByText('agentV2.agentDetail.configure.files.empty.title')).toBeInTheDocument() }) diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx index 31418acb557..d8b5663c3bb 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx @@ -12,15 +12,17 @@ import { FileTreeGuide, } from '@langgenius/dify-ui/file-tree' import { useMutation, useQuery } from '@tanstack/react-query' +import { useAtomValue, useSetAtom } from 'jotai' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { agentComposerFilesAtom } from '@/features/agent-v2/agent-composer/store-modules/files' import { consoleQuery } from '@/service/client' import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context' import { ConfigureSectionAddButton } from '../common/add-button' import { ConfigureSectionEmpty } from '../common/empty' import { ConfigureSection } from '../common/section' import { AgentConfigureTipContent } from '../common/tip-content' -import { FILES_DRIVE_PREFIX, useAgentDriveApiContext, useAgentDriveFiles } from '../drive-context' +import { FILES_DRIVE_PREFIX, useAgentDriveApiContext } from '../drive-context' import { useAgentOrchestrateReadOnly } from '../read-only-context' import { AgentSkillDetailDialog } from '../skills/detail-dialog' import { AgentFileTree } from './tree' @@ -39,6 +41,16 @@ const findAgentFileNode = (files: AgentFileNode[], fileId: string): AgentFileNod } } +const removeAgentFileNode = (files: AgentFileNode[], fileId: string): AgentFileNode[] => files.flatMap((file) => { + if (file.id === fileId) + return [] + + if (file.children) + return [{ ...file, children: removeAgentFileNode(file.children, fileId) }] + + return [file] +}) + function AgentFileItem({ children, depth, @@ -194,7 +206,9 @@ export function AgentFiles() { const [isUploadOpen, setIsUploadOpen] = useState(false) const promptAddCallbackRef = useRef(undefined) const apiContext = useAgentDriveApiContext() - const { query: driveFilesQuery, files } = useAgentDriveFiles({ prefix: FILES_DRIVE_PREFIX }) + const draftFiles = useAtomValue(agentComposerFilesAtom) + const setFiles = useSetAtom(agentComposerFilesAtom) + const files = draftFiles.filter(file => file.driveKey?.startsWith(FILES_DRIVE_PREFIX)) const { mutate: deleteAgentFile } = useMutation(consoleQuery.agent.byAgentId.files.delete.mutationOptions()) const { mutate: deleteWorkflowAgentFile } = useMutation(consoleQuery.apps.byAppId.agent.files.delete.mutationOptions()) const removeFile = useCallback((fileId: string) => { @@ -205,7 +219,7 @@ export function AgentFiles() { return const onSuccess = () => { - void driveFilesQuery.refetch() + setFiles(files => removeAgentFileNode(files, fileId)) } if (apiContext.workflow) { deleteWorkflowAgentFile({ @@ -228,17 +242,20 @@ export function AgentFiles() { key: driveKey, }, }, { onSuccess }) - }, [apiContext, deleteAgentFile, deleteWorkflowAgentFile, driveFilesQuery, files]) + }, [apiContext, deleteAgentFile, deleteWorkflowAgentFile, files, setFiles]) const handleOpenUpload = useCallback((options?: AgentOrchestrateAddActionOptions) => { promptAddCallbackRef.current = options?.onAdded setIsUploadOpen(true) }, []) useRegisterAgentOrchestrateAddAction('files', handleOpenUpload) const handleUploaded = useCallback((file: AgentFileNode) => { - void driveFilesQuery.refetch() + setFiles(files => [ + ...removeAgentFileNode(files, file.id), + file, + ]) promptAddCallbackRef.current?.(file) promptAddCallbackRef.current = undefined - }, [driveFilesQuery]) + }, [setFiles]) const handleUploadOpenChange = useCallback((open: boolean) => { if (!open) promptAddCallbackRef.current = undefined diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/upload-dialog.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/upload-dialog.tsx index 3abb5fdabba..6cb5c0c0019 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/upload-dialog.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/upload-dialog.tsx @@ -30,6 +30,7 @@ function toAgentFileNode(committedFile: AgentDriveFileCommit['file']): AgentFile id: committedFile.file_id, name: committedFile.name, icon: getFileIconType(committedFile.name, committedFile.mime_type), + fileId: committedFile.file_id, driveKey: committedFile.drive_key, } } diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx index da4ca31898b..e02d4ed03df 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx @@ -128,6 +128,22 @@ vi.mock('@/service/client', () => ({ const agentSkillsDraft = { ...defaultAgentSoulConfigFormState, + skills: [ + { + archiveKey: 'tender-analyzer/.DIFY-SKILL-FULL.zip', + description: 'Extracts tender requirements and scoring criteria.', + id: 'tender-analyzer/SKILL.md', + name: 'Tender Analyzer', + path: 'tender-analyzer', + skillMdKey: 'tender-analyzer/SKILL.md', + }, + { + id: 'meeting-brief/SKILL.md', + name: 'Meeting Brief', + path: 'meeting-brief', + skillMdKey: 'meeting-brief/SKILL.md', + }, + ], } satisfies typeof defaultAgentSoulConfigFormState function renderAgentSkills() { @@ -493,19 +509,10 @@ describe('AgentSkills', () => { expect(within(dialog).getByText('agentV2.agentDetail.configure.skills.detail.fileCount:{"count":2}')).toBeInTheDocument() }) - it('should use workflow node drive routes for skill list and preview in inline workflow mode', async () => { + it('should use workflow node drive routes for skill preview in inline workflow mode', async () => { renderWorkflowAgentSkills() - expect(mocks.driveSkillsQueryOptions).toHaveBeenCalledWith({ - input: { - params: { - app_id: 'app-1', - }, - query: { - node_id: 'node-1', - }, - }, - }) + expect(mocks.driveSkillsQueryOptions).not.toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: 'Tender Analyzer', diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/index.tsx index 5715699ac44..8461138cd30 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/index.tsx @@ -3,15 +3,17 @@ import type { AgentOrchestrateAddActionOptions } from '../add-actions-context' import type { AgentSkill } from '@/features/agent-v2/agent-composer/form-state' import { useMutation } from '@tanstack/react-query' +import { useAtomValue, useSetAtom } from 'jotai' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { agentComposerSkillsAtom } from '@/features/agent-v2/agent-composer/store-modules/skills' import { consoleQuery } from '@/service/client' import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context' import { ConfigureSectionAddButton } from '../common/add-button' import { ConfigureSectionEmpty } from '../common/empty' import { ConfigureSection } from '../common/section' import { AgentConfigureTipContent } from '../common/tip-content' -import { useAgentDriveApiContext, useAgentDriveSkills } from '../drive-context' +import { useAgentDriveApiContext } from '../drive-context' import { AgentSkillItem } from './item' import { AgentSkillUploadDialog } from './upload-dialog' @@ -22,7 +24,8 @@ export function AgentSkills() { const [isUploadOpen, setIsUploadOpen] = useState(false) const promptAddCallbackRef = useRef(undefined) const apiContext = useAgentDriveApiContext() - const { query: skillsQuery, skills } = useAgentDriveSkills() + const skills = useAtomValue(agentComposerSkillsAtom) + const setSkills = useSetAtom(agentComposerSkillsAtom) const { mutate: deleteAgentSkill } = useMutation(consoleQuery.agent.byAgentId.skills.bySlug.delete.mutationOptions()) const { mutate: deleteAppSkill } = useMutation(consoleQuery.apps.byAppId.agent.skills.bySlug.delete.mutationOptions()) @@ -33,10 +36,13 @@ export function AgentSkills() { useRegisterAgentOrchestrateAddAction('skills', handleOpenUpload) const handleUploaded = useCallback((skill: AgentSkill) => { - void skillsQuery.refetch() + setSkills(skills => [ + ...skills.filter(item => item.id !== skill.id), + skill, + ]) promptAddCallbackRef.current?.(skill) promptAddCallbackRef.current = undefined - }, [skillsQuery]) + }, [setSkills]) const handleUploadOpenChange = useCallback((open: boolean) => { if (!open) @@ -51,7 +57,7 @@ export function AgentSkills() { return const onSuccess = () => { - void skillsQuery.refetch() + setSkills(skills => skills.filter(item => item.id !== skillId)) } if (apiContext.workflow) { deleteAppSkill({ @@ -72,7 +78,7 @@ export function AgentSkills() { slug: skillSlug, }, }, { onSuccess }) - }, [apiContext, deleteAgentSkill, deleteAppSkill, skills, skillsQuery]) + }, [apiContext, deleteAgentSkill, deleteAppSkill, setSkills, skills]) return ( <> diff --git a/web/i18n/ar-TN/workflow.json b/web/i18n/ar-TN/workflow.json index 6e97214e2d0..1f4c5ac9c31 100644 --- a/web/i18n/ar-TN/workflow.json +++ b/web/i18n/ar-TN/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "مهمة الوكيل", "nodes.agent.task.mention": "إشارة", "nodes.agent.task.placeholder": "صف ما يجب على هذا الوكيل القيام به...", - "nodes.agent.task.tooltip": "حدّد تعليمات المهمة التي يجب على هذا الوكيل اتباعها في سير العمل هذا.", + "nodes.agent.task.tooltip": "موجّه إضافي لمساعدة الوكيل على معالجة هذه العقدة بالتحديد. استخدم / للإشارة صراحةً إلى المتغيرات.\nإذا تم تكوينه بشكل صحيح، فقد تتمكن من الوثوق بالوكيل ليكتشف الأمور بنفسه.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} غير مخول", "nodes.agent.toolNotInstallTooltip": "{{tool}} غير مثبت", "nodes.agent.toolbox": "صندوق الأدوات", diff --git a/web/i18n/de-DE/workflow.json b/web/i18n/de-DE/workflow.json index 8da99b5e473..757b0f47627 100644 --- a/web/i18n/de-DE/workflow.json +++ b/web/i18n/de-DE/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agentenaufgabe", "nodes.agent.task.mention": "Erwähnen", "nodes.agent.task.placeholder": "Beschreiben Sie, was dieser Agent tun soll...", - "nodes.agent.task.tooltip": "Definieren Sie die Aufgabenanweisungen, denen dieser Agent in diesem Workflow folgen soll.", + "nodes.agent.task.tooltip": "Zusätzlicher Prompt, der dem Agenten hilft, genau diesen Knoten zu bearbeiten. Verwende /, um explizit auf Variablen zu verweisen.\nWenn dies richtig konfiguriert ist, kannst du deinem Agenten möglicherweise vertrauen, die Dinge selbst herauszufinden.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Nicht autorisiert", "nodes.agent.toolNotInstallTooltip": "{{tool}} ist nicht installiert", "nodes.agent.toolbox": "Werkzeugkasten", diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index ea7845f63a2..e511d29b854 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agent task", "nodes.agent.task.mention": "Mention", "nodes.agent.task.placeholder": "Describe what this agent should do...", - "nodes.agent.task.tooltip": "Define the task instructions this agent should follow in this workflow.", + "nodes.agent.task.tooltip": "Additional prompt to help agent handle this very node. Use / to make explicit reference to variables.\nIf configured properly, you could be able to trust your agent to figure things out itself.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Not Authorized", "nodes.agent.toolNotInstallTooltip": "{{tool}} is not installed", "nodes.agent.toolbox": "toolbox", diff --git a/web/i18n/es-ES/workflow.json b/web/i18n/es-ES/workflow.json index 9b76c288493..e15ca03bca7 100644 --- a/web/i18n/es-ES/workflow.json +++ b/web/i18n/es-ES/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Tarea del agente", "nodes.agent.task.mention": "Mencionar", "nodes.agent.task.placeholder": "Describe lo que este agente debe hacer...", - "nodes.agent.task.tooltip": "Define las instrucciones de la tarea que este agente debe seguir en este flujo de trabajo.", + "nodes.agent.task.tooltip": "Prompt adicional para ayudar al agente a gestionar este nodo en concreto. Usa / para hacer referencia explícita a variables.\nSi está configurado correctamente, podrías confiar en que tu agente resuelva las cosas por sí mismo.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} No autorizado", "nodes.agent.toolNotInstallTooltip": "{{tool}} no está instalada", "nodes.agent.toolbox": "caja de herramientas", diff --git a/web/i18n/fa-IR/workflow.json b/web/i18n/fa-IR/workflow.json index 2676e5a08eb..88a68150568 100644 --- a/web/i18n/fa-IR/workflow.json +++ b/web/i18n/fa-IR/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "وظیفه عامل", "nodes.agent.task.mention": "اشاره", "nodes.agent.task.placeholder": "شرح دهید این عامل باید چه کاری انجام دهد...", - "nodes.agent.task.tooltip": "دستورالعمل‌های وظیفه‌ای را که این عامل باید در این گردش کار دنبال کند تعریف کنید.", + "nodes.agent.task.tooltip": "پرامپت اضافی برای کمک به عامل در مدیریت همین گره. از / برای ارجاع صریح به متغیرها استفاده کنید.\nاگر به‌درستی پیکربندی شود، می‌توانید به عامل خود اعتماد کنید تا خودش مسائل را حل کند.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} مجوز ندارد", "nodes.agent.toolNotInstallTooltip": "{{tool}} نصب نشده است", "nodes.agent.toolbox": "جعبه ابزار", diff --git a/web/i18n/fr-FR/workflow.json b/web/i18n/fr-FR/workflow.json index 19ea55eca76..44727994911 100644 --- a/web/i18n/fr-FR/workflow.json +++ b/web/i18n/fr-FR/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Tâche de l’agent", "nodes.agent.task.mention": "Mentionner", "nodes.agent.task.placeholder": "Décrivez ce que cet agent doit faire...", - "nodes.agent.task.tooltip": "Définissez les instructions de tâche que cet agent doit suivre dans ce workflow.", + "nodes.agent.task.tooltip": "Prompt supplémentaire pour aider l’agent à traiter ce nœud précis. Utilisez / pour faire explicitement référence aux variables.\nSi la configuration est correcte, vous pourriez faire confiance à votre agent pour comprendre les choses par lui-même.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Non autorisé", "nodes.agent.toolNotInstallTooltip": "{{tool}} n’est pas installé", "nodes.agent.toolbox": "boîte à outils", diff --git a/web/i18n/hi-IN/workflow.json b/web/i18n/hi-IN/workflow.json index f009a7348ad..2151812482f 100644 --- a/web/i18n/hi-IN/workflow.json +++ b/web/i18n/hi-IN/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "एजेंट कार्य", "nodes.agent.task.mention": "उल्लेख करें", "nodes.agent.task.placeholder": "वर्णन करें कि यह एजेंट क्या करे...", - "nodes.agent.task.tooltip": "इस वर्कफ़्लो में इस एजेंट द्वारा पालन किए जाने वाले कार्य निर्देश परिभाषित करें।", + "nodes.agent.task.tooltip": "इस node को संभालने में agent की मदद के लिए अतिरिक्त prompt। Variables का स्पष्ट reference देने के लिए / का उपयोग करें।\nयदि सही तरह से configured हो, तो आप अपने agent पर भरोसा कर सकते हैं कि वह चीज़ें खुद समझ लेगा।", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} अधिकृत नहीं है", "nodes.agent.toolNotInstallTooltip": "{{tool}} स्थापित नहीं है", "nodes.agent.toolbox": "टूलबॉक्स", diff --git a/web/i18n/id-ID/workflow.json b/web/i18n/id-ID/workflow.json index 39f1df2c5c7..0c701f16354 100644 --- a/web/i18n/id-ID/workflow.json +++ b/web/i18n/id-ID/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Tugas agen", "nodes.agent.task.mention": "Sebut", "nodes.agent.task.placeholder": "Jelaskan apa yang harus dilakukan agen ini...", - "nodes.agent.task.tooltip": "Tentukan instruksi tugas yang harus diikuti agen ini dalam alur kerja ini.", + "nodes.agent.task.tooltip": "Prompt tambahan untuk membantu agen menangani node ini. Gunakan / untuk merujuk variabel secara eksplisit.\nJika dikonfigurasi dengan benar, Anda dapat mempercayai agen untuk mencari tahu sendiri.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Tidak Berwenang", "nodes.agent.toolNotInstallTooltip": "{{tool}} tidak terpasang", "nodes.agent.toolbox": "Toolbox", diff --git a/web/i18n/it-IT/workflow.json b/web/i18n/it-IT/workflow.json index 799dd96c967..5ea34524ed3 100644 --- a/web/i18n/it-IT/workflow.json +++ b/web/i18n/it-IT/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Attività dell’agente", "nodes.agent.task.mention": "Menziona", "nodes.agent.task.placeholder": "Descrivi cosa deve fare questo agente...", - "nodes.agent.task.tooltip": "Definisci le istruzioni dell’attività che questo agente deve seguire in questo workflow.", + "nodes.agent.task.tooltip": "Prompt aggiuntivo per aiutare l’agente a gestire proprio questo nodo. Usa / per fare riferimento esplicito alle variabili.\nSe configurato correttamente, potresti poterti fidare dell’agente perché capisca da solo cosa fare.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Non autorizzato", "nodes.agent.toolNotInstallTooltip": "{{tool}} non è installato", "nodes.agent.toolbox": "cassetta degli attrezzi", diff --git a/web/i18n/ja-JP/workflow.json b/web/i18n/ja-JP/workflow.json index e292a8f772c..afca062d3a0 100644 --- a/web/i18n/ja-JP/workflow.json +++ b/web/i18n/ja-JP/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agent タスク", "nodes.agent.task.mention": "メンション", "nodes.agent.task.placeholder": "この Agent が行うべきことを記述してください...", - "nodes.agent.task.tooltip": "この Agent がこのワークフロー内で従うべきタスク指示を定義します。", + "nodes.agent.task.tooltip": "このノードを処理するためにエージェントを補助する追加プロンプトです。変数を明示的に参照するには / を使用します。\n適切に設定されていれば、エージェントが自分で判断して進められると信頼できるようになります。", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 認可されていません", "nodes.agent.toolNotInstallTooltip": "{{tool}}はインストールされていません", "nodes.agent.toolbox": "ツールボックス", diff --git a/web/i18n/ko-KR/workflow.json b/web/i18n/ko-KR/workflow.json index 06125ae5836..c2554902bea 100644 --- a/web/i18n/ko-KR/workflow.json +++ b/web/i18n/ko-KR/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agent 작업", "nodes.agent.task.mention": "멘션", "nodes.agent.task.placeholder": "이 Agent가 수행해야 할 작업을 설명하세요...", - "nodes.agent.task.tooltip": "이 워크플로에서 이 Agent가 따라야 할 작업 지시 사항을 정의합니다.", + "nodes.agent.task.tooltip": "에이전트가 이 노드를 처리하도록 돕는 추가 프롬프트입니다. 변수를 명시적으로 참조하려면 / 를 사용하세요.\n올바르게 구성하면 에이전트가 스스로 판단하도록 신뢰할 수 있습니다.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 권한이 부여되지 않음", "nodes.agent.toolNotInstallTooltip": "{{tool}}이 설치되지 않았습니다.", "nodes.agent.toolbox": "도구", diff --git a/web/i18n/nl-NL/workflow.json b/web/i18n/nl-NL/workflow.json index 5e747ccf943..4893ba732a1 100644 --- a/web/i18n/nl-NL/workflow.json +++ b/web/i18n/nl-NL/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agenttaak", "nodes.agent.task.mention": "Vermelden", "nodes.agent.task.placeholder": "Beschrijf wat deze agent moet doen...", - "nodes.agent.task.tooltip": "Bepaal de taakinstructies die deze agent in deze workflow moet volgen.", + "nodes.agent.task.tooltip": "Aanvullende prompt om de agent te helpen deze specifieke node af te handelen. Gebruik / om expliciet naar variabelen te verwijzen.\nAls dit goed is geconfigureerd, kun je erop vertrouwen dat je agent zelf uitzoekt wat nodig is.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Not Authorized", "nodes.agent.toolNotInstallTooltip": "{{tool}} is not installed", "nodes.agent.toolbox": "toolbox", diff --git a/web/i18n/pl-PL/workflow.json b/web/i18n/pl-PL/workflow.json index d24c9034268..0d1d978f0d1 100644 --- a/web/i18n/pl-PL/workflow.json +++ b/web/i18n/pl-PL/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Zadanie agenta", "nodes.agent.task.mention": "Wzmianka", "nodes.agent.task.placeholder": "Opisz, co ma robić ten agent…", - "nodes.agent.task.tooltip": "Określ instrukcje zadania, które ten agent powinien wykonywać w tym workflow.", + "nodes.agent.task.tooltip": "Dodatkowy prompt, który pomaga agentowi obsłużyć dokładnie ten węzeł. Użyj /, aby jawnie odwołać się do zmiennych.\nJeśli zostanie poprawnie skonfigurowany, możesz zaufać agentowi, że sam rozwiąże problem.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Nieautoryzowany", "nodes.agent.toolNotInstallTooltip": "{{tool}} nie jest zainstalowany", "nodes.agent.toolbox": "skrzynka z narzędziami", diff --git a/web/i18n/pt-BR/workflow.json b/web/i18n/pt-BR/workflow.json index eb35b5fd28e..1c60015ce0a 100644 --- a/web/i18n/pt-BR/workflow.json +++ b/web/i18n/pt-BR/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Tarefa do agente", "nodes.agent.task.mention": "Mencionar", "nodes.agent.task.placeholder": "Descreva o que este agente deve fazer...", - "nodes.agent.task.tooltip": "Defina as instruções da tarefa que este agente deve seguir neste workflow.", + "nodes.agent.task.tooltip": "Prompt adicional para ajudar o agente a lidar com este nó específico. Use / para fazer referência explícita a variáveis.\nSe configurado corretamente, você poderá confiar que o agente descobrirá as coisas por conta própria.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Não autorizado", "nodes.agent.toolNotInstallTooltip": "{{tool}} não está instalado", "nodes.agent.toolbox": "caixa de ferramentas", diff --git a/web/i18n/ro-RO/workflow.json b/web/i18n/ro-RO/workflow.json index 506d99765af..b03a7296d53 100644 --- a/web/i18n/ro-RO/workflow.json +++ b/web/i18n/ro-RO/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Sarcina agentului", "nodes.agent.task.mention": "Menționează", "nodes.agent.task.placeholder": "Descrieți ce ar trebui să facă acest agent...", - "nodes.agent.task.tooltip": "Definiți instrucțiunile sarcinii pe care acest agent trebuie să le urmeze în acest workflow.", + "nodes.agent.task.tooltip": "Prompt suplimentar pentru a ajuta agentul să gestioneze exact acest nod. Folosește / pentru a face referire explicită la variabile.\nDacă este configurat corect, ai putea avea încredere că agentul își va da seama singur ce are de făcut.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Neautorizat", "nodes.agent.toolNotInstallTooltip": "{{tool}} nu este instalat", "nodes.agent.toolbox": "cutie de scule", diff --git a/web/i18n/ru-RU/workflow.json b/web/i18n/ru-RU/workflow.json index 3551d1f21ff..e2ed173998e 100644 --- a/web/i18n/ru-RU/workflow.json +++ b/web/i18n/ru-RU/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Задача агента", "nodes.agent.task.mention": "Упомянуть", "nodes.agent.task.placeholder": "Опишите, что должен делать этот агент…", - "nodes.agent.task.tooltip": "Определите инструкции задачи, которые этот агент должен выполнять в этом рабочем процессе.", + "nodes.agent.task.tooltip": "Дополнительный промпт, который помогает агенту обработать именно этот узел. Используйте /, чтобы явно ссылаться на переменные.\nПри правильной настройке вы сможете доверить агенту самостоятельно разбираться с задачей.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Не авторизован", "nodes.agent.toolNotInstallTooltip": "{{tool}} не установлен", "nodes.agent.toolbox": "ящик для инструментов", diff --git a/web/i18n/sl-SI/workflow.json b/web/i18n/sl-SI/workflow.json index 27b73ada29b..824462716f5 100644 --- a/web/i18n/sl-SI/workflow.json +++ b/web/i18n/sl-SI/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Opravilo agenta", "nodes.agent.task.mention": "Omemba", "nodes.agent.task.placeholder": "Opišite, kaj naj ta agent počne…", - "nodes.agent.task.tooltip": "Določite navodila opravila, ki naj jih ta agent upošteva v tem poteku dela.", + "nodes.agent.task.tooltip": "Dodatni poziv, ki agentu pomaga obravnavati prav to vozlišče. Uporabite / za izrecno sklicevanje na spremenljivke.\nČe je pravilno konfiguriran, lahko agentu zaupate, da bo stvari ugotovil sam.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Ni pooblaščen", "nodes.agent.toolNotInstallTooltip": "{{tool}} ni nameščen", "nodes.agent.toolbox": "delovna orodja", diff --git a/web/i18n/th-TH/workflow.json b/web/i18n/th-TH/workflow.json index 5fc865471dc..52359f3d004 100644 --- a/web/i18n/th-TH/workflow.json +++ b/web/i18n/th-TH/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "งาน Agent", "nodes.agent.task.mention": "กล่าวถึง", "nodes.agent.task.placeholder": "อธิบายสิ่งที่ Agent นี้ควรทำ...", - "nodes.agent.task.tooltip": "กำหนดคำสั่งงานที่ Agent นี้ควรปฏิบัติตามในเวิร์กโฟลว์นี้", + "nodes.agent.task.tooltip": "พรอมป์เพิ่มเติมเพื่อช่วยให้เอเจนต์จัดการโหนดนี้โดยเฉพาะ ใช้ / เพื่ออ้างอิงตัวแปรอย่างชัดเจน\nหากตั้งค่าอย่างเหมาะสม คุณอาจไว้วางใจให้เอเจนต์หาทางจัดการสิ่งต่าง ๆ ได้เอง", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} ไม่ได้รับอนุญาต", "nodes.agent.toolNotInstallTooltip": "{{tool}} ไม่ได้ติดตั้ง", "nodes.agent.toolbox": "เครื่อง มือ", diff --git a/web/i18n/tr-TR/workflow.json b/web/i18n/tr-TR/workflow.json index c1f21cb59bf..433d2a1a973 100644 --- a/web/i18n/tr-TR/workflow.json +++ b/web/i18n/tr-TR/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Ajan görevi", "nodes.agent.task.mention": "Bahset", "nodes.agent.task.placeholder": "Bu ajanın ne yapması gerektiğini açıklayın...", - "nodes.agent.task.tooltip": "Bu ajanın bu iş akışında izlemesi gereken görev talimatlarını tanımlayın.", + "nodes.agent.task.tooltip": "Ajanın tam olarak bu düğümü ele almasına yardımcı olacak ek prompt. Değişkenlere açıkça referans vermek için / kullanın.\nDoğru yapılandırılırsa, ajanın işleri kendi başına çözmesine güvenebilirsiniz.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Yetkili Değil", "nodes.agent.toolNotInstallTooltip": "{{tool}} yüklü değil", "nodes.agent.toolbox": "Araç", diff --git a/web/i18n/uk-UA/workflow.json b/web/i18n/uk-UA/workflow.json index c2d245d32e9..495e233eab6 100644 --- a/web/i18n/uk-UA/workflow.json +++ b/web/i18n/uk-UA/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Задача агента", "nodes.agent.task.mention": "Згадати", "nodes.agent.task.placeholder": "Опишіть, що має робити цей агент…", - "nodes.agent.task.tooltip": "Визначте інструкції задачі, які цей агент має виконувати в цьому робочому процесі.", + "nodes.agent.task.tooltip": "Додатковий промпт, який допомагає агенту обробити саме цей вузол. Використовуйте /, щоб явно посилатися на змінні.\nЯкщо налаштовано правильно, ви зможете довірити агенту самостійно розібратися із завданням.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Не авторизовано", "nodes.agent.toolNotInstallTooltip": "{{tool}} не встановлено", "nodes.agent.toolbox": "ящик для інструментів", diff --git a/web/i18n/vi-VN/workflow.json b/web/i18n/vi-VN/workflow.json index d87cb85560b..5ad996f9c1a 100644 --- a/web/i18n/vi-VN/workflow.json +++ b/web/i18n/vi-VN/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Tác vụ tác nhân", "nodes.agent.task.mention": "Đề cập", "nodes.agent.task.placeholder": "Mô tả tác nhân này nên làm gì...", - "nodes.agent.task.tooltip": "Xác định các hướng dẫn tác vụ mà tác nhân này nên tuân theo trong quy trình làm việc này.", + "nodes.agent.task.tooltip": "Prompt bổ sung để giúp agent xử lý đúng node này. Dùng / để tham chiếu rõ ràng đến các biến.\nNếu được cấu hình đúng, bạn có thể tin agent tự tìm ra cách xử lý.", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Không được ủy quyền", "nodes.agent.toolNotInstallTooltip": "{{tool}} không được cài đặt", "nodes.agent.toolbox": "hộp công cụ", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index cbec4fcc526..f7e3b5cac90 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agent 任务", "nodes.agent.task.mention": "提及", "nodes.agent.task.placeholder": "描述这个 Agent 要完成的任务...", - "nodes.agent.task.tooltip": "定义该 Agent 在当前工作流中需要遵循的任务指令。", + "nodes.agent.task.tooltip": "帮助智能体处理当前节点的附加提示词。使用 / 显式引用变量。\n如果配置得当,你可以信任智能体自行判断如何完成任务。", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 未授权", "nodes.agent.toolNotInstallTooltip": "{{tool}} 未安装", "nodes.agent.toolbox": "工具箱", diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/zh-Hant/workflow.json index 8f1c29fd432..9e99fbc3e26 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/zh-Hant/workflow.json @@ -464,7 +464,7 @@ "nodes.agent.task.label": "Agent 任務", "nodes.agent.task.mention": "提及", "nodes.agent.task.placeholder": "描述這個 Agent 要完成的任務...", - "nodes.agent.task.tooltip": "定義該 Agent 在目前工作流程中需要遵循的任務指令。", + "nodes.agent.task.tooltip": "幫助智慧體處理目前節點的附加提示詞。使用 / 明確引用變數。\n如果設定得當,你可以信任智慧體自行判斷如何完成任務。", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 未授權", "nodes.agent.toolNotInstallTooltip": "{{tool}} 未安裝", "nodes.agent.toolbox": "工具箱",