Davidvandijcke Claude Opus 4.6 commited on
Commit
4dd7a3f
·
1 Parent(s): e579491

Update research assistant: Gemini 3 Flash, expanded context, Observatory theme

Browse files

- Switch app_gradio.py model to gemini-3-flash-preview (matching app.py)
- Add unmasking, ukraine, refugee papers to priority_order in both files
- Add refugee paper to app_gradio.py paper_files
- Expand keyword matching for all papers (disco, ukraine, rto, prodf, etc.)
- Improve system prompt with paper inventory, markdown formatting, guardrails
- Bump excerpt lengths (JMP 50k, others 25k)
- Restyle app.py chat UI to Observatory theme (navy, warm bg, flat, 2px radius)
- Add client-side markdown rendering for assistant messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +159 -121
  2. app_gradio.py +49 -23
app.py CHANGED
@@ -109,13 +109,12 @@ class ProfessionalAssistant:
109
  context_parts = []
110
 
111
  # Add papers in priority order
112
- priority_order = ["r3d","cv", "frechet", "fdr", "disco", "rto", "prodf"]
113
-
114
  for key in priority_order:
115
  if key in self.papers:
116
  paper = self.papers[key]
117
- # Add substantial excerpts
118
- excerpt_length = 30000 if key == "r3d" else 15000
119
  context_parts.append(f"\n[{paper['title']}]\n{paper['text'][:excerpt_length]}")
120
 
121
  return "\n\n".join(context_parts)
@@ -149,27 +148,62 @@ class ProfessionalAssistant:
149
  if "job market" in message_lower or "r3d" in message_lower:
150
  if "r3d" in self.papers:
151
  specific_context = f"\n[R3D - Job Market Paper]\n{self.papers['r3d']['text'][:50000]}\n"
152
- elif "fdr" in message_lower or "discontinuity" in message_lower:
153
  if "fdr" in self.papers:
154
  specific_context = f"\n[FDR Paper]\n{self.papers['fdr']['text'][:30000]}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  # Create prompt
