build errors
Some checks failed
Deploy Nyla Frontend to Integration / build-and-deploy (push) Failing after 8s

This commit is contained in:
Ida 2026-05-20 18:05:08 +02:00
parent f617a2d701
commit 9488a7d95c
6 changed files with 78 additions and 73 deletions

View file

@ -23,7 +23,6 @@ import {
HiOutlineArrowUturnRight,
HiOutlineTrash,
HiOutlineDocumentDuplicate,
HiOutlineArrowLongRight,
HiOutlineChatBubbleLeftEllipsis,
HiOutlineSquares2X2,
} from 'react-icons/hi2';

View file

@ -754,7 +754,10 @@ const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChan
onChange={(e) => {
const typeId = e.target.value;
const nextFields = [...(Array.isArray(f.fields) ? f.fields : [])];
const subRow = { ...(nextFields[j] as Record<string, unknown>), type: typeId };
const subRow: Record<string, unknown> = {
...(nextFields[j] as Record<string, unknown>),
type: typeId,
};
if (formFieldTypeHasConfigurableOptions(typeId)) {
subRow.options = normalizeFormFieldOptions(subRow.options);
}

View file

@ -33,19 +33,6 @@ const _SCOPE_EMOJIS: Record<string, string> = {
const _NEUTRALIZE_ON_EMOJI = '\uD83D\uDD12'; // closed padlock
const _NEUTRALIZE_OFF_EMOJI = '\uD83D\uDD13'; // open padlock
const _RAG_ON_EMOJI = '\uD83E\uDDE0'; // brain
const _RAG_OFF_EMOJI = '\uD83E\uDDE0'; // brain (greyed via CSS filter when off)
/** CSS for the OFF-state of a boolean flag button. We desaturate the colour
* emoji and dim it so the on/off transition is obvious at a glance, even
* when the on/off glyph itself is similar (e.g. brain vs greyed-brain). */
const _OFF_STATE_STYLE: React.CSSProperties = {
filter: 'grayscale(1)',
opacity: 0.45,
};
/** Uniform symbol for any flag whose effective value is 'mixed' across children. */
const _MIXED_SYMBOL = '\u25E9';
/** Internal action keys reserved by the tree for the built-in flag buttons. */
const _ACTION_SCOPE = '__scope__';
@ -209,8 +196,6 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
isDragging,
ownership,
compact,
selectable,
pendingActions,
provider,
onToggleExpand,
onToggleSelect,
@ -223,9 +208,6 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
onSendToChat,
onCycleScope,
onToggleNeutralize,
onToggleRagIndex,
onCreateChild,
onExtraAction,
onDragStart,
onDragOver,
onDragLeave,
@ -294,12 +276,6 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
const canDelete = isOwn && provider.canDelete?.(node);
const canPatchScope = isOwn && provider.canPatchScope?.(node);
const canPatchNeutralize = isOwn && provider.canPatchNeutralize?.(node);
const canPatchRagIndex = isOwn && provider.canPatchRagIndex?.(node);
const canCreateChild =
isOwn &&
!!provider.createChild &&
node.type === 'folder' &&
(provider.canCreate ? provider.canCreate(node.id) : true);
const rowClasses = [
styles.nodeRow,
@ -473,7 +449,7 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
tabIndex={-1}
style={{ opacity: node.neutralize ? 1 : 0.35 }}
>
{_NEUTRALIZE_EMOJI}
{node.neutralize ? _NEUTRALIZE_ON_EMOJI : _NEUTRALIZE_OFF_EMOJI}
</button>
)}
</div>
@ -520,6 +496,9 @@ export function FormGeneratorTree<T = any>({
const [filterText, setFilterText] = useState('');
/** Folders we expanded and confirmed have no visible children → hide chevron like a real leaf */
const [confirmedEmptyFolderIds, setConfirmedEmptyFolderIds] = useState(() => new Set<string>());
/** Per-node set of in-flight action keys (e.g. scope/neutralize/rag) so rows
* can render a spinner over the corresponding button. */
const [pendingActions, setPendingActions] = useState<Map<string, Set<string>>>(() => new Map());
const lastSelectedIdRef = useRef<string | null>(null);
const treeContentRef = useRef<HTMLDivElement>(null);
/** Tracks node ids for which auto-expand has already fired (one-shot). */
@ -667,25 +646,11 @@ export function FormGeneratorTree<T = any>({
const _handleToggleExpand = useCallback(
async (id: string) => {
const wasExpanded = expandedIds.has(id);
const node = nodes.find((n) => n.id === id);
if (node && !wasExpanded) {
const childMap = _buildChildMap(nodes);
const existingChildren = childMap.get(id);
if (!existingChildren || existingChildren.length === 0) {
const childNodes = await provider.loadChildren(id, ownership);
if (childNodes.length > 0) {
setNodes((prev) => [...prev, ...childNodes]);
setConfirmedEmptyFolderIds((prev) => {
const next = new Set(prev);
next.delete(id);
return next;
});
} else if (node.type === 'folder') {
setConfirmedEmptyFolderIds((prev) => new Set(prev).add(id));
}
};
_collectDescendants(id);
if (wasExpanded) {
// Collapse: remove all descendants from nodes state and expandedIds.
const descendantIds = new Set(_collectDescendantIds(id, nodes));
setExpandedIds((prev) => {
const next = new Set(prev);
next.delete(id);
@ -693,17 +658,30 @@ export function FormGeneratorTree<T = any>({
return next;
});
setNodes((prev) => prev.filter((n) => !descendantIds.has(n.id)));
} else {
// Expand: load children from backend (always fresh).
setExpandedIds((prev) => new Set([...prev, id]));
return;
}
// Expand: load children from backend (fresh) and track empty folders so
// we can hide the chevron for confirmed-empty ones.
setExpandedIds((prev) => new Set([...prev, id]));
const childMap = _buildChildMap(nodes);
const existingChildren = childMap.get(id);
if (!existingChildren || existingChildren.length === 0) {
const childNodes = await provider.loadChildren(id, ownership);
if (childNodes.length > 0) {
setNodes((prev) => _mergeNodes(prev, childNodes));
setConfirmedEmptyFolderIds((prev) => {
const next = new Set(prev);
next.delete(id);
return next;
});
} else if (node?.type === 'folder') {
setConfirmedEmptyFolderIds((prev) => new Set(prev).add(id));
}
setTimeout(() => {
_scrollExpandedNodeToCenter(id);
}, 50);
}
setTimeout(() => {
_scrollExpandedNodeToCenter(id);
}, 50);
},
[nodes, expandedIds, provider, ownership, _mergeNodes],
);
@ -818,24 +796,18 @@ export function FormGeneratorTree<T = any>({
if (!trimmed) return;
try {
const newNode = await provider.createChild(parentId, trimmed);
<<<<<<< HEAD
setNodes((prev) => _mergeNodes(prev, [newNode]));
// The provider may have re-parented `newNode` (e.g. onto a synth-root)
// when `parentId === null`; expand whichever parent the resulting node
// actually points at, so the new folder is visible.
const visibleParent = newNode.parentId ?? null;
if (visibleParent) {
setExpandedIds((prev) => new Set(prev).add(visibleParent));
=======
setNodes((prev) => [...prev, newNode]);
if (parentId) {
setConfirmedEmptyFolderIds((prev) => {
const next = new Set(prev);
next.delete(parentId);
next.delete(visibleParent);
return next;
});
setExpandedIds((prev) => new Set(prev).add(parentId));
>>>>>>> ae63020 (finished file tree folder selection in file create node)
setExpandedIds((prev) => new Set(prev).add(visibleParent));
}
} catch {
await _handleRefresh();
@ -1203,11 +1175,7 @@ export function FormGeneratorTree<T = any>({
</div>
)}
<<<<<<< HEAD
{selectable && selectedIds.size > 0 && batchActions.length > 0 && (
=======
{selectedIds.size > 0 && batchActions.length > 0 && !hideRowActionButtons && (
>>>>>>> 7fb9645 (workign on folder location in file create node)
{selectable && selectedIds.size > 0 && batchActions.length > 0 && !hideRowActionButtons && (
<div className={styles.batchToolbar}>
<span className={styles.batchCount}>{selectedIds.size} selected</span>
{batchActions.map((action: TreeBatchAction) => {

View file

@ -56,6 +56,33 @@ function _mapFileToNode(file: FileData, ownership: Ownership): TreeNode {
};
}
/** Stable synthetic root id per ownership scope. The real top-level
* folders/files attach their `parentId` to this id once we re-parent them
* in `loadChildren`. The id stays inside the FE provider; the backend
* never sees it. */
const _SYNTH_ROOT_ID = (ownership: Ownership): string => `__filesRoot:${ownership}`;
/** Build the synthetic root node. Its only job is to:
* - act as a drop-target for moving items back to top-level,
* - expose a global neutralize/scope toggle that cascades to every
* top-level descendant.
* Its scope/neutralize values are intentionally `undefined` (= "no own
* state") the icons render an indeterminate state and a click sets the
* intent on every owned descendant. */
function _makeSyntheticRoot(ownership: Ownership): TreeNode {
return {
id: _SYNTH_ROOT_ID(ownership),
name: '/',
type: 'folder',
parentId: null,
ownership,
icon: <FaFolder style={{ color: '#666' }} />,
defaultExpanded: true,
scope: 'personal',
neutralize: false,
};
}
export function createFolderFileProvider(options: { includeFiles?: boolean } = {}): TreeNodeProvider {
const includeFiles = options.includeFiles !== false;
const ownerParam = (ownership: Ownership) => (ownership === 'own' ? 'me' : 'shared');
@ -127,14 +154,18 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
const foldersRes = await api.get('/api/files/folders/tree', { params: { owner } });
const allFolders: FolderData[] = foldersRes.data ?? [];
const childFolders = allFolders.filter((f) => (f.parentId ?? null) === parentId);
nodes.push(...childFolders.map((f) => _mapFolderToNode(f, ownership, allFolders, includeFiles)));
const childFolders = allFolders.filter((f) => (f.parentId ?? null) === apiParentId);
const folderNodes = childFolders.map((f) => _mapFolderToNode(f, ownership, allFolders, includeFiles));
if (apiParentId === null) {
for (const n of folderNodes) n.parentId = synthRootId;
}
nodes.push(...folderNodes);
if (includeFiles) {
try {
const filters: Record<string, any> = {};
if (parentId) {
filters.folderId = parentId;
if (apiParentId) {
filters.folderId = apiParentId;
}
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
const filesRes = await api.get('/api/files/list', {
@ -147,12 +178,16 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
} else if (Array.isArray(data)) {
rawFiles = data;
}
let matched = rawFiles.filter((f) => (f.folderId ?? null) === parentId);
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);
}
nodes.push(...matched.map((f) => _mapFileToNode(f, ownership)));
const fileNodes = matched.map((f) => _mapFileToNode(f, ownership));
if (apiParentId === null) {
for (const n of fileNodes) n.parentId = synthRootId;
}
nodes.push(...fileNodes);
} catch {
// file list may fail for shared trees; folders still render
}
@ -225,8 +260,8 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
: targetParentId;
await Promise.all(
ids.map((id) => {
if (_isFile(id)) return api.put(`/api/files/${id}`, { folderId: targetParentId });
return api.post(`/api/files/folders/${id}/move`, { parentId: targetParentId });
if (_isFile(id)) return api.put(`/api/files/${id}`, { folderId: apiTarget });
return api.post(`/api/files/folders/${id}/move`, { parentId: apiTarget });
}),
);
},

View file

@ -10,6 +10,7 @@ import { Outlet, useLocation } from 'react-router-dom';
import { FeatureProvider, useFeatureStore } from '../stores/featureStore';
import { MandateNavigation } from '../components/Navigation/MandateNavigation';
import { UserSection } from '../components/Navigation/UserSection';
import { RagRunningBadge } from '../components/RagRunningBadge/RagRunningBadge';
import { KEEP_ALIVE_ROUTES, hideFeatureOutlet } from '../config/keepAliveRoutes';
import type { KeepAliveEntry, KeepAliveScopedEntry, KeepAliveUnscopedEntry } from '../types/keepAlive.types';
import { isKeepAliveScoped } from '../types/keepAlive.types';

View file

@ -559,7 +559,6 @@ const TaskCard: React.FC<TaskCardProps> = ({
dismissing = false,
}) => {
const { t } = useLanguage();
const { request } = useApiRequest();
const { handleFileUpload } = useFileOperations();
const [formData, setFormData] = useState<Record<string, unknown>>({});
const [formPopupOpen, setFormPopupOpen] = useState(false);