working on Publications, Article base page

This commit is contained in:
2025-09-01 18:01:08 +02:00
parent 4e0d77baeb
commit 7b41bd3f75
12 changed files with 274 additions and 333 deletions

View File

@@ -1,253 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
base64 (0.2.0)
benchmark (0.4.0)
bibtex-ruby (6.1.0)
latex-decode (~> 0.0)
racc (~> 1.7)
bigdecimal (3.1.9)
citeproc (1.1.0)
date
forwardable
json
namae (~> 1.0)
observer (< 1.0)
open-uri (< 1.0)
citeproc-ruby (1.1.14)
citeproc (~> 1.0, >= 1.0.9)
csl (~> 1.6)
colorator (1.1.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
csl (1.6.0)
namae (~> 1.0)
rexml
csl-styles (1.0.1.11)
csl (~> 1.0)
csv (3.3.3)
date (3.4.1)
drb (2.2.1)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
faraday (2.12.2)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
ffi (1.17.1-aarch64-linux-gnu)
ffi (1.17.1-aarch64-linux-musl)
ffi (1.17.1-arm-linux-gnu)
ffi (1.17.1-arm-linux-musl)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86_64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
ffi (1.17.1-x86_64-linux-musl)
forwardable (1.3.3)
forwardable-extended (2.6.0)
gemoji (4.1.0)
google-protobuf (4.30.1)
bigdecimal
rake (>= 13)
google-protobuf (4.30.1-aarch64-linux)
bigdecimal
rake (>= 13)
google-protobuf (4.30.1-arm64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.30.1-x86_64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.30.1-x86_64-linux)
bigdecimal
rake (>= 13)
html-pipeline (2.14.3)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jekyll (4.4.1)
addressable (~> 2.4)
base64 (~> 0.2)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
json (~> 2.6)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (~> 0.3, >= 0.3.6)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-archives (2.3.0)
jekyll (>= 3.6, < 5.0)
jekyll-data (1.1.1)
jekyll (>= 3.3, < 5.0.0)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-gist (1.5.0)
octokit (~> 4.2)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-paginate (1.1.0)
jekyll-sass-converter (3.1.0)
sass-embedded (~> 1.75)
jekyll-scholar (7.1.3)
bibtex-ruby (~> 6.0)
citeproc-ruby (~> 1.0)
csl-styles (~> 1.0)
jekyll (~> 4.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
jekyll-webp (1.0.0)
jemoji (0.13.0)
gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
json (2.10.2)
kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
latex-decode (0.4.0)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.6)
mercenary (0.4.0)
minimal-mistakes-jekyll (4.26.2)
jekyll (>= 3.7, < 5.0)
jekyll-feed (~> 0.1)
jekyll-gist (~> 1.5)
jekyll-include-cache (~> 0.1)
jekyll-paginate (~> 1.1)
jekyll-sitemap (~> 1.3)
minitest (5.25.5)
namae (1.2.0)
racc (~> 1.7)
net-http (0.6.0)
uri
nokogiri (1.18.5-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.5-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.18.5-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.5-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.18.5-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.5-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.5-x86_64-linux-musl)
racc (~> 1.4)
observer (0.1.2)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open-uri (0.5.0)
stringio
time
uri
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (6.0.1)
racc (1.8.1)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.4.1)
rouge (4.5.1)
safe_yaml (1.0.5)
sass-embedded (1.86.0-aarch64-linux-gnu)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-aarch64-linux-musl)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-arm-linux-gnueabihf)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-arm-linux-musleabihf)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-arm64-darwin)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-x86_64-darwin)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-x86_64-linux-gnu)
google-protobuf (~> 4.30)
sass-embedded (1.86.0-x86_64-linux-musl)
google-protobuf (~> 4.30)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
securerandom (0.4.1)
stringio (3.1.5)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
time (0.4.1)
date
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.6.0)
uri (1.0.3)
webrick (1.9.1)
PLATFORMS
aarch64-linux
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-gnueabihf
arm-linux-musl
arm-linux-musleabihf
arm64-darwin
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
http_parser.rb (~> 0.6.0)
jekyll
jekyll-archives
jekyll-data
jekyll-include-cache
jekyll-scholar
jekyll-webp
jemoji
json
minimal-mistakes-jekyll
tzinfo (>= 1, < 3)
tzinfo-data
wdm (~> 0.1.1)
BUNDLED WITH
2.6.6