157
- prompt = f"""You are an expert assistant helping visitors learn about David Van Dijcke's research.
158
 
159
  CRITICAL INSTRUCTIONS:
160
  - You are NOT David - you are an expert explaining his work to website visitors
161
  - Speak in third person about David (use "David" or "Van Dijcke", not "I" or "my")
162
- - Be conversational but professional
163
  - Give concise, informative answers (2-3 paragraphs max unless asked for details)
164
  - Don't say "based on the provided papers" - just state facts confidently
165
  - Focus on what makes his work innovative and important
166
  - When discussing papers with multiple authors, always mention coauthors (other than David) in parentheses
 
 
167
 
168
  Key facts:
169
  - David is an econometrician on the 2025-26 job market from University of Michigan
170
  - His job market paper is R3D (Regression Discontinuity Design with Distribution-Valued Outcomes)
171
  - He specializes in functional data analysis and optimal transport methods
172
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  {conversation}
174
 
175
  Full research context:
@@ -210,236 +244,213 @@ async def read_root():
210
  <meta charset="UTF-8">
211
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
212
  <title>David Van Dijcke - Research Assistant</title>
 
 
 
213
  <style>
214
  * {
215
  margin: 0;
216
  padding: 0;
217
  box-sizing: border-box;
218
  }
219
-
220
  body {
221
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif;
222
- background: #ffffff;
223
  height: 100vh;
224
  display: flex;
225
  flex-direction: column;
 
226
  }
227
-
228
  .container {
229
  display: flex;
230
  flex-direction: column;
231
  height: 100vh;
232
  width: 100%;
233
  margin: 0;
234
- background: white;
235
  }
236
-
237
  .header {
238
  display: none;
239
  }
240
-
241
  .chat-container {
242
  flex: 1;
243
  overflow-y: auto;
244
  padding: 1rem;
245
- background: #fafbfc;
246
  min-height: 0;
247
  }
248
 
249
  .message {
250
  margin-bottom: 1rem;
251
  display: flex;
252
- animation: fadeIn 0.3s ease-out;
 
253
  }
254
-
255
  .message.user {
256
  justify-content: flex-end;
257
  }
258
-
259
  .message.assistant {
260
  justify-content: flex-start;
261
  }
262
-
263
  .message-content {
264
  max-width: 75%;
265
  padding: 0.75rem 1rem;
266
- border-radius: 14px;
267
- line-height: 1.5;
268
  font-size: 0.875rem;
269
  }
270
-
 
 
 
 
 
271
  .message.user .message-content {
272
- background: #f7f7f8;
273
- color: #2d3748;
274
- border-bottom-right-radius: 4px;
275
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
276
- border: 1px solid #e5e8eb;
277
  }
278
-
279
  .message.assistant .message-content {
280
- background: white;
281
- color: #2d3748;
282
- border-bottom-left-radius: 4px;
283
- box-shadow: 0 3px 12px rgba(0, 0, 0, 0.08);
284
- border: 1px solid #e2e8f0;
285
  }
286
-
287
  .typing-indicator {
288
  display: none;
289
  padding: 1rem;
290
  margin-bottom: 1rem;
291
  }
292
-
293
  .typing-indicator.active {
294
  display: flex;
295
  }
296
-
297
  .typing-indicator span {
298
  height: 8px;
299
  width: 8px;
300
- background: #8e8ea0;
301
  border-radius: 50%;
302
  display: inline-block;
303
  margin: 0 2px;
304
  animation: typing 1.4s infinite;
305
  }
306
-
307
- .typing-indicator span:nth-child(2) {
308
- animation-delay: 0.2s;
309
- }
310
-
311
- .typing-indicator span:nth-child(3) {
312
- animation-delay: 0.4s;
313
- }
314
-
315
  @keyframes typing {
316
- 0%, 60%, 100% {
317
- transform: translateY(0);
318
- opacity: 0.5;
319
- }
320
- 30% {
321
- transform: translateY(-10px);
322
- opacity: 1;
323
- }
324
  }
325
-
 
 
 
 
 
326
  .input-container {
327
  padding: 1rem 1.5rem;
328
- background: white;
329
- border-top: 1px solid #e2e8f0;
330
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
331
  flex-shrink: 0;
332
  }
333
-
334
  .input-wrapper {
335
  display: flex;
336
- gap: 1rem;
337
  align-items: flex-end;
338
  }
339
-
340
  #messageInput {
341
  flex: 1;
342
  padding: 0.625rem 1rem;
343
- border: 2px solid #e2e8f0;
344
- border-radius: 10px;
345
  font-size: 0.875rem;
346
  font-family: inherit;
347
  resize: none;
348
  outline: none;
349
- transition: all 0.2s ease;
350
- background: #f8fafc;
351
  min-height: 42px;
352
  max-height: 100px;
 
353
  }
354
-
355
  #messageInput:focus {
356
- border-color: #d1d5db;
357
- background: white;
358
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
359
  }
360
-
361
  #sendButton {
362
  padding: 0.625rem 1.5rem;
363
- background: #2d3748;
364
- color: white;
365
  border: none;
366
- border-radius: 10px;
367
  font-weight: 600;
368
  font-size: 0.875rem;
 
369
  cursor: pointer;
370
- transition: all 0.2s ease;
371
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
372
  white-space: nowrap;
373
  }
374
-
375
  #sendButton:hover {
376
- background: #1a202c;
377
- transform: translateY(-1px);
378
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
379
- }
380
-
381
- #sendButton:active {
382
- transform: translateY(0);
383
  }
384
-
385
  #sendButton:disabled {
386
- opacity: 0.6;
387
  cursor: not-allowed;
388
- transform: none;
389
  }
390
-
391
  .suggestions {
392
  display: flex;
393
  gap: 0.5rem;
394
  flex-wrap: wrap;
395
  margin-top: 0.75rem;
396
  padding-top: 0.75rem;
397
- border-top: 1px solid #e2e8f0;
398
  }
399
 
400
  .suggestion {
401
  padding: 0.4rem 0.875rem;
402
- background: #f8fafc;
403
- border: 1px solid #e2e8f0;
404
- border-radius: 16px;
405
  font-size: 0.8125rem;
406
- color: #4a5568;
407
  cursor: pointer;
408
- transition: all 0.2s ease;
409
  }
410
-
411
  .suggestion:hover {
412
- background: #2d3748;
413
- color: white;
414
- border-color: #2d3748;
415
- transform: translateY(-1px);
416
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
417
- }
418
-
419
- @keyframes fadeIn {
420
- from {
421
- opacity: 0;
422
- transform: translateY(10px);
423
- }
424
- to {
425
- opacity: 1;
426
- transform: translateY(0);
427
- }
428
  }
429
-
430
  @media (max-width: 768px) {
431
  .message-content {
432
  max-width: 85%;
433
  }
434
-
435
- .header h1 {
436
- font-size: 1.5rem;
437
- }
438
-
439
  .suggestions {
440
  flex-direction: column;
441
  }
442
-
443
  .suggestion {
444
  text-align: center;
445
  }
@@ -571,17 +582,44 @@ async def read_root():
571
  messageInput.focus();
572
  }
573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  function addMessage(content, role) {
575
  const messageDiv = document.createElement('div');
576
  messageDiv.className = `message ${role}`;
577
-
578
  const contentDiv = document.createElement('div');
579
  contentDiv.className = 'message-content';
580
- contentDiv.textContent = content;
581
-
 
 
 
 
582
  messageDiv.appendChild(contentDiv);
583
  chatContainer.appendChild(messageDiv);
584
-
585
  // Scroll to bottom
586
  chatContainer.scrollTop = chatContainer.scrollHeight;
587
  }
 
109
  context_parts = []
110
 
111
  # Add papers in priority order
112
+ priority_order = ["r3d", "cv", "frechet", "fdr", "disco", "rto", "prodf", "unmasking", "ukraine", "refugee"]
113
+
114
  for key in priority_order:
115
  if key in self.papers:
116
  paper = self.papers[key]
117
+ excerpt_length = 50000 if key == "r3d" else 25000
 
118
  context_parts.append(f"\n[{paper['title']}]\n{paper['text'][:excerpt_length]}")
119
 
120
  return "\n\n".join(context_parts)
 
148
  if "job market" in message_lower or "r3d" in message_lower:
149
  if "r3d" in self.papers:
150
  specific_context = f"\n[R3D - Job Market Paper]\n{self.papers['r3d']['text'][:50000]}\n"
151
+ elif "fdr" in message_lower or "free discontinuity" in message_lower:
152
  if "fdr" in self.papers:
153
  specific_context = f"\n[FDR Paper]\n{self.papers['fdr']['text'][:30000]}\n"
154
+ elif "disco" in message_lower or "synthetic control" in message_lower:
155
+ if "disco" in self.papers:
156
+ specific_context = f"\n[DiSCo Paper]\n{self.papers['disco']['text'][:30000]}\n"
157
+ elif "ukraine" in message_lower or "alert" in message_lower or "conflict" in message_lower:
158
+ if "ukraine" in self.papers:
159
+ specific_context = f"\n[Ukraine Alerts Paper]\n{self.papers['ukraine']['text'][:30000]}\n"
160
+ elif "rto" in message_lower or "return to office" in message_lower or "remote work" in message_lower:
161
+ if "rto" in self.papers:
162
+ specific_context = f"\n[RTO Paper]\n{self.papers['rto']['text'][:30000]}\n"
163
+ elif "production function" in message_lower or "prodf" in message_lower or "revenue" in message_lower:
164
+ if "prodf" in self.papers:
165
+ specific_context = f"\n[Production Functions Paper]\n{self.papers['prodf']['text'][:30000]}\n"
166
+ elif "unmasking" in message_lower or "partisan" in message_lower or "mask" in message_lower:
167
+ if "unmasking" in self.papers:
168
+ specific_context = f"\n[Unmasking Partisanship Paper]\n{self.papers['unmasking']['text'][:30000]}\n"
169
+ elif "refugee" in message_lower or "migration" in message_lower:
170
+ if "refugee" in self.papers:
171
+ specific_context = f"\n[Refugee Return Paper]\n{self.papers['refugee']['text'][:30000]}\n"
172
+ elif "frechet" in message_lower or "jump" in message_lower or "metric space" in message_lower:
173
+ if "frechet" in self.papers:
174
+ specific_context = f"\n[Frechet Paper]\n{self.papers['frechet']['text'][:30000]}\n"
175
 
176
  # Create prompt
177
+ prompt = f"""You are an expert assistant helping visitors learn about David Van Dijcke's research.
178
 
