mirror of
https://github.com/langgenius/dify.git
synced 2026-06-26 14:51:13 +08:00
feat: new skills and files
This commit is contained in:
parent
5f23384d81
commit
4139d0c4fb
@ -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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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),
|
||||
})
|
||||
|
||||
24
web/features/agent-v2/agent-composer/store-modules/files.ts
Normal file
24
web/features/agent-v2/agent-composer/store-modules/files.ts
Normal 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,
|
||||
})
|
||||
},
|
||||
)
|
||||
24
web/features/agent-v2/agent-composer/store-modules/skills.ts
Normal file
24
web/features/agent-v2/agent-composer/store-modules/skills.ts
Normal 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,
|
||||
})
|
||||
},
|
||||
)
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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', () => ({
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
|
||||
@ -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": "صندوق الأدوات",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "جعبه ابزار",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "टूलबॉक्स",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "ツールボックス",
|
||||
|
||||
@ -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": "도구",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "ящик для инструментов",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "เครื่อง มือ",
|
||||
|
||||
@ -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ç",
|
||||
|
||||
@ -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": "ящик для інструментів",
|
||||
|
||||
@ -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ụ",
|
||||
|
||||
@ -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": "工具箱",
|
||||
|
||||
@ -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": "工具箱",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user