View File

@@ -1,12 +1,27 @@
---
title: "Aquarium MARL Environment"
tags: [multi-agent-reinforcement-learning, MARL, simulation, emergence, complex-systems]
tags: [MARL, simulation, emergence, complex-systems, environment]
excerpt: "Aquarium: Open-source MARL environment for predator-prey studies."
teaser: /figures/20_aquarium.png
---
<FloatingImage
src="/figures/20_aquarium.png"
alt="The Multi-Agent Reinforcement Learning Cycle. Plot showing how Agent receive individual rewards from the environment."
width={450}
height={350}
caption="The Multi-Agent Reinforcement Learning Cycle."
/>
The study of complex interactions using Multi-Agent Reinforcement Learning (MARL), particularly **predator-prey dynamics**, often requires specialized simulation environments. To streamline research and avoid redundant development efforts, we introduce **Aquarium**: a versatile, open-source MARL environment specifically designed for investigating predator-prey scenarios and related **emergent behaviors**.
<CenteredImage
src="/figures/20_observation_vector.png"
alt="Diagram detailing the construction of the observation vector for an agent"
width={450}
height={350}
maxWidth="75%"
caption="Construction details of the agent observation vector."
/>
Key Features of Aquarium:
@@ -17,15 +32,13 @@ Key Features of Aquarium:
* **Environmental Parameters:** Key dynamics like agent speeds, prey reproduction rates, predator starvation mechanisms, sensor ranges, and more are fully adjustable.
* **Visualization & Recording:** Includes a resource-efficient visualizer and supports video recording of simulation runs, facilitating qualitative analysis and understanding of agent behaviors.
<div className="my-6 grid grid-cols-1 sm:grid-cols-2 gap-6 items-start">
<div className="text-center">
<Image src="/figures/20_observation_vector.png" alt="Diagram detailing the construction of the observation vector for an agent" width={450} height={350} />
<figcaption className="text-sm text-muted-foreground mt-2">Construction details of the agent observation vector.</figcaption>
</div>
<div className="text-center">
<Image src="/figures/20_capture_statistics.png" alt="Graphs showing average captures or rewards per prey agent under different training regimes" width={450} height={350} />
<figcaption className="text-sm text-muted-foreground mt-2">Performance metrics (e.g., average captures/rewards) comparing training strategies.</figcaption>
</div>
</div>
<CenteredImage
src="/figures/20_capture_statistics.png"
alt="Graphs showing average captures or rewards per prey agent under different training regimes"
width={450}
height={350}
maxWidth="75%"
caption="Performance metrics (e.g., average captures/rewards) comparing training strategies."
/>
To demonstrate its capabilities, we conducted preliminary studies using **Proximal Policy Optimization (PPO)** to train multiple prey agents learning to evade a predator within Aquarium. Consistent with findings in existing MARL literature, our results showed that training agents with **individual policies led to suboptimal performance**, whereas utilizing **parameter sharing** among prey agents significantly improved coordination, sample efficiency, and overall evasion success. <Cite bibtexKey="kolle2024aquarium" />
To demonstrate its capabilities, we conducted preliminary studies using **Proximal Policy Optimization (PPO)** to train multiple prey agents learning to evade a predator within Aquarium. Consistent with findings in existing MARL literature, our results showed that training agents with **individual policies led to suboptimal performance**, whereas utilizing **parameter sharing** among prey agents significantly improved coordination, sample efficiency, and overall evasion success. <Cite bibtexKey="kolle2024aquarium" />

71
src/app/connect/page.tsx Normal file
View File