179
  CRITICAL INSTRUCTIONS:
180
  - You are NOT David - you are an expert explaining his work to website visitors
181
  - Speak in third person about David (use "David" or "Van Dijcke", not "I" or "my")
182
+ - Be conversational but professional
183
  - Give concise, informative answers (2-3 paragraphs max unless asked for details)
184
  - Don't say "based on the provided papers" - just state facts confidently
185
  - Focus on what makes his work innovative and important
186
  - When discussing papers with multiple authors, always mention coauthors (other than David) in parentheses
187
+ - Use markdown formatting: **bold** for emphasis, bullet points for lists
188
+ - If a question is outside the scope of David's research or CV, politely redirect: "That's outside what I can speak to — I'm here to help with David's research and background. Feel free to ask about any of his papers!"
189
 
190
  Key facts:
191
  - David is an econometrician on the 2025-26 job market from University of Michigan
192
  - His job market paper is R3D (Regression Discontinuity Design with Distribution-Valued Outcomes)
193
  - He specializes in functional data analysis and optimal transport methods
194
 
195
+ Available papers you can draw on:
196
+ - R3D (Job Market Paper) — Regression Discontinuity with Distribution-Valued Outcomes
197
+ - Frechet ANOVA — A Test for Jumps in Metric-Space Conditional Means
198
+ - Free Discontinuity Regression (FDR)
199
+ - Distributional Synthetic Controls (DiSCo)
200
+ - Return to Office (RTO)
201
+ - Revenue Production Functions
202
+ - Unmasking Partisanship
203
+ - Ukraine Alerts — Public Response to Government Alerts Saves Lives
204
+ - Refugee Return and Conflict
205
+ - David's CV
206
+
207
  {conversation}
