ArthurSrz commited on
Commit
77fdc03
·
verified ·
1 Parent(s): 441da40

redesign: Doctrine.fr-inspired theme — navy blue palette, Inter font, clean layout

Browse files
Files changed (1) hide show
  1. app.py +151 -50
app.py CHANGED
@@ -45,31 +45,29 @@ if DATASETS.get("circulaires"):
45
  # ---------------------------------------------------------------------------
46
  def run_search(query: str, source_filter: str, date_from: int, date_to: int,
47
  jurisdiction: str, code_name: str, ministere: str):
48
- # Empty query guard
49
  if not query.strip():
50
  return (
51
- gr.update(value="<p style='color:#9ca3af;font-style:italic'>Veuillez entrer une question juridique.</p>"),
52
  gr.update(value=""),
53
  )
54
 
55
- # Query length guard
56
  warning_note = ""
57
  if len(query) > 500:
58
  query = query[:500]
59
- warning_note = "\n\n⚠️ *Requête tronquée à 500 caractères.*"
60
 
61
  try:
62
  embedding = embed_query(query, HF_TOKEN)
63
  except ValueError as e:
64
  return (
65
- gr.update(value=f"<p style='color:#ef4444'>{e}</p>"),
66
  gr.update(value=""),
67
  )
68
 
69
  filters = {}
70
- if date_from and int(date_from) > 2000: # 2000 is slider minimum = "no lower bound"
71
  filters["date_from"] = int(date_from)
72
- if date_to and int(date_to) < 2026: # 2026 is slider maximum = "no upper bound"
73
  filters["date_to"] = int(date_to)
74
  if jurisdiction and jurisdiction != "Tous":
75
  filters["jurisdiction"] = jurisdiction
