feat: new skills and files

This commit is contained in:
Joel 2026-06-25 11:21:34 +08:00
parent 5f23384d81
commit 4139d0c4fb
38 changed files with 503 additions and 179 deletions

View File

@ -103,7 +103,10 @@ export function AgentTaskField({
<FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t(`${i18nPrefix}.task.label`, { ns: 'workflow' })}
</FieldLabel>
<Infotip aria-label={t(`${i18nPrefix}.task.tooltip`, { ns: 'workflow' })}>
<Infotip
aria-label={t(`${i18nPrefix}.task.tooltip`, { ns: 'workflow' })}
popupClassName="whitespace-pre-line"
>
{t(`${i18nPrefix}.task.tooltip`, { ns: 'workflow' })}
</Infotip>
</div>

View File

@ -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',

View File

@ -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<NonNullable<AgentSoulConfig['tools']>['dify_tools']>[number]
type AgentSoulCliToolConfig = NonNullable<NonNullable<AgentSoulConfig['tools']>['cli_tools']>[number]
type AgentSoulToolRuntimeParameterValue = NonNullable<AgentSoulDifyToolConfig['runtime_parameters']>[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),

View File

@ -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: [],

View File

@ -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),
})

View File

@ -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<AgentFileNode[], [DraftFieldUpdate<AgentFileNode[]>], void>(
get => get(agentComposerDraftAtom).files,
(get, set, filesUpdate: DraftFieldUpdate<AgentFileNode[]>) => {
const draft = get(agentComposerDraftAtom)
const files = resolveDraftFieldUpdate(draft.files, filesUpdate)
set(agentComposerDraftAtom, {
...draft,
prompt: syncFileReferenceLabels({
prompt: draft.prompt,
currentFiles: draft.files,
nextFiles: files,
}),
files,
})
},
)

View File

@ -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<AgentSkill[], [DraftFieldUpdate<AgentSkill[]>], void>(
get => get(agentComposerDraftAtom).skills,
(get, set, skillsUpdate: DraftFieldUpdate<AgentSkill[]>) => {
const draft = get(agentComposerDraftAtom)
const skills = resolveDraftFieldUpdate(draft.skills, skillsUpdate)
set(agentComposerDraftAtom, {
...draft,
prompt: syncSkillReferenceLabels({
prompt: draft.prompt,
currentSkills: draft.skills,
nextSkills: skills,
}),
skills,
})
},
)

View File

@ -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()

View File

@ -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', () => ({

View File

@ -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<AgentDriveApiContext | null>(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,
}
}

View File

@ -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 (
<pre data-testid="config-snapshot-probe">
{JSON.stringify(configSnapshot)}
</pre>
)
}
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(
<QueryClientProvider client={queryClient}>
<AgentDriveApiContextProvider value={{ agentId: 'agent-1' }}>
<AgentComposerProvider initialDraft={defaultAgentSoulConfigFormState}>
<AgentFiles />
<ConfigSnapshotProbe />
</AgentComposerProvider>
</AgentDriveApiContextProvider>
</QueryClientProvider>,
)
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()
})

View File

@ -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<AgentOrchestrateAddActionOptions['onAdded']>(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

View File

@ -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,
}
}

View File

@ -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',

View File

@ -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<AgentOrchestrateAddActionOptions['onAdded']>(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 (
<>

View File

@ -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": "صندوق الأدوات",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "جعبه ابزار",

View File

@ -464,7 +464,7 @@
"nodes.agent.task.label": "Tâche de lagent",
"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 lagent à 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}} nest pas installé",
"nodes.agent.toolbox": "boîte à outils",

View File

@ -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": "टूलबॉक्स",

View File

@ -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",

View File

@ -464,7 +464,7 @@
"nodes.agent.task.label": "Attività dellagente",
"nodes.agent.task.mention": "Menziona",
"nodes.agent.task.placeholder": "Descrivi cosa deve fare questo agente...",
"nodes.agent.task.tooltip": "Definisci le istruzioni dellattività che questo agente deve seguire in questo workflow.",
"nodes.agent.task.tooltip": "Prompt aggiuntivo per aiutare lagente a gestire proprio questo nodo. Usa / per fare riferimento esplicito alle variabili.\nSe configurato correttamente, potresti poterti fidare dellagente perché capisca da solo cosa fare.",
"nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Non autorizzato",
"nodes.agent.toolNotInstallTooltip": "{{tool}} non è installato",
"nodes.agent.toolbox": "cassetta degli attrezzi",

View File

@ -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": "ツールボックス",

View File

@ -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": "도구",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "ящик для инструментов",

View File

@ -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",

View File

@ -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": "เครื่อง มือ",

View File

@ -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ç",

View File

@ -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": "ящик для інструментів",

View File

@ -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ụ",

View File

@ -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": "工具箱",

View File

@ -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": "工具箱",