208
 
209
  Full research context:
 
244
  <meta charset="UTF-8">
245
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
246
  <title>David Van Dijcke - Research Assistant</title>
247
+ <link rel="preconnect" href="https://fonts.googleapis.com">
248
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
249
+ <link href="https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600&family=Source+Sans+3:wght@400;600&display=swap" rel="stylesheet">
250
  <style>
251
  * {
252
  margin: 0;
253
  padding: 0;
254
  box-sizing: border-box;
255
  }
256
+
257
  body {
258
+ font-family: "Source Sans 3", -apple-system, BlinkMacSystemFont, sans-serif;
259
+ background: #faf9f7;
260
  height: 100vh;
261
  display: flex;
262
  flex-direction: column;
263
+ color: #1a1a1a;
264
  }
265
+
266
  .container {
267
  display: flex;
268
  flex-direction: column;
269
  height: 100vh;
270
  width: 100%;
271
  margin: 0;
272
+ background: #faf9f7;
273
  }
274
+
275
  .header {
276
  display: none;
277
  }
278
+
279
  .chat-container {
280
  flex: 1;
281
  overflow-y: auto;
282
  padding: 1rem;
283
+ background: #faf9f7;
284
  min-height: 0;
285
  }
286
 
287
  .message {
288
  margin-bottom: 1rem;
289
  display: flex;
290
+ opacity: 0;
291
+ animation: fadeIn 0.25s ease-out forwards;
292
  }
293
+
294
  .message.user {
295
  justify-content: flex-end;
296
  }
297
+
298
  .message.assistant {
299
  justify-content: flex-start;
300
  }
301
+
302
  .message-content {
303
  max-width: 75%;
304
  padding: 0.75rem 1rem;
305
+ border-radius: 2px;
306
+ line-height: 1.6;
307
  font-size: 0.875rem;
308
  }
309
+
310
+ .message-content p { margin-bottom: 0.5em; }
311
+ .message-content p:last-child { margin-bottom: 0; }
312
+ .message-content ul, .message-content ol { margin: 0.4em 0 0.4em 1.25em; }
313
+ .message-content strong { font-weight: 600; }
314
+
315
  .message.user .message-content {
316
+ background: #faf9f7;
317
+ color: #1a1a1a;
318
+ border: 1px solid #232D4B;
319
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 
320
  }