@@ -80,82 +78,181 @@ def run_search(query: str, source_filter: str, date_from: int, date_to: int,
80
 
81
  results = search_all(embedding, DATASETS, source_filter=source_filter, filters=filters)
82
 
83
- # Cross-references: enrich article results with related decisions
84
  enriched_articles = []
85
  for r in results.get("articles", []):
86
  lf_id = r.get("id_legifrance", "")
87
  related = find_related_decisions(lf_id, DATASETS.get("jurisprudence"))
88
  enriched_articles.append((r, related))
89
 
90
- # Build synthesis
91
  synthesis_text = synthesize(query, results, HF_TOKEN) + warning_note
92
 
93
- # Build article cards with cross-references
94
  article_html = "".join(
95
  build_article_card(r, related) for r, related in enriched_articles
96
  )
97
- # Temporarily replace articles list for tab builder (pass raw results for tab counts)
98
  tabs_html = build_tabs_html(results, LOADING_STATUS)
99
 
100
- # Inject enriched article cards into the Articles tab
101
  if article_html and enriched_articles:
102
  plain_article_html = "".join(build_article_card(r) for r, _ in enriched_articles)
103
  tabs_html = tabs_html.replace(plain_article_html, article_html)
104
 
105
  synthesis_html = f"""
106
- <div style="font-family:system-ui,sans-serif;background:#f8fafc;border-radius:8px;
107
- padding:16px 20px;border-left:4px solid #2563eb;margin-bottom:16px">
108
- <p style="font-size:13px;font-weight:700;color:#2563eb;margin:0 0 10px">Synthèse juridique</p>
109
- <div style="font-size:14px;line-height:1.7;color:#1e293b;white-space:pre-wrap">{synthesis_text}</div>
 
 
 
110
  </div>"""
111
 
112
  return gr.update(value=synthesis_html), gr.update(value=tabs_html)
113
 
114
 
115
  # ---------------------------------------------------------------------------
116
- # Gradio layout
117
  # ---------------------------------------------------------------------------
118
- LOADING_MSG = "Chargement des sources juridiques… (peut prendre jusqu'à 90 secondes)" \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  if not all(LOADING_STATUS.values()) else ""
120
 
121
- with gr.Blocks(
122
- title="enirtcod.fr — Recherche juridique française",
123
- css="""
124
- .gradio-container {
125
- max-width: 1100px !important;
126
- margin: 0 auto !important;
127
- width: 100% !important;
128
- padding: 0 16px !important;
129
- box-sizing: border-box !important;
130
- }
131
- footer { display: none !important; }
132
- @media (max-width: 768px) {
133
- .gradio-container { max-width: 100% !important; padding: 0 12px !important; }
134
- }
135
- """,
136
- ) as demo:
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  gr.HTML("""
139
- <div style="text-align:center;padding:24px 0 12px;font-family:system-ui,sans-serif">
140
- <h1 style="font-size:28px;font-weight:800;color:#1e293b;margin:0">⚖️ enirtcod.fr</h1>
141
- <p style="color:#64748b;font-size:14px;margin:6px 0 0">
142
- Alternative open-source à Doctrine.fr · Recherche dans 4 sources juridiques françaises
 
 
143
  </p>
144
  </div>""")
145
 
146
  if LOADING_MSG:
147
  gr.HTML(f"""
148
- <div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;
149
- padding:10px 16px;font-size:13px;color:#92400e;text-align:center">
150
- {LOADING_MSG}
151
  </div>""")
152
 
 
 
 
153
  with gr.Row():
154
  query_box = gr.Textbox(
155
- placeholder="Ex : Quelles sont les conditions de la responsabilité civile délictuelle ?",
156
- label="Question juridique",
157
  lines=2,
158
  scale=5,
 
159
  )
160
  source_selector = gr.Dropdown(
161
  choices=["Tous", "Articles", "Jurisprudence", "Circulaires", "Q&R"],
@@ -164,12 +261,13 @@ with gr.Blocks(
164
  scale=1,
165
  )
166
 
167
- search_btn = gr.Button("🔍 Rechercher", variant="primary")
168
 
169
- with gr.Accordion("Filtres avancés", open=False):
 
170
  with gr.Row():
171
- date_from = gr.Slider(minimum=2000, maximum=2026, step=1, value=2000, label="Année depuis")
172
- date_to = gr.Slider(minimum=2000, maximum=2026, step=1, value=2026, label="Année jusqu'à")
173
  with gr.Row():
174
  juris_filter = gr.Dropdown(
175
  choices=["Tous", "Cour de cassation", "Cour d'appel"],
@@ -184,12 +282,15 @@ with gr.Blocks(
184
  min_filter = gr.Dropdown(
185
  choices=["Tous"] + _ministeres,
186
  value="Tous",
187
- label="Ministère",
188
  )
189
 
190
- synthesis_out = gr.HTML(label="Synthèse")
191
- results_out = gr.HTML(label="Résultats")
 
 
192
 
 
193
  search_btn.click(
194
  fn=run_search,
195
  inputs=[query_box, source_selector, date_from, date_to,
 
45
  # ---------------------------------------------------------------------------
46
  def run_search(query: str, source_filter: str, date_from: int, date_to: int,
47
  jurisdiction: str, code_name: str, ministere: str):
 
48
  if not query.strip():
49
  return (
50
+ gr.update(value='<p style="color:#94a3b8;font-style:italic;padding:12px 0">Veuillez entrer une question juridique.</p>'),
51
  gr.update(value=""),
52
  )
53
 
 
54
  warning_note = ""
55
  if len(query) > 500:
56
  query = query[:500]
57
+ warning_note = "\n\n*Requete tronquee a 500 caracteres.*"
58
 
59
  try:
60
  embedding = embed_query(query, HF_TOKEN)
61
  except ValueError as e:
62
  return (
63
+ gr.update(value=f'<p style="color:#dc2626;padding:12px 0">{e}</p>'),
64
  gr.update(value=""),
65
  )
66
 
67
  filters = {}
68
+ if date_from and int(date_from) > 2000:
69
  filters["date_from"] = int(date_from)
70
+ if date_to and int(date_to) < 2026:
71
  filters["date_to"] = int(date_to)
72
  if jurisdiction and jurisdiction != "Tous":
73
  filters["jurisdiction"] = jurisdiction
 
78
 
79
  results = search_all(embedding, DATASETS, source_filter=source_filter, filters=filters)
80
 
 
81
  enriched_articles = []
82
  for r in results.get("articles", []):
83
  lf_id = r.get("id_legifrance", "")
84
  related = find_related_decisions(lf_id, DATASETS.get("jurisprudence"))
85
  enriched_articles.append((r, related))
86
 
 
87
  synthesis_text = synthesize(query, results, HF_TOKEN) + warning_note
88
 
 
89
  article_html = "".join(
90
  build_article_card(r, related) for r, related in enriched_articles
91
  )
 
92
  tabs_html = build_tabs_html(results, LOADING_STATUS)
93
 
 
94
  if article_html and enriched_articles:
95
  plain_article_html = "".join(build_article_card(r) for r, _ in enriched_articles)
96
  tabs_html = tabs_html.replace(plain_article_html, article_html)
97
 
98
  synthesis_html = f"""
99
+ <div style="background:#ffffff;border-radius:12px;padding:20px 24px;
100
+ border:1px solid #e2e8f0;box-shadow:0 1px 3px rgba(0,0,0,0.04);margin-bottom:20px">
101
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
102
+ <div style="width:4px;height:20px;background:#1e3a5f;border-radius:2px"></div>
103
+ <p style="font-size:14px;font-weight:700;color:#1e3a5f;margin:0;letter-spacing:-0.2px">Synthese juridique</p>
104
+ </div>
105
+ <div style="font-size:14px;line-height:1.75;color:#334155;white-space:pre-wrap">{synthesis_text}</div>
106
  </div>"""
107
 
108
  return gr.update(value=synthesis_html), gr.update(value=tabs_html)
109
 
110
 
111
  # ---------------------------------------------------------------------------
112
+ # Theme & CSS — Doctrine.fr-inspired design
113
  # ---------------------------------------------------------------------------
114
+ CUSTOM_CSS = """
115
+ /* ── Global theme overrides ── */
116
+ :root {
117
+ --color-accent: #1e3a5f !important;
118
+ --color-accent-soft: #e8f0fe !important;
119
+ --button-primary-background-fill: #1e3a5f !important;
120
+ --button-primary-background-fill-hover: #2c5282 !important;
121
+ --button-primary-text-color: #ffffff !important;
122
+ --block-border-color: #e2e8f0 !important;
123
+ --block-background-fill: #ffffff !important;
124
+ --body-background-fill: #f8fafc !important;
125
+ --input-background-fill: #ffffff !important;
126
+ --block-label-text-color: #475569 !important;
127
+ --slider-color: #1e3a5f !important;
128
+ }
129
+
130
+ .gradio-container {
131
+ max-width: 960px !important;
132
+ margin: 0 auto !important;
133
+ width: 100% !important;
134
+ padding: 0 24px !important;
135
+ box-sizing: border-box !important;
136
+ background: #f8fafc !important;
137
+ }
138
+
139
+ /* ── Typography ── */
140
+ .gradio-container, .gradio-container * {
141
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif !important;
142
+ }
143
+
144
+ /* ── Search input ── */
145
+ .gradio-container textarea {
146
+ border: 1.5px solid #cbd5e1 !important;
147
+ border-radius: 10px !important;
148
+ font-size: 15px !important;
149
+ padding: 12px 16px !important;
150
+ transition: border-color 0.2s, box-shadow 0.2s !important;
151
+ }
152
+ .gradio-container textarea:focus {
153
+ border-color: #1e3a5f !important;
154
+ box-shadow: 0 0 0 3px rgba(30, 58, 95, 0.08) !important;
155
+ outline: none !important;
156
+ }
157
+
158
+ /* ── Primary button ── */
159
+ .gradio-container button.primary {
160
+ background: #1e3a5f !important;
161
+ border: none !important;
162
+ border-radius: 10px !important;
163
+ font-weight: 600 !important;
164
+ font-size: 15px !important;
165
+ padding: 10px 24px !important;
166
+ letter-spacing: 0.2px !important;
167
+ transition: background 0.2s, transform 0.1s !important;
168
+ }
169
+ .gradio-container button.primary:hover {
170
+ background: #2c5282 !important;
171
+ transform: translateY(-1px) !important;
172
+ }
173
+
174
+ /* ── Dropdowns ── */
175
+ .gradio-container select,
176
+ .gradio-container .wrap input {
177
+ border: 1.5px solid #cbd5e1 !important;
178
+ border-radius: 8px !important;
179
+ }
180
+
181
+ /* ── Slider: override orange track ── */
182
+ .gradio-container input[type="range"] {
183
+ accent-color: #1e3a5f !important;
184
+ }
185
+ .gradio-container .range-slider {
186
+ accent-color: #1e3a5f !important;
187
+ }
188
+
189
+ /* ── Accordion ── */
190
+ .gradio-container .accordion {
191
+ border: 1px solid #e2e8f0 !important;
192
+ border-radius: 10px !important;
193
+ background: #ffffff !important;
194
+ }
195
+
196
+ /* ── Block panels ── */
197
+ .gradio-container .block {
198
+ border-radius: 10px !important;
199
+ border: 1px solid #e2e8f0 !important;
200
+ box-shadow: 0 1px 2px rgba(0,0,0,0.03) !important;
201
+ }
202
+
203
+ /* ── Hide footer ── */
204
+ footer { display: none !important; }
205
+
206
+ /* ── Responsive ── */
207
+ @media (max-width: 768px) {
208
+ .gradio-container { max-width: 100% !important; padding: 0 12px !important; }
209
+ }
210
+ """
211
+
212
+ LOADING_MSG = "Chargement des index FAISS… (peut prendre jusqu'a 90 secondes)" \
213
  if not all(LOADING_STATUS.values()) else ""
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ # ---------------------------------------------------------------------------
217
+ # Gradio layout
218
+ # ---------------------------------------------------------------------------
219
+ theme = gr.themes.Soft(
220
+ primary_hue=gr.themes.colors.blue,
221
+ secondary_hue=gr.themes.colors.slate,
222
+ neutral_hue=gr.themes.colors.slate,
223
+ font=gr.themes.GoogleFont("Inter"),
224
+ )
225
+
226
+ with gr.Blocks(title="enirtcod.fr — Recherche juridique francaise", css=CUSTOM_CSS, theme=theme) as demo:
227
+
228
+ # ── Header ──
229
  gr.HTML("""
230
+ <div style="text-align:center;padding:32px 0 8px">
231
+ <h1 style="font-size:32px;font-weight:800;color:#1e293b;margin:0;letter-spacing:-0.5px">
232
+ enirtcod<span style="color:#1e3a5f">.fr</span>
233
+ </h1>
234
+ <p style="color:#64748b;font-size:15px;margin:8px 0 0;font-weight:400">
235
+ Recherche semantique dans le droit francais &middot; Open source
236
  </p>
237
  </div>""")
238
 
239
  if LOADING_MSG:
240
  gr.HTML(f"""
241
+ <div style="background:#f0f4ff;border:1px solid #bfdbfe;border-radius:10px;
242
+ padding:12px 20px;font-size:13px;color:#1e40af;text-align:center;margin:8px 0">
243
+ {LOADING_MSG}
244
  </div>""")
245
 
246
+ # ── Search bar ──
247
+ gr.HTML('<div style="height:8px"></div>')
248
+
249
  with gr.Row():
250
  query_box = gr.Textbox(
251
+ placeholder="Posez votre question juridique, comme si vous parliez a un confrere…",
252
+ label="",
253
  lines=2,
254
  scale=5,
255
+ show_label=False,
256
  )
257
  source_selector = gr.Dropdown(
258
  choices=["Tous", "Articles", "Jurisprudence", "Circulaires", "Q&R"],
 
261
  scale=1,
262
  )
263
 
264
+ search_btn = gr.Button("Rechercher", variant="primary", size="lg")
265
 
266
+ # ── Filters ──
267
+ with gr.Accordion("Filtres avances", open=False):
268
  with gr.Row():
269
+ date_from = gr.Slider(minimum=2000, maximum=2026, step=1, value=2000, label="Annee depuis")
270
+ date_to = gr.Slider(minimum=2000, maximum=2026, step=1, value=2026, label="Annee jusqu'a")
271
  with gr.Row():
272
  juris_filter = gr.Dropdown(
273
  choices=["Tous", "Cour de cassation", "Cour d'appel"],
 
282
  min_filter = gr.Dropdown(
283
  choices=["Tous"] + _ministeres,
284
  value="Tous",
285
+ label="Ministere",
286
  )
287
 
288
+ # ── Results ──
289
+ gr.HTML('<div style="height:8px"></div>')
290
+ synthesis_out = gr.HTML(label="Synthese")
291
+ results_out = gr.HTML(label="Resultats")
292
 
293
+ # ── Event handlers ──
294
  search_btn.click(
295
  fn=run_search,
296
  inputs=[query_box, source_selector, date_from, date_to,