@naniteninja/profile-comparison-lib 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -200
- package/fesm2022/naniteninja-profile-comparison-lib.mjs +106 -846
- package/fesm2022/naniteninja-profile-comparison-lib.mjs.map +1 -1
- package/index.d.ts +43 -66
- package/package.json +2 -4
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, Optional, Inject, EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core';
|
|
3
|
-
import { switchMap, map, catchError,
|
|
4
|
-
import { from,
|
|
5
|
-
import * as use from '@tensorflow-models/universal-sentence-encoder';
|
|
6
|
-
import '@tensorflow/tfjs';
|
|
2
|
+
import { InjectionToken, Injectable, Optional, Inject, inject, EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core';
|
|
3
|
+
import { switchMap as switchMap$1, map as map$1, catchError, tap as tap$1 } from 'rxjs/operators';
|
|
4
|
+
import { from, switchMap, map, of, Observable, BehaviorSubject, throwError, tap, retryWhen, concatMap, timer, forkJoin, catchError as catchError$1 } from 'rxjs';
|
|
7
5
|
import * as i1 from '@angular/common/http';
|
|
8
|
-
import { HttpHeaders, HttpClientModule } from '@angular/common/http';
|
|
6
|
+
import { HttpHeaders, HttpClient, HttpClientModule } from '@angular/common/http';
|
|
9
7
|
import * as i2 from '@angular/common';
|
|
10
8
|
import { CommonModule } from '@angular/common';
|
|
11
9
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
12
10
|
|
|
11
|
+
/** Backend mode for the profile comparison component. */
|
|
12
|
+
var BackendMode;
|
|
13
|
+
(function (BackendMode) {
|
|
14
|
+
/** Use the real deployed backend (default). */
|
|
15
|
+
BackendMode[BackendMode["Real"] = 0] = "Real";
|
|
16
|
+
/** Use a local mock server at localhost:3000. */
|
|
17
|
+
BackendMode[BackendMode["Mock"] = 1] = "Mock";
|
|
18
|
+
})(BackendMode || (BackendMode = {}));
|
|
19
|
+
const BACKEND_MODE_URLS = {
|
|
20
|
+
[BackendMode.Real]: 'https://api.test.thecyrano.app/api/profile-comparison/api',
|
|
21
|
+
[BackendMode.Mock]: 'http://localhost:3000/api',
|
|
22
|
+
};
|
|
13
23
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* When not provided or getter returns null, the lib uses the direct path with keys.
|
|
24
|
+
* Legacy injection token for the backend base URL.
|
|
25
|
+
* Prefer using the [backendMode] or [backendUrl] @Input on the component instead.
|
|
26
|
+
* Kept for backward compatibility — lowest priority when resolving the URL.
|
|
18
27
|
*/
|
|
19
28
|
const PROFILE_COMPARISON_API_BASE_URL = new InjectionToken('PROFILE_COMPARISON_API_BASE_URL');
|
|
20
29
|
/** Optional getter: when true, lib and backend service log steps to console for diagnosis. */
|
|
21
30
|
const PROFILE_COMPARISON_VERBOSE_LOGGING = new InjectionToken('PROFILE_COMPARISON_VERBOSE_LOGGING');
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the active backend URL from inputs, with fallback to injection token.
|
|
33
|
+
*
|
|
34
|
+
* Priority:
|
|
35
|
+
* 1. Explicit backendUrl (custom URL string)
|
|
36
|
+
* 2. BackendMode enum (Real or Mock)
|
|
37
|
+
* 3. Legacy PROFILE_COMPARISON_API_BASE_URL token
|
|
38
|
+
*/
|
|
39
|
+
function resolveBackendUrl(backendUrl, backendMode, legacyTokenUrl) {
|
|
40
|
+
if (backendUrl != null && backendUrl.trim().length > 0) {
|
|
41
|
+
return backendUrl.trim();
|
|
42
|
+
}
|
|
43
|
+
const modeUrl = BACKEND_MODE_URLS[backendMode];
|
|
44
|
+
if (modeUrl) {
|
|
45
|
+
return modeUrl;
|
|
46
|
+
}
|
|
47
|
+
if (legacyTokenUrl != null && legacyTokenUrl.trim().length > 0) {
|
|
48
|
+
return legacyTokenUrl.trim();
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
22
52
|
|
|
23
53
|
const DB_NAME = 'profile-comparison-cache';
|
|
24
54
|
const DB_VERSION = 1;
|
|
@@ -180,780 +210,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
180
210
|
args: [{ providedIn: 'root' }]
|
|
181
211
|
}] });
|
|
182
212
|
|
|
183
|
-
class EmbeddingService {
|
|
184
|
-
model$ = null;
|
|
185
|
-
// Get embedding for a single text (returns Observable)
|
|
186
|
-
getEmbedding(text) {
|
|
187
|
-
return this.loadModel().pipe(switchMap((model) => from(model.embed([text]))), switchMap((embeddings) => {
|
|
188
|
-
const t2d = embeddings;
|
|
189
|
-
return from(t2d.array()).pipe(map((arr) => {
|
|
190
|
-
t2d.dispose();
|
|
191
|
-
return Float32Array.from(arr[0]);
|
|
192
|
-
}));
|
|
193
|
-
}), catchError((err) => throwError(() => new Error('Model failed to load or embed: ' + err))));
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Group-align two lists based on semantic similarity, allowing one-to-many relationships.
|
|
197
|
-
*
|
|
198
|
-
* Design goals:
|
|
199
|
-
* - Semantic relevance takes priority over original order or spacing.
|
|
200
|
-
* - Each term in listB is assigned to the single most similar anchor in listA (if above threshold).
|
|
201
|
-
* - For an anchor in listA with multiple highly related listB terms, they are emitted as consecutive rows:
|
|
202
|
-
* [A, B1], ['-', B2], ['-', B3], ... so they render adjacently and remain visually connected.
|
|
203
|
-
* - Low-similarity/unassigned terms from either side are appended at the end and never break groups.
|
|
204
|
-
*/
|
|
205
|
-
groupAlignLists(listA, listB, threshold = 0.25, preserveLeftOrder = true, exclusivityMargin = 0.06, leftCohesion = 0.23) {
|
|
206
|
-
// Short-circuit simple cases
|
|
207
|
-
if ((!listA || listA.length === 0) && (!listB || listB.length === 0)) {
|
|
208
|
-
return of([]);
|
|
209
|
-
}
|
|
210
|
-
if (!listA || listA.length === 0) {
|
|
211
|
-
return of(listB.map((b) => ({ left: '-', right: b, score: 0 })));
|
|
212
|
-
}
|
|
213
|
-
if (!listB || listB.length === 0) {
|
|
214
|
-
return of(listA.map((a) => ({ left: a, right: '-', score: 0 })));
|
|
215
|
-
}
|
|
216
|
-
return this.loadModel().pipe(switchMap((model) => forkJoin([
|
|
217
|
-
from(model.embed(listA)),
|
|
218
|
-
from(model.embed(listB))
|
|
219
|
-
]).pipe(switchMap(([tA, tB]) => {
|
|
220
|
-
return forkJoin([from(tA.array()), from(tB.array())]).pipe(map((tensorArrays) => {
|
|
221
|
-
const [arrA, arrB] = tensorArrays;
|
|
222
|
-
// Dispose tensors to avoid memory leaks
|
|
223
|
-
tA.dispose();
|
|
224
|
-
tB.dispose();
|
|
225
|
-
const aCount = listA.length;
|
|
226
|
-
const bCount = listB.length;
|
|
227
|
-
// Pre-convert to Float32Array for faster cosine computations
|
|
228
|
-
const vecA = new Array(aCount);
|
|
229
|
-
const vecB = new Array(bCount);
|
|
230
|
-
for (let i = 0; i < aCount; i++)
|
|
231
|
-
vecA[i] = Float32Array.from(arrA[i]);
|
|
232
|
-
for (let j = 0; j < bCount; j++)
|
|
233
|
-
vecB[j] = Float32Array.from(arrB[j]);
|
|
234
|
-
// Build full cross-list similarity matrix S[i][j] = sim(A_i, B_j)
|
|
235
|
-
const S = new Array(aCount);
|
|
236
|
-
for (let i = 0; i < aCount; i++) {
|
|
237
|
-
const row = new Array(bCount);
|
|
238
|
-
for (let j = 0; j < bCount; j++) {
|
|
239
|
-
const emb = this.cosineSimilarity(vecA[i], vecB[j]);
|
|
240
|
-
const lex = this.computeLexicalBoost(listA[i], listB[j]);
|
|
241
|
-
// Apply domain gating to prevent mismatched category-vs-activity alignments
|
|
242
|
-
const cap = this.domainCompatibilityCap(listA[i], listB[j]);
|
|
243
|
-
row[j] = Math.min(Math.max(emb, lex), cap);
|
|
244
|
-
}
|
|
245
|
-
S[i] = row;
|
|
246
|
-
}
|
|
247
|
-
// Build within-list similarity for A (A↔A) to allow left-side extras to attach to cohesive anchors.
|
|
248
|
-
// We use the same hybrid semantic/lexical metric.
|
|
249
|
-
const SAA = new Array(aCount);
|
|
250
|
-
for (let i = 0; i < aCount; i++) {
|
|
251
|
-
const rowAA = new Array(aCount);
|
|
252
|
-
for (let k = 0; k < aCount; k++) {
|
|
253
|
-
if (i === k) {
|
|
254
|
-
rowAA[k] = 1; // self
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
const embAA = this.cosineSimilarity(vecA[i], vecA[k]);
|
|
258
|
-
const lexAA = this.computeLexicalBoost(listA[i], listA[k]);
|
|
259
|
-
rowAA[k] = Math.max(embAA, lexAA);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
SAA[i] = rowAA;
|
|
263
|
-
}
|
|
264
|
-
// Build thresholded neighborhoods
|
|
265
|
-
const neighborsA = new Map();
|
|
266
|
-
const neighborsB = new Map();
|
|
267
|
-
for (let i = 0; i < aCount; i++) {
|
|
268
|
-
const row = [];
|
|
269
|
-
for (let j = 0; j < bCount; j++) {
|
|
270
|
-
const s = S[i][j];
|
|
271
|
-
if (s >= threshold) {
|
|
272
|
-
row.push({ j, score: s });
|
|
273
|
-
const rev = neighborsB.get(j) || [];
|
|
274
|
-
rev.push({ i, score: s });
|
|
275
|
-
neighborsB.set(j, rev);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// sort descending for deterministic preference lists
|
|
279
|
-
row.sort((a, b) => b.score - a.score);
|
|
280
|
-
if (row.length)
|
|
281
|
-
neighborsA.set(i, row);
|
|
282
|
-
}
|
|
283
|
-
// Identify exclusive vs ambiguous B nodes
|
|
284
|
-
const exclusiveForA = new Map();
|
|
285
|
-
const ambiguousB = [];
|
|
286
|
-
const unassignedB = [];
|
|
287
|
-
for (let j = 0; j < bCount; j++) {
|
|
288
|
-
const list = (neighborsB.get(j) || []).sort((a, b) => b.score - a.score);
|
|
289
|
-
if (list.length === 0) {
|
|
290
|
-
unassignedB.push(j);
|
|
291
|
-
}
|
|
292
|
-
else if (list.length === 1) {
|
|
293
|
-
const i = list[0].i;
|
|
294
|
-
const arr = exclusiveForA.get(i) || [];
|
|
295
|
-
arr.push({ j, score: list[0].score });
|
|
296
|
-
exclusiveForA.set(i, arr);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
// If best is significantly better than second-best, treat as exclusive to best
|
|
300
|
-
const best = list[0];
|
|
301
|
-
const second = list[1];
|
|
302
|
-
if (best.score - second.score >= exclusivityMargin) {
|
|
303
|
-
const arr = exclusiveForA.get(best.i) || [];
|
|
304
|
-
arr.push({ j, score: best.score });
|
|
305
|
-
exclusiveForA.set(best.i, arr);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
ambiguousB.push(j);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// A-side exclusivity: if an anchor A has a clearly dominant best B, prefer that exclusive pairing.
|
|
313
|
-
const bOwner = new Map();
|
|
314
|
-
for (const [i, arr] of exclusiveForA.entries()) {
|
|
315
|
-
for (const x of arr) {
|
|
316
|
-
const ex = bOwner.get(x.j);
|
|
317
|
-
if (!ex || x.score > ex.score)
|
|
318
|
-
bOwner.set(x.j, { i, score: x.score });
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const ambSetForA = new Set(ambiguousB);
|
|
322
|
-
for (let i = 0; i < aCount; i++) {
|
|
323
|
-
const list = (neighborsA.get(i) || [])
|
|
324
|
-
.slice()
|
|
325
|
-
.sort((a, b) => b.score - a.score);
|
|
326
|
-
if (!list.length)
|
|
327
|
-
continue;
|
|
328
|
-
const top = list[0];
|
|
329
|
-
const second = list[1];
|
|
330
|
-
const hasClearLead = !second || top.score - second.score >= exclusivityMargin;
|
|
331
|
-
if (top.score >= threshold && hasClearLead) {
|
|
332
|
-
const current = bOwner.get(top.j);
|
|
333
|
-
if (!current || top.score > current.score + 1e-6) {
|
|
334
|
-
// Reassign this B exclusively to A=i
|
|
335
|
-
// Remove from any previous exclusiveForA groups
|
|
336
|
-
for (const [ai, arr] of exclusiveForA.entries()) {
|
|
337
|
-
exclusiveForA.set(ai, arr.filter((x) => x.j !== top.j));
|
|
338
|
-
}
|
|
339
|
-
const arr = exclusiveForA.get(i) || [];
|
|
340
|
-
if (!arr.some((x) => x.j === top.j))
|
|
341
|
-
arr.push({ j: top.j, score: top.score });
|
|
342
|
-
arr.sort((u, v) => v.score - u.score);
|
|
343
|
-
exclusiveForA.set(i, arr);
|
|
344
|
-
bOwner.set(top.j, { i, score: top.score });
|
|
345
|
-
ambSetForA.delete(top.j);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
ambiguousB.splice(0, ambiguousB.length, ...Array.from(ambSetForA.values()));
|
|
350
|
-
// Mutual-best pairing stage (within small tolerance) to prioritize exclusive high-similarity pairs
|
|
351
|
-
const mutualTolerance = 0.03;
|
|
352
|
-
const bestBForA = new Array(aCount);
|
|
353
|
-
for (let i = 0; i < aCount; i++) {
|
|
354
|
-
let bBest = -1;
|
|
355
|
-
let bBestScore = -Infinity;
|
|
356
|
-
for (let j = 0; j < bCount; j++) {
|
|
357
|
-
const s = S[i][j];
|
|
358
|
-
if (s > bBestScore) {
|
|
359
|
-
bBestScore = s;
|
|
360
|
-
bBest = j;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
bestBForA[i] = { j: bBest, score: bBestScore };
|
|
364
|
-
}
|
|
365
|
-
const bestAForB = new Array(bCount);
|
|
366
|
-
for (let j = 0; j < bCount; j++) {
|
|
367
|
-
let aBest = -1;
|
|
368
|
-
let aBestScore = -Infinity;
|
|
369
|
-
for (let i = 0; i < aCount; i++) {
|
|
370
|
-
const s = S[i][j];
|
|
371
|
-
if (s > aBestScore) {
|
|
372
|
-
aBestScore = s;
|
|
373
|
-
aBest = i;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
bestAForB[j] = { i: aBest, score: aBestScore };
|
|
377
|
-
}
|
|
378
|
-
// Promote mutual-best pairs to exclusive, removing from ambiguous list if present
|
|
379
|
-
const ambSet = new Set(ambiguousB);
|
|
380
|
-
for (let i = 0; i < aCount; i++) {
|
|
381
|
-
const b = bestBForA[i].j;
|
|
382
|
-
if (b >= 0 && S[i][b] >= threshold) {
|
|
383
|
-
const a = bestAForB[b].i;
|
|
384
|
-
const score = S[i][b];
|
|
385
|
-
// within tolerance of best on both sides
|
|
386
|
-
const isWithinA = score >= bestBForA[i].score - mutualTolerance;
|
|
387
|
-
const isWithinB = score >= bestAForB[b].score - mutualTolerance;
|
|
388
|
-
if (a === i && isWithinA && isWithinB) {
|
|
389
|
-
// Remove from any previous exclusive lists under other anchors
|
|
390
|
-
for (const [ai, arr] of exclusiveForA.entries()) {
|
|
391
|
-
exclusiveForA.set(ai, arr.filter((x) => x.j !== b));
|
|
392
|
-
}
|
|
393
|
-
// Add as exclusive to this anchor
|
|
394
|
-
const arr = exclusiveForA.get(i) || [];
|
|
395
|
-
// Avoid duplicate insert
|
|
396
|
-
if (!arr.some((x) => x.j === b)) {
|
|
397
|
-
arr.push({ j: b, score });
|
|
398
|
-
arr.sort((u, v) => v.score - u.score);
|
|
399
|
-
exclusiveForA.set(i, arr);
|
|
400
|
-
}
|
|
401
|
-
// Remove from ambiguous set
|
|
402
|
-
ambSet.delete(b);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
ambiguousB.splice(0, ambiguousB.length, ...Array.from(ambSet.values()));
|
|
407
|
-
// Build symmetric anchor heads from mutual-best pairs (above threshold)
|
|
408
|
-
const anchorHeads = new Map(); // A_i -> head B_j
|
|
409
|
-
for (let i = 0; i < aCount; i++) {
|
|
410
|
-
const b = bestBForA[i].j;
|
|
411
|
-
if (b >= 0) {
|
|
412
|
-
const score = S[i][b];
|
|
413
|
-
const isWithinA = score >= bestBForA[i].score - mutualTolerance;
|
|
414
|
-
const isWithinB = score >= bestAForB[b].score - mutualTolerance;
|
|
415
|
-
if (bestAForB[b].i === i &&
|
|
416
|
-
score >= threshold &&
|
|
417
|
-
isWithinA &&
|
|
418
|
-
isWithinB) {
|
|
419
|
-
anchorHeads.set(i, b);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// Resolve ambiguous many-to-many by B-centric greedy assignment:
|
|
424
|
-
// each ambiguous B chooses its single most similar A (enables one-to-many on A side).
|
|
425
|
-
const matchedAmbigForA = new Map();
|
|
426
|
-
for (const j of ambiguousB) {
|
|
427
|
-
let bestI = -1;
|
|
428
|
-
let bestScore = -Infinity;
|
|
429
|
-
for (let i = 0; i < aCount; i++) {
|
|
430
|
-
const s = S[i][j];
|
|
431
|
-
if (s > bestScore) {
|
|
432
|
-
bestScore = s;
|
|
433
|
-
bestI = i;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
if (bestI >= 0 && bestScore >= threshold) {
|
|
437
|
-
const arr = matchedAmbigForA.get(bestI) || [];
|
|
438
|
-
arr.push({ j, score: bestScore });
|
|
439
|
-
matchedAmbigForA.set(bestI, arr);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Merge exclusive and matched ambiguous into final assignments per A
|
|
443
|
-
const assignedToA = new Map();
|
|
444
|
-
for (let i = 0; i < aCount; i++) {
|
|
445
|
-
const list = [];
|
|
446
|
-
const ex = exclusiveForA.get(i) || [];
|
|
447
|
-
for (const x of ex)
|
|
448
|
-
list.push(x);
|
|
449
|
-
const amb = matchedAmbigForA.get(i) || [];
|
|
450
|
-
for (const x of amb)
|
|
451
|
-
list.push(x);
|
|
452
|
-
if (list.length) {
|
|
453
|
-
list.sort((a, b) => b.score - a.score);
|
|
454
|
-
assignedToA.set(i, list);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
// Promote anchors for left items that have strong assigned matches even if
|
|
458
|
-
// they were not mutual-best. This guarantees group creation around
|
|
459
|
-
// semantically coherent anchors such as Fish, so items like Clams attach there.
|
|
460
|
-
for (const [i, lst] of assignedToA.entries()) {
|
|
461
|
-
if (!anchorHeads.has(i) && lst.length) {
|
|
462
|
-
anchorHeads.set(i, lst[0].j); // head = top assigned right item
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// Track which B have been assigned (exclusive or ambiguous)
|
|
466
|
-
const assignedBSet = new Set();
|
|
467
|
-
for (const arr of assignedToA.values()) {
|
|
468
|
-
for (const x of arr)
|
|
469
|
-
assignedBSet.add(x.j);
|
|
470
|
-
}
|
|
471
|
-
// Also mark mutual-best anchor head B's as assigned so they are not appended as leftovers
|
|
472
|
-
// even if they weren't already in assignedToA for their group.
|
|
473
|
-
for (const [ai, headJ] of anchorHeads.entries()) {
|
|
474
|
-
assignedBSet.add(headJ);
|
|
475
|
-
}
|
|
476
|
-
// Any B not assigned (and not a head) goes to the bottom unassigned list
|
|
477
|
-
const leftoverB = [];
|
|
478
|
-
for (let j = 0; j < bCount; j++) {
|
|
479
|
-
if (!assignedBSet.has(j))
|
|
480
|
-
leftoverB.push(j);
|
|
481
|
-
}
|
|
482
|
-
// Determine ranking for each A_i.
|
|
483
|
-
// Priority 1: anchors with at least one assigned B (highly related) go to the TOP.
|
|
484
|
-
// Priority 2: among them, sort by descending top assigned similarity.
|
|
485
|
-
// Anchors with NO assignment are pushed to the BOTTOM and sorted by their best potential similarity.
|
|
486
|
-
const rankA = [];
|
|
487
|
-
for (let i = 0; i < aCount; i++) {
|
|
488
|
-
const assigned = assignedToA.get(i) || [];
|
|
489
|
-
let top = 0;
|
|
490
|
-
if (assigned.length > 0) {
|
|
491
|
-
top = assigned.reduce((m, x) => x.score > m ? x.score : m, 0);
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
// If nothing assigned, consider the best potential similarity (even if below threshold)
|
|
495
|
-
let best = 0;
|
|
496
|
-
for (let j = 0; j < bCount; j++)
|
|
497
|
-
best = Math.max(best, S[i][j]);
|
|
498
|
-
top = best;
|
|
499
|
-
}
|
|
500
|
-
rankA.push({
|
|
501
|
-
i,
|
|
502
|
-
score: top,
|
|
503
|
-
hasAssignment: assigned.length > 0,
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
rankA.sort((x, y) => Number(y.hasAssignment) - Number(x.hasAssignment) ||
|
|
507
|
-
y.score - x.score);
|
|
508
|
-
// Within each group, sort assigned B by descending similarity to the anchor
|
|
509
|
-
for (const [i, list] of assignedToA.entries()) {
|
|
510
|
-
list.sort((a, b) => b.score - a.score);
|
|
511
|
-
assignedToA.set(i, list);
|
|
512
|
-
}
|
|
513
|
-
// Determine anchor groups strictly from symmetric mutual-best pairs
|
|
514
|
-
const anchorSet = new Set(anchorHeads.keys());
|
|
515
|
-
// Early-out: if no anchors (no cross-list matches above threshold),
|
|
516
|
-
// fall back to simple left-only rows followed by right-only rows
|
|
517
|
-
if (anchorSet.size === 0) {
|
|
518
|
-
const rowsNoAnchors = [];
|
|
519
|
-
for (let i = 0; i < aCount; i++)
|
|
520
|
-
rowsNoAnchors.push({
|
|
521
|
-
left: listA[i],
|
|
522
|
-
right: '-',
|
|
523
|
-
score: 0,
|
|
524
|
-
});
|
|
525
|
-
for (const j of leftoverB)
|
|
526
|
-
rowsNoAnchors.push({
|
|
527
|
-
left: '-',
|
|
528
|
-
right: listB[j],
|
|
529
|
-
score: 0,
|
|
530
|
-
});
|
|
531
|
-
return rowsNoAnchors;
|
|
532
|
-
}
|
|
533
|
-
// Map each B to its owning anchor A for quick lookup (based on assignedToA)
|
|
534
|
-
const ownerOfB = new Array(bCount).fill(-1);
|
|
535
|
-
for (const [ai, lst] of assignedToA.entries()) {
|
|
536
|
-
for (const x of lst)
|
|
537
|
-
ownerOfB[x.j] = ai;
|
|
538
|
-
}
|
|
539
|
-
const leftUsed = new Set();
|
|
540
|
-
// Mark anchors as used so they won't be re-assigned as extras
|
|
541
|
-
for (const ai of anchorSet.values())
|
|
542
|
-
leftUsed.add(ai);
|
|
543
|
-
// Pre-bucket extras: group -> (right j within that group) -> extras[]
|
|
544
|
-
const extrasByGroup = new Map();
|
|
545
|
-
for (let k = 0; k < aCount; k++) {
|
|
546
|
-
if (leftUsed.has(k))
|
|
547
|
-
continue; // skip anchors
|
|
548
|
-
let bestGroup = -1;
|
|
549
|
-
let bestRightJ = -1;
|
|
550
|
-
let bestScore = -Infinity;
|
|
551
|
-
// Evaluate affinity against each anchor group
|
|
552
|
-
for (const ai of anchorSet.values()) {
|
|
553
|
-
const groupList = assignedToA.get(ai) || [];
|
|
554
|
-
// Ensure the head B is present in the consideration list
|
|
555
|
-
const headJ = anchorHeads.get(ai);
|
|
556
|
-
const listWithHead = groupList.some((x) => x.j === headJ)
|
|
557
|
-
? groupList
|
|
558
|
-
: [{ j: headJ, score: S[ai][headJ] }, ...groupList];
|
|
559
|
-
// Best cross-list similarity to any right item under this anchor
|
|
560
|
-
let localBestJ = -1;
|
|
561
|
-
let localBestCross = -Infinity;
|
|
562
|
-
for (const x of listWithHead) {
|
|
563
|
-
const s = S[k][x.j];
|
|
564
|
-
if (s > localBestCross) {
|
|
565
|
-
localBestCross = s;
|
|
566
|
-
localBestJ = x.j;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
// Within-list cohesion to the anchor left word
|
|
570
|
-
const withinAA = SAA[k][ai];
|
|
571
|
-
const combined = Math.max(localBestCross, withinAA);
|
|
572
|
-
if (combined > bestScore) {
|
|
573
|
-
bestScore = combined;
|
|
574
|
-
bestGroup = ai;
|
|
575
|
-
// Prefer the concrete right target when cross-list is stronger;
|
|
576
|
-
// otherwise fall back to the group's top right assignment.
|
|
577
|
-
bestRightJ =
|
|
578
|
-
localBestCross >= withinAA ? localBestJ : headJ;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
// Attach as extra if cohesive enough to some group
|
|
582
|
-
if (bestGroup >= 0 &&
|
|
583
|
-
bestRightJ >= 0 &&
|
|
584
|
-
bestScore >= leftCohesion) {
|
|
585
|
-
let byRight = extrasByGroup.get(bestGroup);
|
|
586
|
-
if (!byRight) {
|
|
587
|
-
byRight = new Map();
|
|
588
|
-
extrasByGroup.set(bestGroup, byRight);
|
|
589
|
-
}
|
|
590
|
-
const arr = byRight.get(bestRightJ) || [];
|
|
591
|
-
arr.push({ k, jPref: bestRightJ, score: bestScore });
|
|
592
|
-
byRight.set(bestRightJ, arr);
|
|
593
|
-
leftUsed.add(k);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
// Build grouped rows with left extras rendered directly under the specific right item
|
|
597
|
-
// they most strongly match. This ensures highly related words appear exactly below their
|
|
598
|
-
// most similar counterpart on the other side.
|
|
599
|
-
const rows = [];
|
|
600
|
-
const anchorIndices = Array.from(anchorSet.values());
|
|
601
|
-
const orderIndices = preserveLeftOrder
|
|
602
|
-
? anchorIndices.sort((x, y) => x - y)
|
|
603
|
-
: anchorIndices.sort((i1, i2) => {
|
|
604
|
-
const j1 = anchorHeads.get(i1);
|
|
605
|
-
const j2 = anchorHeads.get(i2);
|
|
606
|
-
const diff = S[i2][j2] - S[i1][j1];
|
|
607
|
-
if (Math.abs(diff) > 1e-9)
|
|
608
|
-
return diff;
|
|
609
|
-
// Side-invariant tie-breaker using symmetric pair key
|
|
610
|
-
const key1 = [
|
|
611
|
-
listA[i1].toLowerCase(),
|
|
612
|
-
listB[j1].toLowerCase(),
|
|
613
|
-
]
|
|
614
|
-
.sort()
|
|
615
|
-
.join('|');
|
|
616
|
-
const key2 = [
|
|
617
|
-
listA[i2].toLowerCase(),
|
|
618
|
-
listB[j2].toLowerCase(),
|
|
619
|
-
]
|
|
620
|
-
.sort()
|
|
621
|
-
.join('|');
|
|
622
|
-
return key1.localeCompare(key2);
|
|
623
|
-
});
|
|
624
|
-
for (const i of orderIndices) {
|
|
625
|
-
const groupOrig = assignedToA.get(i) || [];
|
|
626
|
-
const headJ = anchorHeads.get(i);
|
|
627
|
-
// Build group with head first, then others by score
|
|
628
|
-
const others = groupOrig
|
|
629
|
-
.filter((x) => x.j !== headJ)
|
|
630
|
-
.sort((a, b) => {
|
|
631
|
-
const d = b.score - a.score;
|
|
632
|
-
if (Math.abs(d) > 1e-9)
|
|
633
|
-
return d;
|
|
634
|
-
return listB[a.j]
|
|
635
|
-
.toLowerCase()
|
|
636
|
-
.localeCompare(listB[b.j].toLowerCase());
|
|
637
|
-
});
|
|
638
|
-
const group = [{ j: headJ, score: S[i][headJ] }, ...others];
|
|
639
|
-
// First row uses the anchor head pair
|
|
640
|
-
const first = group[0];
|
|
641
|
-
rows.push({
|
|
642
|
-
left: listA[i],
|
|
643
|
-
right: listB[first.j],
|
|
644
|
-
score: first.score,
|
|
645
|
-
});
|
|
646
|
-
// Render left extras that prefer this first right item
|
|
647
|
-
const byRight = extrasByGroup.get(i);
|
|
648
|
-
const extrasForFirst = (byRight && byRight.get(first.j)) || [];
|
|
649
|
-
if (extrasForFirst.length) {
|
|
650
|
-
extrasForFirst.sort((e1, e2) => {
|
|
651
|
-
const s1 = Math.max(S[e1.k][first.j], SAA[e1.k][i]);
|
|
652
|
-
const s2 = Math.max(S[e2.k][first.j], SAA[e2.k][i]);
|
|
653
|
-
const d = s2 - s1;
|
|
654
|
-
if (Math.abs(d) > 1e-9)
|
|
655
|
-
return d;
|
|
656
|
-
return listA[e1.k]
|
|
657
|
-
.toLowerCase()
|
|
658
|
-
.localeCompare(listA[e2.k].toLowerCase());
|
|
659
|
-
});
|
|
660
|
-
for (const ex of extrasForFirst) {
|
|
661
|
-
rows.push({
|
|
662
|
-
left: listA[ex.k],
|
|
663
|
-
right: '-',
|
|
664
|
-
score: Math.max(S[ex.k][first.j], SAA[ex.k][i]),
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
// Render remaining right items with their own left extras directly beneath
|
|
669
|
-
for (let idx = 1; idx < group.length; idx++) {
|
|
670
|
-
const g = group[idx];
|
|
671
|
-
rows.push({ left: '-', right: listB[g.j], score: g.score });
|
|
672
|
-
const extrasForJ = (byRight && byRight.get(g.j)) || [];
|
|
673
|
-
if (extrasForJ.length) {
|
|
674
|
-
extrasForJ.sort((e1, e2) => {
|
|
675
|
-
const s1 = Math.max(S[e1.k][g.j], SAA[e1.k][i]);
|
|
676
|
-
const s2 = Math.max(S[e2.k][g.j], SAA[e2.k][i]);
|
|
677
|
-
const d = s2 - s1;
|
|
678
|
-
if (Math.abs(d) > 1e-9)
|
|
679
|
-
return d;
|
|
680
|
-
return listA[e1.k]
|
|
681
|
-
.toLowerCase()
|
|
682
|
-
.localeCompare(listA[e2.k].toLowerCase());
|
|
683
|
-
});
|
|
684
|
-
for (const ex of extrasForJ) {
|
|
685
|
-
rows.push({
|
|
686
|
-
left: listA[ex.k],
|
|
687
|
-
right: '-',
|
|
688
|
-
score: Math.max(S[ex.k][g.j], SAA[ex.k][i]),
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
// Append leftover left items (not anchors and not attached as extras)
|
|
695
|
-
for (let i = 0; i < aCount; i++) {
|
|
696
|
-
if (!anchorSet.has(i) && !leftUsed.has(i)) {
|
|
697
|
-
rows.push({ left: listA[i], right: '-', score: 0 });
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
// Append unassigned B at the end so they do not disrupt highly related groups
|
|
701
|
-
for (const j of leftoverB) {
|
|
702
|
-
rows.push({ left: '-', right: listB[j], score: 0 });
|
|
703
|
-
}
|
|
704
|
-
return rows;
|
|
705
|
-
}));
|
|
706
|
-
}))), catchError((err) => throwError(() => new Error('Failed to create grouped alignment: ' + err))));
|
|
707
|
-
}
|
|
708
|
-
// Cosine similarity between two vectors
|
|
709
|
-
cosineSimilarity(a, b) {
|
|
710
|
-
let dot = 0, na = 0, nb = 0;
|
|
711
|
-
for (let i = 0; i < a.length; i++) {
|
|
712
|
-
dot += a[i] * b[i];
|
|
713
|
-
na += a[i] * a[i];
|
|
714
|
-
nb += b[i] * b[i];
|
|
715
|
-
}
|
|
716
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-12);
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Align words in listA with their most semantically similar words in listB
|
|
720
|
-
*
|
|
721
|
-
* Algorithm:
|
|
722
|
-
* 1. Generate embeddings for all words in both lists using Universal Sentence Encoder
|
|
723
|
-
* 2. For each word in listA, compare its embedding with all words in listB
|
|
724
|
-
* 3. Find the word in listB with highest cosine similarity score
|
|
725
|
-
* 4. Return alignment results with similarity scores
|
|
726
|
-
*
|
|
727
|
-
* @param listA - Source list of words to align
|
|
728
|
-
* @param listB - Target list of words to find matches in
|
|
729
|
-
* @returns Observable of alignment results with word, alignedWith, score, and index
|
|
730
|
-
*/
|
|
731
|
-
alignLists(listA, listB) {
|
|
732
|
-
return this.loadModel().pipe(
|
|
733
|
-
// Step 1: Generate embeddings for both word lists simultaneously
|
|
734
|
-
switchMap((model) => forkJoin([
|
|
735
|
-
from(model.embed(listA)),
|
|
736
|
-
from(model.embed(listB))
|
|
737
|
-
]).pipe(
|
|
738
|
-
// Step 2: Convert tensor embeddings to JavaScript arrays
|
|
739
|
-
switchMap(([tA, tB]) => {
|
|
740
|
-
return forkJoin([from(tA.array()), from(tB.array())]).pipe(map((tensorArrays) => {
|
|
741
|
-
const [arrA, arrB] = tensorArrays;
|
|
742
|
-
// Clean up TensorFlow tensors to prevent memory leaks
|
|
743
|
-
tA.dispose();
|
|
744
|
-
tB.dispose();
|
|
745
|
-
// For each word in listA, find its best match in listB
|
|
746
|
-
const results = listA.map((wordA, i) => {
|
|
747
|
-
// Get the embedding vector for current word from listA
|
|
748
|
-
const vecA = Float32Array.from(arrA[i]);
|
|
749
|
-
// Initialize variables to track the best match
|
|
750
|
-
let bestScore = -Infinity;
|
|
751
|
-
let bestMatch = null;
|
|
752
|
-
let bestIndex = -1;
|
|
753
|
-
// Compare current word from listA with all words in listB
|
|
754
|
-
for (let j = 0; j < listB.length; j++) {
|
|
755
|
-
// Get the embedding vector for current word from listB
|
|
756
|
-
const vecB = Float32Array.from(arrB[j]);
|
|
757
|
-
// Calculate semantic similarity using cosine similarity
|
|
758
|
-
const score = this.cosineSimilarity(vecA, vecB);
|
|
759
|
-
// Update best match if this similarity is higher
|
|
760
|
-
if (score > bestScore) {
|
|
761
|
-
bestScore = score;
|
|
762
|
-
bestMatch = listB[j];
|
|
763
|
-
bestIndex = j;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
// Return alignment result for this word
|
|
767
|
-
return {
|
|
768
|
-
word: wordA, // Original word from listA
|
|
769
|
-
alignedWith: bestMatch, // Best matching word from listB
|
|
770
|
-
score: bestScore, // Similarity score (0-1, higher is better)
|
|
771
|
-
index: bestIndex, // Index of best match in listB
|
|
772
|
-
};
|
|
773
|
-
});
|
|
774
|
-
// Return all alignment results
|
|
775
|
-
return results;
|
|
776
|
-
}));
|
|
777
|
-
}))), catchError((err) => throwError(() => new Error('Model not loaded or embedding failed: ' + err))));
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Find best matching pairs between two lists above a similarity threshold
|
|
781
|
-
*
|
|
782
|
-
* @param listA - Source list of words
|
|
783
|
-
* @param listB - Target list of words to match against
|
|
784
|
-
* @param threshold - Minimum similarity score (0-1, default: 0.15)
|
|
785
|
-
* @returns Observable of word pairs with similarity scores
|
|
786
|
-
*/
|
|
787
|
-
findBestMatchingPairs(listA, listB, threshold = 0.15) {
|
|
788
|
-
return this.alignLists(listA, listB).pipe(map((alignments) => {
|
|
789
|
-
const usedB = new Set();
|
|
790
|
-
const pairs = [];
|
|
791
|
-
// Sort by similarity score (highest first)
|
|
792
|
-
alignments.sort((a, b) => b.score - a.score);
|
|
793
|
-
// Debug logging for travel/journey matching
|
|
794
|
-
console.log('Embedding Service - All alignments:', alignments.map((alignment) => ({
|
|
795
|
-
word: alignment.word,
|
|
796
|
-
alignedWith: alignment.alignedWith,
|
|
797
|
-
score: alignment.score.toFixed(3),
|
|
798
|
-
})));
|
|
799
|
-
for (const alignment of alignments) {
|
|
800
|
-
if (alignment.score >= threshold && !usedB.has(alignment.index)) {
|
|
801
|
-
pairs.push({
|
|
802
|
-
wordA: alignment.word,
|
|
803
|
-
wordB: alignment.alignedWith,
|
|
804
|
-
score: alignment.score,
|
|
805
|
-
});
|
|
806
|
-
usedB.add(alignment.index);
|
|
807
|
-
// Special logging for travel-related matches
|
|
808
|
-
if (alignment.word.toLowerCase().includes('travel') ||
|
|
809
|
-
alignment.word.toLowerCase().includes('journey') ||
|
|
810
|
-
alignment.alignedWith.toLowerCase().includes('travel') ||
|
|
811
|
-
alignment.alignedWith.toLowerCase().includes('journey')) {
|
|
812
|
-
console.log('🎯 Travel/Journey Match Found:', {
|
|
813
|
-
wordA: alignment.word,
|
|
814
|
-
wordB: alignment.alignedWith,
|
|
815
|
-
score: alignment.score.toFixed(3),
|
|
816
|
-
threshold: threshold,
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return pairs;
|
|
822
|
-
}), catchError((err) => throwError(() => new Error('Failed to find best matching pairs: ' + err))));
|
|
823
|
-
}
|
|
824
|
-
// Calculate similarity between two texts (returns Observable)
|
|
825
|
-
calculateSimilarity(primaryText, comparisonText) {
|
|
826
|
-
return this.getEmbedding(primaryText).pipe(switchMap((emb1) => this.getEmbedding(comparisonText).pipe(map((emb2) => {
|
|
827
|
-
const similarity = this.cosineSimilarity(emb1, emb2);
|
|
828
|
-
// Debug logging for travel/journey similarity
|
|
829
|
-
if (primaryText.toLowerCase().includes('travel') ||
|
|
830
|
-
primaryText.toLowerCase().includes('journey') ||
|
|
831
|
-
comparisonText.toLowerCase().includes('travel') ||
|
|
832
|
-
comparisonText.toLowerCase().includes('journey')) {
|
|
833
|
-
console.log('🔍 Travel/Journey Similarity:', {
|
|
834
|
-
primaryText,
|
|
835
|
-
comparisonText,
|
|
836
|
-
similarity: similarity.toFixed(3),
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
return similarity;
|
|
840
|
-
}))), catchError((err) => throwError(() => new Error('Failed to calculate similarity: ' + err))));
|
|
841
|
-
}
|
|
842
|
-
// Test method to check specific word similarities (Observable only)
|
|
843
|
-
testWordSimilarity(word1, word2) {
|
|
844
|
-
return this.calculateSimilarity(word1, word2).pipe(map((similarity) => {
|
|
845
|
-
console.log(`📊 Similarity between "${word1}" and "${word2}": ${(similarity * 100).toFixed(1)}%`);
|
|
846
|
-
}), catchError((error) => {
|
|
847
|
-
console.error('Error testing word similarity:', error);
|
|
848
|
-
return of();
|
|
849
|
-
}));
|
|
850
|
-
}
|
|
851
|
-
// Private methods
|
|
852
|
-
// Domain compatibility cap to avoid mismatched category-versus-activity pairings.
|
|
853
|
-
// Example: "Fish" (food/animal noun) vs "Bird watching" (activity) should not align.
|
|
854
|
-
// Returns a cap in [0,1] representing the maximum allowed similarity for the pair.
|
|
855
|
-
// Values below the groupAlignLists() threshold (default 0.25) effectively block alignment.
|
|
856
|
-
domainCompatibilityCap(a, b) {
|
|
857
|
-
const norm = (s) => s
|
|
858
|
-
.toLowerCase()
|
|
859
|
-
.replace(/[_-]+/g, ' ')
|
|
860
|
-
.replace(/[^a-z\s]/g, ' ')
|
|
861
|
-
.replace(/\s+/g, ' ')
|
|
862
|
-
.trim();
|
|
863
|
-
const A = norm(a);
|
|
864
|
-
const B = norm(b);
|
|
865
|
-
const watching = /\b[a-z]+\s*watching\b|\bbirdwatching\b/;
|
|
866
|
-
const genericActivities = /\b(hiking|reading|painting|photography|gardening|blogging|writing|gaming|chess|puzzles|travel(?:ing|ling)?|programming|coding|language learning|pottery|clay\s*model(?:ing|ling))\b/;
|
|
867
|
-
const isActivity = (s) => watching.test(s) || genericActivities.test(s);
|
|
868
|
-
const foodAnimal = /\b(fish|sea\s*food|seafood|shell\s*fish|shellfish|clam|clams|mussel|mussels|oyster|oysters|scallop|scallops|shrimp|prawn|prawns|lobster|crab|meat|beef|pork|chicken|egg|eggs)\b/;
|
|
869
|
-
const isFoodAnimal = (s) => foodAnimal.test(s);
|
|
870
|
-
const aAct = isActivity(A);
|
|
871
|
-
const bAct = isActivity(B);
|
|
872
|
-
const aFood = isFoodAnimal(A);
|
|
873
|
-
const bFood = isFoodAnimal(B);
|
|
874
|
-
if ((aAct && bFood) || (bAct && aFood)) {
|
|
875
|
-
// Cap below default threshold so these pairs never form groups
|
|
876
|
-
return 0.2;
|
|
877
|
-
}
|
|
878
|
-
return 1.0;
|
|
879
|
-
}
|
|
880
|
-
// Lightweight lexical similarity booster to catch cases like "AI/ML" ↔ "AI machine learning"
|
|
881
|
-
// Uses token normalization + targeted synonym/expansion rules and token Jaccard.
|
|
882
|
-
computeLexicalBoost(a, b) {
|
|
883
|
-
const expand = (s) => {
|
|
884
|
-
let x = s.toLowerCase();
|
|
885
|
-
// Normalize separators
|
|
886
|
-
x = x.replace(/[_-]+/g, ' ');
|
|
887
|
-
// Targeted expansions
|
|
888
|
-
x = x.replace(/ai\s*\/\s*ml|ai\s*[- ]?ml/g, 'ai machine learning');
|
|
889
|
-
x = x.replace(/\bml\b/g, 'machine learning');
|
|
890
|
-
x = x.replace(/web\s*dev(elopment)?/g, 'web development');
|
|
891
|
-
// Domain expansions – culinary/food categories (to help cases like Fish ↔ Clams)
|
|
892
|
-
x = x.replace(/\bsea\s*food\b/g, 'seafood fish shellfish');
|
|
893
|
-
x = x.replace(/\bshell\s*fish\b/g, 'shellfish clams mussels oysters scallops seafood');
|
|
894
|
-
x = x.replace(/\bfish(es)?\b/g, 'fish seafood');
|
|
895
|
-
x = x.replace(/\bclam(s)?\b/g, 'clams shellfish seafood');
|
|
896
|
-
x = x.replace(/\bmussel(s)?\b/g, 'mussels shellfish seafood');
|
|
897
|
-
x = x.replace(/\boyster(s)?\b/g, 'oysters shellfish seafood');
|
|
898
|
-
x = x.replace(/\bscallop(s)?\b/g, 'scallops shellfish seafood');
|
|
899
|
-
x = x.replace(/\bshrimp(s)?\b/g, 'shrimp seafood');
|
|
900
|
-
x = x.replace(/\bprawn(s)?\b/g, 'prawns shrimp seafood');
|
|
901
|
-
x = x.replace(/\blobster(s)?\b/g, 'lobster seafood');
|
|
902
|
-
x = x.replace(/\bcrab(s)?\b/g, 'crab seafood');
|
|
903
|
-
x = x.replace(/ui\s*\/\s*ux|ui\s*[- ]?ux/g, 'ui ux design');
|
|
904
|
-
return x;
|
|
905
|
-
};
|
|
906
|
-
const normalize = (s) => {
|
|
907
|
-
const x = expand(s)
|
|
908
|
-
.replace(/[^a-z0-9\s]/g, ' ')
|
|
909
|
-
.replace(/\s+/g, ' ')
|
|
910
|
-
.trim();
|
|
911
|
-
return x;
|
|
912
|
-
};
|
|
913
|
-
const A = normalize(a);
|
|
914
|
-
const B = normalize(b);
|
|
915
|
-
if (!A || !B)
|
|
916
|
-
return 0;
|
|
917
|
-
// Strong containment boost (e.g., "ai machine learning" contains "machine learning")
|
|
918
|
-
if (A.includes(B) || B.includes(A)) {
|
|
919
|
-
return Math.max(0.75, Math.min(0.95, Math.max(A.length, B.length) > 12 ? 0.85 : 0.78));
|
|
920
|
-
}
|
|
921
|
-
const toTokens = (s) => s.split(' ').filter(Boolean);
|
|
922
|
-
const setFrom = (arr) => new Set(arr);
|
|
923
|
-
const tA = toTokens(A);
|
|
924
|
-
const tB = toTokens(B);
|
|
925
|
-
if (!tA.length || !tB.length)
|
|
926
|
-
return 0;
|
|
927
|
-
const sA = setFrom(tA);
|
|
928
|
-
const sB = setFrom(tB);
|
|
929
|
-
let inter = 0;
|
|
930
|
-
for (const t of sA)
|
|
931
|
-
if (sB.has(t))
|
|
932
|
-
inter++;
|
|
933
|
-
const union = sA.size + sB.size - inter;
|
|
934
|
-
const jaccard = union > 0 ? inter / union : 0;
|
|
935
|
-
// Small extra for sharing big tokens like "learning"/"development" with qualifiers
|
|
936
|
-
const longTokenBonus = Math.min(0.1, [...sA].filter((t) => t.length >= 6 && sB.has(t)).length * 0.03);
|
|
937
|
-
const score = Math.min(0.95, jaccard + longTokenBonus);
|
|
938
|
-
return score;
|
|
939
|
-
}
|
|
940
|
-
// Lazy load the model only once, as observable
|
|
941
|
-
loadModel() {
|
|
942
|
-
if (!this.model$) {
|
|
943
|
-
this.model$ = defer(() => from(use.load())).pipe(shareReplay(1));
|
|
944
|
-
}
|
|
945
|
-
return this.model$;
|
|
946
|
-
}
|
|
947
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
948
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, providedIn: 'root' });
|
|
949
|
-
}
|
|
950
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, decorators: [{
|
|
951
|
-
type: Injectable,
|
|
952
|
-
args: [{
|
|
953
|
-
providedIn: 'root',
|
|
954
|
-
}]
|
|
955
|
-
}] });
|
|
956
|
-
|
|
957
213
|
class FileConversionService {
|
|
958
214
|
static MIME_TYPE_JPEG = 'image/jpeg';
|
|
959
215
|
static MIME_TYPE_PNG = 'image/png';
|
|
@@ -963,7 +219,7 @@ class FileConversionService {
|
|
|
963
219
|
static FILE_EXTENSION_PNG = '.png';
|
|
964
220
|
static FILE_EXTENSION_WEBP = '.webp';
|
|
965
221
|
urlToFile(url, fileName, mimeType) {
|
|
966
|
-
return from(fetch(url)).pipe(switchMap
|
|
222
|
+
return from(fetch(url)).pipe(switchMap(response => from(response.blob())), map(blob => new File([blob], fileName, { type: mimeType })));
|
|
967
223
|
}
|
|
968
224
|
isDataUrl(str) {
|
|
969
225
|
return typeof str === 'string' && str.startsWith('data:');
|
|
@@ -1008,7 +264,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1008
264
|
class ImageCompressionService {
|
|
1009
265
|
compressImageFile(file, config, overrides = {}) {
|
|
1010
266
|
const cfg = { ...config, ...overrides };
|
|
1011
|
-
return this.loadImageFromFile(file).pipe(switchMap
|
|
267
|
+
return this.loadImageFromFile(file).pipe(switchMap((img) => {
|
|
1012
268
|
let width = img.naturalWidth || img.width;
|
|
1013
269
|
let height = img.naturalHeight || img.height;
|
|
1014
270
|
const scale = Math.min(cfg.maxWidth / width, cfg.maxHeight / height, 1);
|
|
@@ -1024,7 +280,7 @@ class ImageCompressionService {
|
|
|
1024
280
|
};
|
|
1025
281
|
const process = (w, h, q, attempts) => {
|
|
1026
282
|
render(w, h);
|
|
1027
|
-
return this.canvasToBlob(canvas, cfg.format, q).pipe(switchMap
|
|
283
|
+
return this.canvasToBlob(canvas, cfg.format, q).pipe(switchMap((blob) => {
|
|
1028
284
|
if (blob.size > cfg.maxBytes && attempts < 25) {
|
|
1029
285
|
if (q > cfg.qualityMin + 0.005) {
|
|
1030
286
|
return process(w, h, Math.max(cfg.qualityMin, q - cfg.qualityStep), attempts + 1);
|
|
@@ -1040,10 +296,10 @@ class ImageCompressionService {
|
|
|
1040
296
|
return of(blob);
|
|
1041
297
|
}));
|
|
1042
298
|
};
|
|
1043
|
-
return process(width, height, cfg.qualityStart, 0).pipe(switchMap
|
|
299
|
+
return process(width, height, cfg.qualityStart, 0).pipe(switchMap((finalBlob) => {
|
|
1044
300
|
const newName = this.renameFileForFormat(file.name, cfg.format);
|
|
1045
301
|
const compressedFile = new File([finalBlob], newName, { type: cfg.format, lastModified: Date.now() });
|
|
1046
|
-
return this.blobToDataURL(finalBlob).pipe(map
|
|
302
|
+
return this.blobToDataURL(finalBlob).pipe(map((dataUrl) => ({ file: compressedFile, dataUrl })));
|
|
1047
303
|
}));
|
|
1048
304
|
}));
|
|
1049
305
|
}
|
|
@@ -1228,7 +484,7 @@ class OpenAIEmbeddingService {
|
|
|
1228
484
|
if (validA.length === 0 && validB.length === 0)
|
|
1229
485
|
return of({ listA: [], listB: [] });
|
|
1230
486
|
const key = JSON.stringify({ a: validA, b: validB, v: 'spacer-v35' });
|
|
1231
|
-
return from(this.loadSpacerCache()).pipe(switchMap(() => {
|
|
487
|
+
return from(this.loadSpacerCache()).pipe(switchMap$1(() => {
|
|
1232
488
|
if (this.spacerAlignmentCache.has(key)) {
|
|
1233
489
|
console.log('Alignment cache hit');
|
|
1234
490
|
return of(this.spacerAlignmentCache.get(key));
|
|
@@ -1240,7 +496,7 @@ class OpenAIEmbeddingService {
|
|
|
1240
496
|
.post(url, { listA: validA, listB: validB }, {
|
|
1241
497
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
1242
498
|
})
|
|
1243
|
-
.pipe(map((result) => {
|
|
499
|
+
.pipe(map$1((result) => {
|
|
1244
500
|
const finalResult = { listA: result.listA || [], listB: result.listB || [] };
|
|
1245
501
|
this.spacerAlignmentCache.set(key, finalResult);
|
|
1246
502
|
this.persistence.set(CachePersistenceService.STORE_OPENAI_SPACER, key, finalResult);
|
|
@@ -1362,7 +618,7 @@ class OpenAIEmbeddingService {
|
|
|
1362
618
|
temperature: 0.0, // Zero temperature for maximum determinism
|
|
1363
619
|
seed: 42 // Fixed seed for reproducibility
|
|
1364
620
|
};
|
|
1365
|
-
return this.http.post(this.API_URL, body, { headers }).pipe(map(response => {
|
|
621
|
+
return this.http.post(this.API_URL, body, { headers }).pipe(map$1(response => {
|
|
1366
622
|
try {
|
|
1367
623
|
const content = response.choices[0].message.content;
|
|
1368
624
|
console.log('OpenAI LLM Output (getAlignedLists):', content);
|
|
@@ -1520,7 +776,7 @@ class OpenAIEmbeddingService {
|
|
|
1520
776
|
return of([]);
|
|
1521
777
|
const keyDirect = JSON.stringify({ a: validA, b: validB });
|
|
1522
778
|
const keyReverse = JSON.stringify({ a: validB, b: validA });
|
|
1523
|
-
return from(this.loadAlignmentCache()).pipe(switchMap(() => {
|
|
779
|
+
return from(this.loadAlignmentCache()).pipe(switchMap$1(() => {
|
|
1524
780
|
if (this.alignmentCache.has(keyDirect)) {
|
|
1525
781
|
console.log('Alignment cache hit (Direct)');
|
|
1526
782
|
return of(this.alignmentCache.get(keyDirect).rows);
|
|
@@ -1589,7 +845,7 @@ class OpenAIEmbeddingService {
|
|
|
1589
845
|
temperature: 0.2
|
|
1590
846
|
};
|
|
1591
847
|
console.log('Sending single alignment request to OpenAI...');
|
|
1592
|
-
return this.http.post(this.API_URL, body, { headers }).pipe(map(response => {
|
|
848
|
+
return this.http.post(this.API_URL, body, { headers }).pipe(map$1(response => {
|
|
1593
849
|
try {
|
|
1594
850
|
const content = response.choices[0].message.content;
|
|
1595
851
|
console.log('OpenAI LLM Output (groupAlignLists):', content);
|
|
@@ -1767,7 +1023,7 @@ class OpenAIEmbeddingService {
|
|
|
1767
1023
|
return of(0);
|
|
1768
1024
|
}
|
|
1769
1025
|
const simKey = [a, b].sort().join('\0');
|
|
1770
|
-
return from(this.loadSimilarityCache()).pipe(switchMap(() => {
|
|
1026
|
+
return from(this.loadSimilarityCache()).pipe(switchMap$1(() => {
|
|
1771
1027
|
if (this.similarityCache.has(simKey)) {
|
|
1772
1028
|
return of(this.similarityCache.get(simKey));
|
|
1773
1029
|
}
|
|
@@ -1778,7 +1034,7 @@ class OpenAIEmbeddingService {
|
|
|
1778
1034
|
.post(url, { textA: a, textB: b }, {
|
|
1779
1035
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
1780
1036
|
})
|
|
1781
|
-
.pipe(map((res) => {
|
|
1037
|
+
.pipe(map$1((res) => {
|
|
1782
1038
|
const similarity = res.similarity ?? 0;
|
|
1783
1039
|
this.similarityCache.set(simKey, similarity);
|
|
1784
1040
|
this.persistence.set(CachePersistenceService.STORE_OPENAI_SIMILARITY, simKey, similarity);
|
|
@@ -1797,7 +1053,7 @@ class OpenAIEmbeddingService {
|
|
|
1797
1053
|
model: 'text-embedding-3-small',
|
|
1798
1054
|
input: [a, b]
|
|
1799
1055
|
};
|
|
1800
|
-
return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map(res => {
|
|
1056
|
+
return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map$1(res => {
|
|
1801
1057
|
const data = res?.data;
|
|
1802
1058
|
if (!Array.isArray(data) || data.length < 2) {
|
|
1803
1059
|
throw new Error('Invalid embeddings response from OpenAI');
|
|
@@ -1824,7 +1080,7 @@ class OpenAIEmbeddingService {
|
|
|
1824
1080
|
if (!texts || texts.length === 0)
|
|
1825
1081
|
return of([]);
|
|
1826
1082
|
const embKey = JSON.stringify(texts);
|
|
1827
|
-
return from(this.loadEmbeddingsCache()).pipe(switchMap(() => {
|
|
1083
|
+
return from(this.loadEmbeddingsCache()).pipe(switchMap$1(() => {
|
|
1828
1084
|
if (this.embeddingsCache.has(embKey)) {
|
|
1829
1085
|
return of(this.embeddingsCache.get(embKey));
|
|
1830
1086
|
}
|
|
@@ -1835,7 +1091,7 @@ class OpenAIEmbeddingService {
|
|
|
1835
1091
|
.post(url, { input: texts }, {
|
|
1836
1092
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
1837
1093
|
})
|
|
1838
|
-
.pipe(map((res) => {
|
|
1094
|
+
.pipe(map$1((res) => {
|
|
1839
1095
|
const result = (res.data || []).map((item) => item.embedding || []);
|
|
1840
1096
|
this.embeddingsCache.set(embKey, result);
|
|
1841
1097
|
this.persistence.set(CachePersistenceService.STORE_OPENAI_EMBEDDINGS, embKey, result);
|
|
@@ -1854,7 +1110,7 @@ class OpenAIEmbeddingService {
|
|
|
1854
1110
|
model: 'text-embedding-3-small',
|
|
1855
1111
|
input: texts
|
|
1856
1112
|
};
|
|
1857
|
-
return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map(res => {
|
|
1113
|
+
return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map$1(res => {
|
|
1858
1114
|
const data = res?.data;
|
|
1859
1115
|
if (!Array.isArray(data)) {
|
|
1860
1116
|
throw new Error('Invalid embeddings response from OpenAI');
|
|
@@ -2023,7 +1279,7 @@ class ProfileService {
|
|
|
2023
1279
|
const a = (text_1 ?? '').trim();
|
|
2024
1280
|
const b = (text_2 ?? '').trim();
|
|
2025
1281
|
const cacheKey = a && b ? [a, b].sort().join('\0') : '';
|
|
2026
|
-
return from(this.loadCompareCache()).pipe(switchMap(() => {
|
|
1282
|
+
return from(this.loadCompareCache()).pipe(switchMap$1(() => {
|
|
2027
1283
|
if (cacheKey && this.compareInterestsCache.has(cacheKey)) {
|
|
2028
1284
|
return of(this.compareInterestsCache.get(cacheKey));
|
|
2029
1285
|
}
|
|
@@ -2054,7 +1310,7 @@ class ProfileService {
|
|
|
2054
1310
|
detectFace(image, creds, options) {
|
|
2055
1311
|
const rawKey = options?.cacheKey ?? (typeof image === 'string' ? image : undefined);
|
|
2056
1312
|
const cacheKey = ProfileService.faceCacheKey(rawKey);
|
|
2057
|
-
return from(this.loadFaceCache()).pipe(switchMap(() => {
|
|
1313
|
+
return from(this.loadFaceCache()).pipe(switchMap$1(() => {
|
|
2058
1314
|
if (cacheKey && this.faceCache.has(cacheKey)) {
|
|
2059
1315
|
console.log('Face detection cache hit');
|
|
2060
1316
|
return of(this.faceCache.get(cacheKey));
|
|
@@ -2126,7 +1382,7 @@ class ProfileService {
|
|
|
2126
1382
|
}
|
|
2127
1383
|
// Propagate other errors (or second failure) to the caller
|
|
2128
1384
|
return throwError(() => error);
|
|
2129
|
-
}))), map
|
|
1385
|
+
}))), map((raw) => {
|
|
2130
1386
|
// Face++ sometimes returns HTTP 200 with an error payload containing `error_message`
|
|
2131
1387
|
// e.g. "AUTHENTICATION_ERROR: api_key and api_secret does not match." or
|
|
2132
1388
|
// "AUTHORIZATION_ERROR:<reason>" or "CONCURRENCY_LIMIT_EXCEEDED".
|
|
@@ -2206,27 +1462,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
2206
1462
|
* The lib does not call third-party APIs; the backend does.
|
|
2207
1463
|
*/
|
|
2208
1464
|
class ProfileComparisonBackendService {
|
|
2209
|
-
http;
|
|
2210
|
-
apiBaseUrl;
|
|
2211
|
-
getVerboseLogging;
|
|
2212
|
-
constructor(
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
1465
|
+
http = inject(HttpClient);
|
|
1466
|
+
apiBaseUrl = inject(PROFILE_COMPARISON_API_BASE_URL, { optional: true });
|
|
1467
|
+
getVerboseLogging = inject(PROFILE_COMPARISON_VERBOSE_LOGGING, { optional: true });
|
|
1468
|
+
constructor() { }
|
|
1469
|
+
static extractErrorMessage(err) {
|
|
1470
|
+
if (err == null || typeof err !== 'object')
|
|
1471
|
+
return 'Unknown error';
|
|
1472
|
+
const e = err;
|
|
1473
|
+
return e.error?.message || e.message || 'Unknown error';
|
|
2216
1474
|
}
|
|
2217
1475
|
log(...args) {
|
|
2218
1476
|
if (this.getVerboseLogging?.() === true) {
|
|
2219
1477
|
console.log('[ProfileComparisonBackend]', ...args);
|
|
2220
1478
|
}
|
|
2221
1479
|
}
|
|
2222
|
-
|
|
1480
|
+
/** Returns the legacy token URL (for backward compat fallback). */
|
|
1481
|
+
getLegacyTokenUrl() {
|
|
2223
1482
|
const u = this.apiBaseUrl;
|
|
2224
1483
|
if (u == null)
|
|
2225
1484
|
return null;
|
|
2226
1485
|
return typeof u === 'function' ? u() : u;
|
|
2227
1486
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
1487
|
+
/** @deprecated Use getLegacyTokenUrl(). Kept for backward compat. */
|
|
1488
|
+
getBaseUrl() {
|
|
1489
|
+
return this.getLegacyTokenUrl();
|
|
1490
|
+
}
|
|
1491
|
+
getComparison(config, baseUrl) {
|
|
1492
|
+
const base = baseUrl ?? this.getLegacyTokenUrl();
|
|
2230
1493
|
this.log('getComparison', 'baseUrl', base ?? '(null)');
|
|
2231
1494
|
if (!base || !base.trim()) {
|
|
2232
1495
|
this.log('getComparison', 'returning error: backend not configured');
|
|
@@ -2236,29 +1499,17 @@ class ProfileComparisonBackendService {
|
|
|
2236
1499
|
this.log('getComparison', 'POST', url);
|
|
2237
1500
|
return this.http.post(url, { config }).pipe(tap$1(() => this.log('getComparison', 'POST success')), catchError((err) => {
|
|
2238
1501
|
this.log('getComparison', 'POST error', err);
|
|
2239
|
-
|
|
2240
|
-
const status = err?.status ?? err?.statusCode;
|
|
2241
|
-
console.error('[ProfileComparisonBackend] getComparison failed', status, msg);
|
|
1502
|
+
console.error('[ProfileComparisonBackend] getComparison failed', err?.status, ProfileComparisonBackendService.extractErrorMessage(err));
|
|
2242
1503
|
return throwError(() => err);
|
|
2243
1504
|
}));
|
|
2244
1505
|
}
|
|
2245
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, deps: [
|
|
1506
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2246
1507
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, providedIn: 'root' });
|
|
2247
1508
|
}
|
|
2248
1509
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, decorators: [{
|
|
2249
1510
|
type: Injectable,
|
|
2250
1511
|
args: [{ providedIn: 'root' }]
|
|
2251
|
-
}], ctorParameters: () => [
|
|
2252
|
-
type: Optional
|
|
2253
|
-
}, {
|
|
2254
|
-
type: Inject,
|
|
2255
|
-
args: [PROFILE_COMPARISON_API_BASE_URL]
|
|
2256
|
-
}] }, { type: undefined, decorators: [{
|
|
2257
|
-
type: Optional
|
|
2258
|
-
}, {
|
|
2259
|
-
type: Inject,
|
|
2260
|
-
args: [PROFILE_COMPARISON_VERBOSE_LOGGING]
|
|
2261
|
-
}] }] });
|
|
1512
|
+
}], ctorParameters: () => [] });
|
|
2262
1513
|
|
|
2263
1514
|
class ProfileComparisonLibComponent {
|
|
2264
1515
|
backendService;
|
|
@@ -2306,7 +1557,9 @@ class ProfileComparisonLibComponent {
|
|
|
2306
1557
|
user1Image: '',
|
|
2307
1558
|
user2Image: '',
|
|
2308
1559
|
};
|
|
2309
|
-
|
|
1560
|
+
backendMode = BackendMode.Real;
|
|
1561
|
+
backendUrl = null;
|
|
1562
|
+
fadeAllEdges = true;
|
|
2310
1563
|
matrixDataChange = new EventEmitter();
|
|
2311
1564
|
rawLLMOutputChange = new EventEmitter();
|
|
2312
1565
|
viewProfileClick = new EventEmitter();
|
|
@@ -2358,6 +1611,8 @@ class ProfileComparisonLibComponent {
|
|
|
2358
1611
|
matrixData = null;
|
|
2359
1612
|
/** When false, backend URL is not provided — show "Configure backend". */
|
|
2360
1613
|
backendConfigured = false;
|
|
1614
|
+
/** The resolved backend URL computed from inputs and legacy token. */
|
|
1615
|
+
resolvedBackendUrl = null;
|
|
2361
1616
|
compressionConfig = {
|
|
2362
1617
|
maxWidth: ProfileComparisonLibComponent.DEFAULT_MAX_WIDTH,
|
|
2363
1618
|
maxHeight: ProfileComparisonLibComponent.DEFAULT_MAX_HEIGHT,
|
|
@@ -2411,9 +1666,9 @@ class ProfileComparisonLibComponent {
|
|
|
2411
1666
|
});
|
|
2412
1667
|
this.baseConfig = JSON.parse(JSON.stringify(this.config));
|
|
2413
1668
|
this.updateConfigProperties();
|
|
2414
|
-
|
|
2415
|
-
this.backendConfigured = !!
|
|
2416
|
-
this.log('ngOnInit', 'backendConfigured', this.backendConfigured, '
|
|
1669
|
+
this.resolvedBackendUrl = resolveBackendUrl(this.backendUrl, this.backendMode, this.backendService.getLegacyTokenUrl());
|
|
1670
|
+
this.backendConfigured = !!this.resolvedBackendUrl;
|
|
1671
|
+
this.log('ngOnInit', 'backendConfigured', this.backendConfigured, 'resolvedUrl', this.resolvedBackendUrl ?? '(null)');
|
|
2417
1672
|
if (!this.backendConfigured) {
|
|
2418
1673
|
this.log('ngOnInit', 'exiting early: backend not configured');
|
|
2419
1674
|
this.user1Transform = 'translateY(0px)';
|
|
@@ -2441,6 +1696,11 @@ class ProfileComparisonLibComponent {
|
|
|
2441
1696
|
this.waitForImagesAndInitDrag();
|
|
2442
1697
|
}
|
|
2443
1698
|
ngOnChanges(changes) {
|
|
1699
|
+
if (changes['backendMode'] || changes['backendUrl']) {
|
|
1700
|
+
this.resolvedBackendUrl = resolveBackendUrl(this.backendUrl, this.backendMode, this.backendService.getLegacyTokenUrl());
|
|
1701
|
+
this.backendConfigured = !!this.resolvedBackendUrl;
|
|
1702
|
+
this.log('ngOnChanges', 'backend inputs changed', 'resolvedUrl', this.resolvedBackendUrl ?? '(null)');
|
|
1703
|
+
}
|
|
2444
1704
|
if (changes['config']) {
|
|
2445
1705
|
const first = changes['config'].firstChange;
|
|
2446
1706
|
this.log('ngOnChanges', 'config changed', 'firstChange', first, 'backendConfigured', this.backendConfigured);
|
|
@@ -2502,7 +1762,7 @@ class ProfileComparisonLibComponent {
|
|
|
2502
1762
|
user1ImageLen: (configToSend.user1Image || '').length,
|
|
2503
1763
|
user2ImageLen: (configToSend.user2Image || '').length,
|
|
2504
1764
|
});
|
|
2505
|
-
this.computeSub = this.backendService.getComparison(configToSend).subscribe({
|
|
1765
|
+
this.computeSub = this.backendService.getComparison(configToSend, this.resolvedBackendUrl).subscribe({
|
|
2506
1766
|
next: (payload) => {
|
|
2507
1767
|
if (requestId !== this.fetchRequestId) {
|
|
2508
1768
|
this.log('fetchComparison', 'ignoring stale success', { requestId, current: this.fetchRequestId });
|
|
@@ -2886,16 +2146,16 @@ class ProfileComparisonLibComponent {
|
|
|
2886
2146
|
const tasks = [];
|
|
2887
2147
|
if (this.user1Image) {
|
|
2888
2148
|
tasks.push(this.compressImageStringToDataUrl(this.user1Image, 'ConfigUser1').pipe(tap$1((dataUrl) => { if (dataUrl)
|
|
2889
|
-
this.user1Image = dataUrl; }), map
|
|
2149
|
+
this.user1Image = dataUrl; }), map(() => void 0)));
|
|
2890
2150
|
}
|
|
2891
2151
|
if (this.user2Image) {
|
|
2892
2152
|
tasks.push(this.compressImageStringToDataUrl(this.user2Image, 'ConfigUser2').pipe(tap$1((dataUrl) => { if (dataUrl)
|
|
2893
|
-
this.user2Image = dataUrl; }), map
|
|
2153
|
+
this.user2Image = dataUrl; }), map(() => void 0)));
|
|
2894
2154
|
}
|
|
2895
|
-
return tasks.length > 0 ? forkJoin(tasks).pipe(map
|
|
2155
|
+
return tasks.length > 0 ? forkJoin(tasks).pipe(map(() => void 0)) : of(void 0);
|
|
2896
2156
|
}
|
|
2897
2157
|
compressImageStringToDataUrl(imageStr, baseName) {
|
|
2898
|
-
return this.fileConversionService.getFileForImageString(imageStr, `${baseName}.jpg`).pipe(switchMap
|
|
2158
|
+
return this.fileConversionService.getFileForImageString(imageStr, `${baseName}.jpg`).pipe(switchMap(file => this.imageCompressionService.compressImageFile(file, this.compressionConfig)), map(res => res.dataUrl), catchError$1(() => of(null)));
|
|
2899
2159
|
}
|
|
2900
2160
|
getEyeCoordinatesFromBBox(bbox) {
|
|
2901
2161
|
const eyeY = bbox.y + bbox.height * 0.38;
|
|
@@ -3018,11 +2278,11 @@ class ProfileComparisonLibComponent {
|
|
|
3018
2278
|
return !!face && typeof face.x === 'number' && face.width > 0 && face.height > 0;
|
|
3019
2279
|
}
|
|
3020
2280
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibComponent, deps: [{ token: ProfileComparisonBackendService }, { token: i0.Renderer2 }, { token: FileConversionService }, { token: ImageCompressionService }, { token: i0.ChangeDetectorRef }, { token: PROFILE_COMPARISON_VERBOSE_LOGGING, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
3021
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", fadeAllEdges: "fadeAllEdges" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, viewQueries: [{ propertyName: "leftContainer", first: true, predicate: ["leftContainer"], descendants: true }, { propertyName: "rightContainer", first: true, predicate: ["rightContainer"], descendants: true }, { propertyName: "profileFlex", first: true, predicate: ["profileFlex"], descendants: true }, { propertyName: "profileImgLeft", first: true, predicate: ["profileImgLeft"], descendants: true }, { propertyName: "profileImgRight", first: true, predicate: ["profileImgRight"], descendants: true }, { propertyName: "shapeContainer", first: true, predicate: ["shapeContainer"], descendants: true }, { propertyName: "shapeBg", first: true, predicate: ["shapeBg"], descendants: true }, { propertyName: "shapeBg1", first: true, predicate: ["shapeBg1"], descendants: true }, { propertyName: "shapeBg2", first: true, predicate: ["shapeBg2"], descendants: true }, { propertyName: "shapeTextLeft", first: true, predicate: ["shapeTextLeft"], descendants: true }, { propertyName: "shapeTextRight", first: true, predicate: ["shapeTextRight"], descendants: true }, { propertyName: "shapeTextCenter", first: true, predicate: ["shapeTextCenter"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\n Configure backend\n</div>\n\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user1Image\"\n alt=\"User 1\"\n [style.transform]=\"user1Transform\"\n [style.object-position]=\"user1ObjectPosition\"\n (load)=\"onUserImageLoad(1, $event)\"\n />\n </div>\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user2Image\"\n alt=\"User 2\"\n [style.transform]=\"user2Transform\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n <!-- [style.object-fit]=\"'cover'\" -->\n </div>\n </div>\n\n <div #shapeContainer class=\"shape\">\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\n<g filter=\"url(#filter0_i_4_4137)\">\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4137)\">\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\n<g filter=\"url(#filter0_i_4_4141)\">\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4141)\">\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n </div>\n\n <div class=\"shape-text\">\n <div #shapeTextLeft class=\"shape-text-left\">\n <div class=\"scroll-container\" #leftContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson1Interests.length > 0\n ? alignedPerson1Interests\n : displayPerson1Interests.length > 0\n ? displayPerson1Interests\n : person1Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\n </div>\n\n <div #shapeTextCenter class=\"shape-text-center\">\n <ng-container *ngFor=\"let item of centerItem\">\n <p class=\"shape-p-center\">{{ item }}</p>\n </ng-container>\n </div>\n <div #shapeTextRight class=\"shape-text-right\">\n <div class=\"scroll-container\" #rightContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson2Interests.length > 0\n ? alignedPerson2Interests\n : displayPerson2Interests.length > 0\n ? displayPerson2Interests\n : person2Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p-right\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\n </div>\n </div>\n </div>\n\n <!-- Loading indicator for alignment process -->\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\n <div class=\"loading-spinner\"></div>\n </div>\n</div>\n\n\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:.5;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.6;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
2281
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, viewQueries: [{ propertyName: "leftContainer", first: true, predicate: ["leftContainer"], descendants: true }, { propertyName: "rightContainer", first: true, predicate: ["rightContainer"], descendants: true }, { propertyName: "profileFlex", first: true, predicate: ["profileFlex"], descendants: true }, { propertyName: "profileImgLeft", first: true, predicate: ["profileImgLeft"], descendants: true }, { propertyName: "profileImgRight", first: true, predicate: ["profileImgRight"], descendants: true }, { propertyName: "shapeContainer", first: true, predicate: ["shapeContainer"], descendants: true }, { propertyName: "shapeBg", first: true, predicate: ["shapeBg"], descendants: true }, { propertyName: "shapeBg1", first: true, predicate: ["shapeBg1"], descendants: true }, { propertyName: "shapeBg2", first: true, predicate: ["shapeBg2"], descendants: true }, { propertyName: "shapeTextLeft", first: true, predicate: ["shapeTextLeft"], descendants: true }, { propertyName: "shapeTextRight", first: true, predicate: ["shapeTextRight"], descendants: true }, { propertyName: "shapeTextCenter", first: true, predicate: ["shapeTextCenter"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\r\n Configure backend\r\n</div>\r\n\r\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\r\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\r\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user1Image\"\r\n alt=\"User 1\"\r\n [style.transform]=\"user1Transform\"\r\n [style.object-position]=\"user1ObjectPosition\"\r\n (load)=\"onUserImageLoad(1, $event)\"\r\n />\r\n </div>\r\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user2Image\"\r\n alt=\"User 2\"\r\n [style.transform]=\"user2Transform\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n <!-- [style.object-fit]=\"'cover'\" -->\r\n </div>\r\n </div>\r\n\r\n <div #shapeContainer class=\"shape\">\r\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\r\n<g filter=\"url(#filter0_i_4_4137)\">\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4137)\">\r\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\r\n<g filter=\"url(#filter0_i_4_4141)\">\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4141)\">\r\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n </div>\r\n\r\n <div class=\"shape-text\">\r\n <div #shapeTextLeft class=\"shape-text-left\">\r\n <div class=\"scroll-container\" #leftContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson1Interests.length > 0\r\n ? alignedPerson1Interests\r\n : displayPerson1Interests.length > 0\r\n ? displayPerson1Interests\r\n : person1Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\r\n </div>\r\n\r\n <div #shapeTextCenter class=\"shape-text-center\">\r\n <ng-container *ngFor=\"let item of centerItem\">\r\n <p class=\"shape-p-center\">{{ item }}</p>\r\n </ng-container>\r\n </div>\r\n <div #shapeTextRight class=\"shape-text-right\">\r\n <div class=\"scroll-container\" #rightContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson2Interests.length > 0\r\n ? alignedPerson2Interests\r\n : displayPerson2Interests.length > 0\r\n ? displayPerson2Interests\r\n : person2Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p-right\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading indicator for alignment process -->\r\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\r\n <div class=\"loading-spinner\"></div>\r\n </div>\r\n</div>\r\n\r\n\r\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:1;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.75;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
3022
2282
|
}
|
|
3023
2283
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibComponent, decorators: [{
|
|
3024
2284
|
type: Component,
|
|
3025
|
-
args: [{ selector: 'lib-profile-comparison', standalone: false, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\n Configure backend\n</div>\n\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user1Image\"\n alt=\"User 1\"\n [style.transform]=\"user1Transform\"\n [style.object-position]=\"user1ObjectPosition\"\n (load)=\"onUserImageLoad(1, $event)\"\n />\n </div>\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user2Image\"\n alt=\"User 2\"\n [style.transform]=\"user2Transform\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n <!-- [style.object-fit]=\"'cover'\" -->\n </div>\n </div>\n\n <div #shapeContainer class=\"shape\">\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\n<g filter=\"url(#filter0_i_4_4137)\">\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4137)\">\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\n<g filter=\"url(#filter0_i_4_4141)\">\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4141)\">\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n </div>\n\n <div class=\"shape-text\">\n <div #shapeTextLeft class=\"shape-text-left\">\n <div class=\"scroll-container\" #leftContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson1Interests.length > 0\n ? alignedPerson1Interests\n : displayPerson1Interests.length > 0\n ? displayPerson1Interests\n : person1Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\n </div>\n\n <div #shapeTextCenter class=\"shape-text-center\">\n <ng-container *ngFor=\"let item of centerItem\">\n <p class=\"shape-p-center\">{{ item }}</p>\n </ng-container>\n </div>\n <div #shapeTextRight class=\"shape-text-right\">\n <div class=\"scroll-container\" #rightContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson2Interests.length > 0\n ? alignedPerson2Interests\n : displayPerson2Interests.length > 0\n ? displayPerson2Interests\n : person2Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p-right\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\n </div>\n </div>\n </div>\n\n <!-- Loading indicator for alignment process -->\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\n <div class=\"loading-spinner\"></div>\n </div>\n</div>\n\n\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:.5;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.6;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"] }]
|
|
2285
|
+
args: [{ selector: 'lib-profile-comparison', standalone: false, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\r\n Configure backend\r\n</div>\r\n\r\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\r\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\r\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user1Image\"\r\n alt=\"User 1\"\r\n [style.transform]=\"user1Transform\"\r\n [style.object-position]=\"user1ObjectPosition\"\r\n (load)=\"onUserImageLoad(1, $event)\"\r\n />\r\n </div>\r\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user2Image\"\r\n alt=\"User 2\"\r\n [style.transform]=\"user2Transform\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n <!-- [style.object-fit]=\"'cover'\" -->\r\n </div>\r\n </div>\r\n\r\n <div #shapeContainer class=\"shape\">\r\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\r\n<g filter=\"url(#filter0_i_4_4137)\">\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4137)\">\r\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\r\n<g filter=\"url(#filter0_i_4_4141)\">\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4141)\">\r\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n </div>\r\n\r\n <div class=\"shape-text\">\r\n <div #shapeTextLeft class=\"shape-text-left\">\r\n <div class=\"scroll-container\" #leftContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson1Interests.length > 0\r\n ? alignedPerson1Interests\r\n : displayPerson1Interests.length > 0\r\n ? displayPerson1Interests\r\n : person1Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\r\n </div>\r\n\r\n <div #shapeTextCenter class=\"shape-text-center\">\r\n <ng-container *ngFor=\"let item of centerItem\">\r\n <p class=\"shape-p-center\">{{ item }}</p>\r\n </ng-container>\r\n </div>\r\n <div #shapeTextRight class=\"shape-text-right\">\r\n <div class=\"scroll-container\" #rightContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson2Interests.length > 0\r\n ? alignedPerson2Interests\r\n : displayPerson2Interests.length > 0\r\n ? displayPerson2Interests\r\n : person2Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p-right\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading indicator for alignment process -->\r\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\r\n <div class=\"loading-spinner\"></div>\r\n </div>\r\n</div>\r\n\r\n\r\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:1;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.75;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"] }]
|
|
3026
2286
|
}], ctorParameters: () => [{ type: ProfileComparisonBackendService }, { type: i0.Renderer2 }, { type: FileConversionService }, { type: ImageCompressionService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
3027
2287
|
type: Optional
|
|
3028
2288
|
}, {
|
|
@@ -3030,6 +2290,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3030
2290
|
args: [PROFILE_COMPARISON_VERBOSE_LOGGING]
|
|
3031
2291
|
}] }], propDecorators: { config: [{
|
|
3032
2292
|
type: Input
|
|
2293
|
+
}], backendMode: [{
|
|
2294
|
+
type: Input
|
|
2295
|
+
}], backendUrl: [{
|
|
2296
|
+
type: Input
|
|
3033
2297
|
}], fadeAllEdges: [{
|
|
3034
2298
|
type: Input
|
|
3035
2299
|
}], matrixDataChange: [{
|
|
@@ -3083,9 +2347,7 @@ class ProfileComparisonLibModule {
|
|
|
3083
2347
|
FormsModule,
|
|
3084
2348
|
ReactiveFormsModule], exports: [ProfileComparisonLibComponent] });
|
|
3085
2349
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibModule, providers: [
|
|
3086
|
-
ProfileComparisonBackendService
|
|
3087
|
-
EmbeddingService,
|
|
3088
|
-
OpenAIEmbeddingService
|
|
2350
|
+
ProfileComparisonBackendService
|
|
3089
2351
|
], imports: [CommonModule,
|
|
3090
2352
|
HttpClientModule,
|
|
3091
2353
|
FormsModule,
|
|
@@ -3107,9 +2369,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3107
2369
|
ProfileComparisonLibComponent
|
|
3108
2370
|
],
|
|
3109
2371
|
providers: [
|
|
3110
|
-
ProfileComparisonBackendService
|
|
3111
|
-
EmbeddingService,
|
|
3112
|
-
OpenAIEmbeddingService
|
|
2372
|
+
ProfileComparisonBackendService
|
|
3113
2373
|
]
|
|
3114
2374
|
}]
|
|
3115
2375
|
}] });
|
|
@@ -3122,5 +2382,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3122
2382
|
* Generated bundle index. Do not edit.
|
|
3123
2383
|
*/
|
|
3124
2384
|
|
|
3125
|
-
export {
|
|
2385
|
+
export { BACKEND_MODE_URLS, BackendMode, CachePersistenceService, FileConversionService, ImageCompressionService, OpenAIEmbeddingService, PROFILE_COMPARISON_API_BASE_URL, PROFILE_COMPARISON_VERBOSE_LOGGING, ProfileComparisonBackendService, ProfileComparisonLibComponent, ProfileComparisonLibModule, ProfileComparisonLibService, ProfileService, resolveBackendUrl };
|
|
3126
2386
|
//# sourceMappingURL=naniteninja-profile-comparison-lib.mjs.map
|