321
+
322
  .message.assistant .message-content {
323
+ background: #f3f1ed;
324
+ color: #1a1a1a;
325
+ border: 1px solid #e8e5df;
326
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 
327
  }
328
+
329
  .typing-indicator {
330
  display: none;
331
  padding: 1rem;
332
  margin-bottom: 1rem;
333
  }
334
+
335
  .typing-indicator.active {
336
  display: flex;
337
  }
338
+
339
  .typing-indicator span {
340
  height: 8px;
341
  width: 8px;
342
+ background: #4a4a4a;
343
  border-radius: 50%;
344
  display: inline-block;
345
  margin: 0 2px;
346
  animation: typing 1.4s infinite;
347
  }
348
+
349
+ .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
350
+ .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
351
+
 
 
 
 
 
352
  @keyframes typing {
353
+ 0%, 60%, 100% { opacity: 0.3; }
354
+ 30% { opacity: 1; }
 
 
 
 
 
 
355
  }
356
+
357
+ @keyframes fadeIn {
358
+ from { opacity: 0; }
359
+ to { opacity: 1; }
360
+ }
361
+
362
  .input-container {
363
  padding: 1rem 1.5rem;
364
+ background: #faf9f7;
365
+ border-top: 1px solid #e8e5df;
366
+ box-shadow: 0 -1px 3px rgba(0,0,0,0.04);
367
  flex-shrink: 0;
368
  }
369
+
370
  .input-wrapper {
371
  display: flex;
372
+ gap: 0.75rem;
373
  align-items: flex-end;
374
  }
375
+
376
  #messageInput {
377
  flex: 1;
378
  padding: 0.625rem 1rem;
379
+ border: 1px solid #d5d0c8;
380
+ border-radius: 2px;
381
  font-size: 0.875rem;
382
  font-family: inherit;
383
  resize: none;
384
  outline: none;
385
+ transition: border-color 0.15s ease;
386
+ background: #ffffff;
387
  min-height: 42px;
388
  max-height: 100px;
389
+ color: #1a1a1a;
390
  }
391
+
392
  #messageInput:focus {
393
+ border-color: #232D4B;
 
 
394
  }
395
+
396
  #sendButton {
397
  padding: 0.625rem 1.5rem;
398
+ background: #232D4B;
399
+ color: #ffffff;
400
  border: none;
401
+ border-radius: 2px;
402
  font-weight: 600;
403
  font-size: 0.875rem;
404
+ font-family: inherit;
405
  cursor: pointer;
406
+ transition: background 0.15s ease;
 
407
  white-space: nowrap;
408
  }
409
+
410
  #sendButton:hover {
411
+ background: #1a2238;
 
 
 
 
 
 
412
  }
413
+
414
  #sendButton:disabled {
415
+ opacity: 0.5;
416
  cursor: not-allowed;
 
417
  }
418
+
419
  .suggestions {
420
  display: flex;
421
  gap: 0.5rem;
422
  flex-wrap: wrap;
423
  margin-top: 0.75rem;
424
  padding-top: 0.75rem;
425
+ border-top: 1px solid #e8e5df;
426
  }
427
 
428
  .suggestion {
429
  padding: 0.4rem 0.875rem;
430
+ background: #faf9f7;
431
+ border: 1px solid #d5d0c8;
432
+ border-radius: 2px;
433
  font-size: 0.8125rem;
434
+ color: #4a4a4a;
435
  cursor: pointer;
436
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
437
  }
438
+
439
  .suggestion:hover {
440
+ background: #232D4B;
441
+ color: #ffffff;
442
+ border-color: #232D4B;
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  }
444
+
445
  @media (max-width: 768px) {
446
  .message-content {
447
  max-width: 85%;
448
  }
449
+
 
 
 
 
450
  .suggestions {
451
  flex-direction: column;
452
  }
453
+
454
  .suggestion {
455
  text-align: center;
456
  }
 
582
  messageInput.focus();
583
  }