@@ -0,0 +1,71 @@
import { DATA } from "@/data/resume";
import Image from "next/image";
import Link from "next/link";
import BlurFade from "@/components/magicui/blur-fade";
import { Button } from "@/components/ui/button";
import { CenteredImage } from "@/components/centered-image";
const BLUR_FADE_DELAY = 0.05;
export default function ConnectPage() {
const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"];
const socialLinks = Object.entries(DATA.contact.social)
.filter(([name]) => featuredSocials.includes(name));
return (
<main
className="fixed inset-0 flex flex-col items-center justify-center bg-background"
>
<div className="flex flex-col items-center space-y-8 text-center max-w-sm w-full p-6">
<BlurFade delay={BLUR_FADE_DELAY * 1}>
<Image
src="/images/newshot_2.jpg"
alt="Dr. Steffen Illium's headshot"
width={128}
height={128}
className="rounded-full border shadow-sm"
/>
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 2}>
<h1 className="text-4xl font-bold tracking-tight">
Dr. Steffen Illium
</h1>
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 3}>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 w-full">
{socialLinks.map(([name, social]) => (
<Link href={social.url} key={name} target="_blank">
<Button variant="outline" className="w-full">
<social.icon className="size-4 mr-2" />
{name}
</Button>
</Link>
))}
</div>
</BlurFade>
<div className="flex w-full flex-col items-center space-y-4 pb-8">
<BlurFade delay={BLUR_FADE_DELAY * 4} className="w-full px-8">
<hr className="w-full" />
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 5}>
<a href="/images/qr.png" download="SteffenIllium-QRCode.png">
<Image
src="/images/qr.png"
alt="QR Code to connect"
width={256}
height={256}
className="rounded-xl shadow-lg hover:opacity-80 transition-opacity"
/>
</a>
</BlurFade>
</div>
</div>
</main>
);
}

View File

@@ -8,7 +8,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { DATA } from "@/data/resume";
import Markdown from "react-markdown";
import {ContactCard} from "@/components/contact-card";
//import {ContactCard} from "@/components/contact-card";
import {Button} from "@/components/ui/button";
import Link from "next/link";
@@ -170,34 +170,34 @@ export default function Page() {
</div>
</section>
<section id="contact">
<div className="grid items-center justify-center gap-4 px-4 text-center md:px-6 w-full py-12">
<BlurFade delay={BLUR_FADE_DELAY * 16}>
<div className="space-y-3">
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
Contact
</div>
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Get in Touch</h2>
<p className="mx-auto max-w-[800px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
Want to collaborate or have a question?<br/>I&apos;d love to hear from you.
</p>
<div className="grid items-center justify-center gap-4 px-4 text-center md:px-6 w-full py-12">
<BlurFade delay={BLUR_FADE_DELAY * 16}>
<div className="space-y-3">
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
Contact
</div>
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 18}>
<div className="flex flex-wrap gap-2 justify-center">
{Object.entries(DATA.contact.social)
.filter(([_, social]) => social.navbar)
.map(([name, social]) => (
<Link href={social.url} key={name} target="_blank">
<Badge className="flex gap-2 px-3 py-1 text-sm">
<social.icon className="size-4" />
{name}
</Badge>
</Link>
))}
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Get in Touch</h2>
<p className="mx-auto max-w-[800px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
Want to collaborate or have a question?<br/>I&apos;d love to hear from you.
</p>
</div>
</BlurFade>
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 18}>
<div className="flex flex-wrap gap-2 justify-center">
{Object.entries(DATA.contact.social)
.filter(([_, social]) => social.navbar)
.map(([name, social]) => (
<Link href={social.url} key={name} target="_blank">
<Badge className="flex gap-2 px-3 py-1 text-sm">
<social.icon className="size-4" />
{name}
</Badge>
</Link>
))}
</div>
</section>
</BlurFade>
</div>
</section>
</main>
);
}

View File

