@naniteninja/profile-comparison-lib 1.0.27 → 1.0.28
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.
|
@@ -217,10 +217,10 @@ class CachePersistenceService {
|
|
|
217
217
|
static STORE_OPENAI_EMBEDDINGS = STORE_OPENAI_EMBEDDINGS;
|
|
218
218
|
static STORE_PROFILE_FACE = STORE_PROFILE_FACE;
|
|
219
219
|
static STORE_PROFILE_COMPARE = STORE_PROFILE_COMPARE;
|
|
220
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
221
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
220
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: CachePersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
221
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: CachePersistenceService, providedIn: 'root' });
|
|
222
222
|
}
|
|
223
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
223
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: CachePersistenceService, decorators: [{
|
|
224
224
|
type: Injectable,
|
|
225
225
|
args: [{ providedIn: 'root' }]
|
|
226
226
|
}] });
|
|
@@ -328,10 +328,10 @@ class FileConversionService {
|
|
|
328
328
|
const mime = this.getMimeFromFilename(imageStr);
|
|
329
329
|
return this.urlToFile(imageStr, fileBaseName, mime);
|
|
330
330
|
}
|
|
331
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
332
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
331
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: FileConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
332
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: FileConversionService, providedIn: 'root' });
|
|
333
333
|
}
|
|
334
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
334
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: FileConversionService, decorators: [{
|
|
335
335
|
type: Injectable,
|
|
336
336
|
args: [{
|
|
337
337
|
providedIn: 'root'
|
|
@@ -447,10 +447,10 @@ class ImageCompressionService {
|
|
|
447
447
|
return `${base}.webp`;
|
|
448
448
|
return `${base}.jpg`;
|
|
449
449
|
}
|
|
450
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
451
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
450
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ImageCompressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
451
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ImageCompressionService, providedIn: 'root' });
|
|
452
452
|
}
|
|
453
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
453
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ImageCompressionService, decorators: [{
|
|
454
454
|
type: Injectable,
|
|
455
455
|
args: [{
|
|
456
456
|
providedIn: 'root'
|
|
@@ -594,96 +594,96 @@ class OpenAIEmbeddingService {
|
|
|
594
594
|
[OpenAIEmbeddingService.HEADER_CONTENT_TYPE]: OpenAIEmbeddingService.CONTENT_TYPE_JSON,
|
|
595
595
|
[OpenAIEmbeddingService.HEADER_AUTHORIZATION]: `${OpenAIEmbeddingService.BEARER_PREFIX}${apiKey}`
|
|
596
596
|
});
|
|
597
|
-
const prompt = `
|
|
598
|
-
You are an expert semantic alignment engine.
|
|
599
|
-
Task: Align the items from List A to List B based on semantic similarity.
|
|
600
|
-
Context: This is a heuristic, human-semantic alignment (use embeddings later if you want it algorithmically strict).
|
|
601
|
-
|
|
602
|
-
List A: ${JSON.stringify(validA)}
|
|
603
|
-
List B: ${JSON.stringify(validB)}
|
|
604
|
-
|
|
605
|
-
SEMANTIC STRATEGY (3-PASS PROCESS):
|
|
606
|
-
1. **Pass 1: Exact & Strong Matches**: First, lock in all exact synonyms (e.g., "Dog" vs "Puppy") and strong category matches (e.g., "Tennis" vs "Badminton"). These are your anchors.
|
|
607
|
-
2. **Pass 2: Thematic Clustering**: Next, group remaining items by sub-theme (e.g., "Water Sports", "Asian Cuisine"). Align items within these themes even if they aren't perfect pairs.
|
|
608
|
-
3. **Pass 3: Contextual & Loose Matches**: Look for looser associations (e.g., "Reading" vs "Library").
|
|
609
|
-
4. **Isolation**: If an item truly has NO relation to anything remaining in the other list, ISOLATE it using "----". Do NOT force a bad match.
|
|
610
|
-
|
|
611
|
-
Rules:
|
|
612
|
-
1. Align items into clusters with variable dash-gaps to show semantic distance.
|
|
613
|
-
2. Use "-" for a small hop (related but distinct).
|
|
614
|
-
3. Use "----" for a hard theme switch (different category).
|
|
615
|
-
4. You MUST reorder items to create the best semantic clusters.
|
|
616
|
-
5. Ensure the output arrays "listA" and "listB" have the SAME length.
|
|
617
|
-
6. If an item has no match, align it with "-" or "----" as appropriate, or just a gap.
|
|
618
|
-
7. Output MUST be a JSON object with "listA" and "listB".
|
|
619
|
-
8. **COMPLETENESS**: You MUST include EVERY item from the input List A and List B in the output. Do not omit any items.
|
|
620
|
-
9. **Handling Leftovers**: If you have items left over at the end that don't match well, still include them in the list, aligned with "-" if necessary, or grouped together if they are somewhat related.
|
|
621
|
-
10. **Spacer Alignment**: You MUST align "----" with "----" in both lists. Do NOT align a hard switch "----" with a word. If you need a switch on one side, put it on the other side too.
|
|
622
|
-
11. **Strict Side Adherence**: Items from Input List A MUST ONLY appear in Output List A. Items from Input List B MUST ONLY appear in Output List B. Do not mix them up.
|
|
623
|
-
12. **Mandatory Spacers**: You MUST insert a spacer ("-" or "----") between clusters of related items. Avoid putting "----" between every single item; use it only when the topic truly shifts.
|
|
624
|
-
13. **NO TRAILING SPACER**: The last item in both lists MUST be a word. Do NOT put a spacer at the very end.
|
|
625
|
-
14. **Length Mismatch Handling**: If one list is shorter than the other, you MUST fill the empty slots in the shorter list with spacers ("-") ONLY. DO NOT invent words or reuse words to fill the space.
|
|
626
|
-
15. **HIGH SIMILARITY CONSOLIDATION (>= 0.8)**: This is CRITICAL for center display:
|
|
627
|
-
- When you find a pair with VERY HIGH semantic similarity (>= 0.8), you MUST output the SAME TEXT in both listA and listB at that position.
|
|
628
|
-
- **Key principle**: If the items are synonyms, very similar activities, or one is a specific type of the other, consolidate them!
|
|
629
|
-
- **STRICT CONSTRAINT**: You can ONLY consolidate if BOTH items actually exist in the input lists and are semantically related.
|
|
630
|
-
- **NEVER** invent an item for List B just to make a pair with List A. If List A has "figma" and List B has NOTHING related, output "figma" in List A and "-" in List B. DO NOT put "figma" in List B.
|
|
631
|
-
- **STRICT SYMMETRY**: If you use the "×" symbol to consolidate, you MUST output the EXACT SAME string in BOTH "listA" and "listB" at that index.
|
|
632
|
-
- This allows the system to automatically move these items to the center display!
|
|
633
|
-
- **NEVER** use "×" on one side and a spacer on the other. That is a critical failure.
|
|
634
|
-
- **NEVER** include similarity scores or any other numbers in the output strings unless they are part of the original item text.
|
|
635
|
-
- **ONLY** use "×" as the separator for consolidated items. Do not use "/", "&", or other symbols for this purpose.
|
|
636
|
-
- Example: "Jogging" (List A) + "Running" (List B) -> "Jogging × Running" in BOTH listA and listB.
|
|
637
|
-
- **NEVER DROP CONSOLIDATED ITEMS**: If you consolidate "Jogging" and "Running" into "Jogging × Running", you MUST put this string in **BOTH** List A and List B. Do NOT put it in List A and a spacer in List B. That would delete "Running" from the output.
|
|
638
|
-
- **CRITICAL ERROR PREVENTION**: Do NOT drop items from List B. If "Running" is in Input List B, it MUST appear in Output List B (either as "Running" or "Jogging × Running"). If you output "Jogging × Running" in List A but "-" in List B, you have FAILED.
|
|
639
|
-
- **COMPLETENESS CHECK**: You MUST verify that EVERY item from the input lists is present in the output. If "Running" is in Input List B, it MUST be in Output List B (or consolidated). DO NOT DROP ITEMS.
|
|
640
|
-
- **DETERMINISM**: Process the list from top to bottom. Do not change your output between runs.
|
|
641
|
-
- This allows the system to automatically move these items to the center display!
|
|
642
|
-
16. Do not insert any new, inferred, or related words. Each pair must be created strictly from the existing list items only.
|
|
643
|
-
|
|
644
|
-
NEGATIVE CONSTRAINTS:
|
|
645
|
-
- NEVER put an item from List A into List B.
|
|
646
|
-
- NEVER put an item from List B into List A.
|
|
647
|
-
- NEVER invent new items. Only use items from the input lists.
|
|
648
|
-
- **NEVER DUPLICATE ITEMS**. Each item from the input list must appear EXACTLY ONCE in the output.
|
|
649
|
-
|
|
650
|
-
VERIFICATION STEP:
|
|
651
|
-
Before outputting, verify every single item:
|
|
652
|
-
- "Is this item in Input List A?" -> If yes, put it in Output List A.
|
|
653
|
-
- "Is this item in Input List B?" -> If yes, put it in Output List B.
|
|
654
|
-
- "Is this item in neither?" -> It is a hallucination. DELETE IT.
|
|
655
|
-
|
|
656
|
-
CRITICAL: OUTPUT MUST CONTAIN EXACTLY THE SAME ITEMS AS INPUT. NO DELETIONS ALLOWED.
|
|
657
|
-
|
|
658
|
-
FINAL SANITY CHECK:
|
|
659
|
-
- **HALLUCINATION CHECK**: Go through every word in your Output List A. Is it EXACTLY present in Input List A? If not, DELETE IT. Do the same for List B.
|
|
660
|
-
- **DUPLICATION CHECK**: Scan List A and List B. Does ANY word appear more than once? If so, DELETE the extra occurrences immediately. Each item must appear EXACTLY ONCE.
|
|
661
|
-
- **COUNT CHECK**: Count the number of non-spacer items in your Output List A. Does it match the number of items in Input List A? If not, you are missing items. FIND THEM AND ADD THEM.
|
|
662
|
-
- **COUNT CHECK**: Do the same for List B.
|
|
663
|
-
- Look at your generated "listA". Are there any items that belong to Input List B? REMOVE THEM immediately.
|
|
664
|
-
- Look at your generated "listB". Are there any items that belong to Input List A? REMOVE THEM immediately.
|
|
665
|
-
- **Did you miss any items from Input List A? ADD THEM NOW.**
|
|
666
|
-
- **Did you miss any items from Input List B? ADD THEM NOW.**
|
|
667
|
-
- If you are unsure where an item belongs, check the Input Lists provided above. Do NOT guess.
|
|
668
|
-
|
|
669
|
-
Example of Desired Output Logic:
|
|
670
|
-
- "Volunteering at animal shelter" vs "Dog training" -> Related? Yes. Similarity >= 0.8? No (different activities)
|
|
671
|
-
- NEXT PAIR: "Trail biking" vs "Rock climbing" -> Is this related to Animals? No. -> INSERT "----"
|
|
672
|
-
- "Trail biking" vs "Rock climbing" -> Related? Yes (Outdoors).
|
|
673
|
-
- NEXT PAIR: "Jogging" vs "Running" -> Similarity >= 0.8? YES! -> Output "Jogging × Running" in BOTH lists
|
|
674
|
-
- "Jogging" vs "Running" -> Related? Yes (Exercise).
|
|
675
|
-
- NEXT PAIR: "Beach volleyball" vs "Salsa dancing" -> Is this related to Exercise? Yes. -> INSERT "-" (Small hop)
|
|
676
|
-
- "Beach volleyball" vs "Salsa dancing" -> Related? Yes (Hobbies). Similarity >= 0.8? No (different hobbies)
|
|
677
|
-
- NEXT PAIR: "Fermenting kimchi" vs "Korean cooking" -> Is this related to Hobbies? No. -> INSERT "----"
|
|
678
|
-
|
|
679
|
-
JSON Output Structure:
|
|
680
|
-
{
|
|
681
|
-
"listA": ["Consolidated Item × Match", "----", "Unique Item A", "-" , "Unique Item A1" , "-" , "Unique Item A2" ,"----" , "Unique Item A3", "-" , "Unique Item A4 × Unique Item B4" , "----" , Unique Item A5, "----" , Uniue Item A6 ],
|
|
682
|
-
"listB": ["Consolidated Item × Match", "----", "Unique Item B", "-" , "Unique Item B1" , "-" , "Unique Item B2" ,"----" , "Unique Item B3", "-" , "Unique Item A4 × Unique Item B4" , "----" , Unique Item B5, "----" , - ]
|
|
683
|
-
}
|
|
684
|
-
Note: In this case, List A has more items, so the last remaining item is displayed as a single card.In other cases, List B may have more words, and in that situation, the extra word from List B is shown as a single card.
|
|
685
|
-
Each unique item in List A has a corresponding unique item in List B that is highly similar in meaning or related in terms of concept.
|
|
686
|
-
VARIATION & SIMILARITY CONTROL: Maintain strict consistency. If the same semantic relationship appears multiple times, align it identically every time.
|
|
597
|
+
const prompt = `
|
|
598
|
+
You are an expert semantic alignment engine.
|
|
599
|
+
Task: Align the items from List A to List B based on semantic similarity.
|
|
600
|
+
Context: This is a heuristic, human-semantic alignment (use embeddings later if you want it algorithmically strict).
|
|
601
|
+
|
|
602
|
+
List A: ${JSON.stringify(validA)}
|
|
603
|
+
List B: ${JSON.stringify(validB)}
|
|
604
|
+
|
|
605
|
+
SEMANTIC STRATEGY (3-PASS PROCESS):
|
|
606
|
+
1. **Pass 1: Exact & Strong Matches**: First, lock in all exact synonyms (e.g., "Dog" vs "Puppy") and strong category matches (e.g., "Tennis" vs "Badminton"). These are your anchors.
|
|
607
|
+
2. **Pass 2: Thematic Clustering**: Next, group remaining items by sub-theme (e.g., "Water Sports", "Asian Cuisine"). Align items within these themes even if they aren't perfect pairs.
|
|
608
|
+
3. **Pass 3: Contextual & Loose Matches**: Look for looser associations (e.g., "Reading" vs "Library").
|
|
609
|
+
4. **Isolation**: If an item truly has NO relation to anything remaining in the other list, ISOLATE it using "----". Do NOT force a bad match.
|
|
610
|
+
|
|
611
|
+
Rules:
|
|
612
|
+
1. Align items into clusters with variable dash-gaps to show semantic distance.
|
|
613
|
+
2. Use "-" for a small hop (related but distinct).
|
|
614
|
+
3. Use "----" for a hard theme switch (different category).
|
|
615
|
+
4. You MUST reorder items to create the best semantic clusters.
|
|
616
|
+
5. Ensure the output arrays "listA" and "listB" have the SAME length.
|
|
617
|
+
6. If an item has no match, align it with "-" or "----" as appropriate, or just a gap.
|
|
618
|
+
7. Output MUST be a JSON object with "listA" and "listB".
|
|
619
|
+
8. **COMPLETENESS**: You MUST include EVERY item from the input List A and List B in the output. Do not omit any items.
|
|
620
|
+
9. **Handling Leftovers**: If you have items left over at the end that don't match well, still include them in the list, aligned with "-" if necessary, or grouped together if they are somewhat related.
|
|
621
|
+
10. **Spacer Alignment**: You MUST align "----" with "----" in both lists. Do NOT align a hard switch "----" with a word. If you need a switch on one side, put it on the other side too.
|
|
622
|
+
11. **Strict Side Adherence**: Items from Input List A MUST ONLY appear in Output List A. Items from Input List B MUST ONLY appear in Output List B. Do not mix them up.
|
|
623
|
+
12. **Mandatory Spacers**: You MUST insert a spacer ("-" or "----") between clusters of related items. Avoid putting "----" between every single item; use it only when the topic truly shifts.
|
|
624
|
+
13. **NO TRAILING SPACER**: The last item in both lists MUST be a word. Do NOT put a spacer at the very end.
|
|
625
|
+
14. **Length Mismatch Handling**: If one list is shorter than the other, you MUST fill the empty slots in the shorter list with spacers ("-") ONLY. DO NOT invent words or reuse words to fill the space.
|
|
626
|
+
15. **HIGH SIMILARITY CONSOLIDATION (>= 0.8)**: This is CRITICAL for center display:
|
|
627
|
+
- When you find a pair with VERY HIGH semantic similarity (>= 0.8), you MUST output the SAME TEXT in both listA and listB at that position.
|
|
628
|
+
- **Key principle**: If the items are synonyms, very similar activities, or one is a specific type of the other, consolidate them!
|
|
629
|
+
- **STRICT CONSTRAINT**: You can ONLY consolidate if BOTH items actually exist in the input lists and are semantically related.
|
|
630
|
+
- **NEVER** invent an item for List B just to make a pair with List A. If List A has "figma" and List B has NOTHING related, output "figma" in List A and "-" in List B. DO NOT put "figma" in List B.
|
|
631
|
+
- **STRICT SYMMETRY**: If you use the "×" symbol to consolidate, you MUST output the EXACT SAME string in BOTH "listA" and "listB" at that index.
|
|
632
|
+
- This allows the system to automatically move these items to the center display!
|
|
633
|
+
- **NEVER** use "×" on one side and a spacer on the other. That is a critical failure.
|
|
634
|
+
- **NEVER** include similarity scores or any other numbers in the output strings unless they are part of the original item text.
|
|
635
|
+
- **ONLY** use "×" as the separator for consolidated items. Do not use "/", "&", or other symbols for this purpose.
|
|
636
|
+
- Example: "Jogging" (List A) + "Running" (List B) -> "Jogging × Running" in BOTH listA and listB.
|
|
637
|
+
- **NEVER DROP CONSOLIDATED ITEMS**: If you consolidate "Jogging" and "Running" into "Jogging × Running", you MUST put this string in **BOTH** List A and List B. Do NOT put it in List A and a spacer in List B. That would delete "Running" from the output.
|
|
638
|
+
- **CRITICAL ERROR PREVENTION**: Do NOT drop items from List B. If "Running" is in Input List B, it MUST appear in Output List B (either as "Running" or "Jogging × Running"). If you output "Jogging × Running" in List A but "-" in List B, you have FAILED.
|
|
639
|
+
- **COMPLETENESS CHECK**: You MUST verify that EVERY item from the input lists is present in the output. If "Running" is in Input List B, it MUST be in Output List B (or consolidated). DO NOT DROP ITEMS.
|
|
640
|
+
- **DETERMINISM**: Process the list from top to bottom. Do not change your output between runs.
|
|
641
|
+
- This allows the system to automatically move these items to the center display!
|
|
642
|
+
16. Do not insert any new, inferred, or related words. Each pair must be created strictly from the existing list items only.
|
|
643
|
+
|
|
644
|
+
NEGATIVE CONSTRAINTS:
|
|
645
|
+
- NEVER put an item from List A into List B.
|
|
646
|
+
- NEVER put an item from List B into List A.
|
|
647
|
+
- NEVER invent new items. Only use items from the input lists.
|
|
648
|
+
- **NEVER DUPLICATE ITEMS**. Each item from the input list must appear EXACTLY ONCE in the output.
|
|
649
|
+
|
|
650
|
+
VERIFICATION STEP:
|
|
651
|
+
Before outputting, verify every single item:
|
|
652
|
+
- "Is this item in Input List A?" -> If yes, put it in Output List A.
|
|
653
|
+
- "Is this item in Input List B?" -> If yes, put it in Output List B.
|
|
654
|
+
- "Is this item in neither?" -> It is a hallucination. DELETE IT.
|
|
655
|
+
|
|
656
|
+
CRITICAL: OUTPUT MUST CONTAIN EXACTLY THE SAME ITEMS AS INPUT. NO DELETIONS ALLOWED.
|
|
657
|
+
|
|
658
|
+
FINAL SANITY CHECK:
|
|
659
|
+
- **HALLUCINATION CHECK**: Go through every word in your Output List A. Is it EXACTLY present in Input List A? If not, DELETE IT. Do the same for List B.
|
|
660
|
+
- **DUPLICATION CHECK**: Scan List A and List B. Does ANY word appear more than once? If so, DELETE the extra occurrences immediately. Each item must appear EXACTLY ONCE.
|
|
661
|
+
- **COUNT CHECK**: Count the number of non-spacer items in your Output List A. Does it match the number of items in Input List A? If not, you are missing items. FIND THEM AND ADD THEM.
|
|
662
|
+
- **COUNT CHECK**: Do the same for List B.
|
|
663
|
+
- Look at your generated "listA". Are there any items that belong to Input List B? REMOVE THEM immediately.
|
|
664
|
+
- Look at your generated "listB". Are there any items that belong to Input List A? REMOVE THEM immediately.
|
|
665
|
+
- **Did you miss any items from Input List A? ADD THEM NOW.**
|
|
666
|
+
- **Did you miss any items from Input List B? ADD THEM NOW.**
|
|
667
|
+
- If you are unsure where an item belongs, check the Input Lists provided above. Do NOT guess.
|
|
668
|
+
|
|
669
|
+
Example of Desired Output Logic:
|
|
670
|
+
- "Volunteering at animal shelter" vs "Dog training" -> Related? Yes. Similarity >= 0.8? No (different activities)
|
|
671
|
+
- NEXT PAIR: "Trail biking" vs "Rock climbing" -> Is this related to Animals? No. -> INSERT "----"
|
|
672
|
+
- "Trail biking" vs "Rock climbing" -> Related? Yes (Outdoors).
|
|
673
|
+
- NEXT PAIR: "Jogging" vs "Running" -> Similarity >= 0.8? YES! -> Output "Jogging × Running" in BOTH lists
|
|
674
|
+
- "Jogging" vs "Running" -> Related? Yes (Exercise).
|
|
675
|
+
- NEXT PAIR: "Beach volleyball" vs "Salsa dancing" -> Is this related to Exercise? Yes. -> INSERT "-" (Small hop)
|
|
676
|
+
- "Beach volleyball" vs "Salsa dancing" -> Related? Yes (Hobbies). Similarity >= 0.8? No (different hobbies)
|
|
677
|
+
- NEXT PAIR: "Fermenting kimchi" vs "Korean cooking" -> Is this related to Hobbies? No. -> INSERT "----"
|
|
678
|
+
|
|
679
|
+
JSON Output Structure:
|
|
680
|
+
{
|
|
681
|
+
"listA": ["Consolidated Item × Match", "----", "Unique Item A", "-" , "Unique Item A1" , "-" , "Unique Item A2" ,"----" , "Unique Item A3", "-" , "Unique Item A4 × Unique Item B4" , "----" , Unique Item A5, "----" , Uniue Item A6 ],
|
|
682
|
+
"listB": ["Consolidated Item × Match", "----", "Unique Item B", "-" , "Unique Item B1" , "-" , "Unique Item B2" ,"----" , "Unique Item B3", "-" , "Unique Item A4 × Unique Item B4" , "----" , Unique Item B5, "----" , - ]
|
|
683
|
+
}
|
|
684
|
+
Note: In this case, List A has more items, so the last remaining item is displayed as a single card.In other cases, List B may have more words, and in that situation, the extra word from List B is shown as a single card.
|
|
685
|
+
Each unique item in List A has a corresponding unique item in List B that is highly similar in meaning or related in terms of concept.
|
|
686
|
+
VARIATION & SIMILARITY CONTROL: Maintain strict consistency. If the same semantic relationship appears multiple times, align it identically every time.
|
|
687
687
|
`;
|
|
688
688
|
const body = {
|
|
689
689
|
model: this.MODEL,
|
|
@@ -877,40 +877,40 @@ class OpenAIEmbeddingService {
|
|
|
877
877
|
[OpenAIEmbeddingService.HEADER_AUTHORIZATION]: `${OpenAIEmbeddingService.BEARER_PREFIX}${apiKey}`
|
|
878
878
|
});
|
|
879
879
|
// Construct the prompt
|
|
880
|
-
const prompt = `
|
|
881
|
-
You are an expert semantic alignment engine.
|
|
882
|
-
Task: Align the items from List A to List B based on semantic similarity.
|
|
883
|
-
|
|
884
|
-
List A: ${JSON.stringify(validA)}
|
|
885
|
-
List B: ${JSON.stringify(validB)}
|
|
886
|
-
|
|
887
|
-
Rules:
|
|
888
|
-
1. Match items that are semantically similar.
|
|
889
|
-
2. Support One-to-One, One-to-Many, and Many-to-Many matches.
|
|
890
|
-
3. **CRITICAL**: Avoid duplicate alignments. If multiple items in List B are highly similar to a single item in List A, group them ALL under that single List A item.
|
|
891
|
-
4. **CONSISTENCY**: The alignment must be symmetric. If "Mutton" matches "Chicken" when "Mutton" is on the left, it MUST also match "Chicken" when "Mutton" is on the right.
|
|
892
|
-
5. **ORDER**: You MUST reorder the items from BOTH lists to create the best semantic clusters. Place related groups next to each other (e.g., put all food items together, all tech items together).
|
|
893
|
-
6. If an item in List A has no match in List B, map it to "-".
|
|
894
|
-
7. If an item in List B is not matched to anything in List A, add a row with "-" on the left.
|
|
895
|
-
8. Output MUST be a JSON object with a single key "alignment" containing an array of objects.
|
|
896
|
-
9. Each object in the array must have:
|
|
897
|
-
- "left": string (item from List A or "-")
|
|
898
|
-
- "right": string (item from List B or "-")
|
|
899
|
-
- "score": number (similarity score between 0 and 1)
|
|
900
|
-
|
|
901
|
-
Example Output Format:
|
|
902
|
-
{
|
|
903
|
-
"alignment": [
|
|
904
|
-
{ "left": "Seafood", "right": "Fish", "score": 0.9 },
|
|
905
|
-
{ "left": "Seafood", "right": "Clams", "score": 0.85 },
|
|
906
|
-
{ "left": "Seafood", "right": "Crab", "score": 0.88 },
|
|
907
|
-
{ "left": "Chicken", "right": "Meat", "score": 0.8 },
|
|
908
|
-
{ "left": "Chicken", "right": "Poultry", "score": 0.9 },
|
|
909
|
-
{ "left": "Chicken", "right": "Mutton", "score": 0.85 },
|
|
910
|
-
{ "left": "Coffee", "right": "Cafés", "score": 0.9 },
|
|
911
|
-
{ "left": "Coding", "right": "-", "score": 0 }
|
|
912
|
-
]
|
|
913
|
-
}
|
|
880
|
+
const prompt = `
|
|
881
|
+
You are an expert semantic alignment engine.
|
|
882
|
+
Task: Align the items from List A to List B based on semantic similarity.
|
|
883
|
+
|
|
884
|
+
List A: ${JSON.stringify(validA)}
|
|
885
|
+
List B: ${JSON.stringify(validB)}
|
|
886
|
+
|
|
887
|
+
Rules:
|
|
888
|
+
1. Match items that are semantically similar.
|
|
889
|
+
2. Support One-to-One, One-to-Many, and Many-to-Many matches.
|
|
890
|
+
3. **CRITICAL**: Avoid duplicate alignments. If multiple items in List B are highly similar to a single item in List A, group them ALL under that single List A item.
|
|
891
|
+
4. **CONSISTENCY**: The alignment must be symmetric. If "Mutton" matches "Chicken" when "Mutton" is on the left, it MUST also match "Chicken" when "Mutton" is on the right.
|
|
892
|
+
5. **ORDER**: You MUST reorder the items from BOTH lists to create the best semantic clusters. Place related groups next to each other (e.g., put all food items together, all tech items together).
|
|
893
|
+
6. If an item in List A has no match in List B, map it to "-".
|
|
894
|
+
7. If an item in List B is not matched to anything in List A, add a row with "-" on the left.
|
|
895
|
+
8. Output MUST be a JSON object with a single key "alignment" containing an array of objects.
|
|
896
|
+
9. Each object in the array must have:
|
|
897
|
+
- "left": string (item from List A or "-")
|
|
898
|
+
- "right": string (item from List B or "-")
|
|
899
|
+
- "score": number (similarity score between 0 and 1)
|
|
900
|
+
|
|
901
|
+
Example Output Format:
|
|
902
|
+
{
|
|
903
|
+
"alignment": [
|
|
904
|
+
{ "left": "Seafood", "right": "Fish", "score": 0.9 },
|
|
905
|
+
{ "left": "Seafood", "right": "Clams", "score": 0.85 },
|
|
906
|
+
{ "left": "Seafood", "right": "Crab", "score": 0.88 },
|
|
907
|
+
{ "left": "Chicken", "right": "Meat", "score": 0.8 },
|
|
908
|
+
{ "left": "Chicken", "right": "Poultry", "score": 0.9 },
|
|
909
|
+
{ "left": "Chicken", "right": "Mutton", "score": 0.85 },
|
|
910
|
+
{ "left": "Coffee", "right": "Cafés", "score": 0.9 },
|
|
911
|
+
{ "left": "Coding", "right": "-", "score": 0 }
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
914
|
`;
|
|
915
915
|
const body = {
|
|
916
916
|
model: this.MODEL,
|
|
@@ -1229,10 +1229,10 @@ class OpenAIEmbeddingService {
|
|
|
1229
1229
|
this.persistence.clear(CachePersistenceService.STORE_OPENAI_SIMILARITY);
|
|
1230
1230
|
this.persistence.clear(CachePersistenceService.STORE_OPENAI_EMBEDDINGS);
|
|
1231
1231
|
}
|
|
1232
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1233
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1232
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: OpenAIEmbeddingService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1233
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: OpenAIEmbeddingService, providedIn: 'root' });
|
|
1234
1234
|
}
|
|
1235
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1235
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: OpenAIEmbeddingService, decorators: [{
|
|
1236
1236
|
type: Injectable,
|
|
1237
1237
|
args: [{
|
|
1238
1238
|
providedIn: 'root'
|
|
@@ -1264,10 +1264,10 @@ class ProfileComparisonLibService {
|
|
|
1264
1264
|
getVersion() {
|
|
1265
1265
|
return '0.0.0';
|
|
1266
1266
|
}
|
|
1267
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1268
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1268
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibService, providedIn: 'root' });
|
|
1269
1269
|
}
|
|
1270
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibService, decorators: [{
|
|
1271
1271
|
type: Injectable,
|
|
1272
1272
|
args: [{
|
|
1273
1273
|
providedIn: 'root'
|
|
@@ -1521,10 +1521,10 @@ class ProfileService {
|
|
|
1521
1521
|
this.compareInterestsCache.clear();
|
|
1522
1522
|
this.persistence.clear(CachePersistenceService.STORE_PROFILE_COMPARE);
|
|
1523
1523
|
}
|
|
1524
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1525
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1524
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1525
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileService, providedIn: 'root' });
|
|
1526
1526
|
}
|
|
1527
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1527
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileService, decorators: [{
|
|
1528
1528
|
type: Injectable,
|
|
1529
1529
|
args: [{ providedIn: 'root' }]
|
|
1530
1530
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
|
|
@@ -1580,10 +1580,10 @@ class ProfileComparisonBackendService {
|
|
|
1580
1580
|
return throwError(() => err);
|
|
1581
1581
|
}));
|
|
1582
1582
|
}
|
|
1583
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1584
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1583
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonBackendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1584
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonBackendService, providedIn: 'root' });
|
|
1585
1585
|
}
|
|
1586
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1586
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonBackendService, decorators: [{
|
|
1587
1587
|
type: Injectable,
|
|
1588
1588
|
args: [{ providedIn: 'root' }]
|
|
1589
1589
|
}], ctorParameters: () => [] });
|
|
@@ -2015,11 +2015,15 @@ class ProfileComparisonLibComponent {
|
|
|
2015
2015
|
this.log('fetchComparison', 'ignoring stale error', { requestId, current: this.fetchRequestId, status: err?.status });
|
|
2016
2016
|
return;
|
|
2017
2017
|
}
|
|
2018
|
-
this.log('fetchComparison', 'backend
|
|
2019
|
-
const msg = err?.error?.message ?? err?.message ?? err?.statusText ?? (typeof err?.error === 'string' ? err.error : null) ?? 'Request failed';
|
|
2020
|
-
this.backendError = msg;
|
|
2021
|
-
console.error('[ProfileComparisonLib] fetchComparison error', err?.status ?? '', msg);
|
|
2018
|
+
this.log('fetchComparison', 'backend unavailable, using raw interests as fallback', err);
|
|
2022
2019
|
this.isAligning = false;
|
|
2020
|
+
this.backendError = null;
|
|
2021
|
+
this.displayPerson1Interests = [...this.person1Interests];
|
|
2022
|
+
this.displayPerson2Interests = [...this.person2Interests];
|
|
2023
|
+
this.alignedPerson1Interests = [];
|
|
2024
|
+
this.alignedPerson2Interests = [];
|
|
2025
|
+
this.centerItem = this.findNaiveOverlap(this.person1Interests, this.person2Interests);
|
|
2026
|
+
this.buildForceGraph();
|
|
2023
2027
|
this.cdr.detectChanges();
|
|
2024
2028
|
}
|
|
2025
2029
|
});
|
|
@@ -2573,6 +2577,11 @@ class ProfileComparisonLibComponent {
|
|
|
2573
2577
|
}
|
|
2574
2578
|
return selected;
|
|
2575
2579
|
}
|
|
2580
|
+
findNaiveOverlap(list1, list2) {
|
|
2581
|
+
const norm = (s) => s.trim().toLowerCase();
|
|
2582
|
+
const set2 = new Set((list2 ?? []).map(norm));
|
|
2583
|
+
return (list1 ?? []).filter(item => set2.has(norm(item)));
|
|
2584
|
+
}
|
|
2576
2585
|
filterCenterItemsByThreshold(centerItems, p1Items, p2Items) {
|
|
2577
2586
|
if (!centerItems?.length)
|
|
2578
2587
|
return [];
|
|
@@ -4177,10 +4186,10 @@ class ProfileComparisonLibComponent {
|
|
|
4177
4186
|
this.draggedNode = null;
|
|
4178
4187
|
this.isDragging = false;
|
|
4179
4188
|
}
|
|
4180
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4181
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.24", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", user1File: "user1File", user2File: "user2File", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges", person1Name: "person1Name", person2Name: "person2Name", imageProxyPath: "imageProxyPath" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, host: { listeners: { "window:resize": "onWindowResize()", "document:click": "onDocumentClick()", "document:mousemove": "onDocumentDragMove($event)", "document:touchmove": "onDocumentDragMove($event)", "document:mouseup": "onDocumentDragEnd()", "document:touchend": "onDocumentDragEnd()" } }, 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 }, { propertyName: "chipsGrid", first: true, predicate: ["chipsGrid"], 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 #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg class=\"image-noise-overlay\" style=\"position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 0;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 320\" fill=\"none\" preserveAspectRatio=\"none\">\r\n <g filter=\"url(#filter0_n_1_1753)\">\r\n <rect width=\"100%\" height=\"100%\" fill=\"url(#paint0_radial_1_1753)\" fill-opacity=\"0.33\"/>\r\n </g>\r\n <defs>\r\n <filter id=\"filter0_n_1_1753\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" 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 <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.10869565606117249 0.10869565606117249\" stitchTiles=\"stitch\" numOctaves=\"3\" result=\"noise\" seed=\"7339\"/>\r\n <feColorMatrix in=\"noise\" type=\"luminanceToAlpha\" result=\"alphaNoise\"/>\r\n <feComponentTransfer in=\"alphaNoise\" result=\"coloredNoise1\">\r\n <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \"/>\r\n </feComponentTransfer>\r\n <feComposite operator=\"in\" in2=\"shape\" in=\"coloredNoise1\" result=\"noise1Clipped\"/>\r\n <feFlood flood-color=\"rgba(0, 0, 0, 0.25)\" result=\"color1Flood\"/>\r\n <feComposite operator=\"in\" in2=\"noise1Clipped\" in=\"color1Flood\" result=\"color1\"/>\r\n <feMerge result=\"effect1_noise_1_1753\">\r\n <feMergeNode in=\"shape\"/>\r\n <feMergeNode in=\"color1\"/>\r\n </feMerge>\r\n </filter>\r\n <radialGradient id=\"paint0_radial_1_1753\" cx=\"50%\" cy=\"50%\" r=\"50%\" fx=\"50%\" fy=\"50%\">\r\n <stop offset=\"0%\" stop-opacity=\"0\"/>\r\n <stop offset=\"100%\" stop-opacity=\"0.25\"/>\r\n </radialGradient>\r\n </defs>\r\n </svg>\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.top]=\"user1Top\"\r\n [style.height]=\"user1Height\"\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.top]=\"user2Top\"\r\n [style.height]=\"user2Height\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n </div>\r\n <div class=\"image-shade-overlay\"></div>\r\n\r\n <!-- Frosted Glass Overlay Layer -->\r\n <div class=\"overlay-container\">\r\n <!-- Top Headers \u2014 absolutely positioned overlays, exactly as original -->\r\n <div class=\"header-label left\">{{ person1Name }}</div>\r\n <div class=\"header-label center\">Shared</div>\r\n <div class=\"header-label right\">{{ person2Name }}</div>\r\n\r\n <!-- Force-directed chip canvas (scrollable) -->\r\n <div class=\"chips-grid\" #chipsGrid>\r\n <div class=\"force-canvas\" [style.height.px]=\"forceCanvasHeight\">\r\n <!-- SVG Connections Layer -->\r\n <svg class=\"connections-svg\" [style.height.px]=\"forceCanvasHeight\">\r\n <line\r\n *ngFor=\"let link of sortedForceLinks\"\r\n [attr.x1]=\"getLinkSourceX(link)\"\r\n [attr.y1]=\"getLinkSourceY(link)\"\r\n [attr.x2]=\"getLinkTargetX(link)\"\r\n [attr.y2]=\"getLinkTargetY(link)\"\r\n class=\"connection-line\"\r\n [class.active]=\"isLinkConnectedToActive(link)\"\r\n [class.cross-group]=\"isCrossGroupLink(link)\"\r\n ></line>\r\n </svg>\r\n\r\n <div\r\n *ngFor=\"let node of forceNodes\"\r\n class=\"frosted-chip\"\r\n [class.person-a]=\"node.group === 'p1'\"\r\n [class.shared]=\"node.group === 'shared'\"\r\n [class.person-b]=\"node.group === 'p2'\"\r\n [class.active]=\"activeNode === node\"\r\n [class.dimmed]=\"activeNode && !isNodeConnected(node)\"\r\n [style.left.px]=\"node.x\"\r\n [style.top.px]=\"node.y\"\r\n [style.padding]=\"chipPaddingYPx + 'px ' + chipPaddingXPx + 'px'\"\r\n (mouseenter)=\"onNodeMouseEnter(node)\"\r\n (mouseleave)=\"onNodeMouseLeave(node)\"\r\n (click)=\"onNodeClick(node, $event)\"\r\n (mousedown)=\"onNodeDragStart(node, $event)\"\r\n (touchstart)=\"onNodeDragStart(node, $event)\"\r\n >\r\n <span\r\n class=\"chip-label\"\r\n [style.font-size.px]=\"chipFontSizePx\"\r\n [style.line-height]=\"chipLineHeight\"\r\n >{{ node.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Bottom Actions -->\r\n <div class=\"view-profile left\" (click)=\"onViewProfile('left')\">View Profile</div>\r\n <div class=\"view-profile right\" (click)=\"onViewProfile('right')\">View Profile</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: ["@charset \"UTF-8\";:host{display:block;width:100%;max-width:100%;overflow-x:hidden!important;box-sizing:border-box!important}.profile-img.left{position:absolute;top:0;bottom:0;left:0;width:58%;height:100%;margin:0;z-index:1;-webkit-mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%);mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%)}.profile-img.right{position:absolute;inset:0 0 0 auto;width:58%;height:100%;margin:0;z-index:2;-webkit-mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%);mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%)}@media(min-width:768px){.profile-img.left{width:65%;-webkit-mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%);mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%)}.profile-img.right{width:65%;-webkit-mask-image:linear-gradient(to right,transparent 10%,black 60%,black 100%);mask-image:linear-gradient(to right,transparent 10%,black 60%,black 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:100%;max-width:768px;margin:0 auto;overflow:hidden;position:relative;border-radius:12px}.profile-flex{display:flex;width:100%;height:440px;overflow:hidden;background:transparent;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 18%,black 82%,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 18%,black 82%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.profile-flex .image-shade-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(41,37,45,.25) 100%)}.profile-flex .image-noise-overlay{position:absolute;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:none;overflow:hidden;z-index:0}.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{height:100%;position:relative;flex-shrink:0;background:transparent;margin:0;padding:0;left:0;top:0;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:1;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}.overlay-container{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:10;-webkit-user-select:none;user-select:none;overflow:hidden;background:#29252da6;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px);--name-label-font-size: 14px}.header-label{position:absolute;top:20px;font-family:Gilroy,sans-serif;text-shadow:0 1px 8px rgba(0,0,0,.8);pointer-events:none}.header-label.left{left:25%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.header-label.center{left:50%;transform:translate(-50%);font-size:12px;font-weight:400;color:#fffc;letter-spacing:0}.header-label.right{left:75%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.chips-grid{position:absolute;inset:45px 0;overflow-y:auto;overscroll-behavior:none;scrollbar-width:none;box-sizing:border-box}.chips-grid::-webkit-scrollbar{display:none}.force-canvas{position:relative;width:100%;box-sizing:border-box;min-height:120px}.connections-svg{position:absolute;top:0;left:0;width:100%;pointer-events:none;z-index:1}@keyframes lineGlow{0%{opacity:0;stroke-dashoffset:8}30%{opacity:.15}to{opacity:.6;stroke-dashoffset:0}}.connection-line{stroke:#fe3c72d1;stroke-width:1.5px;stroke-dasharray:4;opacity:0;pointer-events:none;transition:opacity .3s ease}.connection-line.active{animation:lineGlow .7s cubic-bezier(.4,0,.2,1) forwards}.frosted-chip{position:absolute;cursor:pointer;pointer-events:auto;transition:transform .2s ease,box-shadow .2s ease,background .2s ease,opacity .3s ease;flex-shrink:0;padding:6px 18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;transform:translate(-50%,-50%);z-index:2;border:1px solid rgba(255,255,255,.24);background:#ffffff30;box-shadow:inset 0 1.5px 2px #ffffff59,0 0 0 1px #0f172a38;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.frosted-chip:hover{transform:translate(-50%,-50%) scale(1.04);background:#ffffff29;cursor:grab}.frosted-chip.active{background:#ffffff4d;box-shadow:0 0 15px #fe3c7261,inset 0 1.5px 2px #ffffff80,0 0 0 1px #ffffff52;transform:translate(-50%,-50%) scale(1.06);z-index:10;cursor:grabbing}.frosted-chip.active .chip-label{color:#fff!important}.frosted-chip.dimmed{opacity:.35}.frosted-chip .chip-label{position:relative;color:#a8a8a8;text-align:center;font-family:Gilroy,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:140%;letter-spacing:-.01em;text-shadow:none;white-space:nowrap;pointer-events:none;z-index:1}.ghost-chip{visibility:hidden!important;pointer-events:none!important}.view-profile{position:absolute;bottom:8px;color:#ffffff80;text-shadow:0 2px 4.2px #000;font-family:Calistoga,serif;font-size:var(--name-label-font-size);font-style:normal;font-weight:400;line-height:normal;cursor:pointer;pointer-events:auto;transition:opacity .2s ease,transform .2s ease}.view-profile:hover{opacity:.85}.view-profile.left{left:8%;width:34%;text-align:left;transform:none}.view-profile.left:hover{transform:translateY(-2px)}.view-profile.right{right:8%;width:34%;text-align:right;left:auto;transform:none}.view-profile.right:hover{transform:translateY(-2px)}#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:absolute;inset:0;width:100%;height:100%;background:#00000040;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;pointer-events:auto}.loading-spinner{width:44px;height:44px;border:3.5px solid rgba(255,255,255,.25);border-top:3.5px solid #ffffff;border-radius:50%;animation:spin .8s linear infinite;box-shadow:0 0 15px #fff3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:14px;font-weight:500;color:#ffffffd9;letter-spacing:.02em}.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}@media(max-width:1023px){.profile-screen{max-width:640px}.profile-flex{height:400px}.profile-img{height:550px}}@media(max-width:767px){.profile-screen,.profile-flex,.overlay-container{width:100%;max-width:100%;overflow-x:hidden;box-sizing:border-box}.profile-screen{border-radius:0}.profile-flex{height:360px}.profile-img{height:510px}.chips-grid{width:100%;max-width:100%;padding:0;top:45px;bottom:50px;box-sizing:border-box}.frosted-chip{padding:4px 10px;max-width:100%;box-sizing:border-box}.frosted-chip .chip-label{font-size:11px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.view-profile{bottom:10px}}@media(max-width:480px){.overlay-container{--name-label-font-size: 12px}.profile-flex{height:340px}.profile-img{height:480px}.frosted-chip{padding:3px 8px}.frosted-chip .chip-label,.header-label.center{font-size:10px}}\n"], dependencies: [{ kind: "directive", type: i2_1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2_1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
4189
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibComponent, deps: [{ token: ProfileComparisonBackendService }, { token: i0.Renderer2 }, { token: FileConversionService }, { token: ImageCompressionService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i0.ElementRef }, { token: PROFILE_COMPARISON_VERBOSE_LOGGING, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
4190
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.26", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", user1File: "user1File", user2File: "user2File", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges", person1Name: "person1Name", person2Name: "person2Name", imageProxyPath: "imageProxyPath" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, host: { listeners: { "window:resize": "onWindowResize()", "document:click": "onDocumentClick()", "document:mousemove": "onDocumentDragMove($event)", "document:touchmove": "onDocumentDragMove($event)", "document:mouseup": "onDocumentDragEnd()", "document:touchend": "onDocumentDragEnd()" } }, 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 }, { propertyName: "chipsGrid", first: true, predicate: ["chipsGrid"], 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 #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg class=\"image-noise-overlay\" style=\"position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 0;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 320\" fill=\"none\" preserveAspectRatio=\"none\">\r\n <g filter=\"url(#filter0_n_1_1753)\">\r\n <rect width=\"100%\" height=\"100%\" fill=\"url(#paint0_radial_1_1753)\" fill-opacity=\"0.33\"/>\r\n </g>\r\n <defs>\r\n <filter id=\"filter0_n_1_1753\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" 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 <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.10869565606117249 0.10869565606117249\" stitchTiles=\"stitch\" numOctaves=\"3\" result=\"noise\" seed=\"7339\"/>\r\n <feColorMatrix in=\"noise\" type=\"luminanceToAlpha\" result=\"alphaNoise\"/>\r\n <feComponentTransfer in=\"alphaNoise\" result=\"coloredNoise1\">\r\n <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \"/>\r\n </feComponentTransfer>\r\n <feComposite operator=\"in\" in2=\"shape\" in=\"coloredNoise1\" result=\"noise1Clipped\"/>\r\n <feFlood flood-color=\"rgba(0, 0, 0, 0.25)\" result=\"color1Flood\"/>\r\n <feComposite operator=\"in\" in2=\"noise1Clipped\" in=\"color1Flood\" result=\"color1\"/>\r\n <feMerge result=\"effect1_noise_1_1753\">\r\n <feMergeNode in=\"shape\"/>\r\n <feMergeNode in=\"color1\"/>\r\n </feMerge>\r\n </filter>\r\n <radialGradient id=\"paint0_radial_1_1753\" cx=\"50%\" cy=\"50%\" r=\"50%\" fx=\"50%\" fy=\"50%\">\r\n <stop offset=\"0%\" stop-opacity=\"0\"/>\r\n <stop offset=\"100%\" stop-opacity=\"0.25\"/>\r\n </radialGradient>\r\n </defs>\r\n </svg>\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.top]=\"user1Top\"\r\n [style.height]=\"user1Height\"\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.top]=\"user2Top\"\r\n [style.height]=\"user2Height\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n </div>\r\n <div class=\"image-shade-overlay\"></div>\r\n\r\n <!-- Frosted Glass Overlay Layer -->\r\n <div class=\"overlay-container\">\r\n <!-- Top Headers \u2014 absolutely positioned overlays, exactly as original -->\r\n <div class=\"header-label left\">{{ person1Name }}</div>\r\n <div class=\"header-label center\">Shared</div>\r\n <div class=\"header-label right\">{{ person2Name }}</div>\r\n\r\n <!-- Force-directed chip canvas (scrollable) -->\r\n <div class=\"chips-grid\" #chipsGrid>\r\n <div class=\"force-canvas\" [style.height.px]=\"forceCanvasHeight\">\r\n <!-- SVG Connections Layer -->\r\n <svg class=\"connections-svg\" [style.height.px]=\"forceCanvasHeight\">\r\n <line\r\n *ngFor=\"let link of sortedForceLinks\"\r\n [attr.x1]=\"getLinkSourceX(link)\"\r\n [attr.y1]=\"getLinkSourceY(link)\"\r\n [attr.x2]=\"getLinkTargetX(link)\"\r\n [attr.y2]=\"getLinkTargetY(link)\"\r\n class=\"connection-line\"\r\n [class.active]=\"isLinkConnectedToActive(link)\"\r\n [class.cross-group]=\"isCrossGroupLink(link)\"\r\n ></line>\r\n </svg>\r\n\r\n <div\r\n *ngFor=\"let node of forceNodes\"\r\n class=\"frosted-chip\"\r\n [class.person-a]=\"node.group === 'p1'\"\r\n [class.shared]=\"node.group === 'shared'\"\r\n [class.person-b]=\"node.group === 'p2'\"\r\n [class.active]=\"activeNode === node\"\r\n [class.dimmed]=\"activeNode && !isNodeConnected(node)\"\r\n [style.left.px]=\"node.x\"\r\n [style.top.px]=\"node.y\"\r\n [style.padding]=\"chipPaddingYPx + 'px ' + chipPaddingXPx + 'px'\"\r\n (mouseenter)=\"onNodeMouseEnter(node)\"\r\n (mouseleave)=\"onNodeMouseLeave(node)\"\r\n (click)=\"onNodeClick(node, $event)\"\r\n (mousedown)=\"onNodeDragStart(node, $event)\"\r\n (touchstart)=\"onNodeDragStart(node, $event)\"\r\n >\r\n <span\r\n class=\"chip-label\"\r\n [style.font-size.px]=\"chipFontSizePx\"\r\n [style.line-height]=\"chipLineHeight\"\r\n >{{ node.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Bottom Actions -->\r\n <div class=\"view-profile left\" (click)=\"onViewProfile('left')\">View Profile</div>\r\n <div class=\"view-profile right\" (click)=\"onViewProfile('right')\">View Profile</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: ["@charset \"UTF-8\";:host{display:block;width:100%;max-width:100%;overflow-x:hidden!important;box-sizing:border-box!important}.profile-img.left{position:absolute;top:0;bottom:0;left:0;width:58%;height:100%;margin:0;z-index:1;-webkit-mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%);mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%)}.profile-img.right{position:absolute;inset:0 0 0 auto;width:58%;height:100%;margin:0;z-index:2;-webkit-mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%);mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%)}@media(min-width:768px){.profile-img.left{width:65%;-webkit-mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%);mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%)}.profile-img.right{width:65%;-webkit-mask-image:linear-gradient(to right,transparent 10%,black 60%,black 100%);mask-image:linear-gradient(to right,transparent 10%,black 60%,black 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:100%;max-width:768px;margin:0 auto;overflow:hidden;position:relative;border-radius:12px}.profile-flex{display:flex;width:100%;height:440px;overflow:hidden;background:transparent;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 18%,black 82%,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 18%,black 82%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.profile-flex .image-shade-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(41,37,45,.25) 100%)}.profile-flex .image-noise-overlay{position:absolute;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:none;overflow:hidden;z-index:0}.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{height:100%;position:relative;flex-shrink:0;background:transparent;margin:0;padding:0;left:0;top:0;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:1;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}.overlay-container{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:10;-webkit-user-select:none;user-select:none;overflow:hidden;background:#29252da6;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px);--name-label-font-size: 14px}.header-label{position:absolute;top:20px;font-family:Gilroy,sans-serif;text-shadow:0 1px 8px rgba(0,0,0,.8);pointer-events:none}.header-label.left{left:25%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.header-label.center{left:50%;transform:translate(-50%);font-size:12px;font-weight:400;color:#fffc;letter-spacing:0}.header-label.right{left:75%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.chips-grid{position:absolute;inset:45px 0;overflow-y:auto;overscroll-behavior:none;scrollbar-width:none;box-sizing:border-box}.chips-grid::-webkit-scrollbar{display:none}.force-canvas{position:relative;width:100%;box-sizing:border-box;min-height:120px}.connections-svg{position:absolute;top:0;left:0;width:100%;pointer-events:none;z-index:1}@keyframes lineGlow{0%{opacity:0;stroke-dashoffset:8}30%{opacity:.15}to{opacity:.6;stroke-dashoffset:0}}.connection-line{stroke:#fe3c72d1;stroke-width:1.5px;stroke-dasharray:4;opacity:0;pointer-events:none;transition:opacity .3s ease}.connection-line.active{animation:lineGlow .7s cubic-bezier(.4,0,.2,1) forwards}.frosted-chip{position:absolute;cursor:pointer;pointer-events:auto;transition:transform .2s ease,box-shadow .2s ease,background .2s ease,opacity .3s ease;flex-shrink:0;padding:6px 18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;transform:translate(-50%,-50%);z-index:2;border:1px solid rgba(255,255,255,.24);background:#ffffff30;box-shadow:inset 0 1.5px 2px #ffffff59,0 0 0 1px #0f172a38;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.frosted-chip:hover{transform:translate(-50%,-50%) scale(1.04);background:#ffffff29;cursor:grab}.frosted-chip.active{background:#ffffff4d;box-shadow:0 0 15px #fe3c7261,inset 0 1.5px 2px #ffffff80,0 0 0 1px #ffffff52;transform:translate(-50%,-50%) scale(1.06);z-index:10;cursor:grabbing}.frosted-chip.active .chip-label{color:#fff!important}.frosted-chip.dimmed{opacity:.35}.frosted-chip .chip-label{position:relative;color:#a8a8a8;text-align:center;font-family:Gilroy,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:140%;letter-spacing:-.01em;text-shadow:none;white-space:nowrap;pointer-events:none;z-index:1}.ghost-chip{visibility:hidden!important;pointer-events:none!important}.view-profile{position:absolute;bottom:8px;color:#ffffff80;text-shadow:0 2px 4.2px #000;font-family:Calistoga,serif;font-size:var(--name-label-font-size);font-style:normal;font-weight:400;line-height:normal;cursor:pointer;pointer-events:auto;transition:opacity .2s ease,transform .2s ease}.view-profile:hover{opacity:.85}.view-profile.left{left:8%;width:34%;text-align:left;transform:none}.view-profile.left:hover{transform:translateY(-2px)}.view-profile.right{right:8%;width:34%;text-align:right;left:auto;transform:none}.view-profile.right:hover{transform:translateY(-2px)}#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:absolute;inset:0;width:100%;height:100%;background:#00000040;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;pointer-events:auto}.loading-spinner{width:44px;height:44px;border:3.5px solid rgba(255,255,255,.25);border-top:3.5px solid #ffffff;border-radius:50%;animation:spin .8s linear infinite;box-shadow:0 0 15px #fff3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:14px;font-weight:500;color:#ffffffd9;letter-spacing:.02em}.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}@media(max-width:1023px){.profile-screen{max-width:640px}.profile-flex{height:400px}.profile-img{height:550px}}@media(max-width:767px){.profile-screen,.profile-flex,.overlay-container{width:100%;max-width:100%;overflow-x:hidden;box-sizing:border-box}.profile-screen{border-radius:0}.profile-flex{height:360px}.profile-img{height:510px}.chips-grid{width:100%;max-width:100%;padding:0;top:45px;bottom:50px;box-sizing:border-box}.frosted-chip{padding:4px 10px;max-width:100%;box-sizing:border-box}.frosted-chip .chip-label{font-size:11px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.view-profile{bottom:10px}}@media(max-width:480px){.overlay-container{--name-label-font-size: 12px}.profile-flex{height:340px}.profile-img{height:480px}.frosted-chip{padding:3px 8px}.frosted-chip .chip-label,.header-label.center{font-size:10px}}\n"], dependencies: [{ kind: "directive", type: i2_1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2_1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
4182
4191
|
}
|
|
4183
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4192
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibComponent, decorators: [{
|
|
4184
4193
|
type: Component,
|
|
4185
4194
|
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 #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg class=\"image-noise-overlay\" style=\"position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 0;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 320\" fill=\"none\" preserveAspectRatio=\"none\">\r\n <g filter=\"url(#filter0_n_1_1753)\">\r\n <rect width=\"100%\" height=\"100%\" fill=\"url(#paint0_radial_1_1753)\" fill-opacity=\"0.33\"/>\r\n </g>\r\n <defs>\r\n <filter id=\"filter0_n_1_1753\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" 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 <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.10869565606117249 0.10869565606117249\" stitchTiles=\"stitch\" numOctaves=\"3\" result=\"noise\" seed=\"7339\"/>\r\n <feColorMatrix in=\"noise\" type=\"luminanceToAlpha\" result=\"alphaNoise\"/>\r\n <feComponentTransfer in=\"alphaNoise\" result=\"coloredNoise1\">\r\n <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \"/>\r\n </feComponentTransfer>\r\n <feComposite operator=\"in\" in2=\"shape\" in=\"coloredNoise1\" result=\"noise1Clipped\"/>\r\n <feFlood flood-color=\"rgba(0, 0, 0, 0.25)\" result=\"color1Flood\"/>\r\n <feComposite operator=\"in\" in2=\"noise1Clipped\" in=\"color1Flood\" result=\"color1\"/>\r\n <feMerge result=\"effect1_noise_1_1753\">\r\n <feMergeNode in=\"shape\"/>\r\n <feMergeNode in=\"color1\"/>\r\n </feMerge>\r\n </filter>\r\n <radialGradient id=\"paint0_radial_1_1753\" cx=\"50%\" cy=\"50%\" r=\"50%\" fx=\"50%\" fy=\"50%\">\r\n <stop offset=\"0%\" stop-opacity=\"0\"/>\r\n <stop offset=\"100%\" stop-opacity=\"0.25\"/>\r\n </radialGradient>\r\n </defs>\r\n </svg>\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.top]=\"user1Top\"\r\n [style.height]=\"user1Height\"\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.top]=\"user2Top\"\r\n [style.height]=\"user2Height\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n </div>\r\n <div class=\"image-shade-overlay\"></div>\r\n\r\n <!-- Frosted Glass Overlay Layer -->\r\n <div class=\"overlay-container\">\r\n <!-- Top Headers \u2014 absolutely positioned overlays, exactly as original -->\r\n <div class=\"header-label left\">{{ person1Name }}</div>\r\n <div class=\"header-label center\">Shared</div>\r\n <div class=\"header-label right\">{{ person2Name }}</div>\r\n\r\n <!-- Force-directed chip canvas (scrollable) -->\r\n <div class=\"chips-grid\" #chipsGrid>\r\n <div class=\"force-canvas\" [style.height.px]=\"forceCanvasHeight\">\r\n <!-- SVG Connections Layer -->\r\n <svg class=\"connections-svg\" [style.height.px]=\"forceCanvasHeight\">\r\n <line\r\n *ngFor=\"let link of sortedForceLinks\"\r\n [attr.x1]=\"getLinkSourceX(link)\"\r\n [attr.y1]=\"getLinkSourceY(link)\"\r\n [attr.x2]=\"getLinkTargetX(link)\"\r\n [attr.y2]=\"getLinkTargetY(link)\"\r\n class=\"connection-line\"\r\n [class.active]=\"isLinkConnectedToActive(link)\"\r\n [class.cross-group]=\"isCrossGroupLink(link)\"\r\n ></line>\r\n </svg>\r\n\r\n <div\r\n *ngFor=\"let node of forceNodes\"\r\n class=\"frosted-chip\"\r\n [class.person-a]=\"node.group === 'p1'\"\r\n [class.shared]=\"node.group === 'shared'\"\r\n [class.person-b]=\"node.group === 'p2'\"\r\n [class.active]=\"activeNode === node\"\r\n [class.dimmed]=\"activeNode && !isNodeConnected(node)\"\r\n [style.left.px]=\"node.x\"\r\n [style.top.px]=\"node.y\"\r\n [style.padding]=\"chipPaddingYPx + 'px ' + chipPaddingXPx + 'px'\"\r\n (mouseenter)=\"onNodeMouseEnter(node)\"\r\n (mouseleave)=\"onNodeMouseLeave(node)\"\r\n (click)=\"onNodeClick(node, $event)\"\r\n (mousedown)=\"onNodeDragStart(node, $event)\"\r\n (touchstart)=\"onNodeDragStart(node, $event)\"\r\n >\r\n <span\r\n class=\"chip-label\"\r\n [style.font-size.px]=\"chipFontSizePx\"\r\n [style.line-height]=\"chipLineHeight\"\r\n >{{ node.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Bottom Actions -->\r\n <div class=\"view-profile left\" (click)=\"onViewProfile('left')\">View Profile</div>\r\n <div class=\"view-profile right\" (click)=\"onViewProfile('right')\">View Profile</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: ["@charset \"UTF-8\";:host{display:block;width:100%;max-width:100%;overflow-x:hidden!important;box-sizing:border-box!important}.profile-img.left{position:absolute;top:0;bottom:0;left:0;width:58%;height:100%;margin:0;z-index:1;-webkit-mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%);mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%)}.profile-img.right{position:absolute;inset:0 0 0 auto;width:58%;height:100%;margin:0;z-index:2;-webkit-mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%);mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%)}@media(min-width:768px){.profile-img.left{width:65%;-webkit-mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%);mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%)}.profile-img.right{width:65%;-webkit-mask-image:linear-gradient(to right,transparent 10%,black 60%,black 100%);mask-image:linear-gradient(to right,transparent 10%,black 60%,black 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:100%;max-width:768px;margin:0 auto;overflow:hidden;position:relative;border-radius:12px}.profile-flex{display:flex;width:100%;height:440px;overflow:hidden;background:transparent;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 18%,black 82%,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 18%,black 82%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.profile-flex .image-shade-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(41,37,45,.25) 100%)}.profile-flex .image-noise-overlay{position:absolute;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:none;overflow:hidden;z-index:0}.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{height:100%;position:relative;flex-shrink:0;background:transparent;margin:0;padding:0;left:0;top:0;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:1;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}.overlay-container{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:10;-webkit-user-select:none;user-select:none;overflow:hidden;background:#29252da6;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px);--name-label-font-size: 14px}.header-label{position:absolute;top:20px;font-family:Gilroy,sans-serif;text-shadow:0 1px 8px rgba(0,0,0,.8);pointer-events:none}.header-label.left{left:25%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.header-label.center{left:50%;transform:translate(-50%);font-size:12px;font-weight:400;color:#fffc;letter-spacing:0}.header-label.right{left:75%;transform:translate(-50%);font-size:var(--name-label-font-size);font-weight:400;color:#fffffff2;text-align:center}.chips-grid{position:absolute;inset:45px 0;overflow-y:auto;overscroll-behavior:none;scrollbar-width:none;box-sizing:border-box}.chips-grid::-webkit-scrollbar{display:none}.force-canvas{position:relative;width:100%;box-sizing:border-box;min-height:120px}.connections-svg{position:absolute;top:0;left:0;width:100%;pointer-events:none;z-index:1}@keyframes lineGlow{0%{opacity:0;stroke-dashoffset:8}30%{opacity:.15}to{opacity:.6;stroke-dashoffset:0}}.connection-line{stroke:#fe3c72d1;stroke-width:1.5px;stroke-dasharray:4;opacity:0;pointer-events:none;transition:opacity .3s ease}.connection-line.active{animation:lineGlow .7s cubic-bezier(.4,0,.2,1) forwards}.frosted-chip{position:absolute;cursor:pointer;pointer-events:auto;transition:transform .2s ease,box-shadow .2s ease,background .2s ease,opacity .3s ease;flex-shrink:0;padding:6px 18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;transform:translate(-50%,-50%);z-index:2;border:1px solid rgba(255,255,255,.24);background:#ffffff30;box-shadow:inset 0 1.5px 2px #ffffff59,0 0 0 1px #0f172a38;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.frosted-chip:hover{transform:translate(-50%,-50%) scale(1.04);background:#ffffff29;cursor:grab}.frosted-chip.active{background:#ffffff4d;box-shadow:0 0 15px #fe3c7261,inset 0 1.5px 2px #ffffff80,0 0 0 1px #ffffff52;transform:translate(-50%,-50%) scale(1.06);z-index:10;cursor:grabbing}.frosted-chip.active .chip-label{color:#fff!important}.frosted-chip.dimmed{opacity:.35}.frosted-chip .chip-label{position:relative;color:#a8a8a8;text-align:center;font-family:Gilroy,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:140%;letter-spacing:-.01em;text-shadow:none;white-space:nowrap;pointer-events:none;z-index:1}.ghost-chip{visibility:hidden!important;pointer-events:none!important}.view-profile{position:absolute;bottom:8px;color:#ffffff80;text-shadow:0 2px 4.2px #000;font-family:Calistoga,serif;font-size:var(--name-label-font-size);font-style:normal;font-weight:400;line-height:normal;cursor:pointer;pointer-events:auto;transition:opacity .2s ease,transform .2s ease}.view-profile:hover{opacity:.85}.view-profile.left{left:8%;width:34%;text-align:left;transform:none}.view-profile.left:hover{transform:translateY(-2px)}.view-profile.right{right:8%;width:34%;text-align:right;left:auto;transform:none}.view-profile.right:hover{transform:translateY(-2px)}#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:absolute;inset:0;width:100%;height:100%;background:#00000040;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;pointer-events:auto}.loading-spinner{width:44px;height:44px;border:3.5px solid rgba(255,255,255,.25);border-top:3.5px solid #ffffff;border-radius:50%;animation:spin .8s linear infinite;box-shadow:0 0 15px #fff3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:14px;font-weight:500;color:#ffffffd9;letter-spacing:.02em}.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}@media(max-width:1023px){.profile-screen{max-width:640px}.profile-flex{height:400px}.profile-img{height:550px}}@media(max-width:767px){.profile-screen,.profile-flex,.overlay-container{width:100%;max-width:100%;overflow-x:hidden;box-sizing:border-box}.profile-screen{border-radius:0}.profile-flex{height:360px}.profile-img{height:510px}.chips-grid{width:100%;max-width:100%;padding:0;top:45px;bottom:50px;box-sizing:border-box}.frosted-chip{padding:4px 10px;max-width:100%;box-sizing:border-box}.frosted-chip .chip-label{font-size:11px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.view-profile{bottom:10px}}@media(max-width:480px){.overlay-container{--name-label-font-size: 12px}.profile-flex{height:340px}.profile-img{height:480px}.frosted-chip{padding:3px 8px}.frosted-chip .chip-label,.header-label.center{font-size:10px}}\n"] }]
|
|
4186
4195
|
}], ctorParameters: () => [{ type: ProfileComparisonBackendService }, { type: i0.Renderer2 }, { type: FileConversionService }, { type: ImageCompressionService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: i0.ElementRef }, { type: undefined, decorators: [{
|
|
@@ -4272,19 +4281,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
|
|
|
4272
4281
|
}] } });
|
|
4273
4282
|
|
|
4274
4283
|
class ProfileComparisonLibModule {
|
|
4275
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
4276
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.
|
|
4284
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
4285
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibModule, declarations: [ProfileComparisonLibComponent], imports: [CommonModule,
|
|
4277
4286
|
HttpClientModule,
|
|
4278
4287
|
FormsModule,
|
|
4279
4288
|
ReactiveFormsModule], exports: [ProfileComparisonLibComponent] });
|
|
4280
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.
|
|
4289
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibModule, providers: [
|
|
4281
4290
|
ProfileComparisonBackendService
|
|
4282
4291
|
], imports: [CommonModule,
|
|
4283
4292
|
HttpClientModule,
|
|
4284
4293
|
FormsModule,
|
|
4285
4294
|
ReactiveFormsModule] });
|
|
4286
4295
|
}
|
|
4287
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4296
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: ProfileComparisonLibModule, decorators: [{
|
|
4288
4297
|
type: NgModule,
|
|
4289
4298
|
args: [{
|
|
4290
4299
|
declarations: [
|