584
 
585
+ function renderMarkdown(text) {
586
+ // Escape HTML first
587
+ var lt = String.fromCharCode(60);
588
+ var re_amp = new RegExp('&', 'g');
589
+ var re_lt = new RegExp(lt, 'g');
590
+ var re_gt = new RegExp('>', 'g');
591
+ var html = text.replace(re_amp, '&amp;').replace(re_lt, '&lt;').replace(re_gt, '&gt;');
592
+ // Bold then italic
593
+ html = html.replace(new RegExp('\\\\*\\\\*(.+?)\\\\*\\\\*', 'g'), lt + 'strong>$1' + lt + '/strong>');
594
+ html = html.replace(new RegExp('\\\\*(.+?)\\\\*', 'g'), lt + 'em>$1' + lt + '/em>');
595
+ // Unordered lists
596
+ html = html.replace(new RegExp('^- (.+)$', 'gm'), lt + 'li>$1' + lt + '/li>');
597
+ html = html.replace(new RegExp('((?:' + lt + 'li>.*?' + lt + '/li>[\\\\n]?)+)', 'g'), lt + 'ul>$1' + lt + '/ul>');
598
+ // Paragraphs
599
+ html = html.replace(new RegExp('\\\\n\\\\n', 'g'), lt + '/p>' + lt + 'p>');
600
+ html = lt + 'p>' + html + lt + '/p>';
601
+ // Line breaks
602
+ html = html.replace(new RegExp('\\\\n', 'g'), lt + 'br>');
603
+ // Clean empty paragraphs
604
+ html = html.replace(new RegExp(lt + 'p>' + lt + '/p>', 'g'), '');
605
+ return html;
606
+ }
607
+
608
  function addMessage(content, role) {
609
  const messageDiv = document.createElement('div');
610
  messageDiv.className = `message ${role}`;
611
+
612
  const contentDiv = document.createElement('div');
613
  contentDiv.className = 'message-content';
614
+ if (role === 'assistant') {
615
+ contentDiv.innerHTML = renderMarkdown(content);
616
+ } else {
617
+ contentDiv.textContent = content;
618
+ }
619
+
620
  messageDiv.appendChild(contentDiv);
621
  chatContainer.appendChild(messageDiv);
622
+
623
  // Scroll to bottom
624
  chatContainer.scrollTop = chatContainer.scrollHeight;
625
  }
app_gradio.py CHANGED
@@ -23,28 +23,19 @@ class ProfessionalAssistant:
23
  if api_key:
24
  genai.configure(api_key=api_key)
25
  try:
26
- # Try Gemini 2.5 Pro first (best quality)
27
- self.model = genai.GenerativeModel('gemini-2.5-pro')
28
- print("Using Gemini 2.5 Pro")
29
  except:
30
  try:
31
- # Fall back to Gemini 2.5 Flash
32
  self.model = genai.GenerativeModel('gemini-2.5-flash')
33
  print("Using Gemini 2.5 Flash")
34
  except:
35
  try:
36
- # Try Gemini 2.0 Pro
37
- self.model = genai.GenerativeModel('gemini-2.0-pro')
38
- print("Using Gemini 2.0 Pro")
39
  except:
40
- try:
41
- # Final fallback to Gemini 2.0 Flash
42
- self.model = genai.GenerativeModel('gemini-2.0-flash')
43
- print("Using Gemini 2.0 Flash")
44
- except:
45
- # If all else fails, try any available model
46
- self.model = genai.GenerativeModel('gemini-pro')
47
- print("Using Gemini Pro (fallback)")
48
  else:
49
  self.model = None
50
 
@@ -71,7 +62,8 @@ class ProfessionalAssistant:
71
  "rto": ("rto.pdf", "Return to Office"),
72
  "prodf": ("prodf.pdf", "Revenue Production Functions"),
73
  "unmasking": ("unmasking_partisanship.pdf", "Unmasking Partisanship"),
74
- "ukraine": ("van-dijcke-et-al-public-response-to-government-alerts-saves-lives-during-russian-invasion-of-ukraine.pdf", "Ukraine Alerts")
 
75
  }
76
 
77
  for key, (filename, title) in paper_files.items():
