Spaces:
Running
Running
Tobias Brugger
commited on
Commit
·
35396c2
1
Parent(s):
62c4986
formatting + some styling
Browse files- src/lib/DatasetViewer.svelte +207 -75
src/lib/DatasetViewer.svelte
CHANGED
|
@@ -1,34 +1,35 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import { fetchAllRows } from
|
| 3 |
-
import type { TermDefinition, EquivalencyScore } from
|
| 4 |
-
import { marked } from
|
| 5 |
|
| 6 |
// Available jurisdictions
|
| 7 |
const availableJurisdictions = [
|
| 8 |
-
{ code:
|
| 9 |
-
{ code:
|
| 10 |
];
|
| 11 |
|
| 12 |
// Hardcoded configuration
|
| 13 |
-
const dataset = import.meta.env.VITE_HUGGINGFACE_ORG +
|
| 14 |
-
const scoresDataset =
|
| 15 |
-
|
|
|
|
| 16 |
// Direct translations dictionary (English -> Swedish)
|
| 17 |
const directTranslations: Record<string, string> = {
|
| 18 |
"proximate cause": "adekvat kausalitet",
|
| 19 |
"articles of association": "bolagsordning",
|
| 20 |
-
|
| 21 |
-
|
| 22 |
"cooperative apartment": "bostadrätt",
|
| 23 |
"company name": "firma",
|
| 24 |
-
|
| 25 |
"dispose of": "förfoga över",
|
| 26 |
"rights of first refusal": "hembud",
|
| 27 |
"implied consent": "konkludent handlande",
|
| 28 |
"aiding and abetting": "medverkande",
|
| 29 |
"trading prohibition": "näringsförbud",
|
| 30 |
"affiliated company": "närstående bolag",
|
| 31 |
-
|
| 32 |
"dismiss on the merits": "ogilla",
|
| 33 |
"the rule of contra proferentem": "oklarhetsregeln",
|
| 34 |
"joinder of parties": "processgemenskap",
|
|
@@ -42,32 +43,33 @@
|
|
| 42 |
translationMap.set(en.toLowerCase(), sv.toLowerCase());
|
| 43 |
translationMap.set(sv.toLowerCase(), en.toLowerCase());
|
| 44 |
});
|
| 45 |
-
|
| 46 |
// Form inputs
|
| 47 |
-
let jurisdiction1 = $state(
|
| 48 |
-
let jurisdiction2 = $state(
|
| 49 |
-
|
| 50 |
// Data state
|
| 51 |
let data1 = $state<TermDefinition[]>([]);
|
| 52 |
let data2 = $state<TermDefinition[]>([]);
|
| 53 |
let equivalencyScores = $state<EquivalencyScore[]>([]);
|
| 54 |
let selectedTerm1 = $state<string | null>(null);
|
| 55 |
let selectedTerm2 = $state<string | null>(null);
|
| 56 |
-
|
| 57 |
// UI state
|
| 58 |
let loading = $state(false);
|
| 59 |
-
let error = $state(
|
| 60 |
let hasLoaded = $state(false);
|
|
|
|
| 61 |
|
| 62 |
// Track previous values to detect changes
|
| 63 |
-
let prevJurisdiction1 = $state(
|
| 64 |
-
let prevJurisdiction2 = $state(
|
| 65 |
|
| 66 |
// Reload when jurisdictions change (but not on initial mount)
|
| 67 |
$effect(() => {
|
| 68 |
const j1 = jurisdiction1;
|
| 69 |
const j2 = jurisdiction2;
|
| 70 |
-
|
| 71 |
if (hasLoaded && (j1 !== prevJurisdiction1 || j2 !== prevJurisdiction2)) {
|
| 72 |
prevJurisdiction1 = j1;
|
| 73 |
prevJurisdiction2 = j2;
|
|
@@ -77,12 +79,12 @@
|
|
| 77 |
|
| 78 |
async function loadComparison() {
|
| 79 |
if (jurisdiction1 === jurisdiction2) {
|
| 80 |
-
error =
|
| 81 |
return;
|
| 82 |
}
|
| 83 |
|
| 84 |
loading = true;
|
| 85 |
-
error =
|
| 86 |
data1 = [];
|
| 87 |
data2 = [];
|
| 88 |
equivalencyScores = [];
|
|
@@ -97,50 +99,56 @@
|
|
| 97 |
|
| 98 |
// Fetch both jurisdictions and equivalency scores in parallel
|
| 99 |
const [rows1, rows2, scoresRows] = await Promise.all([
|
| 100 |
-
fetchAllRows(dataset, jurisdiction1,
|
| 101 |
-
fetchAllRows(dataset, jurisdiction2,
|
| 102 |
-
fetchAllRows(scoresDataset, scoresConfig,
|
| 103 |
]);
|
| 104 |
|
| 105 |
-
data1 = (rows1 as TermDefinition[]).sort((a, b) =>
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
equivalencyScores = scoresRows as EquivalencyScore[];
|
| 108 |
-
|
| 109 |
-
console.log(
|
| 110 |
-
console.log(
|
| 111 |
-
console.log(
|
| 112 |
-
|
| 113 |
hasLoaded = true;
|
| 114 |
|
| 115 |
// Auto-select first term if available
|
| 116 |
if (data1.length > 0) selectedTerm1 = data1[0].term;
|
| 117 |
if (data2.length > 0) selectedTerm2 = data2[0].term;
|
| 118 |
-
|
| 119 |
} catch (e) {
|
| 120 |
-
error = e instanceof Error ? e.message :
|
| 121 |
-
console.error(
|
| 122 |
} finally {
|
| 123 |
loading = false;
|
| 124 |
}
|
| 125 |
}
|
| 126 |
|
| 127 |
function getSelectedDefinition1(): string {
|
| 128 |
-
if (!selectedTerm1) return
|
| 129 |
-
const found = data1.find(item => item.term === selectedTerm1);
|
| 130 |
-
return found?.definition ||
|
| 131 |
}
|
| 132 |
|
| 133 |
function getSelectedDefinition2(): string {
|
| 134 |
-
if (!selectedTerm2) return
|
| 135 |
-
const found = data2.find(item => item.term === selectedTerm2);
|
| 136 |
-
return found?.definition ||
|
| 137 |
}
|
| 138 |
|
| 139 |
function normalizeTermForComparison(term: string): string {
|
| 140 |
// Replace underscores with spaces and normalize whitespace for comparison
|
| 141 |
-
return term.replace(/_/g,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
}
|
| 143 |
-
|
| 144 |
// Get direct translation equivalent for a term
|
| 145 |
function getDirectTranslation(term: string): string | null {
|
| 146 |
const normalized = normalizeTermForComparison(term);
|
|
@@ -148,12 +156,17 @@
|
|
| 148 |
}
|
| 149 |
|
| 150 |
// Check if a term should be highlighted based on the selected term in the other jurisdiction
|
| 151 |
-
function shouldHighlightTerm(
|
|
|
|
|
|
|
|
|
|
| 152 |
if (!selectedInOtherJurisdiction) return false;
|
| 153 |
-
|
| 154 |
-
const equivalentOfSelected = getDirectTranslation(
|
|
|
|
|
|
|
| 155 |
if (!equivalentOfSelected) return false;
|
| 156 |
-
|
| 157 |
const normalizedTerm = normalizeTermForComparison(term);
|
| 158 |
return normalizedTerm === equivalentOfSelected;
|
| 159 |
}
|
|
@@ -168,28 +181,47 @@
|
|
| 168 |
const normalizedTerm2 = normalizeTermForComparison(selectedTerm2);
|
| 169 |
|
| 170 |
// Look for the score in both directions, comparing normalized terms
|
| 171 |
-
const score = equivalencyScores.find(s => {
|
| 172 |
const normalizedJ1 = normalizeTermForComparison(s.term_j1);
|
| 173 |
const normalizedJ2 = normalizeTermForComparison(s.term_j2);
|
| 174 |
-
|
| 175 |
return (
|
| 176 |
-
(normalizedJ1 === normalizedTerm1 &&
|
|
|
|
| 177 |
(normalizedJ1 === normalizedTerm2 && normalizedJ2 === normalizedTerm1)
|
| 178 |
);
|
| 179 |
});
|
| 180 |
|
| 181 |
return score || null;
|
| 182 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
</script>
|
| 184 |
|
| 185 |
<div class="jurisdiction-comparison">
|
| 186 |
<h2>Jurisdiction Term Comparison</h2>
|
| 187 |
-
|
| 188 |
<div class="form">
|
| 189 |
<div class="form-row">
|
| 190 |
<div class="form-group">
|
| 191 |
<label for="jurisdiction1">Jurisdiction 1:</label>
|
| 192 |
-
<select
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
{#each availableJurisdictions as jur}
|
| 194 |
<option value={jur.code}>{jur.label}</option>
|
| 195 |
{/each}
|
|
@@ -198,7 +230,11 @@
|
|
| 198 |
|
| 199 |
<div class="form-group">
|
| 200 |
<label for="jurisdiction2">Jurisdiction 2:</label>
|
| 201 |
-
<select
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
{#each availableJurisdictions as jur}
|
| 203 |
<option value={jur.code}>{jur.label}</option>
|
| 204 |
{/each}
|
|
@@ -208,14 +244,15 @@
|
|
| 208 |
|
| 209 |
{#if !hasLoaded}
|
| 210 |
<button onclick={loadComparison} disabled={loading}>
|
| 211 |
-
{loading ?
|
| 212 |
</button>
|
| 213 |
{/if}
|
| 214 |
</div>
|
| 215 |
|
| 216 |
{#if error}
|
| 217 |
<div class="error">
|
| 218 |
-
<strong>Error:</strong>
|
|
|
|
| 219 |
</div>
|
| 220 |
{/if}
|
| 221 |
|
|
@@ -223,17 +260,24 @@
|
|
| 223 |
<div class="comparison-container">
|
| 224 |
<!-- Jurisdiction 1 -->
|
| 225 |
<div class="jurisdiction-column">
|
| 226 |
-
<h3>
|
|
|
|
|
|
|
| 227 |
<div class="term-selector">
|
| 228 |
<label for="term1">Select Term:</label>
|
| 229 |
<select id="term1" bind:value={selectedTerm1}>
|
| 230 |
{#each data1 as item}
|
| 231 |
<option value={item.term}>
|
| 232 |
-
{shouldHighlightTerm(item.term, selectedTerm2)
|
|
|
|
|
|
|
| 233 |
</option>
|
| 234 |
{/each}
|
| 235 |
</select>
|
| 236 |
-
<small class="translation-hint"
|
|
|
|
|
|
|
|
|
|
| 237 |
</div>
|
| 238 |
{#if selectedTerm1}
|
| 239 |
<div class="definition-box">
|
|
@@ -250,17 +294,24 @@
|
|
| 250 |
|
| 251 |
<!-- Jurisdiction 2 -->
|
| 252 |
<div class="jurisdiction-column">
|
| 253 |
-
<h3>
|
|
|
|
|
|
|
| 254 |
<div class="term-selector">
|
| 255 |
<label for="term2">Select Term:</label>
|
| 256 |
<select id="term2" bind:value={selectedTerm2}>
|
| 257 |
{#each data2 as item}
|
| 258 |
<option value={item.term}>
|
| 259 |
-
{shouldHighlightTerm(item.term, selectedTerm1)
|
|
|
|
|
|
|
| 260 |
</option>
|
| 261 |
{/each}
|
| 262 |
</select>
|
| 263 |
-
<small class="translation-hint"
|
|
|
|
|
|
|
|
|
|
| 264 |
</div>
|
| 265 |
{#if selectedTerm2}
|
| 266 |
<div class="definition-box">
|
|
@@ -287,33 +338,72 @@
|
|
| 287 |
</div>
|
| 288 |
{#if score.comparative_law_note}
|
| 289 |
<div class="comparative-note">
|
| 290 |
-
<h4>Comparative Law Note</h4>
|
| 291 |
<div class="markdown-content">
|
| 292 |
{@html marked(score.comparative_law_note)}
|
| 293 |
</div>
|
| 294 |
</div>
|
| 295 |
{/if}
|
| 296 |
-
|
| 297 |
{#if score.comparisons && score.comparisons.length > 0}
|
|
|
|
|
|
|
| 298 |
<div class="comparisons-section">
|
| 299 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
<div class="comparisons-list">
|
| 301 |
-
{#each
|
| 302 |
<div class="comparison-item">
|
| 303 |
<div class="comparison-header">
|
| 304 |
-
<span class="comparison-category">
|
|
|
|
|
|
|
| 305 |
{#if comparison.subcategory}
|
| 306 |
-
<span class="comparison-subcategory">
|
|
|
|
|
|
|
| 307 |
{/if}
|
| 308 |
-
<span class="comparison-score"
|
|
|
|
|
|
|
| 309 |
</div>
|
| 310 |
<div class="comparison-meta">
|
| 311 |
-
<span class="comparison-weight"
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
</div>
|
| 314 |
{#if comparison.reasoning}
|
| 315 |
<div class="comparison-reasoning">
|
| 316 |
-
|
| 317 |
</div>
|
| 318 |
{/if}
|
| 319 |
</div>
|
|
@@ -383,7 +473,8 @@
|
|
| 383 |
color: #555;
|
| 384 |
}
|
| 385 |
|
| 386 |
-
input,
|
|
|
|
| 387 |
width: 100%;
|
| 388 |
padding: 0.5rem;
|
| 389 |
border: 1px solid #ddd;
|
|
@@ -394,7 +485,8 @@
|
|
| 394 |
color: #333;
|
| 395 |
}
|
| 396 |
|
| 397 |
-
input:disabled,
|
|
|
|
| 398 |
background: #e9e9e9;
|
| 399 |
cursor: not-allowed;
|
| 400 |
}
|
|
@@ -658,7 +750,7 @@
|
|
| 658 |
}
|
| 659 |
|
| 660 |
.comparison-subcategory {
|
| 661 |
-
color: #
|
| 662 |
font-size: 0.9rem;
|
| 663 |
}
|
| 664 |
|
|
@@ -696,9 +788,49 @@
|
|
| 696 |
font-size: 0.9rem;
|
| 697 |
line-height: 1.5;
|
| 698 |
color: #444;
|
|
|
|
| 699 |
}
|
| 700 |
|
| 701 |
.comparison-reasoning strong {
|
| 702 |
color: #333;
|
| 703 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
</style>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { fetchAllRows } from "./huggingfaceApi";
|
| 3 |
+
import type { TermDefinition, EquivalencyScore } from "./types";
|
| 4 |
+
import { marked } from "marked";
|
| 5 |
|
| 6 |
// Available jurisdictions
|
| 7 |
const availableJurisdictions = [
|
| 8 |
+
{ code: "en-us", label: "English (US)" },
|
| 9 |
+
{ code: "sv-se", label: "Swedish (SE)" },
|
| 10 |
];
|
| 11 |
|
| 12 |
// Hardcoded configuration
|
| 13 |
+
const dataset = import.meta.env.VITE_HUGGINGFACE_ORG + "/structured-answers";
|
| 14 |
+
const scoresDataset =
|
| 15 |
+
import.meta.env.VITE_HUGGINGFACE_ORG + "/equivalency-scores";
|
| 16 |
+
|
| 17 |
// Direct translations dictionary (English -> Swedish)
|
| 18 |
const directTranslations: Record<string, string> = {
|
| 19 |
"proximate cause": "adekvat kausalitet",
|
| 20 |
"articles of association": "bolagsordning",
|
| 21 |
+
bylaws: "bolagsordning",
|
| 22 |
+
condominium: "bostadrätt",
|
| 23 |
"cooperative apartment": "bostadrätt",
|
| 24 |
"company name": "firma",
|
| 25 |
+
company: "firma",
|
| 26 |
"dispose of": "förfoga över",
|
| 27 |
"rights of first refusal": "hembud",
|
| 28 |
"implied consent": "konkludent handlande",
|
| 29 |
"aiding and abetting": "medverkande",
|
| 30 |
"trading prohibition": "näringsförbud",
|
| 31 |
"affiliated company": "närstående bolag",
|
| 32 |
+
molestation: "ofredande",
|
| 33 |
"dismiss on the merits": "ogilla",
|
| 34 |
"the rule of contra proferentem": "oklarhetsregeln",
|
| 35 |
"joinder of parties": "processgemenskap",
|
|
|
|
| 43 |
translationMap.set(en.toLowerCase(), sv.toLowerCase());
|
| 44 |
translationMap.set(sv.toLowerCase(), en.toLowerCase());
|
| 45 |
});
|
| 46 |
+
|
| 47 |
// Form inputs
|
| 48 |
+
let jurisdiction1 = $state("en-us");
|
| 49 |
+
let jurisdiction2 = $state("sv-se");
|
| 50 |
+
|
| 51 |
// Data state
|
| 52 |
let data1 = $state<TermDefinition[]>([]);
|
| 53 |
let data2 = $state<TermDefinition[]>([]);
|
| 54 |
let equivalencyScores = $state<EquivalencyScore[]>([]);
|
| 55 |
let selectedTerm1 = $state<string | null>(null);
|
| 56 |
let selectedTerm2 = $state<string | null>(null);
|
| 57 |
+
|
| 58 |
// UI state
|
| 59 |
let loading = $state(false);
|
| 60 |
+
let error = $state("");
|
| 61 |
let hasLoaded = $state(false);
|
| 62 |
+
let selectedCategory = $state<string>("All");
|
| 63 |
|
| 64 |
// Track previous values to detect changes
|
| 65 |
+
let prevJurisdiction1 = $state("en-us");
|
| 66 |
+
let prevJurisdiction2 = $state("sv-se");
|
| 67 |
|
| 68 |
// Reload when jurisdictions change (but not on initial mount)
|
| 69 |
$effect(() => {
|
| 70 |
const j1 = jurisdiction1;
|
| 71 |
const j2 = jurisdiction2;
|
| 72 |
+
|
| 73 |
if (hasLoaded && (j1 !== prevJurisdiction1 || j2 !== prevJurisdiction2)) {
|
| 74 |
prevJurisdiction1 = j1;
|
| 75 |
prevJurisdiction2 = j2;
|
|
|
|
| 79 |
|
| 80 |
async function loadComparison() {
|
| 81 |
if (jurisdiction1 === jurisdiction2) {
|
| 82 |
+
error = "Please select two different jurisdictions";
|
| 83 |
return;
|
| 84 |
}
|
| 85 |
|
| 86 |
loading = true;
|
| 87 |
+
error = "";
|
| 88 |
data1 = [];
|
| 89 |
data2 = [];
|
| 90 |
equivalencyScores = [];
|
|
|
|
| 99 |
|
| 100 |
// Fetch both jurisdictions and equivalency scores in parallel
|
| 101 |
const [rows1, rows2, scoresRows] = await Promise.all([
|
| 102 |
+
fetchAllRows(dataset, jurisdiction1, "train", undefined),
|
| 103 |
+
fetchAllRows(dataset, jurisdiction2, "train", undefined),
|
| 104 |
+
fetchAllRows(scoresDataset, scoresConfig, "train", undefined),
|
| 105 |
]);
|
| 106 |
|
| 107 |
+
data1 = (rows1 as TermDefinition[]).sort((a, b) =>
|
| 108 |
+
a.term.localeCompare(b.term)
|
| 109 |
+
);
|
| 110 |
+
data2 = (rows2 as TermDefinition[]).sort((a, b) =>
|
| 111 |
+
a.term.localeCompare(b.term)
|
| 112 |
+
);
|
| 113 |
equivalencyScores = scoresRows as EquivalencyScore[];
|
| 114 |
+
|
| 115 |
+
console.log("Jurisdiction 1 data:", data1);
|
| 116 |
+
console.log("Jurisdiction 2 data:", data2);
|
| 117 |
+
console.log("Equivalency scores:", equivalencyScores);
|
| 118 |
+
|
| 119 |
hasLoaded = true;
|
| 120 |
|
| 121 |
// Auto-select first term if available
|
| 122 |
if (data1.length > 0) selectedTerm1 = data1[0].term;
|
| 123 |
if (data2.length > 0) selectedTerm2 = data2[0].term;
|
|
|
|
| 124 |
} catch (e) {
|
| 125 |
+
error = e instanceof Error ? e.message : "Failed to load jurisdictions";
|
| 126 |
+
console.error("Jurisdiction loading error:", e);
|
| 127 |
} finally {
|
| 128 |
loading = false;
|
| 129 |
}
|
| 130 |
}
|
| 131 |
|
| 132 |
function getSelectedDefinition1(): string {
|
| 133 |
+
if (!selectedTerm1) return "";
|
| 134 |
+
const found = data1.find((item) => item.term === selectedTerm1);
|
| 135 |
+
return found?.definition || "";
|
| 136 |
}
|
| 137 |
|
| 138 |
function getSelectedDefinition2(): string {
|
| 139 |
+
if (!selectedTerm2) return "";
|
| 140 |
+
const found = data2.find((item) => item.term === selectedTerm2);
|
| 141 |
+
return found?.definition || "";
|
| 142 |
}
|
| 143 |
|
| 144 |
function normalizeTermForComparison(term: string): string {
|
| 145 |
// Replace underscores with spaces and normalize whitespace for comparison
|
| 146 |
+
return term.replace(/_/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
| 147 |
+
}
|
| 148 |
+
function formatCategoryName(name: string): string {
|
| 149 |
+
// Replace underscores with spaces for display
|
| 150 |
+
return name.replace(/_/g, " ");
|
| 151 |
}
|
|
|
|
| 152 |
// Get direct translation equivalent for a term
|
| 153 |
function getDirectTranslation(term: string): string | null {
|
| 154 |
const normalized = normalizeTermForComparison(term);
|
|
|
|
| 156 |
}
|
| 157 |
|
| 158 |
// Check if a term should be highlighted based on the selected term in the other jurisdiction
|
| 159 |
+
function shouldHighlightTerm(
|
| 160 |
+
term: string,
|
| 161 |
+
selectedInOtherJurisdiction: string | null
|
| 162 |
+
): boolean {
|
| 163 |
if (!selectedInOtherJurisdiction) return false;
|
| 164 |
+
|
| 165 |
+
const equivalentOfSelected = getDirectTranslation(
|
| 166 |
+
selectedInOtherJurisdiction
|
| 167 |
+
);
|
| 168 |
if (!equivalentOfSelected) return false;
|
| 169 |
+
|
| 170 |
const normalizedTerm = normalizeTermForComparison(term);
|
| 171 |
return normalizedTerm === equivalentOfSelected;
|
| 172 |
}
|
|
|
|
| 181 |
const normalizedTerm2 = normalizeTermForComparison(selectedTerm2);
|
| 182 |
|
| 183 |
// Look for the score in both directions, comparing normalized terms
|
| 184 |
+
const score = equivalencyScores.find((s) => {
|
| 185 |
const normalizedJ1 = normalizeTermForComparison(s.term_j1);
|
| 186 |
const normalizedJ2 = normalizeTermForComparison(s.term_j2);
|
| 187 |
+
|
| 188 |
return (
|
| 189 |
+
(normalizedJ1 === normalizedTerm1 &&
|
| 190 |
+
normalizedJ2 === normalizedTerm2) ||
|
| 191 |
(normalizedJ1 === normalizedTerm2 && normalizedJ2 === normalizedTerm1)
|
| 192 |
);
|
| 193 |
});
|
| 194 |
|
| 195 |
return score || null;
|
| 196 |
}
|
| 197 |
+
|
| 198 |
+
// Get unique categories from comparisons
|
| 199 |
+
function getUniqueCategories(score: EquivalencyScore | null): string[] {
|
| 200 |
+
if (!score || !score.comparisons) return [];
|
| 201 |
+
const categories = new Set(score.comparisons.map((c) => c.category));
|
| 202 |
+
return Array.from(categories).sort();
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// Filter comparisons by selected category
|
| 206 |
+
function getFilteredComparisons(score: EquivalencyScore | null) {
|
| 207 |
+
if (!score || !score.comparisons) return [];
|
| 208 |
+
if (selectedCategory === "All") return score.comparisons;
|
| 209 |
+
return score.comparisons.filter((c) => c.category === selectedCategory);
|
| 210 |
+
}
|
| 211 |
</script>
|
| 212 |
|
| 213 |
<div class="jurisdiction-comparison">
|
| 214 |
<h2>Jurisdiction Term Comparison</h2>
|
| 215 |
+
|
| 216 |
<div class="form">
|
| 217 |
<div class="form-row">
|
| 218 |
<div class="form-group">
|
| 219 |
<label for="jurisdiction1">Jurisdiction 1:</label>
|
| 220 |
+
<select
|
| 221 |
+
id="jurisdiction1"
|
| 222 |
+
bind:value={jurisdiction1}
|
| 223 |
+
disabled={loading}
|
| 224 |
+
>
|
| 225 |
{#each availableJurisdictions as jur}
|
| 226 |
<option value={jur.code}>{jur.label}</option>
|
| 227 |
{/each}
|
|
|
|
| 230 |
|
| 231 |
<div class="form-group">
|
| 232 |
<label for="jurisdiction2">Jurisdiction 2:</label>
|
| 233 |
+
<select
|
| 234 |
+
id="jurisdiction2"
|
| 235 |
+
bind:value={jurisdiction2}
|
| 236 |
+
disabled={loading}
|
| 237 |
+
>
|
| 238 |
{#each availableJurisdictions as jur}
|
| 239 |
<option value={jur.code}>{jur.label}</option>
|
| 240 |
{/each}
|
|
|
|
| 244 |
|
| 245 |
{#if !hasLoaded}
|
| 246 |
<button onclick={loadComparison} disabled={loading}>
|
| 247 |
+
{loading ? "Loading..." : "Load Comparison"}
|
| 248 |
</button>
|
| 249 |
{/if}
|
| 250 |
</div>
|
| 251 |
|
| 252 |
{#if error}
|
| 253 |
<div class="error">
|
| 254 |
+
<strong>Error:</strong>
|
| 255 |
+
{error}
|
| 256 |
</div>
|
| 257 |
{/if}
|
| 258 |
|
|
|
|
| 260 |
<div class="comparison-container">
|
| 261 |
<!-- Jurisdiction 1 -->
|
| 262 |
<div class="jurisdiction-column">
|
| 263 |
+
<h3>
|
| 264 |
+
{availableJurisdictions.find((j) => j.code === jurisdiction1)?.label}
|
| 265 |
+
</h3>
|
| 266 |
<div class="term-selector">
|
| 267 |
<label for="term1">Select Term:</label>
|
| 268 |
<select id="term1" bind:value={selectedTerm1}>
|
| 269 |
{#each data1 as item}
|
| 270 |
<option value={item.term}>
|
| 271 |
+
{shouldHighlightTerm(item.term, selectedTerm2)
|
| 272 |
+
? "★ "
|
| 273 |
+
: ""}{item.term}
|
| 274 |
</option>
|
| 275 |
{/each}
|
| 276 |
</select>
|
| 277 |
+
<small class="translation-hint"
|
| 278 |
+
>★ = Direct translation of the term selected in the opposite
|
| 279 |
+
jurisdiction</small
|
| 280 |
+
>
|
| 281 |
</div>
|
| 282 |
{#if selectedTerm1}
|
| 283 |
<div class="definition-box">
|
|
|
|
| 294 |
|
| 295 |
<!-- Jurisdiction 2 -->
|
| 296 |
<div class="jurisdiction-column">
|
| 297 |
+
<h3>
|
| 298 |
+
{availableJurisdictions.find((j) => j.code === jurisdiction2)?.label}
|
| 299 |
+
</h3>
|
| 300 |
<div class="term-selector">
|
| 301 |
<label for="term2">Select Term:</label>
|
| 302 |
<select id="term2" bind:value={selectedTerm2}>
|
| 303 |
{#each data2 as item}
|
| 304 |
<option value={item.term}>
|
| 305 |
+
{shouldHighlightTerm(item.term, selectedTerm1)
|
| 306 |
+
? "★ "
|
| 307 |
+
: ""}{item.term}
|
| 308 |
</option>
|
| 309 |
{/each}
|
| 310 |
</select>
|
| 311 |
+
<small class="translation-hint"
|
| 312 |
+
>★ = Direct translation of the term selected in the opposite
|
| 313 |
+
jurisdiction</small
|
| 314 |
+
>
|
| 315 |
</div>
|
| 316 |
{#if selectedTerm2}
|
| 317 |
<div class="definition-box">
|
|
|
|
| 338 |
</div>
|
| 339 |
{#if score.comparative_law_note}
|
| 340 |
<div class="comparative-note">
|
|
|
|
| 341 |
<div class="markdown-content">
|
| 342 |
{@html marked(score.comparative_law_note)}
|
| 343 |
</div>
|
| 344 |
</div>
|
| 345 |
{/if}
|
| 346 |
+
|
| 347 |
{#if score.comparisons && score.comparisons.length > 0}
|
| 348 |
+
{@const categories = getUniqueCategories(score)}
|
| 349 |
+
{@const filteredComparisons = getFilteredComparisons(score)}
|
| 350 |
<div class="comparisons-section">
|
| 351 |
+
<h3>Detailed Comparisons</h3>
|
| 352 |
+
|
| 353 |
+
<!-- Category Filter Pills -->
|
| 354 |
+
{#if categories.length > 0}
|
| 355 |
+
<div class="category-filters">
|
| 356 |
+
<button
|
| 357 |
+
class="filter-pill"
|
| 358 |
+
class:active={selectedCategory === "All"}
|
| 359 |
+
onclick={() => (selectedCategory = "All")}
|
| 360 |
+
>
|
| 361 |
+
All ({score.comparisons.length})
|
| 362 |
+
</button>
|
| 363 |
+
{#each categories as category}
|
| 364 |
+
{@const count = score.comparisons.filter(
|
| 365 |
+
(c) => c.category === category
|
| 366 |
+
).length}
|
| 367 |
+
<button
|
| 368 |
+
class="filter-pill"
|
| 369 |
+
class:active={selectedCategory === category}
|
| 370 |
+
onclick={() => (selectedCategory = category)}
|
| 371 |
+
>
|
| 372 |
+
{formatCategoryName(category)} ({count})
|
| 373 |
+
</button>
|
| 374 |
+
{/each}
|
| 375 |
+
</div>
|
| 376 |
+
{/if}
|
| 377 |
+
|
| 378 |
<div class="comparisons-list">
|
| 379 |
+
{#each filteredComparisons as comparison}
|
| 380 |
<div class="comparison-item">
|
| 381 |
<div class="comparison-header">
|
| 382 |
+
<span class="comparison-category">
|
| 383 |
+
{formatCategoryName(comparison.category)}
|
| 384 |
+
</span>
|
| 385 |
{#if comparison.subcategory}
|
| 386 |
+
<span class="comparison-subcategory">
|
| 387 |
+
› {formatCategoryName(comparison.subcategory)}
|
| 388 |
+
</span>
|
| 389 |
{/if}
|
| 390 |
+
<span class="comparison-score"
|
| 391 |
+
>{comparison.similarity_score.toFixed(2)}</span
|
| 392 |
+
>
|
| 393 |
</div>
|
| 394 |
<div class="comparison-meta">
|
| 395 |
+
<span class="comparison-weight"
|
| 396 |
+
>Weight: {comparison.weight.toFixed(2)}</span
|
| 397 |
+
>
|
| 398 |
+
<span class="comparison-weighted-score"
|
| 399 |
+
>Weighted Score: {comparison.weighted_similarity_score.toFixed(
|
| 400 |
+
2
|
| 401 |
+
)}</span
|
| 402 |
+
>
|
| 403 |
</div>
|
| 404 |
{#if comparison.reasoning}
|
| 405 |
<div class="comparison-reasoning">
|
| 406 |
+
{comparison.reasoning}
|
| 407 |
</div>
|
| 408 |
{/if}
|
| 409 |
</div>
|
|
|
|
| 473 |
color: #555;
|
| 474 |
}
|
| 475 |
|
| 476 |
+
input,
|
| 477 |
+
select {
|
| 478 |
width: 100%;
|
| 479 |
padding: 0.5rem;
|
| 480 |
border: 1px solid #ddd;
|
|
|
|
| 485 |
color: #333;
|
| 486 |
}
|
| 487 |
|
| 488 |
+
input:disabled,
|
| 489 |
+
select:disabled {
|
| 490 |
background: #e9e9e9;
|
| 491 |
cursor: not-allowed;
|
| 492 |
}
|
|
|
|
| 750 |
}
|
| 751 |
|
| 752 |
.comparison-subcategory {
|
| 753 |
+
color: #464444;
|
| 754 |
font-size: 0.9rem;
|
| 755 |
}
|
| 756 |
|
|
|
|
| 788 |
font-size: 0.9rem;
|
| 789 |
line-height: 1.5;
|
| 790 |
color: #444;
|
| 791 |
+
text-align: left;
|
| 792 |
}
|
| 793 |
|
| 794 |
.comparison-reasoning strong {
|
| 795 |
color: #333;
|
| 796 |
}
|
| 797 |
+
|
| 798 |
+
/* Category filter pills */
|
| 799 |
+
.category-filters {
|
| 800 |
+
display: flex;
|
| 801 |
+
flex-wrap: wrap;
|
| 802 |
+
gap: 0.5rem;
|
| 803 |
+
margin-bottom: 1.5rem;
|
| 804 |
+
padding-bottom: 1rem;
|
| 805 |
+
border-bottom: 1px solid #e0e0e0;
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
.filter-pill {
|
| 809 |
+
background: #f0f0f0;
|
| 810 |
+
color: #555;
|
| 811 |
+
border: 1px solid #ddd;
|
| 812 |
+
border-radius: 20px;
|
| 813 |
+
padding: 0.5rem 1rem;
|
| 814 |
+
font-size: 0.9rem;
|
| 815 |
+
cursor: pointer;
|
| 816 |
+
transition: all 0.2s;
|
| 817 |
+
width: auto;
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
.filter-pill:hover {
|
| 821 |
+
background: #e0e0e0;
|
| 822 |
+
border-color: #999;
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
.filter-pill.active {
|
| 826 |
+
background: #646cff;
|
| 827 |
+
color: white;
|
| 828 |
+
border-color: #646cff;
|
| 829 |
+
font-weight: 600;
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
.filter-pill.active:hover {
|
| 833 |
+
background: #535bf2;
|
| 834 |
+
border-color: #535bf2;
|
| 835 |
+
}
|
| 836 |
</style>
|