diff --git a/src/api/fileApi.ts b/src/api/fileApi.ts index 75151c3..c153b5f 100644 --- a/src/api/fileApi.ts +++ b/src/api/fileApi.ts @@ -36,6 +36,7 @@ export interface PaginationParams { search?: string; viewKey?: string; groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>; + owner?: 'all' | 'me' | 'shared'; } export interface PaginatedResponse { @@ -109,6 +110,7 @@ export async function fetchFiles( if (params.search) paginationObj.search = params.search; if (params.viewKey) paginationObj.viewKey = params.viewKey; if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels; + if (params.owner) requestParams.owner = params.owner; if (Object.keys(paginationObj).length > 0) { requestParams.pagination = JSON.stringify(paginationObj); diff --git a/src/components/FlowEditor/editor/NodeConfigPanel.tsx b/src/components/FlowEditor/editor/NodeConfigPanel.tsx index c60600b..9f8462c 100644 --- a/src/components/FlowEditor/editor/NodeConfigPanel.tsx +++ b/src/components/FlowEditor/editor/NodeConfigPanel.tsx @@ -9,6 +9,7 @@ import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } f import type { ApiRequestFunction } from '../../../api/workflowApi'; import { getLabel } from '../nodes/shared/utils'; import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers'; +import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer'; import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker'; import { findRequiredErrors } from '../nodes/shared/paramValidation'; import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext'; @@ -253,6 +254,7 @@ export const NodeConfigPanel: React.FC = ({ node, for (const param of sortedParameters) { if (param.frontendType === 'hidden') continue; + if (param.name === 'context') continue; if (CONTEXT_EXTRACT_CHUNK_SET.has(param.name)) continue; if (!parameterVisibleForFrontendOptions(param, params, nodeType)) continue; @@ -378,6 +380,15 @@ export const NodeConfigPanel: React.FC = ({ node, t, ]); + const extractContentContextParam = useMemo((): NodeTypeParameter | null => { + if (!node || !nodeType || node.type !== CONTEXT_EXTRACT_CONTENT_NODE_TYPE) return null; + const param = sortedParameters.find((p) => p.name === 'context') ?? null; + if (!param) return null; + if (param.frontendType === 'hidden') return null; + if (!parameterVisibleForFrontendOptions(param, params, nodeType)) return null; + return param; + }, [node, nodeType, sortedParameters, params]); + if (!node || !nodeType) return null; const isTrigger = node.type.startsWith('trigger.'); @@ -483,11 +494,71 @@ export const NodeConfigPanel: React.FC = ({ node, )} {extractContentAccordionItems !== null ? ( - - key={`${node.id}-extract-accordion`} - defaultOpenId={null} - items={extractContentAccordionItems} - /> + <> + {extractContentContextParam ? ( +
+
+ {extractContentContextParam.required && ( + + * + + )} + {verboseSchema && extractContentContextParam.type && ( + + {extractContentContextParam.type} + + )} +
+ updateParam(extractContentContextParam.name, val)} + allParams={params} + instanceId={instanceId} + request={request} + nodeType={node.type} + onPatchParams={patchParams} + /> +
+ ) : null} + {extractContentAccordionItems.length > 0 ? ( + + key={`${node.id}-extract-accordion`} + defaultOpenId={null} + items={extractContentAccordionItems} + /> + ) : null} + ) : ( parameters.map((param: NodeTypeParameter) => { // Safety net: hidden params have no UI footprint at all — no row, diff --git a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx index cb3bb2d..f843fa0 100644 --- a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx +++ b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx @@ -1,7 +1,6 @@ import { FaFolder, FaFile, FaTrash } from 'react-icons/fa'; import type { TreeNodeProvider, TreeNode, Ownership, ScopeValue, TreeBatchAction } from '../types'; import api from '../../../../api'; -import { getUserDataCache } from '../../../../utils/userCache'; interface FolderData { id: string; @@ -137,7 +136,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = { if ((f.parentId ?? null) === null) out.add(f.id); } const paginationParam = JSON.stringify({ filters: { folderId: null }, pageSize: 500 }); - const filesRes = await api.get('/api/files/list', { params: { pagination: paginationParam } }); + const filesRes = await api.get('/api/files/list', { params: { pagination: paginationParam, owner } }); const data = filesRes.data; const rawFiles: FileData[] = (data && typeof data === 'object' && 'items' in data) ? (Array.isArray(data.items) ? data.items : []) @@ -193,7 +192,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = { } const paginationParam = JSON.stringify({ filters, pageSize: 500 }); const filesRes = await api.get('/api/files/list', { - params: { pagination: paginationParam }, + params: { pagination: paginationParam, owner }, }); const data = filesRes.data; let rawFiles: FileData[] = []; @@ -203,10 +202,6 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = { rawFiles = data; } let matched = rawFiles.filter((f) => (f.folderId ?? null) === apiParentId); - if (ownership === 'shared') { - const myId = getUserDataCache()?.id; - if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId); - } const fileNodes = matched.map((f) => _mapFileToNode(f, ownership)); if (apiParentId === null) { for (const n of fileNodes) n.parentId = synthRootId; diff --git a/src/hooks/useFiles.ts b/src/hooks/useFiles.ts index 704d778..17c9c3e 100644 --- a/src/hooks/useFiles.ts +++ b/src/hooks/useFiles.ts @@ -69,6 +69,7 @@ export interface PaginationParams { filters?: Record; search?: string; viewKey?: string; + owner?: 'all' | 'me' | 'shared'; } // Files list hook @@ -150,6 +151,7 @@ export function useUserFiles() { groupField: string; groupDirection?: 'asc' | 'desc'; groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>; + owner?: 'all' | 'me' | 'shared'; }) => { const levels = base.groupByLevels?.length ? base.groupByLevels @@ -164,7 +166,11 @@ export function useUserFiles() { if (base.sort?.length) (pObj as { sort: typeof base.sort }).sort = base.sort; if (base.viewKey) (pObj as { viewKey: string }).viewKey = base.viewKey; const { data } = await api.get('/api/files/list', { - params: { mode: 'groupSummary', pagination: JSON.stringify(pObj) }, + params: { + mode: 'groupSummary', + pagination: JSON.stringify(pObj), + ...(base.owner ? { owner: base.owner } : {}), + }, }); return Array.isArray(data?.groups) ? data.groups : []; }, @@ -192,7 +198,10 @@ export function useUserFiles() { if (paginationParams.search) (pObj as { search: string }).search = paginationParams.search; if (paginationParams.viewKey) (pObj as { viewKey: string }).viewKey = paginationParams.viewKey; const { data } = await api.get('/api/files/list', { - params: { pagination: JSON.stringify(pObj) }, + params: { + pagination: JSON.stringify(pObj), + ...(paginationParams.owner ? { owner: paginationParams.owner } : {}), + }, }); if (data && typeof data === 'object' && 'items' in data) { return { items: data.items, pagination: data.pagination }; diff --git a/src/pages/basedata/FilesPage.tsx b/src/pages/basedata/FilesPage.tsx index e5dbecd..6c7a861 100644 --- a/src/pages/basedata/FilesPage.tsx +++ b/src/pages/basedata/FilesPage.tsx @@ -31,6 +31,17 @@ interface UserFile { } type ViewMode = 'folder' | 'all'; +type FileOwnerScope = 'all' | 'me' | 'shared'; + +function normalizeFolderFilterId(folderId: string | null): string | null { + if (!folderId) return null; + if (folderId.startsWith('__filesRoot:')) return null; + return folderId; +} + +function isSyntheticRootFolderId(folderId: string | null): boolean { + return Boolean(folderId && folderId.startsWith('__filesRoot:')); +} export const FilesPage: React.FC = () => { const { t } = useLanguage(); @@ -74,6 +85,7 @@ export const FilesPage: React.FC = () => { const [editingFile, setEditingFile] = useState(null); const [selectedFiles, setSelectedFiles] = useState([]); const [selectedFolderId, setSelectedFolderId] = useState(null); + const [selectedOwnership, setSelectedOwnership] = useState<'own' | 'shared' | null>('own'); const [highlightedFileId, setHighlightedFileId] = useState(null); const [treeWidth, setTreeWidth] = useState(300); @@ -103,14 +115,24 @@ export const FilesPage: React.FC = () => { const _tableRefetch = useCallback(async (params?: any) => { const nextParams = { ...(params || {}) }; const nextFilters = { ...(nextParams.filters || {}) }; - if (viewMode === 'folder' && selectedFolderId) { - nextFilters.folderId = selectedFolderId; + const normalizedFolderId = normalizeFolderFilterId(selectedFolderId); + const rootSelected = isSyntheticRootFolderId(selectedFolderId); + const owner: FileOwnerScope = + selectedOwnership === 'own' + ? 'me' + : selectedOwnership === 'shared' + ? 'shared' + : 'all'; + if (viewMode === 'folder' && selectedFolderId && !rootSelected) { + nextFilters.folderId = normalizedFolderId; } else { delete nextFilters.folderId; } nextParams.filters = nextFilters; + if (owner !== 'all') nextParams.owner = owner; + else delete nextParams.owner; await tableRefetch(nextParams); - }, [tableRefetch, selectedFolderId, viewMode]); + }, [tableRefetch, selectedFolderId, selectedOwnership, viewMode]); const fetchGroupSectionSummaries = useCallback( async (base: { @@ -122,12 +144,20 @@ export const FilesPage: React.FC = () => { groupDirection?: 'asc' | 'desc'; }) => { const filters = { ...(base.filters || {}) }; - if (viewMode === 'folder' && selectedFolderId) { - filters.folderId = selectedFolderId; + const normalizedFolderId = normalizeFolderFilterId(selectedFolderId); + const rootSelected = isSyntheticRootFolderId(selectedFolderId); + if (viewMode === 'folder' && selectedFolderId && !rootSelected) { + filters.folderId = normalizedFolderId; } - return fetchGroupSectionSummariesFromHook({ ...base, filters }); + const owner: FileOwnerScope = + selectedOwnership === 'own' + ? 'me' + : selectedOwnership === 'shared' + ? 'shared' + : 'all'; + return fetchGroupSectionSummariesFromHook({ ...base, filters, owner }); }, - [fetchGroupSectionSummariesFromHook, viewMode, selectedFolderId], + [fetchGroupSectionSummariesFromHook, viewMode, selectedFolderId, selectedOwnership], ); const refetchForSection = useCallback( @@ -137,12 +167,20 @@ export const FilesPage: React.FC = () => { parentColumnFilters?: Record, ) => { const merged = { ...(parentColumnFilters || {}) }; - if (viewMode === 'folder' && selectedFolderId) { - merged.folderId = selectedFolderId; + const normalizedFolderId = normalizeFolderFilterId(selectedFolderId); + const rootSelected = isSyntheticRootFolderId(selectedFolderId); + if (viewMode === 'folder' && selectedFolderId && !rootSelected) { + merged.folderId = normalizedFolderId; } - return refetchForSectionFromHook(paginationParams, sectionFilter, merged); + const owner: FileOwnerScope = + selectedOwnership === 'own' + ? 'me' + : selectedOwnership === 'shared' + ? 'shared' + : 'all'; + return refetchForSectionFromHook({ ...paginationParams, owner }, sectionFilter, merged); }, - [refetchForSectionFromHook, viewMode, selectedFolderId], + [refetchForSectionFromHook, viewMode, selectedFolderId, selectedOwnership], ); const _refreshAll = useCallback(async () => { @@ -152,14 +190,15 @@ export const FilesPage: React.FC = () => { useEffect(() => { _tableRefetch({ page: 1, pageSize: 25 }); - }, [selectedFolderId, viewMode, _tableRefetch]); + }, [selectedFolderId, selectedOwnership, viewMode, _tableRefetch]); // ── Tree interaction ────────────────────────────────────────────────── const _handleTreeNodeClick = useCallback((node: TreeNode) => { + setSelectedOwnership(node.ownership); if (node.type === 'folder') { setSelectedFolderId(node.id); } else if (node.type === 'file') { - setSelectedFolderId(node.parentId); + setSelectedFolderId(node.parentId ?? null); setHighlightedFileId(node.id); requestAnimationFrame(() => { const row = document.querySelector('tr[data-highlighted="true"]');