@@ -97,13 +89,12 @@ class ProfessionalAssistant:
97
  context_parts = []
98
 
99
  # Add papers in priority order
100
- priority_order = ["r3d","cv", "frechet", "fdr", "disco", "rto", "prodf"]
101
-
102
  for key in priority_order:
103
  if key in self.papers:
104
  paper = self.papers[key]
105
- # Add substantial excerpts
106
- excerpt_length = 30000 if key == "r3d" else 15000
107
  context_parts.append(f"\n[{paper['title']}]\n{paper['text'][:excerpt_length]}")
108
 
109
  return "\n\n".join(context_parts)
@@ -132,27 +123,62 @@ class ProfessionalAssistant:
132
  if "job market" in message_lower or "r3d" in message_lower:
133
  if "r3d" in self.papers:
134
  specific_context = f"\n[R3D - Job Market Paper]\n{self.papers['r3d']['text'][:50000]}\n"
135
- elif "fdr" in message_lower or "discontinuity" in message_lower:
136
  if "fdr" in self.papers:
137
  specific_context = f"\n[FDR Paper]\n{self.papers['fdr']['text'][:30000]}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  # Create prompt
140
- prompt = f"""You are an expert assistant helping visitors learn about David Van Dijcke's research.
141
 
142
  CRITICAL INSTRUCTIONS:
143
  - You are NOT David - you are an expert explaining his work to website visitors
144
  - Speak in third person about David (use "David" or "Van Dijcke", not "I" or "my")
145
- - Be conversational but professional
146
  - Give concise, informative answers (2-3 paragraphs max unless asked for details)
147
  - Don't say "based on the provided papers" - just state facts confidently
148
  - Focus on what makes his work innovative and important
149
  - When discussing papers with multiple authors, always mention coauthors (other than David) in parentheses
 
 
150
 
151
  Key facts:
152
  - David is an econometrician on the 2025-26 job market from University of Michigan
153
  - His job market paper is R3D (Regression Discontinuity Design with Distribution-Valued Outcomes)
154
  - He specializes in functional data analysis and optimal transport methods
155
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  {conversation}
157
 
158
  Full research context:
 
23
  if api_key:
24
  genai.configure(api_key=api_key)
25
  try:
26
+ self.model = genai.GenerativeModel('gemini-3-flash-preview')
27
+ print("Using Gemini 3 Flash Preview")
 
28
  except:
29
  try:
 
30
  self.model = genai.GenerativeModel('gemini-2.5-flash')
31
  print("Using Gemini 2.5 Flash")
32
  except:
33
  try:
34
+ self.model = genai.GenerativeModel('gemini-2.0-flash')
35
+ print("Using Gemini 2.0 Flash")
 
36
  except:
37
+ self.model = genai.GenerativeModel('gemini-pro')
38
+ print("Using Gemini Pro (fallback)")
 
 
 
 
 
 
39
  else:
40
  self.model = None
41
 
 
62
  "rto": ("rto.pdf", "Return to Office"),
63
  "prodf": ("prodf.pdf", "Revenue Production Functions"),
64
  "unmasking": ("unmasking_partisanship.pdf", "Unmasking Partisanship"),
65
+ "ukraine": ("van-dijcke-et-al-public-response-to-government-alerts-saves-lives-during-russian-invasion-of-ukraine.pdf", "Ukraine Alerts"),
66
+ "refugee": ("refugee_return.pdf", "Refugee Return and Conflict: Evidence from a Natural Experiment")
67
  }
68
 
69
  for key, (filename, title) in paper_files.items():
 
89
  context_parts = []
90
 
91
  # Add papers in priority order
92
+ priority_order = ["r3d", "cv", "frechet", "fdr", "disco", "rto", "prodf", "unmasking", "ukraine", "refugee"]
93
+
94
  for key in priority_order:
95
  if key in self.papers:
96
  paper = self.papers[key]
97
+ excerpt_length = 50000 if key == "r3d" else 25000
 
98
  context_parts.append(f"\n[{paper['title']}]\n{paper['text'][:excerpt_length]}")
99
 
100
  return "\n\n".join(context_parts)
 
123
  if "job market" in message_lower or "r3d" in message_lower:
124
  if "r3d" in self.papers:
125
  specific_context = f"\n[R3D - Job Market Paper]\n{self.papers['r3d']['text'][:50000]}\n"
126
+ elif "fdr" in message_lower or "free discontinuity" in message_lower:
127
  if "fdr" in self.papers:
128
  specific_context = f"\n[FDR Paper]\n{self.papers['fdr']['text'][:30000]}\n"
129
+ elif "disco" in message_lower or "synthetic control" in message_lower:
130
+ if "disco" in self.papers:
131
+ specific_context = f"\n[DiSCo Paper]\n{self.papers['disco']['text'][:30000]}\n"
132
+ elif "ukraine" in message_lower or "alert" in message_lower or "conflict" in message_lower:
133
+ if "ukraine" in self.papers:
134
+ specific_context = f"\n[Ukraine Alerts Paper]\n{self.papers['ukraine']['text'][:30000]}\n"
135
+ elif "rto" in message_lower or "return to office" in message_lower or "remote work" in message_lower:
136
+ if "rto" in self.papers:
137
+ specific_context = f"\n[RTO Paper]\n{self.papers['rto']['text'][:30000]}\n"
138
+ elif "production function" in message_lower or "prodf" in message_lower or "revenue" in message_lower:
139
+ if "prodf" in self.papers:
140
+ specific_context = f"\n[Production Functions Paper]\n{self.papers['prodf']['text'][:30000]}\n"
141
+ elif "unmasking" in message_lower or "partisan" in message_lower or "mask" in message_lower:
142
+ if "unmasking" in self.papers:
143
+ specific_context = f"\n[Unmasking Partisanship Paper]\n{self.papers['unmasking']['text'][:30000]}\n"
144
+ elif "refugee" in message_lower or "migration" in message_lower:
145
+ if "refugee" in self.papers:
146
+ specific_context = f"\n[Refugee Return Paper]\n{self.papers['refugee']['text'][:30000]}\n"
147
+ elif "frechet" in message_lower or "jump" in message_lower or "metric space" in message_lower:
148
+ if "frechet" in self.papers:
149
+ specific_context = f"\n[Frechet Paper]\n{self.papers['frechet']['text'][:30000]}\n"
150
 
151
  # Create prompt
152
+ prompt = f"""You are an expert assistant helping visitors learn about David Van Dijcke's research.
153
 
