f4b404's picture
Upload folder using huggingface_hub
f55c62a verified
Raw
History Blame Contribute Delete
8.24 kB
export interface DiffItem {
path: string[];
type: 'CHANGE' | 'ADD' | 'REMOVE';
oldValue?: any;
newValue?: any;
}
function isObject(val: any) {
return val != null && typeof val === 'object' && !Array.isArray(val);
}
function isEmptyValue(val: any) {
return (
val == null ||
(Array.isArray(val) && val.length === 0) ||
(isObject(val) && Object.keys(val).length === 0)
);
}
export function sortKeys(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(sortKeys);
}
if (isObject(obj)) {
return Object.keys(obj)
.sort()
.reduce((sortedObj: any, key) => {
sortedObj[key] = sortKeys(obj[key]);
return sortedObj;
}, {});
}
return obj;
}
export function getObjectDiff(
obj1: any,
obj2: any,
path: string[] = []
): DiffItem[] {
const diffs: DiffItem[] = [];
const ignoredKeys = new Set([
'uuid',
'trusted',
'encryptedPassword',
'showChanges',
'ip',
'accessToken',
]);
if (obj1 == null && obj2 == null) return diffs;
// Treat empty objects/arrays as equal to null/undefined to avoid ghost diffs
if (isEmptyValue(obj1) && isEmptyValue(obj2)) return diffs;
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1 === obj2) return [];
try {
if (JSON.stringify(obj1) === JSON.stringify(obj2)) return [];
} catch (e) {
console.warn('Fast array comparison failed:', e);
}
const lastPath = path.length > 0 ? path[path.length - 1] : '';
const isPresets = lastPath === 'presets';
const isCatalogModifications = lastPath === 'catalogModifications';
const getKey = (item: any) => {
if (!isObject(item)) {
return String(item);
}
if (isCatalogModifications && item.id && item.type)
return `${item.id}_${item.type}`;
if (item.instanceId) return item.instanceId;
if (item.id) return item.id;
if (item.pattern) return item.pattern;
if (item.condition && Array.isArray(item.addons)) {
try {
return `grouping:${item.condition}:${JSON.stringify(item.addons)}`;
} catch {
return item.condition;
}
}
if (item.condition) return item.condition;
if (item.expression) return item.expression;
if (Array.isArray(item.addons)) {
try {
return `addons:${JSON.stringify(item.addons)}`;
} catch {
return null;
}
}
if (item.key) return item.key;
if (item.name) return item.name;
return null;
};
const keys1 = obj1.map(getKey);
const keys2 = obj2.map(getKey);
const canKey =
keys1.every((k) => k !== null) &&
keys2.every((k) => k !== null) &&
new Set(keys1).size === obj1.length &&
new Set(keys2).size === obj2.length;
if (canKey) {
const oldMap = new Map();
const oldOrder: any[] = [];
obj1.forEach((item: any) => {
const key = getKey(item);
oldMap.set(key, item);
oldOrder.push(key);
});
const newMap = new Map();
const newOrder: any[] = [];
obj2.forEach((item: any) => {
const key = getKey(item);
newMap.set(key, item);
newOrder.push(key);
});
const getLabel = (key: any) => {
const item = newMap.get(key) || oldMap.get(key);
if (item?.expression && item?.score !== undefined) {
return `${item.expression} (Score: ${item.score})`;
}
if (item?.addons && Array.isArray(item.addons)) {
const firstAddon = item.addons[0] || '';
const count = item.addons.length;
const summary = firstAddon
? ` (${firstAddon}${count > 1 ? ` +${count - 1}` : ''})`
: '';
return `Group: ${item.condition || 'true'}${summary}`;
}
if (item?.name) {
if (isCatalogModifications && item.type) {
return `${item.name} (${item.type})`;
}
return item.name;
}
return item?.options?.name || item?.instanceId || item?.id || key;
};
oldMap.forEach((val, key) => {
if (!newMap.has(key)) {
const originalIndex = obj1.findIndex((i: any) => getKey(i) === key);
diffs.push({
path: [...path, `[${originalIndex}]`],
type: 'REMOVE',
oldValue: isPresets ? `Deleted: ${getLabel(key)}` : val,
});
}
});
newMap.forEach((val, key) => {
const newIndex = obj2.findIndex((i: any) => getKey(i) === key);
if (!oldMap.has(key)) {
diffs.push({
path: [...path, `[${newIndex}]`],
type: 'ADD',
newValue: val,
});
} else {
const oldVal = oldMap.get(key);
diffs.push(...getObjectDiff(oldVal, val, [...path, `[${newIndex}]`]));
}
});
const intersectionOld = oldOrder.filter((key) => newOrder.includes(key));
const intersectionNew = newOrder.filter((key) => oldOrder.includes(key));
const isOrderChanged =
intersectionOld.length !== intersectionNew.length ||
intersectionOld.some((key, i) => key !== intersectionNew[i]);
if (isOrderChanged) {
diffs.push({
path: [...path],
type: 'CHANGE',
oldValue: oldOrder.map(getLabel),
newValue: newOrder.map(getLabel),
});
}
return diffs;
}
let i = 0;
let j = 0;
while (i < obj1.length || j < obj2.length) {
if (i >= obj1.length) {
diffs.push({
path: [...path, `[${j}]`],
type: 'ADD',
newValue: obj2[j],
});
j++;
continue;
}
if (j >= obj2.length) {
diffs.push({
path: [...path, `[${i}]`],
type: 'REMOVE',
oldValue: obj1[i],
});
i++;
continue;
}
const diff = getObjectDiff(obj1[i], obj2[j], [...path, `[${j}]`]);
if (diff.length === 0) {
i++;
j++;
continue;
}
let isDeletion = false;
if (i + 1 < obj1.length) {
if (getObjectDiff(obj1[i + 1], obj2[j], []).length === 0) {
isDeletion = true;
}
}
let isInsertion = false;
if (j + 1 < obj2.length) {
if (getObjectDiff(obj1[i], obj2[j + 1], []).length === 0) {
isInsertion = true;
}
}
if (isDeletion && !isInsertion) {
diffs.push({
path: [...path, `[${i}]`],
type: 'REMOVE',
oldValue: obj1[i],
});
i++;
} else if (isInsertion && !isDeletion) {
diffs.push({
path: [...path, `[${j}]`],
type: 'ADD',
newValue: obj2[j],
});
j++;
} else {
diffs.push(...diff);
i++;
j++;
}
}
return diffs;
}
if (!isObject(obj1) || !isObject(obj2)) {
if (obj1 === obj2) return diffs;
try {
if (JSON.stringify(obj1) === JSON.stringify(obj2)) return diffs;
} catch {}
return [
{
path,
type: 'CHANGE',
oldValue: obj1,
newValue: obj2,
},
];
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
for (const key of keys1) {
if (ignoredKeys.has(key)) continue;
if (!Object.prototype.hasOwnProperty.call(obj2, key)) {
if (obj1[key] == null || isEmptyValue(obj1[key])) continue;
diffs.push({
path: [...path, key],
type: 'REMOVE',
oldValue: obj1[key],
});
} else {
diffs.push(...getObjectDiff(obj1[key], obj2[key], [...path, key]));
}
}
for (const key of keys2) {
if (ignoredKeys.has(key)) continue;
if (!Object.prototype.hasOwnProperty.call(obj1, key)) {
if (obj2[key] == null || isEmptyValue(obj2[key])) continue;
diffs.push({
path: [...path, key],
type: 'ADD',
newValue: obj2[key],
});
}
}
return diffs;
}
export function formatValue(value: any): string {
if (typeof value === 'object' && value !== null) {
try {
return JSON.stringify(value, null, 2);
} catch {
return '[Circular Reference]';
}
}
return String(value);
}