@@ -3,10 +3,11 @@
import { CitationProvider } from '@/context/citation-context';
import { ReferencesContainer } from '@/components/references-container';
import { CustomMDX } from '@/components/custom-mdx';
import { Breadcrumbs } from "./breadcrumbs";
import Link from 'next/link';
import { Publication } from '@/lib/publications';
import { ProjectNavigation } from './project-navigation';
// --- FIX: Import the Image component ---
import { Tag } from 'lucide-react';
import Image from 'next/image';
interface Post {
@@ -16,8 +17,8 @@ interface Post {
excerpt?: string;
teaser?: string;
tags?: string[];
// --- FIX: Add the optional 'icon' property to the interface ---
icon?: string;
date?: string;
};
}
@@ -32,45 +33,70 @@ interface ArticleProps {
export function Article({ post, publications, navigation, basePath }: ArticleProps) {
return (
<CitationProvider publications={publications}>
<main className="flex flex-col min-h-[100dvh] py-8">
<article className="prose prose-stone dark:prose-invert max-w-none">
<header className="mb-8">
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl mb-2">{post.frontmatter.title}</h1>
{post.frontmatter.excerpt && (<p className="text-lg text-muted-foreground mt-1">{post.frontmatter.excerpt}</p>)}
</header>
{post.frontmatter.icon && (
<div className="float-left mr-4 mb-0">
<Image
src={post.frontmatter.icon}
alt={`${post.frontmatter.title} icon`}
width={64}
height={64}
className=""
<main className="flex flex-col min-h-[100dvh] space-y-10">
<section id="article">
<div className="mx-auto w-full max-w-4xl space-y-8">
<div className="flex flex-col gap-y-2 sm:flex-row sm:justify-between sm:items-center">
<Breadcrumbs
basePath={basePath}
baseLabel={basePath.charAt(0).toUpperCase() + basePath.slice(1)}
/>
{/* MOVED: The date is now on the right side of the container */}
{post.frontmatter.date && (
<time className="text-sm text-muted-foreground" dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
)}
</div>
)}
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none">
{post.frontmatter.title}
</h1>
{post.frontmatter.excerpt && (
<p className="text-muted-foreground">
{post.frontmatter.excerpt}
</p>
)}
</div>
<article className="prose prose-stone dark:prose-invert max-w-none">
{post.frontmatter.icon && (
<div className="float-left mr-4 mb-2">
<Image
src={post.frontmatter.icon}
alt={`${post.frontmatter.title} icon`}
width={64}
height={64}
className="rounded-full"
/>
</div>
)}
<CustomMDX code={post.code} />
</article>
<div className="mt-8">
<CustomMDX code={post.code} />
{/* Tags, References, and Navigation now live outside the `prose` scope for better control */}
{post.frontmatter.tags && (
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
<Tag className="size-4 text-muted-foreground" />
{post.frontmatter.tags.map((tag: string) => (
<Link key={tag} href={`/tags/${tag}`} className="px-3 py-1 text-sm font-medium bg-secondary text-secondary-foreground transition-colors hover:bg-primary hover:text-primary-foreground">
{tag}
</Link>
))}
</div>
)}
<ReferencesContainer />
{navigation && (
<ProjectNavigation prev={navigation.prev} next={navigation.next} basePath={basePath} />
)}
</div>
{post.frontmatter.tags && (
<div className="flex flex-wrap gap-2 mt-8">
{post.frontmatter.tags.map((tag: string) => (
<Link key={tag} href={`/tags/${tag}`} className="px-3 py-1 text-sm font-medium bg-secondary text-secondary-foreground transition-colors hover:bg-primary hover:text-primary-foreground">
{tag}
</Link>
))}
</div>
)}
<ReferencesContainer />
{navigation && (
<ProjectNavigation prev={navigation.prev} next={navigation.next} basePath={basePath} />
)}
</article>
</section>
</main>
</CitationProvider>
);

View File

@@ -0,0 +1,21 @@
import { ChevronRight, HomeIcon } from "lucide-react";
import Link from "next/link";
interface BreadcrumbsProps {
basePath: string;
baseLabel: string;
}
export function Breadcrumbs({ basePath, baseLabel }: BreadcrumbsProps) {
return (
<nav className="flex items-center space-x-2 text-sm text-muted-foreground">
<Link href="/" className="hover:text-primary transition-colors">
<HomeIcon className="size-4" />
</Link>
<ChevronRight className="size-4" />
<Link href={`/${basePath}`} className="hover:text-primary transition-colors">
{baseLabel}
</Link>
</nav>
);
}

View File

@@ -0,0 +1,51 @@
"use client";
import Image from "next/image";
import { cn } from "@/lib/utils";
interface CenteredImageProps {
src: string;
alt: string;
width: number;
height: number;
caption?: string;
// New prop to control the max width as a percentage
maxWidth?: "50%" | "75%" | "100%";
}
export function CenteredImage({
src,
alt,
width,
height,
caption,
maxWidth = "100%", // Default to 100% of the column width
}: CenteredImageProps) {
// Map the user-friendly prop to actual TailwindCSS classes
const widthClasses = {
"50%": "sm:max-w-[50%]",
"75%": "sm:max-w-[75%]",
"100%": "max-w-full", // `max-w-full` is the default for responsive images
};
return (
// Use <figure> for the image and its caption
// `not-prose` escapes Tailwind Typography styles
// `mx-auto` is the key to centering the block
<figure className={cn("not-prose mx-auto my-6", widthClasses[maxWidth])}>
<Image
src={src}
alt={alt}
width={width}
height={height}
// `mx-auto` on the image itself helps in some flex contexts
className="mx-auto rounded-lg"
/>
{caption && (
<figcaption className="mt-2 text-sm text-center italic text-muted-foreground">
{caption}
</figcaption>
)}
</figure>
);
}

View File

@@ -8,6 +8,7 @@ import { usePathname } from "next/navigation";
export function Header() {
const [isVisible, setIsVisible] = useState(false);
const pathname = usePathname();
if (pathname === "/connect") return null;
const isMainPage = pathname === "/";
useEffect(() => {

View File

@@ -6,6 +6,7 @@ import React from "react";
import { InfoBox } from "./infobox";
import { Cite } from "./cite";
import { FloatingImage } from "./floating-image";
import { CenteredImage } from "./centered-image";
function CustomLink(props: any) {
@@ -77,4 +78,5 @@ export const mdxComponents = {
InfoBox,
Cite,
FloatingImage: FloatingImage,
CenteredImage: CenteredImage,
};

View File

@@ -40,7 +40,7 @@ export function ReferencesContainer() {
year={pub.year}
pdfUrl={pub.pdfUrl}
bibtex={pub.bibtex}
// The pdfAvailable prop is now safely omitted
pdfAvailable={pub.pdfAvailable}
/>
);
})}

View File

@@ -40,13 +40,16 @@ export async function getPostBySlug(type: 'projects' | 'research' | 'teaching',
const fullPath = path.join(typeDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const dateMatch = fileName.match(/^\d{4}-\d{2}-\d{2}/);
const dateFromFilename = dateMatch ? dateMatch[0] : new Date().toISOString();
const { code, frontmatter } = await bundleMDX({
source: fileContents,
});
return {
slug,
frontmatter: frontmatter as Frontmatter,
frontmatter: { ...frontmatter, date: dateFromFilename } as Frontmatter,
code,
};
}

View File

@@ -2,8 +2,10 @@ import { parse } from "@retorquere/bibtex-parser";
import fs from "fs";
import path from "path";
// Your existing path is correct.
const bibliographyPath = path.join(process.cwd(), "_bibliography.bib");
// --- FIX: Add `pdfAvailable` to the Publication interface ---
export interface Publication {
key: string;
title: string;
@@ -12,6 +14,8 @@ export interface Publication {
year: string;
bibtex: string;
pdfUrl: string;
url?: string; // Also make url optional to be safe
pdfAvailable?: boolean; // This will hold the result of our check
}
export function getPublicationsData(): Publication[] {
@@ -25,7 +29,6 @@ export function getPublicationsData(): Publication[] {
)
: [];
// A simple way to reconstruct the bibtex string for a single entry
let bibtexEntryString = `@${entry.type}{${entry.key},\n`;
for (const [key, value] of Object.entries(entry.fields)) {
if (key === 'author') {
@@ -42,8 +45,10 @@ export function getPublicationsData(): Publication[] {
}
bibtexEntryString += `}`
const journalField = entry.fields.booktitle || entry.fields.journal;
const pdfPath = path.join(process.cwd(), "public", "publications", `${entry.key}.pdf`);
const pdfExists = fs.existsSync(pdfPath);
return {
key: entry.key,
@@ -54,6 +59,7 @@ export function getPublicationsData(): Publication[] {
url: Array.isArray(entry.fields.url) ? entry.fields.url.join(" ") : entry.fields.url,
bibtex: bibtexEntryString,
pdfUrl: `/publications/${entry.key}.pdf`,
pdfAvailable: pdfExists, // <-- Add the result here
};
});
}
}