154
  CRITICAL INSTRUCTIONS:
155
  - You are NOT David - you are an expert explaining his work to website visitors
156
  - Speak in third person about David (use "David" or "Van Dijcke", not "I" or "my")
157
+ - Be conversational but professional
158
  - Give concise, informative answers (2-3 paragraphs max unless asked for details)
159
  - Don't say "based on the provided papers" - just state facts confidently
160
  - Focus on what makes his work innovative and important
161
  - When discussing papers with multiple authors, always mention coauthors (other than David) in parentheses
162
+ - Use markdown formatting: **bold** for emphasis, bullet points for lists
163
+ - If a question is outside the scope of David's research or CV, politely redirect: "That's outside what I can speak to — I'm here to help with David's research and background. Feel free to ask about any of his papers!"
164
 
165
  Key facts:
166
  - David is an econometrician on the 2025-26 job market from University of Michigan
167
  - His job market paper is R3D (Regression Discontinuity Design with Distribution-Valued Outcomes)
168
  - He specializes in functional data analysis and optimal transport methods
169
 
170
+ Available papers you can draw on:
171
+ - R3D (Job Market Paper) — Regression Discontinuity with Distribution-Valued Outcomes
172
+ - Frechet ANOVA — A Test for Jumps in Metric-Space Conditional Means
173
+ - Free Discontinuity Regression (FDR)
174
+ - Distributional Synthetic Controls (DiSCo)
175
+ - Return to Office (RTO)
176
+ - Revenue Production Functions
177
+ - Unmasking Partisanship
178
+ - Ukraine Alerts — Public Response to Government Alerts Saves Lives
179
+ - Refugee Return and Conflict
180
+ - David's CV
181
+
182
  {conversation}
183
 
184
  Full research context: