diff --git a/content/projects/2020-05-01-FIKS.mdx b/content/projects/2020-05-01-FIKS.mdx index 9fe27df2..cca7e9e9 100644 --- a/content/projects/2020-05-01-FIKS.mdx +++ b/content/projects/2020-05-01-FIKS.mdx @@ -8,35 +8,36 @@ role: Researcher, Software Developer skills: Multi-Agent Reinforcement Learning (MARL), Emergence Analysis, AI Safety, Simulation Environment Design, Python, Gymnasium API, Software Engineering, Unity (Visualization), Industry Collaboration --- -
- +{/* The InfoBox now contains ALL metadata, creating a clean sidebar. */} + + {/* Section 2: Project Info */} +

Overview

+

+ Project: AI-Fusion
+ Partner: Fraunhofer IKS
+ Duration: 2022 - 2023 +

+ + logo +
+
+
+ {/* Section 1: Resources */} +

Resources

- logo -
-
- - In collaboration with Fraunhofer IKS, the AI-Fusion project addressed the critical challenge of understanding and ensuring safety in multi-agent reinforcement learning (MARL) systems. Emergence, defined as the arising of complex, often unpredictable, system-level dynamics from local interactions between agents and their environment, was a central focus due to its implications for system safety and reliability. - -
+ - **Project:** AI-Fusion
- **Partner:** [Fraunhofer Institute for Cognitive Systems (IKS)](https://www.iks.fraunhofer.de/)
- **Duration:** 2022 - 2023
- **Objective:** To investigate the detection and mitigation of potentially unsafe emergent behaviors in complex systems composed of multiple interacting AI agents, particularly in scenarios involving heterogeneous agents (e.g., mixed-vendor autonomous systems). -
-
- ---- +{/* All the main content now flows naturally in a single column. */} +In collaboration with Fraunhofer IKS, the AI-Fusion project addressed the critical challenge of understanding and ensuring safety in multi-agent reinforcement learning (MARL) systems. Emergence, defined as the arising of complex, often unpredictable, system-level dynamics from local interactions between agents and their environment, was a central focus due to its implications for system safety and reliability. The project's objective was to investigate the detection and mitigation of potentially unsafe emergent behaviors in complex systems composed of multiple interacting AI agents, particularly in scenarios involving heterogeneous agents (e.g., mixed-vendor autonomous systems). To facilitate research into these phenomena, key contributions included the development of specialized simulation tools: **1. High-Performance MARL Simulation Environment:** - * A flexible and efficient simulation environment was developed in Python, adhering to the [Gymnasium (formerly Gym) API specification](https://gymnasium.farama.org/main/). * **Purpose:** Designed specifically for training and evaluating reinforcement learning algorithms in multi-agent contexts prone to emergent behaviors. * **Features:** @@ -45,16 +46,13 @@ To facilitate research into these phenomena, key contributions included the deve * **Performance:** Optimized for efficient simulation runs, enabling extensive experimentation. **2. Unity-Based Demonstrator Unit:** - * A complementary visualization tool was created using the Unity engine. * **Purpose:** Allows for the replay, inspection, and detailed analysis of specific simulation scenarios and agent interactions. * **Utility:** Aids researchers in identifying and understanding the mechanisms behind observed emergent dynamics. * [View Demonstrator on GitHub](https://github.com/illiumst/F-IKS_demonstrator) -
-
- Diagram illustrating the concept of emergence from interactions between agents and environment + Diagram illustrating the concept of emergence from interactions between agents and environment
Conceptual relationship defining emergence in multi-agent systems.
diff --git a/src/app/globals.css b/src/app/globals.css index 5b81a858..47198111 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -141,4 +141,14 @@ code[data-line-numbers-max-digits="2"] > [data-line]::before { code[data-line-numbers-max-digits="3"] > [data-line]::before { width: 3rem; +} + +.prose a { + /* Use the CSS variable we defined in the component */ + color: var(--accent-color); +} + +.prose a:hover { + /* Optional: slightly darken or lighten the color on hover for better feedback */ + opacity: 0.8; } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f2a8c361..6186d2ef 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,6 +7,7 @@ import { cn } from "@/lib/utils"; import type { Metadata } from "next"; import { Inter as FontSans } from "next/font/google"; import "./globals.css"; +import { AccentColorProvider } from "@/context/accent-color-context"; const fontSans = FontSans({ subsets: ["latin"], @@ -64,11 +65,13 @@ export default function RootLayout({ > - -
- {children} - - + + +
+ {children} + + + diff --git a/src/app/projects/[slug]/page.tsx b/src/app/projects/[slug]/page.tsx index c7c72547..3c8c3929 100644 --- a/src/app/projects/[slug]/page.tsx +++ b/src/app/projects/[slug]/page.tsx @@ -1,12 +1,12 @@ -import { getPostBySlug, getPostSlugs } from '@/lib/mdx'; -import { CitationProvider } from '@/context/citation-context'; -import { ReferencesContainer } from '@/components/references-container'; +import { getPostBySlug, getPostSlugs, getPostFrontmatter } from '@/lib/mdx'; // Assume getPostFrontmatter exists import { notFound } from 'next/navigation'; -import Image from 'next/image'; import { DATA } from '@/data/resume'; import { getPublicationsData } from '@/lib/publications'; -import { CustomMDX } from '@/components/custom-mdx'; -import Link from 'next/link'; +import { ProjectArticle } from '@/components/project-article'; + +// Helper function to get frontmatter for a slug (you may need to create this in lib/mdx.ts) +// It's a lighter version of getPostBySlug that only parses frontmatter. +// If you don't have it, we can just use getPostBySlug, it's just slightly less performant. export async function generateStaticParams() { const slugs = getPostSlugs('projects'); @@ -15,9 +15,7 @@ export async function generateStaticParams() { export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await getPostBySlug('projects', params.slug); - if (!post) { - return {}; - } + if (!post) { return {}; } return { title: post.frontmatter.title, description: post.frontmatter.description || DATA.description, @@ -25,76 +23,28 @@ export async function generateMetadata({ params }: { params: { slug: string } }) } export default async function ProjectPage({ params }: { params: { slug: string } }) { - const post = await getPostBySlug('projects', params.slug); + const { slug } = params; + const post = await getPostBySlug('projects', slug); const publications = getPublicationsData(); if (!post) { - return notFound(); + notFound(); } - return ( - -
-
-
-

- {post.frontmatter.title} -

- {post.frontmatter.excerpt && ( -

{post.frontmatter.excerpt}

- )} -
+ // --- Logic for Previous/Next Navigation --- + const allSlugs = getPostSlugs('projects'); // Or get them from your DATA file if they are ordered there + const currentIndex = allSlugs.findIndex((s) => s === slug); - {post.frontmatter.show_teaser && post.frontmatter.teaser && ( -
- {`${post.frontmatter.title} -
- )} + const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; + const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; - {post.frontmatter.tags && ( -
- {post.frontmatter.tags.map((tag: string) => ( - - {tag} - - ))} -
- )} - -
- {post.frontmatter.icon ? ( - // If there is an icon, render it and then the content. - // The parent div with `flow-root` contains the float correctly. -
- {`${post.frontmatter.title} - -
- ) : ( - // If there is NO icon, wrap the content in a div for the drop-cap effect. -
- -
- )} -
- -
-
-
- ); -} + const prevPost = prevSlug ? await getPostBySlug('projects', prevSlug) : null; + const nextPost = nextSlug ? await getPostBySlug('projects', nextSlug) : null; + + const navigation = { + prev: prevPost ? { slug: prevSlug, title: prevPost.frontmatter.title } : null, + next: nextPost ? { slug: nextSlug, title: nextPost.frontmatter.title } : null, + }; + + return ; +} \ No newline at end of file diff --git a/src/app/publications/page.tsx b/src/app/publications/page.tsx index f8302837..cf4679dc 100644 --- a/src/app/publications/page.tsx +++ b/src/app/publications/page.tsx @@ -50,13 +50,15 @@ export default function PublicationsPage() { ))} -
+
{/* Increased spacing between year sections */} {years.map((year) => ( -
-

- {year} -

-
+
+
+

+ {year} +

+
+
{/* Main content column */} {publicationsByYear[year].map((pub) => ( -

{title}

-
{children}
+

{title}

+
{children}
); -} +} \ No newline at end of file diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx index b70534c6..1914c23a 100644 --- a/src/components/mdx.tsx +++ b/src/components/mdx.tsx @@ -74,3 +74,15 @@ export const mdxComponents = { InfoBox, Cite, }; + +const serverSafeComponents = { + // KEEP components that DO NOT use hooks + h1: mdxComponents.h1, + h2: mdxComponents.h2, + h3: mdxComponents.h3, + h4: mdxComponents.h4, + h5: mdxComponents.h5, + h6: mdxComponents.h6, + Image: mdxComponents.Image, + a: mdxComponents.a, +}; \ No newline at end of file diff --git a/src/components/project-article.tsx b/src/components/project-article.tsx new file mode 100644 index 00000000..1d8f05a4 --- /dev/null +++ b/src/components/project-article.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { CitationProvider } from '@/context/citation-context'; +import { ReferencesContainer } from '@/components/references-container'; +import Image from 'next/image'; +import { CustomMDX } from '@/components/custom-mdx'; +import Link from 'next/link'; +import { Publication } from '@/lib/publications'; +import { useAccentColor } from '@/context/accent-color-context'; +import { ProjectNavigation } from './project-navigation'; + +// Define the types for our props +interface Post { + source: any; + frontmatter: { + title: string; + excerpt?: string; + show_teaser?: boolean; + teaser?: string; + tags?: string[]; + icon?: string; + }; +} + +interface NavigationLink { + slug: string; + title: string; +} + +interface ProjectArticleProps { + post: Post; + publications: Publication[]; + navigation: { + prev: NavigationLink | null; + next: NavigationLink | null; + }; + basePath: string; +} + +export function ProjectArticle({ post, publications, navigation, basePath }: ProjectArticleProps) { + const accentColor = useAccentColor(); + + return ( + +
+
+
+

+ {post.frontmatter.title} +

+ {post.frontmatter.excerpt && ( +

{post.frontmatter.excerpt}

+ )} +
+ + {post.frontmatter.show_teaser && post.frontmatter.teaser && ( +
+ {`${post.frontmatter.title} +
+ )} + + {post.frontmatter.tags && ( +
+ {post.frontmatter.tags.map((tag: string) => ( + e.currentTarget.style.backgroundColor = accentColor} + onMouseOut={(e) => e.currentTarget.style.backgroundColor = ''} + > + {tag} + + ))} +
+ )} + +
+ {post.frontmatter.icon ? ( +
+ {`${post.frontmatter.title} + +
+ ) : ( +
+ +
+ )} +
+ + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/project-navigation.tsx b/src/components/project-navigation.tsx new file mode 100644 index 00000000..965091fc --- /dev/null +++ b/src/components/project-navigation.tsx @@ -0,0 +1,57 @@ +"use client"; + +import Link from 'next/link'; +import { ArrowLeft, ArrowRight } from 'lucide-react'; +import { useAccentColor } from '@/context/accent-color-context'; + +interface NavLink { + slug: string; + title: string; +} + +interface ProjectNavigationProps { + prev?: NavLink | null; + next?: NavLink | null; + // --- FIX #1: Added basePath prop for reusability --- + basePath: string; +} + +export function ProjectNavigation({ prev, next, basePath }: ProjectNavigationProps) { + const accentColor = useAccentColor(); + + if (!prev && !next) return null; + + return ( + // --- FIX #2: Added 'not-prose' to remove unwanted link styles --- +
+
+ {prev && ( + // Use the dynamic basePath + +
+ + Previous +
+
+ {prev.title} +
+ + )} +
+
+ {next && ( + // Use the dynamic basePath + +
+ Next + +
+
+ {next.title} +
+ + )} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/publication-card.tsx b/src/components/publication-card.tsx index 12796db5..236ff9bc 100644 --- a/src/components/publication-card.tsx +++ b/src/components/publication-card.tsx @@ -1,11 +1,11 @@ "use client"; -import { Card, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { FileText, Copy, Download, Paperclip } from "lucide-react"; +import { FileText, Copy, Download, Paperclip, BookOpen, Check, X } from "lucide-react"; import { useState, useEffect } from "react"; import { cn } from "@/lib/utils"; import Image from "next/image"; -import Link from "next/link"; // Import the Link component +import Link from "next/link"; +import { CardTitle } from "@/components/ui/card"; interface Props { bibtexKey: string; @@ -17,7 +17,8 @@ interface Props { pdfUrl?: string; bibtex: string; className?: string; - pdfAvailable: boolean; + // --- FIX IS HERE: Made pdfAvailable optional --- + pdfAvailable?: boolean; } export function PublicationCard({ @@ -30,9 +31,10 @@ export function PublicationCard({ pdfUrl, bibtex, className, - pdfAvailable, + // --- FIX IS HERE: Default pdfAvailable to false if not provided --- + pdfAvailable = false, }: Props) { - const [copied, setCopied] = useState(false); + const [copyStatus, setCopyStatus] = useState<"idle" | "success" | "error">("idle"); const [imageError, setImageError] = useState(false); const [downloadUrl, setDownloadUrl] = useState(null); const [isClient, setIsClient] = useState(false); @@ -44,9 +46,16 @@ export function PublicationCard({ e.stopPropagation(); if (isClient && navigator.clipboard?.writeText) { navigator.clipboard.writeText(bibtex).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }).catch(err => console.error("Failed to copy BibTeX:", err)); + setCopyStatus("success"); + setTimeout(() => setCopyStatus("idle"), 2000); + }).catch(err => { + console.error("Failed to copy BibTeX:", err); + setCopyStatus("error"); + setTimeout(() => setCopyStatus("idle"), 2000); + }); + } else { + setCopyStatus("error"); + setTimeout(() => setCopyStatus("idle"), 2000); } }; @@ -69,7 +78,7 @@ export function PublicationCard({ const ImageContent = ( <> {!imageError ? ( - {`Preview setImageError(true)} sizes="(max-width: 768px) 100vw, 33vw" /> + {`Preview setImageError(true)} sizes="96px" /> ) : (
@@ -79,42 +88,47 @@ export function PublicationCard({ ); return ( -
- {url ? ( - - {ImageContent} - - ) : ( -
{ImageContent}
- )} -
-
- - {url ? ( - - {title} - - ) : ( - title - )} - -

- {formattedAuthors}. {journal}. {year} -

-
-
- - - {pdfUrl && pdfAvailable && ( - +
+
+ {url ? ( + + {ImageContent} + + ) : ( + ImageContent + )} +
+
+ + {url ? ( + + {title} + + ) : ( + title )} -
+ +

+ {formattedAuthors}. {journal}. {year} +

+
+
+ + + {pdfUrl && pdfAvailable && ( + + )}
); -} +} \ No newline at end of file diff --git a/src/components/references-container.tsx b/src/components/references-container.tsx index 0c16b2d8..051ddcff 100644 --- a/src/components/references-container.tsx +++ b/src/components/references-container.tsx @@ -2,11 +2,11 @@ import React, { useEffect, useState } from 'react'; import { useCitations } from '@/context/citation-context'; -import { PublicationCard } from './publication-card'; // Changed import +import { PublicationCard } from './publication-card'; import { BookOpen } from 'lucide-react'; export function ReferencesContainer() { - const { citedKeys, getPublicationByKey } = useCitations(); // Added getPublicationByKey + const { citedKeys, getPublicationByKey } = useCitations(); const [isClient, setIsClient] = useState(false); useEffect(() => { @@ -20,12 +20,13 @@ export function ReferencesContainer() { const sortedKeys = Array.from(citedKeys).sort(); return ( -
{/* Adjusted whitespace */} +

References

-
+ {/* --- FIX IS IN THE LINE BELOW: Added not-prose --- */} +
{sortedKeys.map(key => { const pub = getPublicationByKey(key); if (!pub) return null; @@ -39,10 +40,11 @@ export function ReferencesContainer() { year={pub.year} pdfUrl={pub.pdfUrl} bibtex={pub.bibtex} + // The pdfAvailable prop is now safely omitted /> ); })}
); -} +} \ No newline at end of file diff --git a/src/context/accent-color-context.tsx b/src/context/accent-color-context.tsx new file mode 100644 index 00000000..1e5a8456 --- /dev/null +++ b/src/context/accent-color-context.tsx @@ -0,0 +1,35 @@ +"use client"; + +import React, { createContext, useContext, useMemo } from 'react'; + +// A curated palette of visually appealing accent colors from Tailwind's palette +const ACCENT_COLORS = [ + '#f43f5e', // rose-500 + '#3b82f6', // blue-500 + '#16a34a', // green-600 + '#8b5cf6', // violet-500 + '#f97316', // orange-500 + '#06b6d4', // cyan-500 + '#d946ef', // fuchsia-500 +]; + +const AccentColorContext = createContext(ACCENT_COLORS[0]); + +export function AccentColorProvider({ children }: { children: React.ReactNode }) { + const accentColor = useMemo(() => { + // This calculation ensures the color is stable for the duration + const interval = 180 * 60 * 1000; // 180 minutes in milliseconds + const timeBucket = Math.floor(Date.now() / interval); + return ACCENT_COLORS[timeBucket % ACCENT_COLORS.length]; + }, []); // Empty dependency array means this runs only once on mount + + return ( + + {children} + + ); +} + +export function useAccentColor() { + return useContext(AccentColorContext); +} \ No newline at end of file