mirror of
https://github.com/langgenius/dify.git
synced 2026-09-01 15:09:21 +08:00
fix: split sandbox files into saved and temporary tabs (#40478)
This commit is contained in:
+9
-35
@@ -19,29 +19,16 @@ describe('AgentWorkingDirectoryBreadcrumb', () => {
|
||||
expect(screen.getByRole('button', { name: '.' })).toHaveAttribute('aria-current', 'page')
|
||||
})
|
||||
|
||||
it('should render home as the current path when path is home', () => {
|
||||
it('should render the saved-files root path', () => {
|
||||
render(<AgentWorkingDirectoryBreadcrumb path="~" onPathChange={vi.fn()} />)
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.home',
|
||||
}),
|
||||
).toHaveAttribute('aria-current', 'page')
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.workingDirectory',
|
||||
}),
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '~' })).toHaveAttribute('aria-current', 'page')
|
||||
})
|
||||
|
||||
it('should render the workspace cwd directly and show tilde as home', () => {
|
||||
it('should render the saved-files prefix before its path segments', () => {
|
||||
render(<AgentWorkingDirectoryBreadcrumb path="~/web-game" onPathChange={vi.fn()} />)
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.home',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '~' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'web-game',
|
||||
@@ -52,17 +39,8 @@ describe('AgentWorkingDirectoryBreadcrumb', () => {
|
||||
it('should collapse middle breadcrumb layers when path is deeper than three layers', () => {
|
||||
render(<AgentWorkingDirectoryBreadcrumb path="~/web-game/src/app" onPathChange={vi.fn()} />)
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.home',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '~' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '...' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.workingDirectory',
|
||||
}),
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'web-game' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'src' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'app' })).toHaveAttribute('aria-current', 'page')
|
||||
@@ -70,16 +48,12 @@ describe('AgentWorkingDirectoryBreadcrumb', () => {
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
it('should request home path when home is clicked', async () => {
|
||||
it('should request the saved-files root when its prefix is clicked', async () => {
|
||||
const user = userEvent.setup()
|
||||
const handlePathChange = vi.fn()
|
||||
render(<AgentWorkingDirectoryBreadcrumb path="~/web-game" onPathChange={handlePathChange} />)
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.home',
|
||||
}),
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: '~' }))
|
||||
|
||||
expect(handlePathChange).toHaveBeenCalledWith('~')
|
||||
})
|
||||
@@ -88,12 +62,12 @@ describe('AgentWorkingDirectoryBreadcrumb', () => {
|
||||
const user = userEvent.setup()
|
||||
const handlePathChange = vi.fn()
|
||||
render(
|
||||
<AgentWorkingDirectoryBreadcrumb path="~/web-game/src" onPathChange={handlePathChange} />,
|
||||
<AgentWorkingDirectoryBreadcrumb path="./web-game/src" onPathChange={handlePathChange} />,
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'web-game' }))
|
||||
|
||||
expect(handlePathChange).toHaveBeenCalledWith('~/web-game')
|
||||
expect(handlePathChange).toHaveBeenCalledWith('./web-game')
|
||||
})
|
||||
|
||||
it('should request a hidden breadcrumb path from the ellipsis menu', async () => {
|
||||
|
||||
+83
-11
@@ -59,8 +59,8 @@ const previewSourceCases = [
|
||||
...agentSource,
|
||||
callerId: 'conversation-2',
|
||||
} satisfies AgentWorkingDirectorySource,
|
||||
imagePaths: ['workspace/chart-a.png', 'workspace/chart-b.png'],
|
||||
nonImagePath: 'workspace/model.bin',
|
||||
imagePaths: ['chart-a.png', 'chart-b.png'],
|
||||
nonImagePath: 'model.bin',
|
||||
previewClient: mocks.sandboxFileDownloadClientPost,
|
||||
urls: [
|
||||
'https://example.com/agent-chart-a.png',
|
||||
@@ -249,7 +249,7 @@ function mockFileListEntries(
|
||||
queryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: [`${source.type}-sandbox-files`, input, entries],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? (source.type === 'agent' ? '~/workspace' : '.'),
|
||||
path: input.query?.path ?? '~',
|
||||
entries,
|
||||
}),
|
||||
}))
|
||||
@@ -312,12 +312,12 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
mocks.sandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['sandbox-files', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '~/workspace',
|
||||
path: input.query?.path ?? '~',
|
||||
entries: [
|
||||
{ name: 'workspace/report.md', type: 'file' },
|
||||
{ name: 'workspace/notes.md', type: 'file' },
|
||||
{ name: 'workspace/chart.png', type: 'file' },
|
||||
{ name: 'workspace/model.bin', type: 'file' },
|
||||
{ name: 'report.md', type: 'file' },
|
||||
{ name: 'notes.md', type: 'file' },
|
||||
{ name: 'chart.png', type: 'file' },
|
||||
{ name: 'model.bin', type: 'file' },
|
||||
],
|
||||
}),
|
||||
}))
|
||||
@@ -350,6 +350,78 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should separate saved and temporary files by their sandbox path roots', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.sandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['sandbox-files-by-root', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '~',
|
||||
entries:
|
||||
input.query?.path === '.'
|
||||
? [{ name: 'scratch.txt', type: 'file' }]
|
||||
: [{ name: 'saved.txt', type: 'file' }],
|
||||
}),
|
||||
}))
|
||||
renderWorkingDirectoryPanel()
|
||||
|
||||
const savedFilesTab = await screen.findByRole('tab', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.savedFiles',
|
||||
})
|
||||
const temporaryFilesTab = screen.getByRole('tab', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.temporaryFiles',
|
||||
})
|
||||
|
||||
expect(savedFilesTab).toHaveAttribute('aria-selected', 'true')
|
||||
expect(
|
||||
await screen.findByRole('tabpanel', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.savedFiles',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(mocks.sandboxFilesQueryOptions).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
input: expect.objectContaining({
|
||||
query: expect.objectContaining({ path: '~' }),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
await user.click(temporaryFilesTab)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('tab', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.temporaryFiles',
|
||||
}),
|
||||
).toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
expect(
|
||||
screen.getByRole('tabpanel', {
|
||||
name: 'agentV2.agentDetail.configure.workingDirectory.temporaryFiles',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(mocks.sandboxFilesQueryOptions).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
input: expect.objectContaining({
|
||||
query: expect.objectContaining({ path: '.' }),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(mocks.sandboxFileReadQueryOptions).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
input: expect.objectContaining({
|
||||
query: expect.objectContaining({ path: './scratch.txt' }),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
expect(mocks.sandboxInfoQueryOptions).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should download the selected working directory file from the preview header download action', async () => {
|
||||
const user = userEvent.setup()
|
||||
const download = createDeferred<{ url: string }>()
|
||||
@@ -381,7 +453,7 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
body: {
|
||||
caller_type: 'conversation',
|
||||
caller_id: 'conversation-1',
|
||||
path: '~/workspace/notes.md',
|
||||
path: '~/notes.md',
|
||||
},
|
||||
})
|
||||
expect(mocks.downloadUrl).toHaveBeenCalledWith({
|
||||
@@ -425,7 +497,7 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
body: {
|
||||
caller_type: 'conversation',
|
||||
caller_id: 'conversation-1',
|
||||
path: '~/workspace/model.bin',
|
||||
path: '~/model.bin',
|
||||
},
|
||||
})
|
||||
expect(mocks.downloadUrl).toHaveBeenCalledWith({
|
||||
@@ -458,7 +530,7 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
body: {
|
||||
caller_type: 'conversation',
|
||||
caller_id: 'conversation-1',
|
||||
path: '~/workspace/chart.png',
|
||||
path: '~/chart.png',
|
||||
},
|
||||
})
|
||||
expect(
|
||||
|
||||
+31
-61
@@ -9,13 +9,14 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const AGENT_WORKING_DIRECTORY_HOME_PATH = '~'
|
||||
const AGENT_WORKING_DIRECTORY_ROOT_PATH = '.'
|
||||
export const AGENT_SAVED_FILES_ROOT_PATH = '~'
|
||||
export const AGENT_TEMPORARY_FILES_ROOT_PATH = '.'
|
||||
|
||||
export type AgentWorkingDirectoryPath =
|
||||
| typeof AGENT_WORKING_DIRECTORY_HOME_PATH
|
||||
| typeof AGENT_WORKING_DIRECTORY_ROOT_PATH
|
||||
| string
|
||||
export type AgentWorkingDirectoryRootPath =
|
||||
| typeof AGENT_SAVED_FILES_ROOT_PATH
|
||||
| typeof AGENT_TEMPORARY_FILES_ROOT_PATH
|
||||
|
||||
export type AgentWorkingDirectoryPath = AgentWorkingDirectoryRootPath | string
|
||||
|
||||
type AgentWorkingDirectoryBreadcrumbItemData = {
|
||||
iconClassName: string
|
||||
@@ -24,76 +25,48 @@ type AgentWorkingDirectoryBreadcrumbItemData = {
|
||||
}
|
||||
|
||||
const normalizeWorkingDirectoryPath = (path: AgentWorkingDirectoryPath) => {
|
||||
if (path === AGENT_WORKING_DIRECTORY_ROOT_PATH || path === AGENT_WORKING_DIRECTORY_HOME_PATH)
|
||||
return path
|
||||
if (path === AGENT_TEMPORARY_FILES_ROOT_PATH || path === AGENT_SAVED_FILES_ROOT_PATH) return path
|
||||
|
||||
if (path.startsWith('~/'))
|
||||
return `${AGENT_WORKING_DIRECTORY_HOME_PATH}/${path.slice(2).replace(/^\/+|\/+$/g, '')}`
|
||||
return `${AGENT_SAVED_FILES_ROOT_PATH}/${path.slice(2).replace(/^\/+|\/+$/g, '')}`
|
||||
|
||||
return path.replace(/^\.\/+/, '').replace(/^\/+|\/+$/g, '')
|
||||
}
|
||||
|
||||
function buildPathFromSegments(segments: string[], options: { startsFromHome: boolean }) {
|
||||
if (options.startsFromHome)
|
||||
return segments.length
|
||||
? `${AGENT_WORKING_DIRECTORY_HOME_PATH}/${segments.join('/')}`
|
||||
: AGENT_WORKING_DIRECTORY_HOME_PATH
|
||||
|
||||
return segments.length ? segments.join('/') : AGENT_WORKING_DIRECTORY_ROOT_PATH
|
||||
function buildPathFromSegments(rootPath: AgentWorkingDirectoryRootPath, segments: string[]) {
|
||||
return segments.length ? `${rootPath}/${segments.join('/')}` : rootPath
|
||||
}
|
||||
|
||||
function getBreadcrumbItems({
|
||||
homeLabel,
|
||||
path,
|
||||
}: {
|
||||
homeLabel: string
|
||||
path: AgentWorkingDirectoryPath
|
||||
}): AgentWorkingDirectoryBreadcrumbItemData[] {
|
||||
function getBreadcrumbItems(
|
||||
path: AgentWorkingDirectoryPath,
|
||||
): AgentWorkingDirectoryBreadcrumbItemData[] {
|
||||
const normalizedPath = normalizeWorkingDirectoryPath(path)
|
||||
const normalizedHomeLabel = homeLabel === 'home' ? 'Home' : homeLabel
|
||||
|
||||
if (normalizedPath === AGENT_WORKING_DIRECTORY_HOME_PATH) {
|
||||
return [
|
||||
{
|
||||
iconClassName: 'i-ri-folder-3-line',
|
||||
label: normalizedHomeLabel,
|
||||
path: AGENT_WORKING_DIRECTORY_HOME_PATH,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (normalizedPath === AGENT_WORKING_DIRECTORY_ROOT_PATH) {
|
||||
return [
|
||||
{
|
||||
iconClassName: 'i-ri-folder-3-line',
|
||||
label: AGENT_WORKING_DIRECTORY_ROOT_PATH,
|
||||
path: AGENT_WORKING_DIRECTORY_ROOT_PATH,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const startsFromHome = normalizedPath.startsWith(`${AGENT_WORKING_DIRECTORY_HOME_PATH}/`)
|
||||
const segments = startsFromHome
|
||||
? normalizedPath.slice(2).split('/').filter(Boolean)
|
||||
: normalizedPath.split('/').filter(Boolean)
|
||||
const rootPath =
|
||||
normalizedPath === AGENT_SAVED_FILES_ROOT_PATH ||
|
||||
normalizedPath.startsWith(`${AGENT_SAVED_FILES_ROOT_PATH}/`)
|
||||
? AGENT_SAVED_FILES_ROOT_PATH
|
||||
: AGENT_TEMPORARY_FILES_ROOT_PATH
|
||||
const segments =
|
||||
rootPath === AGENT_SAVED_FILES_ROOT_PATH
|
||||
? normalizedPath.slice(2).split('/').filter(Boolean)
|
||||
: normalizedPath
|
||||
.replace(/^\.\/?/, '')
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
|
||||
const rootItem: AgentWorkingDirectoryBreadcrumbItemData = {
|
||||
iconClassName: 'i-ri-folder-3-line',
|
||||
label: startsFromHome ? normalizedHomeLabel : segments[0]!,
|
||||
path: buildPathFromSegments(startsFromHome ? [] : segments.slice(0, 1), { startsFromHome }),
|
||||
label: rootPath,
|
||||
path: rootPath,
|
||||
}
|
||||
|
||||
return [
|
||||
rootItem,
|
||||
...segments.slice(startsFromHome ? 0 : 1).map((segment, index) => {
|
||||
const pathSegments = startsFromHome
|
||||
? segments.slice(0, index + 1)
|
||||
: segments.slice(0, index + 2)
|
||||
|
||||
...segments.map((segment, index) => {
|
||||
return {
|
||||
iconClassName: 'i-ri-folder-3-line',
|
||||
label: segment,
|
||||
path: buildPathFromSegments(pathSegments, { startsFromHome }),
|
||||
path: buildPathFromSegments(rootPath, segments.slice(0, index + 1)),
|
||||
}
|
||||
}),
|
||||
]
|
||||
@@ -148,10 +121,7 @@ export function AgentWorkingDirectoryBreadcrumb({
|
||||
onPathChange: (path: AgentWorkingDirectoryPath) => void
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const items = getBreadcrumbItems({
|
||||
homeLabel: t(($) => $['agentDetail.configure.workingDirectory.home']),
|
||||
path,
|
||||
})
|
||||
const items = getBreadcrumbItems(path)
|
||||
const { hiddenItems, visibleItems } = getVisibleBreadcrumbItems(items)
|
||||
|
||||
const renderSeparator = (key: string) => (
|
||||
|
||||
+95
-108
@@ -6,11 +6,14 @@ import type {
|
||||
SandboxReadResponse,
|
||||
} from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { AgentSkillDetailDownloadAction } from '../orchestrate/skills/detail-dialog'
|
||||
import type { AgentWorkingDirectoryPath } from './working-directory-breadcrumb'
|
||||
import type {
|
||||
AgentWorkingDirectoryPath,
|
||||
AgentWorkingDirectoryRootPath,
|
||||
} from './working-directory-breadcrumb'
|
||||
import type { AgentFileNode } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { Dialog } from '@langgenius/dify-ui/dialog'
|
||||
import { Tabs, TabsIndicator, TabsList, TabsPanel, TabsTab } from '@langgenius/dify-ui/tabs'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { skipToken, useMutation, useQueries, useQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -18,7 +21,11 @@ import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { downloadUrl } from '@/utils/download'
|
||||
import { getFileIconType } from '../orchestrate/files/file-icon'
|
||||
import { AgentSkillDetailDialog } from '../orchestrate/skills/detail-dialog'
|
||||
import { AgentWorkingDirectoryBreadcrumb } from './working-directory-breadcrumb'
|
||||
import {
|
||||
AGENT_SAVED_FILES_ROOT_PATH,
|
||||
AGENT_TEMPORARY_FILES_ROOT_PATH,
|
||||
AgentWorkingDirectoryBreadcrumb,
|
||||
} from './working-directory-breadcrumb'
|
||||
|
||||
type AgentWorkingDirectoryPanelProps = {
|
||||
source: AgentWorkingDirectorySource
|
||||
@@ -45,31 +52,37 @@ type SandboxErrorPayload = {
|
||||
code?: string
|
||||
}
|
||||
|
||||
const normalizeSandboxPath = (path: string) => {
|
||||
const normalizedPath = path
|
||||
const getSandboxRootPath = (path: string): AgentWorkingDirectoryRootPath =>
|
||||
path === AGENT_SAVED_FILES_ROOT_PATH || path.startsWith(`${AGENT_SAVED_FILES_ROOT_PATH}/`)
|
||||
? AGENT_SAVED_FILES_ROOT_PATH
|
||||
: AGENT_TEMPORARY_FILES_ROOT_PATH
|
||||
|
||||
const getSandboxRelativePath = (path: string) => {
|
||||
const relativePath = path
|
||||
.replace(/^~(?:\/|$)/, '')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/^\/+|\/+$/g, '')
|
||||
return normalizedPath === '.' ? '' : normalizedPath
|
||||
return relativePath === AGENT_TEMPORARY_FILES_ROOT_PATH ? '' : relativePath
|
||||
}
|
||||
|
||||
const toSandboxHomePath = (path: string) => {
|
||||
if (path === '.') return '.'
|
||||
const toSandboxApiPath = (path: string) => {
|
||||
const rootPath = getSandboxRootPath(path)
|
||||
const relativePath = getSandboxRelativePath(path)
|
||||
|
||||
const normalizedPath = normalizeSandboxPath(path)
|
||||
return normalizedPath ? `~/${normalizedPath}` : '~'
|
||||
return relativePath ? `${rootPath}/${relativePath}` : rootPath
|
||||
}
|
||||
|
||||
const toSandboxApiPath = toSandboxHomePath
|
||||
|
||||
const joinSandboxPath = (basePath: string, name: string) => {
|
||||
const normalizedBasePath = normalizeSandboxPath(basePath)
|
||||
return normalizedBasePath ? `${normalizedBasePath}/${name}` : name
|
||||
const rootPath = getSandboxRootPath(basePath)
|
||||
const baseRelativePath = getSandboxRelativePath(basePath)
|
||||
const relativePath = [baseRelativePath, name].filter(Boolean).join('/')
|
||||
|
||||
return relativePath ? `${rootPath}/${relativePath}` : rootPath
|
||||
}
|
||||
|
||||
function getSandboxEntryRelativePathSegments(entryName: string, basePath: string) {
|
||||
const normalizedBasePath = normalizeSandboxPath(basePath)
|
||||
const normalizedEntryName = normalizeSandboxPath(entryName)
|
||||
const normalizedBasePath = getSandboxRelativePath(basePath)
|
||||
const normalizedEntryName = getSandboxRelativePath(entryName)
|
||||
|
||||
if (!normalizedEntryName) return []
|
||||
|
||||
@@ -91,14 +104,14 @@ function buildSandboxFileTree(
|
||||
basePath = '.',
|
||||
options: { nestRootPath?: string; nestUnderBasePath?: boolean } = {},
|
||||
): AgentFileNode[] {
|
||||
const normalizedBasePath = normalizeSandboxPath(basePath)
|
||||
const normalizedNestRootPath = normalizeSandboxPath(options.nestRootPath ?? '.')
|
||||
const normalizedBasePath = getSandboxRelativePath(basePath)
|
||||
const normalizedNestRootPath = getSandboxRelativePath(options.nestRootPath ?? '.')
|
||||
const rootFiles: AgentFileNode[] = []
|
||||
let baseFolder: AgentFileNode | undefined
|
||||
|
||||
if (options.nestUnderBasePath && normalizedBasePath) {
|
||||
let currentFiles = rootFiles
|
||||
let currentPath = normalizedNestRootPath
|
||||
let currentPath = toSandboxApiPath(options.nestRootPath ?? basePath)
|
||||
const basePathSegments = normalizedBasePath.split('/').filter(Boolean)
|
||||
const nestRootPathSegments = normalizedNestRootPath.split('/').filter(Boolean)
|
||||
const nestedBasePathSegments =
|
||||
@@ -128,7 +141,7 @@ function buildSandboxFileTree(
|
||||
if (!pathSegments.length) continue
|
||||
|
||||
let currentFiles = baseFolder?.children ?? rootFiles
|
||||
let currentPath = normalizedBasePath
|
||||
let currentPath = toSandboxApiPath(basePath)
|
||||
|
||||
pathSegments.forEach((segment, index) => {
|
||||
const isLeaf = index === pathSegments.length - 1
|
||||
@@ -225,17 +238,6 @@ async function isNoActiveBindingError(error: unknown) {
|
||||
|
||||
const isNotFoundResponse = (error: unknown) => error instanceof Response && error.status === 404
|
||||
|
||||
function isSandboxPathWithinDirectory(path: string, directory: string) {
|
||||
const normalizedPath = normalizeSandboxPath(path)
|
||||
const normalizedDirectory = normalizeSandboxPath(directory)
|
||||
|
||||
if (!normalizedDirectory) return true
|
||||
|
||||
return (
|
||||
normalizedPath === normalizedDirectory || normalizedPath.startsWith(`${normalizedDirectory}/`)
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentWorkingDirectoryPanel({
|
||||
source,
|
||||
onOpenChange,
|
||||
@@ -243,55 +245,30 @@ export function AgentWorkingDirectoryPanel({
|
||||
}: AgentWorkingDirectoryPanelProps) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [selectedDirectoryPath, setSelectedDirectoryPath] = useState<AgentWorkingDirectoryPath>()
|
||||
const [selectedDirectoryPath, setSelectedDirectoryPath] = useState<AgentWorkingDirectoryPath>(
|
||||
AGENT_SAVED_FILES_ROOT_PATH,
|
||||
)
|
||||
const [selectedFileId, setSelectedFileId] = useState<string>()
|
||||
const [loadedFolderPaths, setLoadedFolderPaths] = useState<string[]>([])
|
||||
const [openFolderPaths, setOpenFolderPaths] = useState<string[]>([])
|
||||
const [pendingOpenFolderPaths, setPendingOpenFolderPaths] = useState<string[]>([])
|
||||
const [downloadActionLoadingTarget, setDownloadActionLoadingTarget] =
|
||||
useState<AgentSkillDetailDownloadAction | null>(null)
|
||||
const sandboxInfoQueryOptions = consoleQuery.agent.byAgentId.sandbox.get.queryOptions({
|
||||
input:
|
||||
source.type === 'agent'
|
||||
? {
|
||||
const directoryPath = selectedDirectoryPath
|
||||
const selectedRootPath = getSandboxRootPath(directoryPath)
|
||||
const getFileListQueryOptions = (path: string) =>
|
||||
source.type === 'agent'
|
||||
? consoleQuery.agent.byAgentId.sandbox.files.get.queryOptions({
|
||||
input: {
|
||||
params: {
|
||||
agent_id: source.agentId,
|
||||
},
|
||||
query: {
|
||||
caller_type: source.callerType,
|
||||
caller_id: source.callerId,
|
||||
path: toSandboxApiPath(path),
|
||||
},
|
||||
}
|
||||
: skipToken,
|
||||
context: {
|
||||
silent: true,
|
||||
},
|
||||
})
|
||||
const sandboxInfoQuery = useQuery({
|
||||
...sandboxInfoQueryOptions,
|
||||
enabled: open && source.type === 'agent',
|
||||
retry: false,
|
||||
})
|
||||
const isSandboxInfoLoading = source.type === 'agent' && sandboxInfoQuery.isPending
|
||||
const workspaceDirectoryPath = sandboxInfoQuery.data?.workspace_cwd
|
||||
const directoryPath = selectedDirectoryPath ?? workspaceDirectoryPath ?? '.'
|
||||
const showReturnToWorkspaceButton =
|
||||
!!workspaceDirectoryPath && !isSandboxPathWithinDirectory(directoryPath, workspaceDirectoryPath)
|
||||
const getFileListQueryOptions = (path: string) =>
|
||||
source.type === 'agent'
|
||||
? consoleQuery.agent.byAgentId.sandbox.files.get.queryOptions({
|
||||
input: !isSandboxInfoLoading
|
||||
? {
|
||||
params: {
|
||||
agent_id: source.agentId,
|
||||
},
|
||||
query: {
|
||||
caller_type: source.callerType,
|
||||
caller_id: source.callerId,
|
||||
path: toSandboxApiPath(path),
|
||||
},
|
||||
}
|
||||
: skipToken,
|
||||
},
|
||||
context: {
|
||||
silent: true,
|
||||
},
|
||||
@@ -368,18 +345,18 @@ export function AgentWorkingDirectoryPanel({
|
||||
(files, query, index) => {
|
||||
return mergeSandboxFileTree(
|
||||
files,
|
||||
buildSandboxFileTree(query.data?.entries, loadedFolderPaths[index] ?? query.data?.path, {
|
||||
buildSandboxFileTree(query.data?.entries, loadedFolderPaths[index], {
|
||||
nestRootPath: directoryPath,
|
||||
nestUnderBasePath: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
buildSandboxFileTree(fileListQuery.data?.entries, fileListQuery.data?.path),
|
||||
buildSandboxFileTree(fileListQuery.data?.entries, directoryPath),
|
||||
)
|
||||
const selectedWorkingDirectoryFile =
|
||||
findReadableFile(workingDirectoryFiles, selectedFileId) ??
|
||||
findFirstReadableFile(workingDirectoryFiles)
|
||||
const isFileListLoading = isSandboxInfoLoading || fileListQuery.isPending
|
||||
const isFileListLoading = fileListQuery.isPending
|
||||
const loadingFolderPaths = new Set(
|
||||
loadedFolderPaths.filter((path, index) => expandedFolderQueries[index]?.isPending),
|
||||
)
|
||||
@@ -569,46 +546,56 @@ export function AgentWorkingDirectoryPanel({
|
||||
detail={{
|
||||
description: t(($) => $['agentDetail.configure.workingDirectory.description']),
|
||||
fileCount: countReadableFiles(workingDirectoryFiles),
|
||||
fileListHeader: isSandboxInfoLoading ? (
|
||||
<h3
|
||||
id="agent-skill-detail-files-heading"
|
||||
className="px-4 pt-3.5 pb-3 system-xl-semibold text-text-primary"
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.fileSystem'])}
|
||||
</h3>
|
||||
) : (
|
||||
fileListHeader: (
|
||||
<div className="flex shrink-0 flex-col">
|
||||
<div className="flex items-center gap-1 px-4 pt-3.5 pb-3">
|
||||
<h3
|
||||
id="agent-skill-detail-files-heading"
|
||||
className="min-w-0 flex-1 system-xl-semibold text-text-primary"
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.fileSystem'])}
|
||||
</h3>
|
||||
{showReturnToWorkspaceButton && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
aria-label={t(
|
||||
($) => $['agentDetail.configure.workingDirectory.returnToWorkspace'],
|
||||
)}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded-md p-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
onClick={() => handleDirectoryPathChange(workspaceDirectoryPath)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-arrow-go-back-line size-3.5" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent placement="top">
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.returnToWorkspace'])}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<AgentWorkingDirectoryBreadcrumb
|
||||
path={directoryPath}
|
||||
onPathChange={handleDirectoryPathChange}
|
||||
/>
|
||||
<h3
|
||||
id="agent-skill-detail-files-heading"
|
||||
className="px-4 pt-3.5 pb-2 system-xl-semibold text-text-primary"
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.fileSystem'])}
|
||||
</h3>
|
||||
<Tabs
|
||||
value={selectedRootPath}
|
||||
onValueChange={(path) =>
|
||||
handleDirectoryPathChange(path as AgentWorkingDirectoryPath)
|
||||
}
|
||||
>
|
||||
<TabsList className="relative h-9 gap-4 border-b-[0.5px] border-divider-regular px-4">
|
||||
<TabsTab
|
||||
value={AGENT_SAVED_FILES_ROOT_PATH}
|
||||
className="h-full min-w-0 pt-0 pb-0 system-sm-semibold data-active:border-transparent"
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.savedFiles'])}
|
||||
</TabsTab>
|
||||
<TabsTab
|
||||
value={AGENT_TEMPORARY_FILES_ROOT_PATH}
|
||||
className="h-full min-w-0 pt-0 pb-0 system-sm-semibold data-active:border-transparent"
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.workingDirectory.temporaryFiles'])}
|
||||
</TabsTab>
|
||||
<TabsIndicator
|
||||
className="pointer-events-none absolute bottom-0 left-0 h-0 border-b-2 border-components-tab-active transition-[translate,width] duration-150 ease-in-out motion-reduce:transition-none"
|
||||
style={{
|
||||
translate: 'var(--active-tab-left)',
|
||||
width: 'var(--active-tab-width)',
|
||||
}}
|
||||
/>
|
||||
</TabsList>
|
||||
<TabsPanel value={AGENT_SAVED_FILES_ROOT_PATH} tabIndex={-1}>
|
||||
<AgentWorkingDirectoryBreadcrumb
|
||||
path={directoryPath}
|
||||
onPathChange={handleDirectoryPathChange}
|
||||
/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value={AGENT_TEMPORARY_FILES_ROOT_PATH} tabIndex={-1}>
|
||||
<AgentWorkingDirectoryBreadcrumb
|
||||
path={directoryPath}
|
||||
onPathChange={handleDirectoryPathChange}
|
||||
/>
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
),
|
||||
fileListLoading: isSandboxInfoLoading,
|
||||
fileListPanelClassName: 'w-[360px]',
|
||||
fileListTreeClassName: 'px-0',
|
||||
fileListTreeListClassName: 'px-1',
|
||||
@@ -657,7 +644,7 @@ export function AgentWorkingDirectoryPanel({
|
||||
: paths.filter((path) => path !== file.id),
|
||||
)
|
||||
},
|
||||
onFolderDoubleClick: ({ file }) => handleDirectoryPathChange(toSandboxHomePath(file.id)),
|
||||
onFolderDoubleClick: ({ file }) => handleDirectoryPathChange(file.id),
|
||||
onSelectFile: (selectedFile) => setSelectedFileId(selectedFile.id),
|
||||
renderFolderSuffix: ({ file }) =>
|
||||
loadingFolderPaths.has(file.id) ? (
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "الرئيسية",
|
||||
"agentDetail.configure.workingDirectory.open": "فتح دليل العمل",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "العودة إلى دليل العمل",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "الملفات المحفوظة",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "الملفات المؤقتة",
|
||||
"agentDetail.configure.workingDirectory.title": "دليل العمل",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "ملفات دليل العمل",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "دليل العمل",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "Start",
|
||||
"agentDetail.configure.workingDirectory.open": "Arbeitsverzeichnis öffnen",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Zurück zum Arbeitsverzeichnis",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Gespeicherte Dateien",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Temporäre Dateien",
|
||||
"agentDetail.configure.workingDirectory.title": "Arbeitsverzeichnis",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Dateien im Arbeitsverzeichnis",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "Arbeitsverzeichnis",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "Open working directory",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Back to working directory",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Saved Files",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Temporary Files",
|
||||
"agentDetail.configure.workingDirectory.title": "Working directory",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Working directory files",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "working directory",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "inicio",
|
||||
"agentDetail.configure.workingDirectory.open": "Abrir directorio de trabajo",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Volver al directorio de trabajo",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Archivos guardados",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Archivos temporales",
|
||||
"agentDetail.configure.workingDirectory.title": "Directorio de trabajo",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Archivos del directorio de trabajo",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "directorio de trabajo",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "خانه",
|
||||
"agentDetail.configure.workingDirectory.open": "باز کردن پوشه کاری",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "بازگشت به پوشه کاری",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "فایلهای ذخیرهشده",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "فایلهای موقت",
|
||||
"agentDetail.configure.workingDirectory.title": "پوشه کاری",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "فایلهای پوشه کاری",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "پوشه کاری",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "accueil",
|
||||
"agentDetail.configure.workingDirectory.open": "Ouvrir le répertoire de travail",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Retour au répertoire de travail",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Fichiers enregistrés",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Fichiers temporaires",
|
||||
"agentDetail.configure.workingDirectory.title": "Répertoire de travail",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Fichiers du répertoire de travail",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "répertoire de travail",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "होम",
|
||||
"agentDetail.configure.workingDirectory.open": "वर्किंग डायरेक्टरी खोलें",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "वर्किंग डायरेक्टरी पर वापस जाएँ",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "सहेजी गई फ़ाइलें",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "अस्थायी फ़ाइलें",
|
||||
"agentDetail.configure.workingDirectory.title": "वर्किंग डायरेक्टरी",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "वर्किंग डायरेक्टरी फाइलें",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "वर्किंग डायरेक्टरी",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "beranda",
|
||||
"agentDetail.configure.workingDirectory.open": "Buka direktori kerja",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Kembali ke direktori kerja",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "File tersimpan",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "File sementara",
|
||||
"agentDetail.configure.workingDirectory.title": "Direktori kerja",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "File direktori kerja",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "direktori kerja",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "Apri directory di lavoro",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Torna alla directory di lavoro",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "File salvati",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "File temporanei",
|
||||
"agentDetail.configure.workingDirectory.title": "Directory di lavoro",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "File della directory di lavoro",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "directory di lavoro",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "ホーム",
|
||||
"agentDetail.configure.workingDirectory.open": "作業ディレクトリを開く",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "作業ディレクトリに戻る",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "保存済みファイル",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "一時ファイル",
|
||||
"agentDetail.configure.workingDirectory.title": "作業ディレクトリ",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "作業ディレクトリのファイル",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "作業ディレクトリ",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "홈",
|
||||
"agentDetail.configure.workingDirectory.open": "작업 디렉터리 열기",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "작업 디렉터리로 돌아가기",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "저장된 파일",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "임시 파일",
|
||||
"agentDetail.configure.workingDirectory.title": "작업 디렉터리",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "작업 디렉터리 파일",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "작업 디렉터리",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "ເປີດໂຟນເດີເຮັດວຽກ",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "ກັບຄືນໄປໂຟນເດີເຮັດວຽກ",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "ໄຟລ໌ທີ່ບັນທຶກໄວ້",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "ໄຟລ໌ຊົ່ວຄາວ",
|
||||
"agentDetail.configure.workingDirectory.title": "ໂຟນເດີເຮັດວຽກ (Working directory)",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "ໄຟລ໌ໃນໂຟນເດີເຮັດວຽກ",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "working directory",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "Werkmap openen",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Terug naar werkmap",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Opgeslagen bestanden",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Tijdelijke bestanden",
|
||||
"agentDetail.configure.workingDirectory.title": "Werkmap",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Bestanden in de werkmap",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "werkmap",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "Otwórz katalog roboczy",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Wróć do katalogu roboczego",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Zapisane pliki",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Pliki tymczasowe",
|
||||
"agentDetail.configure.workingDirectory.title": "Katalog roboczy",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Pliki katalogu roboczego",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "katalog roboczy",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "início",
|
||||
"agentDetail.configure.workingDirectory.open": "Abrir diretório de trabalho",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Voltar ao diretório de trabalho",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Arquivos salvos",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Arquivos temporários",
|
||||
"agentDetail.configure.workingDirectory.title": "Diretório de trabalho",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Arquivos do diretório de trabalho",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "diretório de trabalho",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "acasă",
|
||||
"agentDetail.configure.workingDirectory.open": "Deschide directorul de lucru",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Înapoi la directorul de lucru",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Fișiere salvate",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Fișiere temporare",
|
||||
"agentDetail.configure.workingDirectory.title": "Director de lucru",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Fișiere din directorul de lucru",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "director de lucru",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "главная",
|
||||
"agentDetail.configure.workingDirectory.open": "Открыть рабочий каталог",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Вернуться в рабочий каталог",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Сохранённые файлы",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Временные файлы",
|
||||
"agentDetail.configure.workingDirectory.title": "Рабочий каталог",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Файлы рабочего каталога",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "рабочий каталог",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "domov",
|
||||
"agentDetail.configure.workingDirectory.open": "Odpri delovni imenik",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Nazaj v delovni imenik",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Shranjene datoteke",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Začasne datoteke",
|
||||
"agentDetail.configure.workingDirectory.title": "Delovni imenik",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Datoteke delovnega imenika",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "delovni imenik",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "หน้าแรก",
|
||||
"agentDetail.configure.workingDirectory.open": "เปิดไดเรกทอรีทำงาน",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "กลับไปยังไดเรกทอรีทำงาน",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "ไฟล์ที่บันทึก",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "ไฟล์ชั่วคราว",
|
||||
"agentDetail.configure.workingDirectory.title": "ไดเรกทอรีทำงาน",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "ไฟล์ในไดเรกทอรีทำงาน",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "ไดเรกทอรีทำงาน",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "ana sayfa",
|
||||
"agentDetail.configure.workingDirectory.open": "Çalışma dizinini aç",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Çalışma dizinine dön",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Kaydedilen dosyalar",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Geçici dosyalar",
|
||||
"agentDetail.configure.workingDirectory.title": "Çalışma dizini",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Çalışma dizini dosyaları",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "çalışma dizini",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "головна",
|
||||
"agentDetail.configure.workingDirectory.open": "Відкрити робочий каталог",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Повернутися до робочого каталогу",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Збережені файли",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Тимчасові файли",
|
||||
"agentDetail.configure.workingDirectory.title": "Робочий каталог",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Файли робочого каталогу",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "робочий каталог",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "trang chủ",
|
||||
"agentDetail.configure.workingDirectory.open": "Mở thư mục làm việc",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "Quay lại thư mục làm việc",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "Tệp đã lưu",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "Tệp tạm thời",
|
||||
"agentDetail.configure.workingDirectory.title": "Thư mục làm việc",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "Tệp trong thư mục làm việc",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "thư mục làm việc",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "打开工作目录",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "返回工作目录",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "已保存文件",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "临时文件",
|
||||
"agentDetail.configure.workingDirectory.title": "工作目录",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "工作目录文件",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "工作目录",
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
"agentDetail.configure.workingDirectory.home": "home",
|
||||
"agentDetail.configure.workingDirectory.open": "開啟工作目錄",
|
||||
"agentDetail.configure.workingDirectory.returnToWorkspace": "返回工作目錄",
|
||||
"agentDetail.configure.workingDirectory.savedFiles": "已儲存檔案",
|
||||
"agentDetail.configure.workingDirectory.temporaryFiles": "暫存檔案",
|
||||
"agentDetail.configure.workingDirectory.title": "工作目錄",
|
||||
"agentDetail.configure.workingDirectory.treeLabel": "工作目錄檔案",
|
||||
"agentDetail.configure.workingDirectory.workingDirectory": "工作目錄",
|
||||
|
||||
Reference in New Issue
Block a user