fix: resolve file conflicts

This commit is contained in:
yassinedorbozgithub
2025-04-11 20:54:50 +01:00
161 changed files with 1988 additions and 1409 deletions

View File

@@ -24,11 +24,13 @@ export const GenericFormDialog = <T,>({
Form,
rowKey,
payload: data,
editText,
addText,
...rest
}: GenericFormDialogProps<T>) => {
const { t } = useTranslate();
const hasRow = rowKey ? data?.[rowKey] : data;
const translationKey = hasRow ? rest.editText : rest.addText;
const translationKey = hasRow ? editText : addText;
return (
<Form

View File

@@ -392,6 +392,7 @@ export default function NlpSample() {
`nlpsample/export${type ? `?type=${type}` : ""}`,
)}
startIcon={<DownloadIcon />}
disabled={dataGridProps?.rows?.length === 0}
>
{t("button.export")}
</Button>

View File

@@ -55,10 +55,10 @@ export const NlpValues = ({ entityId }: { entityId: string }) => {
const canHaveSynonyms = nlpEntity?.lookups?.[0] === NlpLookups.keywords;
const { onSearch, searchPayload } = useSearch<INlpValue>({
$eq: [{ entity: entityId }],
$or: ["doc", "value"]
$or: ["doc", "value"],
});
const { dataGridProps } = useFind(
{ entity: EntityType.NLP_VALUE },
{ entity: EntityType.NLP_VALUE, format: Format.FULL },
{
params: searchPayload,
},
@@ -103,7 +103,7 @@ export const NlpValues = ({ entityId }: { entityId: string }) => {
],
t("label.operations"),
);
const synonymsColumn = {
const synonymsColumn = {
flex: 3,
field: "synonyms",
headerName: t("label.synonyms"),
@@ -125,6 +125,24 @@ export const NlpValues = ({ entityId }: { entityId: string }) => {
disableColumnMenu: true,
renderHeader,
},
{
flex: 2,
field: "nlpSamplesCount",
align: "center",
headerName: t("label.nlp_samples_count"),
sortable: true,
disableColumnMenu: true,
headerAlign: "center",
renderHeader,
renderCell: ({ row }) => (
<Chip
sx={{ alignContent: "center" }}
id={row.id}
label={row.nlpSamplesCount}
variant="inbox"
/>
),
},
{
flex: 3,
field: "doc",

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@@ -7,6 +7,7 @@
*/
import { Grid } from "@mui/material";
import { useMemo } from "react";
import PluginIcon from "@/app-components/svg/toolbar/PluginIcon";
import { useFind } from "@/hooks/crud/useFind";
@@ -17,18 +18,22 @@ import { Block, StyledTitle } from "./Aside";
export const CustomBlocks = () => {
const { t } = useTranslate();
const { data: customBlocks } = useFind(
const { data: customBlocks = [] } = useFind(
{ entity: EntityType.CUSTOM_BLOCK },
{ hasCount: false },
);
const memoizedCustomBlocks = useMemo(
() => customBlocks.sort((a, b) => a.id.localeCompare(b.id)),
[customBlocks],
);
return customBlocks?.length ? (
return memoizedCustomBlocks.length ? (
<>
<Grid mb="2">
<StyledTitle>{t("title.custom_blocks")}</StyledTitle>
</Grid>
<Grid container>
{customBlocks?.map((customBlock) => (
{memoizedCustomBlocks.map((customBlock) => (
<Block
key={customBlock.id}
title={t(`title.${customBlock.namespace}`, {

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@@ -9,13 +9,21 @@
import { createContext, ReactNode, useContext } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { IBlockAttributes, IBlock } from "@/types/block.types";
import { IBlock, IBlockAttributes } from "@/types/block.types";
// Create a custom context for the block value
const BlockContext = createContext<IBlock | undefined>(undefined);
// Custom hook to use block context
export const useBlock = () => useContext(BlockContext);
export const useBlock = () => {
const context = useContext(BlockContext);
if (!context) {
throw new Error("useBlock must be used within an BlockContext");
}
return context;
};
// This component wraps FormProvider and adds block to its context
function BlockFormProvider({
@@ -23,7 +31,7 @@ function BlockFormProvider({
methods,
block,
}: {
methods: UseFormReturn<IBlockAttributes, any, undefined>;
methods: UseFormReturn<IBlockAttributes>;
block: IBlock | undefined;
children: ReactNode;
}) {

View File

@@ -11,15 +11,190 @@ import styled from "@emotion/styled";
import {
DefaultLinkFactory,
DefaultLinkWidget,
NodeModel,
PortModel
} from "@projectstorm/react-diagrams";
import { AdvancedLinkModel } from "./AdvancedLinkModel";
const PROXIMITY_THRESHOLD = 500;
const MIN_DISTANCE = 0.1;
const MAX_DISTANCE = 2000;
const CONTROL_POINT_PADDING = 10;
const BACKWARD_LINK_THRESHOLD = 12; // pixels
const MIN_SCALE_FACTOR = 1.5;
const MAX_SCALE_FACTOR = 2.0;
interface Point {
x: number;
y: number;
}
interface Boundaries {
left: number,
right: number,
top: number,
bottom: number,
}
interface Dimensions {
width: number,
height: number,
}
// Helper function to get port dimensions
const getPortDimensions = (port: PortModel): Dimensions => {
return {
width: port.width || CONTROL_POINT_PADDING,
height: port.height || CONTROL_POINT_PADDING,
};
};
// Helper function to calculate port center point
const getPortCenterPoint = (port: PortModel): Point => {
const portSize = getPortDimensions(port);
return {
x: port.getPosition().x + portSize.width / 2,
y: port.getPosition().y + portSize.height / 2,
};
};
/**
* Logarithmic scaling function that adjusts between 1.5 and 2 based on distance,
* minimum distance, and maximum distance.
* @param distance - The distance to scale.
* @param minDistance - A small value to prevent division by zero or too small values.
* @param maxDistance - The maximum expected distance.
*/
const logFactor = (
distance: number,
minDistance: number,
maxDistance: number
): number => {
const scale = Math.log(distance + minDistance) / Math.log(maxDistance + minDistance);
return MIN_SCALE_FACTOR + scale * (MAX_SCALE_FACTOR - MIN_SCALE_FACTOR); // Scaled to range between 1.5 and 2
};
/**
* Calculates the horizontal (X-axis) overlap in pixels between two node boundaries.
* Returns 0 if there is no overlap.
*/
const calculateXOverlap = (
sourceBounds: Boundaries,
targetBounds: Boundaries
): number => {
return Math.max(
0,
Math.min(sourceBounds.right, targetBounds.right) -
Math.max(sourceBounds.left, targetBounds.left)
);
};
/**
* Calculates the vertical (Y-axis) overlap in pixels between two node boundaries.
* Returns 0 if there is no overlap.
*/
const calculateYOverlap = (
sourceBounds: Boundaries,
targetBounds: Boundaries
): number => {
return Math.max(
0,
Math.min(sourceBounds.bottom, targetBounds.bottom) -
Math.max(sourceBounds.top, targetBounds.top)
);
};
/**
* Converts an overlap amount into a ratio (0 to 1) based on the larger of the two node dimensions.
* Useful for dynamically adjusting offsets based on how much nodes visually intersect.
*/
const calculateOverlapRatio = (
overlapAmount: number,
sourceDimension: number,
targetDimension: number
): number => {
const maxRange = Math.max(sourceDimension, targetDimension);
return overlapAmount / maxRange;
};
/**
* Computes the Euclidean distance between two points.
* Used to scale offsets and curve control points based on how far apart nodes are.
*/
const calculateDistance = (startPoint: Point, endPoint: Point): number => {
return Math.sqrt(
Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2)
);
};
/**
* Calculates the bounding box of a node based on its position and size.
* Returns an object with `left`, `right`, `top`, and `bottom` properties representing the node's edges.
*/
const calculateNodeBoundaries = (node: NodeModel): Boundaries => {
return {
left: node.getPosition().x,
right: node.getPosition().x + node.width,
top: node.getPosition().y,
bottom: node.getPosition().y + node.height,
};
};
/**
* Calculates the width and height of a node based on the position of one of its ports.
*
* This approach avoids relying on the node's width and height properties,
* which may not be accurate or available at render time due to asynchronous rendering behavior.
*
* Instead, it uses the relative position of the port to infer the size of the node.
* Assumes that the port's position reflects the visual layout and placement on the node.
*
* @param port - A PortModel instance attached to the node
* @returns An object containing the inferred width and height of the node
*/
const calculateNodeDimension = (port: PortModel): Dimensions => {
// Get the top-left position of the node
const nodePos = port.getNode().getPosition();
// Get the top-left position of the port
const portPos = port.getPosition();
// Width is the horizontal distance from the node's left to the port's right edge
const width = (portPos.x - nodePos.x) + port.width;
// Height is estimated by doubling the vertical offset from the node to the port
// (port is vertically centered), then adding the port's height
const height = Math.abs(portPos.y - nodePos.y) * 2 + port.height;
return { width, height };
};
/**
* Calculates a single control point for a cubic Bézier curve.
* Adjusts based on direction, dynamic offset, and node boundaries.
*/
const calculateControlPoint = (
anchor: Point,
horizontalOffset: number,
verticalOffset: number,
verticalDirection: number,
nodeBounds: Boundaries,
isStart: boolean,
controlPointPadding: number
): Point => {
let x =
anchor.x + (isStart ? horizontalOffset : -horizontalOffset);
let y =
anchor.y + (isStart ? verticalDirection * verticalOffset : -verticalDirection * verticalOffset);
// Apply minimum horizontal constraint
x = isStart
? Math.max(x, nodeBounds.right + controlPointPadding)
: Math.min(x, nodeBounds.left - controlPointPadding);
// Apply vertical constraint based on direction
y =
verticalDirection > 0
? isStart
? Math.max(y, nodeBounds.bottom + controlPointPadding)
: Math.min(y, nodeBounds.top - controlPointPadding)
: isStart
? Math.min(y, nodeBounds.top - controlPointPadding)
: Math.max(y, nodeBounds.bottom + controlPointPadding);
return { x, y };
};
const createCurvedPath = (start: Point, end: Point, nodeHeight: number) => {
const controlPoint1X = start.x + nodeHeight - 20;
const controlPoint1Y = start.y - nodeHeight;
@@ -28,6 +203,74 @@ const createCurvedPath = (start: Point, end: Point, nodeHeight: number) => {
return `M ${start.x},${start.y} C ${controlPoint1X},${controlPoint1Y} ${controlPoint2X},${controlPoint2Y} ${end.x},${end.y}`;
};
const createBackwardCurvedPath = (
sourcePort: PortModel,
targetPort: PortModel,
) => {
// Set a threshold for node proximity, below which dynamic adjustments to offsets are applied
// This helps in reducing abrupt curve steepness when nodes are close to each other
const proximityThreshold = PROXIMITY_THRESHOLD;
const minDistance = MIN_DISTANCE;
const maxDistance = MAX_DISTANCE;
const sourceNode = sourcePort.getNode();
const targetNode = targetPort.getNode();
// Get node dimensions
const { width: sourceNodeWidth, height: sourceNodeHeight } = calculateNodeDimension(sourcePort);
const { width: targetNodeWidth, height: targetNodeHeight } = calculateNodeDimension(targetPort);
// Get node boundaries
const sourceNodeBounds: Boundaries = calculateNodeBoundaries(sourceNode);
const targetNodeBounds: Boundaries = calculateNodeBoundaries(targetNode);
// **NEW:** Adjust `start` and `end` to match the exact center of ports
const adjustedStart: Point = getPortCenterPoint(sourcePort);
const adjustedEnd: Point = getPortCenterPoint(targetPort);
// Calculate the distance between nodes
const nodeDistance: number = calculateDistance(adjustedStart, adjustedEnd);
// Use node dimensions and distance to calculate dynamic offsets
const horizontalOffset: number = Math.max(sourceNodeWidth, targetNodeWidth);
const verticalOffset: number = Math.max(sourceNodeHeight, targetNodeHeight);
// Dynamic factor, adjusting horizontal and vertical offsets based on the distance
let adjustedHorizontalOffset: number = horizontalOffset * logFactor(nodeDistance, minDistance, maxDistance);
let adjustedVerticalOffset: number = verticalOffset * logFactor(nodeDistance, minDistance, maxDistance);
// Horizontal overlap ratio (0 = no overlap, 1 = fully overlapping horizontally)
const xOverlapAmount: number = calculateXOverlap(sourceNodeBounds, targetNodeBounds);
const xOverlapRatio: number = calculateOverlapRatio(xOverlapAmount, sourceNodeWidth, targetNodeWidth);
// Vertical overlap ratio (0 = no overlap, 1 = fully overlapping vertically)
const yOverlapAmount: number = calculateYOverlap(sourceNodeBounds, targetNodeBounds);
const yOverlapRatio: number = calculateOverlapRatio(yOverlapAmount, sourceNodeHeight, targetNodeHeight);
// Determine vertical direction for Y alignment
const verticalDirection: number = adjustedEnd.y >= adjustedStart.y ? 1 : -1;
// If Node Distance is small, multiply offsets by overlap ratios
// to avoid abrupt curve steepness
if (nodeDistance < proximityThreshold) {
adjustedHorizontalOffset *= xOverlapRatio;
adjustedVerticalOffset *= yOverlapRatio;
}
// Compute control points with dynamic offset
const controlPoint1 = calculateControlPoint(
adjustedStart,
adjustedHorizontalOffset,
adjustedVerticalOffset,
verticalDirection,
sourceNodeBounds,
true,
CONTROL_POINT_PADDING
);
const controlPoint2 = calculateControlPoint(
adjustedEnd,
adjustedHorizontalOffset,
adjustedVerticalOffset,
verticalDirection,
targetNodeBounds,
false,
CONTROL_POINT_PADDING
);
// Return the cubic Bezier curve
return `M ${adjustedStart.x},${adjustedStart.y} C ${controlPoint1.x},${controlPoint1.y} ${controlPoint2.x},${controlPoint2.y} ${adjustedEnd.x},${adjustedEnd.y}`;
};
namespace S {
export const Keyframes = keyframes`
@@ -68,41 +311,52 @@ export class AdvancedLinkFactory extends DefaultLinkFactory {
model: AdvancedLinkModel,
selected: boolean,
path: string,
) {
const isSelfLoop =
model.getSourcePort().getNode() === model.getTargetPort().getNode();
) {
const backwardLinkThreshold = BACKWARD_LINK_THRESHOLD;
const sourcePort = model.getSourcePort();
const targetPort = model.getTargetPort();
const isSelfLoop = sourcePort.getNode() === targetPort.getNode();
const sourcePortPosition = sourcePort.getPosition();
const targetPortPosition = targetPort.getPosition();
const startPoint: Point = {
x: sourcePortPosition.x + 20,
y: sourcePortPosition.y + 20,
};
const endPoint: Point = {
x: targetPortPosition.x + 20,
y: targetPortPosition.y + 20,
};
// Check if it's a backward link (moving left)
const isBackward = startPoint.x - endPoint.x > backwardLinkThreshold;
if (isSelfLoop) {
// Adjust the path to create a curve
const sourcePortPosition = model.getSourcePort().getPosition();
const targetPortPosition = model.getTargetPort().getPosition();
const startPoint: Point = {
x: sourcePortPosition.x + 20,
y: sourcePortPosition.y + 20,
};
const endPoint: Point = {
x: targetPortPosition.x + 20,
y: targetPortPosition.y + 20,
};
const targetPortHeight = model.getTargetPort().height;
const targetNdeHeight =
(model.getTargetPort().getPosition().y -
model.getTargetPort().getNode().getPosition().y) *
// Adjust start Point to match the exact source port's centre
const adjustedStartPoint: Point = getPortCenterPoint(sourcePort);
// Handle self-loop (curved) links
const targetPortHeight = targetPort.height;
const targetNodeHeight =
(targetPort.getPosition().y -
targetPort.getNode().getPosition().y) *
2 +
targetPortHeight;
path = createCurvedPath(startPoint, endPoint, targetNdeHeight);
}
return (
path = createCurvedPath(adjustedStartPoint, endPoint, targetNodeHeight);
} else if (isBackward) {
// Handle backward (leftward) link with refined function
path = createBackwardCurvedPath(sourcePort, targetPort);
}
return (
<S.Path
selected={selected}
stroke={
selected ? model.getOptions().selectedColor : model.getOptions().color
selected
? model.getOptions().selectedColor
: model.getOptions().color
}
strokeWidth={model.getOptions().width}
d={path}
/>
);
}
}
}

View File

@@ -8,7 +8,7 @@
import getConfig from "next/config";
import { useRouter } from "next/router";
import { createContext, ReactNode, useEffect, useState } from "react";
import { createContext, ReactNode } from "react";
import {
QueryObserverResult,
RefetchOptions,
@@ -25,7 +25,6 @@ import { useSubscribeBroadcastChannel } from "@/hooks/useSubscribeBroadcastChann
import { useTranslate } from "@/hooks/useTranslate";
import { RouterType } from "@/services/types";
import { IUser } from "@/types/user.types";
import { getFromQuery } from "@/utils/URL";
export interface AuthContextValue {
user: IUser | undefined;
@@ -51,10 +50,8 @@ const { publicRuntimeConfig } = getConfig();
export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => {
const router = useRouter();
const [search, setSearch] = useState("");
const hasPublicPath = PUBLIC_PATHS.includes(router.pathname);
const { i18n } = useTranslate();
const [isReady, setIsReady] = useState(false);
const queryClient = useQueryClient();
const updateLanguage = (lang: string) => {
i18n.changeLanguage(lang);
@@ -66,11 +63,11 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => {
};
const authRedirection = async (isAuthenticated: boolean) => {
if (isAuthenticated) {
const redirect = getFromQuery({ search, key: "redirect" });
const nextPage = redirect && decodeURIComponent(redirect);
if (nextPage?.startsWith("/")) {
await router.push(nextPage);
if (
router.query.redirect &&
router.query.redirect.toString().startsWith("/")
) {
await router.push(router.query.redirect.toString());
} else if (hasPublicPath) {
await router.push(RouterType.HOME);
}
@@ -109,14 +106,9 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => {
router.reload();
});
useEffect(() => {
const search = location.search;
setSearch(search);
setIsReady(true);
}, []);
if (!isReady || isLoading) return <Progress />;
if (isLoading) {
return <Progress />;
}
return (
<AuthContext.Provider

View File

@@ -19,6 +19,7 @@ export interface INlpValueAttributes {
expressions?: string[];
metadata?: Record<string, any>;
builtin?: boolean;
nlpSamplesCount?: number;
}
export interface INlpValueStub extends IBaseSchema, INlpValueAttributes {}

View File

@@ -8,26 +8,6 @@
import qs from "qs";
export const getFromQuery = ({
key,
search,
defaultValue = "",
}: {
key: string;
search?: string;
defaultValue?: string;
}) => {
try {
const paramsString = search || window.location.search;
const searchParams = new URLSearchParams(paramsString);
const loadCampaign = searchParams.get(key) || defaultValue;
return loadCampaign;
} catch (e) {
return defaultValue;
}
};
export const buildURL = (baseUrl: string, relativePath: string): string => {
try {
return new URL(relativePath).toString();