Merge branch 'articles'
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Arthur Danjou
|
||||
Copyright (c) 2025 Arthur Danjou
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
150
README.md
@@ -18,14 +18,9 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
- [Features](#-features)
|
||||
- [Tech Stack](#️-tech-stack)
|
||||
- [Project Structure](#-project-structure)
|
||||
- [Development](#-development)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Configuration](#configuration)
|
||||
- [Running Locally](#running-locally)
|
||||
- [Deployment](#deployment)
|
||||
- [Adding Content](#-adding-content)
|
||||
- [Portfolio Projects](#portfolio-projects)
|
||||
- [Projects](#projects)
|
||||
- [Writings](#writings)
|
||||
- [Uses Page](#uses-page)
|
||||
- [Integrations](#-integrations)
|
||||
- [License](#-license)
|
||||
@@ -54,6 +49,11 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/)
|
||||
- **Package Manager** → [pnpm](https://pnpm.io/)
|
||||
- **Internationalization** → [Nuxt i18n](https://i18n.nuxtjs.org/)
|
||||
- **Database ORM** → [Drizzle](https://orm.drizzle.team/)
|
||||
- **Composables** → [VueUse](https://vueuse.org/)
|
||||
- **Validation** → [Zod](https://zod.dev/)
|
||||
- **Globe Visualization** → [Cobe](https://github.com/shuding/cobe)
|
||||
- **Icons** → [Iconify](https://iconify.design/)
|
||||
|
||||
## 📂 Project Structure
|
||||
|
||||
@@ -61,12 +61,14 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
├── assets/ # Static assets like global styles
|
||||
├── components/ # Vue components
|
||||
├── content/ # Markdown content for the portfolio
|
||||
│ ├── portfolio/ # Portfolio projects
|
||||
│ ├── projects/ # Portfolio projects
|
||||
│ ├── writings/ # Writings
|
||||
│ └── uses/ # Uses page items
|
||||
├── layouts/ # Page layouts
|
||||
├── pages/ # Application pages
|
||||
├── public/ # Public static files
|
||||
│ └── portfolio/ # Portfolio images
|
||||
│ ├── projects/ # Projects images
|
||||
│ └── writings/ # Writings images
|
||||
├── server/ # Server API routes
|
||||
├── utils/ # Utility functions
|
||||
├── .env.example # Example environment variables
|
||||
@@ -75,88 +77,24 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
## 🚀 Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) (v16 or later)
|
||||
- [pnpm](https://pnpm.io/) (v7 or later)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/yourusername/portfolio-2024.git
|
||||
cd portfolio-2024
|
||||
|
||||
# Install dependencies (with hoisting for Nuxt 3 compatibility)
|
||||
pnpm i --shamefully-hoist
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a `.env` file in the root directory with the following variables:
|
||||
|
||||
```env
|
||||
# WakaTime Integration
|
||||
NUXT_WAKATIME_USER_ID=your_wakatime_user_id
|
||||
NUXT_WAKATIME_CODING=your_wakatime_coding_endpoint
|
||||
NUXT_WAKATIME_LANGUAGES=your_wakatime_languages_endpoint
|
||||
NUXT_WAKATIME_OS=your_wakatime_os_endpoint
|
||||
NUXT_WAKATIME_EDITORS=your_wakatime_editors_endpoint
|
||||
|
||||
# SEO
|
||||
NUXT_PUBLIC_SITE_URL=https://your-domain.com
|
||||
|
||||
# Nuxt Hub Deployment
|
||||
NUXT_HUB_PROJECT_KEY=your_nuxt_hub_project_key
|
||||
|
||||
# Discord Integration
|
||||
NUXT_DISCORD_ID=your_discord_app_id
|
||||
NUXT_DISCORD_TOKEN=your_discord_token
|
||||
NUXT_DISCORD_USER_ID=your_discord_user_id
|
||||
|
||||
# Cloud Files
|
||||
NUXT_PUBLIC_CLOUD_RESUME=https://link-to-your-resume.pdf
|
||||
|
||||
# Internationalization
|
||||
NUXT_PUBLIC_I18N_BASE_URL=https://your-domain.com
|
||||
```
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
|
||||
# Preview production build
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
The portfolio is configured to deploy automatically using NuxtHub. Push changes to your main branch to trigger a deployment.
|
||||
|
||||
## 🍱 Adding Content
|
||||
|
||||
### Portfolio Projects
|
||||
### Projects
|
||||
|
||||
1. Create a new `.md` file in the `/content/portfolio/` directory
|
||||
1. Create a new `.md` file in the `/content/projects/` directory
|
||||
2. Follow the structure of existing projects:
|
||||
|
||||
```md
|
||||
---
|
||||
---
|
||||
slug: project-slug
|
||||
title: Project Title
|
||||
description: Brief description of the project
|
||||
date: 2023-01-01
|
||||
img: /portfolio/project-image.png
|
||||
tags: [tag1, tag2, tag3]
|
||||
links:
|
||||
website: https://project-url.com
|
||||
github: https://github.com/user/repo
|
||||
description: A brief description of the project
|
||||
publishedAt: YYYY/MM/DD
|
||||
readingTime: 1
|
||||
cover: project-slug/cover.png
|
||||
tags:
|
||||
- web
|
||||
---
|
||||
|
||||
## Project content goes here
|
||||
@@ -164,20 +102,48 @@ links:
|
||||
Detailed description and information about the project.
|
||||
```
|
||||
|
||||
3. Add related project images to `/public/portfolio/`
|
||||
3. Add related project images to `/public/projects/project-slug/`
|
||||
|
||||
### Writings
|
||||
|
||||
1. Create a new `.md` file in the `/content/writings/` directory
|
||||
2. Follow the structure of existing projects:
|
||||
|
||||
```md
|
||||
---
|
||||
slug: article-slug
|
||||
title: The title of the article
|
||||
description: A brief description of the article
|
||||
readingTime: 1
|
||||
publishedAt: YYYY/MM/DD
|
||||
cover: article-slug/cover.png
|
||||
tags:
|
||||
- tag1
|
||||
- tag2
|
||||
- tag3
|
||||
---
|
||||
|
||||
## Writing content goes here
|
||||
|
||||
Detailed description and information about the article.
|
||||
```
|
||||
|
||||
3. Add related writing images to `/public/writings/article-slug/`
|
||||
|
||||
### Uses Page
|
||||
|
||||
Add new items to the `/content/uses/` directory following the existing pattern:
|
||||
|
||||
```md
|
||||
---
|
||||
category: Category Name
|
||||
items:
|
||||
- name: Item Name
|
||||
description: Item description
|
||||
link: https://item-url.com
|
||||
---
|
||||
```json
|
||||
{
|
||||
"name": "Name of the item",
|
||||
"description": {
|
||||
"en": "Item description in English",
|
||||
"fr": "Item description in French",
|
||||
"es": "Item description in Spanish"
|
||||
},
|
||||
"category": "Item category name"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔌 Integrations
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
link: [{ rel: 'icon', type: 'image/webp', href: '/favicon.webp' }],
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -28,13 +28,22 @@ const navs = [
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'portfolio',
|
||||
fr: 'portfolio',
|
||||
en: 'writings',
|
||||
fr: 'écrits',
|
||||
es: 'escritos',
|
||||
},
|
||||
to: '/portfolio',
|
||||
to: '/writings',
|
||||
icon: 'books-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'projects',
|
||||
fr: 'projets',
|
||||
es: 'proyectos',
|
||||
},
|
||||
to: '/projects',
|
||||
icon: 'code-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'resume',
|
||||
|
||||
@@ -11,19 +11,19 @@ defineProps({
|
||||
})
|
||||
|
||||
const colorVariants = {
|
||||
gray: 'text-gray-500 decoration-gray-400',
|
||||
red: 'text-red-500 decoration-red-400',
|
||||
yellow: 'text-yellow-500 decoration-yellow-400',
|
||||
green: 'text-green-500 decoration-green-400',
|
||||
blue: 'text-blue-500 decoration-blue-400',
|
||||
indigo: 'text-indigo-500 decoration-indigo-400',
|
||||
purple: 'text-purple-500 decoration-purple-400',
|
||||
pink: 'text-pink-500 decoration-pink-400',
|
||||
sky: 'text-sky-500 decoration-sky-400',
|
||||
zinc: 'text-zinc-500 decoration-zinc-400',
|
||||
orange: 'text-orange-500 decoration-orange-400',
|
||||
amber: 'text-amber-500 decoration-amber-400',
|
||||
emerald: 'text-emerald-500 decoration-emerald-400',
|
||||
gray: 'text-gray-500/80 decoration-gray-400/80',
|
||||
red: 'text-red-500/80 decoration-red-400/80',
|
||||
yellow: 'text-yellow-500/80 decoration-yellow-400/80',
|
||||
green: 'text-green-500/80 decoration-green-400/80',
|
||||
blue: 'text-blue-500/80 decoration-blue-400/80',
|
||||
indigo: 'text-indigo-500/80 decoration-indigo-400/80',
|
||||
purple: 'text-purple-500/80 decoration-purple-400/80',
|
||||
pink: 'text-pink-500/80 decoration-pink-400/80',
|
||||
sky: 'text-sky-500/80 decoration-sky-400/80',
|
||||
zinc: 'text-zinc-500/80 decoration-zinc-400/80',
|
||||
orange: 'text-orange-500/80 decoration-orange-400/80',
|
||||
amber: 'text-amber-500/80 decoration-amber-400/80',
|
||||
emerald: 'text-emerald-500/80 decoration-emerald-400/80',
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@ const colorVariants = {
|
||||
:name="`i-logos:${icon}`"
|
||||
/>
|
||||
<span
|
||||
:class="colorVariants[color]"
|
||||
:class="colorVariants[color as keyof typeof colorVariants]"
|
||||
class="sofia font-medium underline-offset-2 underline"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -5,7 +5,7 @@ defineProps<{ src: string, label: string, caption?: string }>()
|
||||
<template>
|
||||
<div class="flex flex-col justify-center items-center prose-none my-8">
|
||||
<img :src="src" :alt="label" class="w-full h-auto m-0 prose-none">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-300 prose-none">
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-300 prose-none">
|
||||
{{ caption }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,22 @@ import type { UseTimeAgoMessages } from '@vueuse/core'
|
||||
import type { Activity } from '~~/types'
|
||||
import { activityMessages, IDEs } from '~~/types'
|
||||
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch('/api/activity'))
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch<Activity>('/api/activity'))
|
||||
|
||||
useIntervalFn(async () => await refresh(), 5000)
|
||||
const codingActivity = computed(() => {
|
||||
const activities = activity.value!.data.activities.filter(activity => IDEs.some(ide => ide.name === activity.name))
|
||||
if (activities.length > 1) {
|
||||
const randomIndex = Math.floor(Math.random() * activities.length)
|
||||
return activities[randomIndex]
|
||||
}
|
||||
return activities[0]
|
||||
const activities = activity.value!.data.activities.filter(activity => IDEs.some(ide => ide.name === activity.name)).map(activity => ({
|
||||
...activity,
|
||||
name: activity.assets?.small_text === 'Cursor' ? 'Cursor' : activity.name,
|
||||
}))
|
||||
|
||||
return activities.length > 1
|
||||
? activities[Math.floor(Math.random() * activities.length)]
|
||||
: activities[0]
|
||||
})
|
||||
|
||||
const { locale, locales } = useI18n()
|
||||
@@ -23,7 +30,7 @@ const isActive = computed(() => {
|
||||
|
||||
const { name, details, state } = codingActivity.value
|
||||
|
||||
return name === 'Visual Studio Code'
|
||||
return name === 'Visual Studio Code' || name === 'Cursor'
|
||||
? !details.includes('Idling')
|
||||
: state.toLowerCase().includes('editing')
|
||||
})
|
||||
@@ -43,7 +50,7 @@ const getActivity = computed(() => {
|
||||
.replace('Workspace:', '')
|
||||
.trim()
|
||||
: ''
|
||||
const stateWord = state.split(' ')[1]
|
||||
const stateWord = state.split(' ')[1] || t('secret')
|
||||
const ago = useTimeAgo(timestamps.start, {
|
||||
messages: activityMessages[locale.value] as UseTimeAgoMessages,
|
||||
}).value
|
||||
@@ -62,10 +69,6 @@ const getActivity = computed(() => {
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -91,11 +94,11 @@ const { t } = useI18n({
|
||||
keypath="working"
|
||||
tag="div"
|
||||
>
|
||||
<template #project>
|
||||
<strong>{{ getActivity.project }}</strong>
|
||||
</template>
|
||||
<template #state>
|
||||
<i>{{ getActivity.state }}</i>
|
||||
<strong>{{ getActivity.state.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ') }}</strong>
|
||||
</template>
|
||||
<template #project>
|
||||
<i>{{ getActivity.project.replaceAll('Editing', '') }}</i>
|
||||
</template>
|
||||
<template #editor>
|
||||
<span class="space-x-1">
|
||||
@@ -148,36 +151,39 @@ const { t } = useI18n({
|
||||
{
|
||||
"en": {
|
||||
"offline": "I'm currently offline. Come back later to see what I'm working on.",
|
||||
"working": "I'm actually working on {project}, editing {state}, using {editor}. I've started {start}, the {format}.",
|
||||
"working": "I'm actually working on {state}, editing {project}, using {editor}. I've started {start}, the {format}.",
|
||||
"idling": "I'm idling on my computer with {editor} running in background.",
|
||||
"tooltip": {
|
||||
"online": "I'm online 👋",
|
||||
"offline": "I'm offline 🫥",
|
||||
"idling": "I'm sleeping 😴"
|
||||
},
|
||||
"separator": "at"
|
||||
"separator": "at",
|
||||
"secret": "Secret Project"
|
||||
},
|
||||
"fr": {
|
||||
"offline": "Je suis actuellement hors ligne. Revenez plus tard pour voir sur quoi je travaille.",
|
||||
"working": "Je travaille actuellement sur {project}, éditant {state}, en utilisant {editor}. J'ai commencé {start}, le {format}.",
|
||||
"working": "Je travaille actuellement sur {state}, éditant {project}, en utilisant {editor}. J'ai commencé {start}, le {format}.",
|
||||
"idling": "Je suis en veille sur mon ordinateur avec {editor} en arrière-plan.",
|
||||
"tooltip": {
|
||||
"online": "Je suis connecté 👋",
|
||||
"offline": "Je suis déconnecté 🫥",
|
||||
"idling": "Je dors 😴"
|
||||
},
|
||||
"separator": "à"
|
||||
"separator": "à",
|
||||
"secret": "Projet Secret"
|
||||
},
|
||||
"es": {
|
||||
"offline": "Ahora mismo estoy desconectado. Vuelve más tarde para ver en lo que estoy trabajando.",
|
||||
"working": "Estoy trabajando en {project}, editando {state}, y utilizando {editor}. He empezado {start}, el {format}.",
|
||||
"working": "Estoy trabajando en {state}, editando {project}, y utilizando {editor}. He empezado {start}, el {format}.",
|
||||
"idling": "Estoy en reposo en mi ordenador con {editor} en segundo plano.",
|
||||
"tooltip": {
|
||||
"online": "Estoy conectado 👋",
|
||||
"offline": "Estoy desconectado 🫥",
|
||||
"idling": "Estoy durmiendo 😴"
|
||||
},
|
||||
"separator": "a"
|
||||
"separator": "a",
|
||||
"secret": "Proyecto Secreto"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { COBEOptions } from 'cobe'
|
||||
import createGlobe from 'cobe'
|
||||
import { useSpring } from 'vue-use-spring'
|
||||
|
||||
interface GlobeProps {
|
||||
class?: string
|
||||
config?: Partial<COBEOptions>
|
||||
mass?: number
|
||||
tension?: number
|
||||
friction?: number
|
||||
precision?: number
|
||||
locations?: Array<{ latitude: number, longitude: number }>
|
||||
myLocation?: { latitude: number, longitude: number }
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<GlobeProps>(), {
|
||||
mass: 1,
|
||||
tension: 280,
|
||||
friction: 100,
|
||||
precision: 0.001,
|
||||
})
|
||||
|
||||
const DEFAULT_CONFIG: COBEOptions = {
|
||||
width: 400,
|
||||
height: 400,
|
||||
onRender: () => {},
|
||||
devicePixelRatio: 2,
|
||||
phi: 0,
|
||||
theta: 0.3,
|
||||
dark: 0,
|
||||
diffuse: 0.4,
|
||||
mapSamples: 20000,
|
||||
mapBrightness: 1.7,
|
||||
baseColor: [0.5, 0.5, 0.5],
|
||||
opacity: 0.7,
|
||||
markerColor: [160 / 255, 160 / 255, 160 / 255],
|
||||
glowColor: [0.4, 0.4, 0.4],
|
||||
markers: [],
|
||||
}
|
||||
|
||||
const globeCanvasRef = ref<HTMLCanvasElement>()
|
||||
const phi = ref(0)
|
||||
const width = ref(0)
|
||||
const pointerInteracting = ref()
|
||||
const pointerInteractionMovement = ref()
|
||||
|
||||
let globe: ReturnType<typeof createGlobe> | null = null
|
||||
|
||||
const spring = useSpring(
|
||||
{
|
||||
r: 0,
|
||||
},
|
||||
{
|
||||
mass: props.mass,
|
||||
tension: props.tension,
|
||||
friction: props.friction,
|
||||
precision: props.precision,
|
||||
},
|
||||
)
|
||||
|
||||
function updatePointerInteraction(clientX: number | null) {
|
||||
if (clientX !== null) {
|
||||
pointerInteracting.value = clientX - (pointerInteractionMovement.value ?? clientX)
|
||||
}
|
||||
else {
|
||||
pointerInteracting.value = null
|
||||
}
|
||||
|
||||
if (globeCanvasRef.value) {
|
||||
globeCanvasRef.value.style.cursor = clientX ? 'grabbing' : 'grab'
|
||||
}
|
||||
}
|
||||
|
||||
function updateMovement(clientX: number) {
|
||||
if (pointerInteracting.value !== null) {
|
||||
const delta = clientX - (pointerInteracting.value ?? clientX)
|
||||
pointerInteractionMovement.value = delta
|
||||
spring.r = delta / 200
|
||||
}
|
||||
}
|
||||
|
||||
function onRender(state: Record<string, unknown>) {
|
||||
if (!pointerInteracting.value) {
|
||||
phi.value += 0.005
|
||||
}
|
||||
|
||||
state.phi = phi.value + spring.r
|
||||
state.width = width.value * 2
|
||||
state.height = width.value * 2
|
||||
state.markers = props.locations?.map(location => ({
|
||||
location: [location.latitude, location.longitude],
|
||||
size: props.myLocation?.latitude === location.latitude && props.myLocation?.longitude === location.longitude ? 0.1 : 0.05,
|
||||
}))
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
if (globeCanvasRef.value) {
|
||||
width.value = globeCanvasRef.value.offsetWidth
|
||||
}
|
||||
}
|
||||
|
||||
function createGlobeOnMounted() {
|
||||
const config = { ...DEFAULT_CONFIG, ...props.config }
|
||||
|
||||
globe = createGlobe(globeCanvasRef.value!, {
|
||||
...config,
|
||||
width: width.value,
|
||||
height: width.value,
|
||||
onRender,
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', onResize)
|
||||
onResize()
|
||||
createGlobeOnMounted()
|
||||
|
||||
setTimeout(() => (globeCanvasRef.value!.style.opacity = '1'))
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
globe?.destroy()
|
||||
window.removeEventListener('resize', onResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="props.class">
|
||||
<canvas
|
||||
ref="globeCanvasRef"
|
||||
class="size-full opacity-0 transition-opacity duration-1000 ease-in-out [contain:layout_paint_size]"
|
||||
@pointerdown="(e) => updatePointerInteraction(e.clientX)"
|
||||
@pointerup="updatePointerInteraction(null)"
|
||||
@pointerout="updatePointerInteraction(null)"
|
||||
@mousemove="(e) => updateMovement(e.clientX)"
|
||||
@touchmove="(e) => e.touches[0] && updateMovement(e.touches[0].clientX)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,8 +31,8 @@ defineProps({
|
||||
/>
|
||||
<span
|
||||
class="duration-300 underline-offset-2 font-bold text-md text-black dark:text-white underline decoration-gray-300 dark:decoration-neutral-700 group-hover:decoration-black dark:group-hover:decoration-white"
|
||||
>{{
|
||||
label
|
||||
}}</span>
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
@@ -9,12 +9,12 @@ const { t } = useI18n({
|
||||
<div class="flex items-center">
|
||||
<ClientOnly>
|
||||
<UTooltip text="It's me 👋">
|
||||
<div class="flex items-center w-12 h-12">
|
||||
<div class="flex items-center6">
|
||||
<UAvatar
|
||||
alt="Avatar"
|
||||
class="hover:rotate-[360deg] duration-500 transform-gpu"
|
||||
size="md"
|
||||
src="/favicon.png"
|
||||
class="hover:rotate-[360deg] duration-500 transform-gpu rounded-full"
|
||||
size="xl"
|
||||
src="/favicon.webp"
|
||||
/>
|
||||
</div>
|
||||
</UTooltip>
|
||||
|
||||
@@ -6,8 +6,6 @@ const { data: page } = await useAsyncData(`/home/${locale.value}`, () => {
|
||||
}, {
|
||||
watch: [locale],
|
||||
})
|
||||
|
||||
const { myLocation, locations } = useVisitors()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -17,10 +15,5 @@ const { myLocation, locations } = useVisitors()
|
||||
<HomeActivity />
|
||||
<HomeQuote />
|
||||
<HomeCatchPhrase />
|
||||
<HomeGlobe
|
||||
:my-location
|
||||
:locations
|
||||
class="mt-8 mx-auto aspect-[1/1] duration-500 md:w-1/2"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Tag } from '~~/types'
|
||||
import { TAGS } from '~~/types'
|
||||
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Shelf',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const tagFilter = ref<string[]>([])
|
||||
|
||||
const { data: writings, refresh } = await useAsyncData('all-portfolio', async () => {
|
||||
const writings = await queryCollection('portfolio')
|
||||
.order('publishedAt', 'DESC')
|
||||
.all()
|
||||
return writings.filter((writing) => {
|
||||
if (tagFilter.value.length === 0)
|
||||
return true
|
||||
return writing.tags.some(tag => tagFilter.value.includes(tag.toLowerCase()))
|
||||
})
|
||||
})
|
||||
|
||||
watch(tagFilter, async () => await refresh())
|
||||
|
||||
const tags: Array<Tag> = [
|
||||
...TAGS.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
]
|
||||
|
||||
function updateTag(payload: Tag[]) {
|
||||
tagFilter.value = payload.map(tag => tag.label.toLowerCase())
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-12">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<UAlert
|
||||
v-if="locale !== 'en'"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<div class="flex justify-end sticky top-4 z-50">
|
||||
<USelectMenu
|
||||
:placeholder="t('tags')"
|
||||
:items="tags"
|
||||
multiple
|
||||
color="neutral"
|
||||
:highlight-on-hover="true"
|
||||
class="w-full md:w-1/3"
|
||||
@update:model-value="updateTag"
|
||||
/>
|
||||
</div>
|
||||
<ul class="grid grid-cols-1 gap-4">
|
||||
<NuxtLink
|
||||
v-for="(writing, id) in writings"
|
||||
:key="id"
|
||||
:to="writing.path"
|
||||
>
|
||||
<li
|
||||
class=" h-full border p-4 border-neutral-200 rounded-md hover:border-neutral-500 dark:border-neutral-800 dark:hover:border-neutral-600 duration-300"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-black dark:text-white"
|
||||
>
|
||||
{{ writing.title }}
|
||||
</h1>
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(writing.publishedAt, 'DD MMMM YYYY').value }} </p>·
|
||||
<UIcon name="ph:timer-duotone" size="16" />
|
||||
<p>{{ writing.readingTime }}min long</p>
|
||||
</div>
|
||||
<h3>
|
||||
{{ writing.description }}
|
||||
</h3>
|
||||
</article>
|
||||
<div class="flex gap-2 mt-4 flex-wrap">
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
v-for="tag in writing.tags.sort((a: any, b: any) => a.localeCompare(b))"
|
||||
:key="tag"
|
||||
:color="TAGS.find(color => color.label.toLowerCase() === tag)?.color"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
class="rounded-full"
|
||||
>
|
||||
<div class="flex gap-1 items-center">
|
||||
<UIcon :name="TAGS.find(icon => icon.label.toLowerCase() === tag)?.icon || ''" size="16" />
|
||||
<p>{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}</p>
|
||||
</div>
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "Writing on my life, development, academic and personal projects and passions",
|
||||
"description": "All my thoughts on programming, mathematics, artificial intelligence design, etc., are put together in chronological order. I also write about my projects, my discoveries, and my thoughts.",
|
||||
"alert": {
|
||||
"title": "Translations alert!",
|
||||
"description": "Due to time constraints, all article translations will be available only in English. Thank you for your understanding."
|
||||
},
|
||||
"tags": "Select tags to filter"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Écrits sur ma vie, le développement, mes projets et mes passions.",
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique. J'écris aussi sur mes projets, mes découvertes et mes pensées.",
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
},
|
||||
"tags": "Sélectionner des tags pour filtrer"
|
||||
},
|
||||
"es": {
|
||||
"title": "Escritos sobre mi vida, el desarrollo, mis proyectos y mis pasiones.",
|
||||
"description": " Todas mis reflexiones sobre la programación, las matemáticas, la conception de la inteligencia artificial, etc. están puestas en orden cronológico. También escribo sobre mis proyectos, mis descubrimientos y mis pensamientos.",
|
||||
"alert": {
|
||||
"title": "Cuidado con las traducciones !",
|
||||
"description": "Por problema de tiempo, los artículos están solo disponibles en ingles. Gracias por vuestra comprensión."
|
||||
},
|
||||
"tags": "Seleccionar etiquetas para filtrar"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<style scoped>
|
||||
.tablist > button {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
</style>
|
||||
168
app/pages/projects/[slug].vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: project } = await useAsyncData(`projects/${route.params.slug}`, () =>
|
||||
queryCollection('projects').path(`/projects/${route.params.slug}`).first())
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: project.value?.title,
|
||||
description: project.value?.description,
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="project">
|
||||
<div class="flex">
|
||||
<NuxtLinkLocale
|
||||
class="flex items-center gap-2 mb-8 group text-sm hover:text-black dark:hover:text-white duration-300"
|
||||
to="/projects"
|
||||
>
|
||||
<UIcon
|
||||
class="group-hover:-translate-x-1 transform duration-300"
|
||||
name="i-ph-arrow-left-duotone"
|
||||
size="20"
|
||||
/>
|
||||
{{ t('back') }}
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="flex items-end justify-between gap-2 flex-wrap">
|
||||
<h1
|
||||
class="font-bold text-3xl text-black dark:text-white"
|
||||
>
|
||||
{{ project.title }}
|
||||
</h1>
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(project.publishedAt, 'DD MMMM YYYY').value }} </p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-base">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.cover"
|
||||
class="w-full rounded-md my-8"
|
||||
>
|
||||
<ProseImg
|
||||
:src="`/projects/${project.cover}`"
|
||||
label="Project cover"
|
||||
/>
|
||||
</div>
|
||||
<USeparator
|
||||
class="my-4"
|
||||
icon="i-ph-pencil-line-duotone"
|
||||
/>
|
||||
<UAlert
|
||||
v-if="locale !== 'en'"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
class="mb-8"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<ContentRenderer
|
||||
:value="project"
|
||||
class="!max-w-none prose dark:prose-invert"
|
||||
/>
|
||||
<div class="mt-16 flex gap-8 items-start p-8 border border-gray-200 dark:border-neutral-700 rounded-md">
|
||||
<NuxtImg
|
||||
src="/arthur.webp"
|
||||
alt="Arthur Danjou"
|
||||
class="w-24 h-24 rounded-full"
|
||||
/>
|
||||
<i18n-t
|
||||
keypath="thanks"
|
||||
tag="p"
|
||||
class="text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<template #linkedin>
|
||||
<HomeLink
|
||||
href="https://www.linkedin.com/in/arthur-danjou/"
|
||||
icon="i-ph-linkedin-logo-duotone"
|
||||
label="LinkedIn"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #github>
|
||||
<HomeLink
|
||||
href="https://github.com/arthurdanjou"
|
||||
icon="i-ph-github-logo-duotone"
|
||||
label="GitHub"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #name>
|
||||
<strong class="text-neutral-800 dark:text-neutral-200">{{ t('name') }}</strong>
|
||||
</template>
|
||||
<template #jump>
|
||||
<br> <br>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.prose h2 a,
|
||||
.prose h3 a,
|
||||
.prose h4 a {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
.prose img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.katex-html {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"alert": {
|
||||
"title": "Translations alert!",
|
||||
"description": "Due to time constraints, all article translations will be available only in English. Thank you for your understanding."
|
||||
},
|
||||
"back": "Go back",
|
||||
"thanks": "Hi! I'm {name}, a Master's student in Applied Mathematics with a passion for AI, statistics, and building cool things with code. {jump} I love turning ideas into real, working systems—whether it's a machine learning model, a self-hosted service, or a data-driven project.{jump} This project is part of my journey to explore and apply what I learn every day. I share it here hoping it'll inspire or help others, just like I've been inspired by the open-source and tech communities. {jump} Feel free to reach out on {linkedin} or {github} if you have questions, feedback, or just want to connect!",
|
||||
"name": "Arthur"
|
||||
|
||||
},
|
||||
"fr": {
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
},
|
||||
"back": "Retourner en arrière",
|
||||
"thanks": "Bonjour ! Je suis {name}, étudiant en Master de Mathématiques Appliquées avec une passion pour l'IA, les statistiques et la création de projets intéressants avec du code. {jump} J'adore transformer des idées en systèmes réels et fonctionnels, que ce soit un modèle de machine learning, un service auto-hébergé ou un projet basé sur les données. {jump} Ce projet fait partie de mon parcours pour explorer et appliquer ce que j'apprends chaque jour. Je le partage ici dans l'espoir qu'il inspire ou aide d'autres personnes, tout comme j'ai été inspiré par les communautés open-source et tech. {jump} N'hésitez pas à me contacter sur {linkedin} ou {github} si vous avez des questions, des retours ou si vous souhaitez simplement échanger !",
|
||||
"name": "Arthur"
|
||||
},
|
||||
"es": {
|
||||
"alert": {
|
||||
"title": "Cuidado con las traducciones!",
|
||||
"description": " Por problemas de tiempo, los artículos solo están disponibles en inglés. Gracias por vuestra comprensión.ug ñeóicula."
|
||||
},
|
||||
"back": "Volver atrás",
|
||||
"thanks": "¡Hola! Soy {name}, estudiante de Máster en Matemáticas Aplicadas con una pasión por la IA, la estadística y la creación de cosas interesantes con código. {jump} Me encanta convertir ideas en sistemas reales y funcionales, ya sea un modelo de aprendizaje automático, un servicio autoalojado o un proyecto basado en datos. {jump} Este proyecto forma parte de mi camino para explorar y aplicar lo que aprendo cada día. Lo comparto aquí con la esperanza de que inspire o ayude a otros, así como yo he sido inspirado por las comunidades de código abierto y tecnología. {jump} No dudes en contactarme en {linkedin} o {github} si tienes preguntas, comentarios o simplemente quieres conectar!",
|
||||
"name": "Arthur"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
92
app/pages/projects/index.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import { TAGS } from '~~/types'
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Projects',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const { data: projects } = await useAsyncData('all-projects', () => {
|
||||
return queryCollection('projects')
|
||||
.order('publishedAt', 'DESC')
|
||||
.all()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-12">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<ul class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<NuxtLink
|
||||
v-for="(project, id) in projects"
|
||||
:key="id"
|
||||
:to="project.path"
|
||||
>
|
||||
<li
|
||||
class="flex flex-col justify-between h-full border p-4 border-neutral-200 rounded-md hover:border-neutral-500 dark:border-neutral-800 dark:hover:border-neutral-600 duration-300"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<div
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<h1 class="font-bold text-lg text-black dark:text-white">
|
||||
{{ project.title }}
|
||||
</h1>
|
||||
<h3 class="text-md text-neutral-500 dark:text-neutral-400">
|
||||
{{ project.description }}
|
||||
</h3>
|
||||
</div>
|
||||
</article>
|
||||
<div class="flex items-center justify-between gap-2 mt-4">
|
||||
<div
|
||||
class="text-sm text-neutral-500 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(project.publishedAt, 'DD MMMM YYYY').value }} </p>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
v-for="tag in project.tags.sort((a: any, b: any) => a.localeCompare(b))"
|
||||
:key="tag"
|
||||
:color="TAGS.find(color => color.label.toLowerCase() === tag)?.color as any"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
class="rounded-full"
|
||||
>
|
||||
<div class="flex gap-1 items-center">
|
||||
<UIcon :name="TAGS.find(icon => icon.label.toLowerCase() === tag)?.icon || ''" size="16" />
|
||||
<p>{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}</p>
|
||||
</div>
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "All my projects I have worked on, both academic and personal",
|
||||
"description": "A collection of my projects using R, Python, or web development technologies. These projects span various domains, including data analysis, machine learning, and web applications, showcasing my skills in coding, problem-solving, and project development."
|
||||
},
|
||||
"fr": {
|
||||
"title": "Tous mes projets auxquels j'ai travaillé, académiques et personnels",
|
||||
"description": "Une collection de mes projets réalisés en R, Python, ou en développement web. Ces projets couvrent divers domaines, y compris l'analyse de données, l'apprentissage automatique et les applications web, mettant en avant mes compétences en codage, résolution de problèmes et développement de projets."
|
||||
},
|
||||
"es": {
|
||||
"title": "Todos mis proyectos en los que he trabajado, académicos y personales",
|
||||
"description": "Una colección de mis proyectos realizados en R, Python o tecnologías de desarrollo web. Estos proyectos abarcan diversos campos, como análisis de datos, aprendizaje automático y aplicaciones web, mostrando mis habilidades en programación, resolución de problemas y desarrollo de proyectos."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: post } = await useAsyncData(`portfolio/${route.params.slug}`, () =>
|
||||
queryCollection('portfolio').path(`/portfolio/${route.params.slug}`).first())
|
||||
const { data: post } = await useAsyncData(`writings/${route.params.slug}`, () =>
|
||||
queryCollection('writings').path(`/writings/${route.params.slug}`).first())
|
||||
|
||||
const {
|
||||
data: postDB,
|
||||
refresh,
|
||||
} = await useAsyncData(`portfolio/${route.params.slug}/db`, () => $fetch(`/api/posts/${route.params.slug}`, { method: 'POST' }))
|
||||
} = await useAsyncData(`writings/${route.params.slug}/db`, () => $fetch(`/api/posts/${route.params.slug}`, { method: 'POST' }))
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { t } = useI18n({
|
||||
@@ -22,7 +22,7 @@ function top() {
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard({
|
||||
source: `https://arthurdanjou.fr/portfolio/${route.params.slug}`,
|
||||
source: `https://arthurdanjou.fr/writings/${route.params.slug}`,
|
||||
copiedDuring: 4000,
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ function scrollToSection(id: string) {
|
||||
<div class="flex">
|
||||
<NuxtLinkLocale
|
||||
class="flex items-center gap-2 mb-8 group text-sm hover:text-black dark:hover:text-white duration-300"
|
||||
to="/portfolio"
|
||||
to="/writings"
|
||||
>
|
||||
<UIcon
|
||||
class="group-hover:-translate-x-1 transform duration-300"
|
||||
@@ -115,7 +115,7 @@ function scrollToSection(id: string) {
|
||||
{{ post.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="post.body.toc && post.body.toc.links.length > 0" class="flex justify-end sticky top-0 z-50">
|
||||
<div v-if="post.body.toc && post.body.toc.links.length > 0" class="flex justify-end sticky top-0 z-50 !cursor-pointer">
|
||||
<UPopover
|
||||
mode="click"
|
||||
:content="{
|
||||
@@ -128,7 +128,6 @@ function scrollToSection(id: string) {
|
||||
:label="t('toc')"
|
||||
color="neutral"
|
||||
variant="solid"
|
||||
class="mt-2"
|
||||
/>
|
||||
|
||||
<template #content>
|
||||
@@ -144,7 +143,7 @@ function scrollToSection(id: string) {
|
||||
variant="link"
|
||||
color="neutral"
|
||||
:block="true"
|
||||
class="flex justify-start items-start"
|
||||
class="flex justify-start items-start p-1"
|
||||
@click="scrollToSection(link.id)"
|
||||
/>
|
||||
</div>
|
||||
@@ -156,9 +155,9 @@ function scrollToSection(id: string) {
|
||||
v-if="post.cover"
|
||||
class="w-full rounded-md mb-8"
|
||||
>
|
||||
<NuxtImg
|
||||
:src="`/portfolio/${post.cover}`"
|
||||
alt="Writing cover"
|
||||
<ProseImg
|
||||
:src="`/writings/${post.cover}`"
|
||||
label="Writing cover"
|
||||
/>
|
||||
</div>
|
||||
<USeparator
|
||||
@@ -170,22 +169,50 @@ function scrollToSection(id: string) {
|
||||
:value="post"
|
||||
class="!max-w-none prose dark:prose-invert"
|
||||
/>
|
||||
<USeparator
|
||||
class="my-16"
|
||||
icon="i-ph-hands-clapping-duotone"
|
||||
/>
|
||||
<div class="space-y-8">
|
||||
<i18n-t
|
||||
keypath="thanks"
|
||||
tag="p"
|
||||
>
|
||||
<template #like>
|
||||
<strong>{{ t('like') }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div class="flex gap-4 items-center flex-wrap">
|
||||
<div class="space-y-8 mt-16">
|
||||
<div class="flex gap-8 items-start p-8 border border-gray-200 dark:border-neutral-700 rounded-md">
|
||||
<NuxtImg
|
||||
src="/arthur.webp"
|
||||
alt="Arthur Danjou"
|
||||
class="w-24 h-24 rounded-full"
|
||||
/>
|
||||
<i18n-t
|
||||
keypath="thanks"
|
||||
tag="p"
|
||||
class="text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<template #linkedin>
|
||||
<HomeLink
|
||||
href="https://www.linkedin.com/in/arthur-danjou/"
|
||||
icon="i-ph-linkedin-logo-duotone"
|
||||
label="LinkedIn"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #github>
|
||||
<HomeLink
|
||||
href="https://github.com/arthurdanjou"
|
||||
icon="i-ph-github-logo-duotone"
|
||||
label="GitHub"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #comment>
|
||||
<strong class="text-neutral-800 dark:text-neutral-200">{{ t('comment') }}</strong>
|
||||
</template>
|
||||
<template #name>
|
||||
<strong class="text-neutral-800 dark:text-neutral-200">{{ t('name') }}</strong>
|
||||
</template>
|
||||
<template #jump>
|
||||
<br> <br>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="flex gap-8 items-center flex-wrap">
|
||||
<UButton
|
||||
:label="postDB?.likes > 1 ? `${postDB?.likes} likes` : `${postDB?.likes} like`"
|
||||
:label="(postDB?.likes ?? 0) > 1 ? `${postDB?.likes ?? 0} likes` : `${postDB?.likes ?? 0} like`"
|
||||
:color="likeCookie ? 'red' : 'neutral'"
|
||||
icon="i-ph-heart-duotone"
|
||||
size="lg"
|
||||
@@ -259,8 +286,9 @@ html {
|
||||
"title": "Translations alert!",
|
||||
"description": "Due to time constraints, all article translations will be available only in English. Thank you for your understanding."
|
||||
},
|
||||
"thanks": "Thanks for reading this post! If you liked it, please consider sharing it with your friends. {like}",
|
||||
"like": "Don't forget to leave a like!",
|
||||
"thanks": "Thanks for reading! My name is {name}, and I love writing about AI, data science, and the intersection between mathematics and programming. {jump} I've been coding and exploring math for years, and I'm always learning something new—whether it's self-hosting tools in my homelab, experimenting with machine learning models, or diving into statistical methods. {jump} I share my knowledge here because I know how valuable clear, hands-on resources can be, especially when you're just getting started or exploring something deeply technical. {jump} If you have any questions or just want to chat, feel free to reach out to me on {linkedin} or {github }. {jump} I hope you enjoyed this post and learned something useful. If you did, {comment}—it really helps and means a lot!",
|
||||
"comment": "consider sharing it",
|
||||
"name": "Arthur",
|
||||
"link": {
|
||||
"copied": "Link copied",
|
||||
"copy": "Copy link"
|
||||
@@ -282,8 +310,9 @@ html {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
},
|
||||
"thanks": "Merci d'avoir lu cet article! Si vous l'avez aimé, n'hésitez pas à le partager avec vos amis. {like}",
|
||||
"like": "N'oubliez pas de laisser un like!",
|
||||
"thanks": "Merci de votre lecture ! Je m'appelle {name}, et j'adore écrire sur l'intelligence artificielle, la data science, et tout ce qui se situe à l'intersection entre les mathématiques et la programmation. {jump} Je code et j'explore les maths depuis des années, et j'apprends encore de nouvelles choses chaque jour — que ce soit en auto-hébergeant des outils dans mon homelab, en expérimentant des modèles de machine learning ou en approfondissant des méthodes statistiques. {jump} Je partage mes connaissances ici parce que je sais à quel point des ressources claires, pratiques et accessibles peuvent être précieuses, surtout quand on débute ou qu'on explore un sujet technique en profondeur. {jump} Si vous avez des questions ou simplement envie d'échanger, n'hésitez pas à laisser un commentaire ci-dessous ou à me contacter sur {linkedin} ou {github}. {jump} J'espère que cet article vous a plu et qu'il vous a appris quelque chose d'utile. Si c'est le cas, {comment} — ça m'aide beaucoup et ça me fait vraiment plaisir !",
|
||||
"comment": "pensez à le partager",
|
||||
"name": "Arthur",
|
||||
"link": {
|
||||
"copied": "Lien copié",
|
||||
"copy": "Copier le lien"
|
||||
@@ -303,10 +332,11 @@ html {
|
||||
},
|
||||
"alert": {
|
||||
"title": "Cuidado con las traducciones!",
|
||||
"description": " Por problemas de tiempo, los artículos solo están disponibles en inglés. Gracias por vuestra comprensión.ug ñeóicula."
|
||||
"description": "Por problemas de tiempo, los artículos solo están disponibles en inglés. Gracias por vuestra comprensión.ug ñeóicula."
|
||||
},
|
||||
"thanks": "Muchas gracias por leer este postougracoias afic! Si te ha gustado, no dudes en compartirlo con tus amigos.tsaf. {like}",
|
||||
"like": "No te olvides de dejar un like!",
|
||||
"thanks": "¡Gracias por leer! Me llamo {name} y me encanta escribir sobre inteligencia artificial, ciencia de datos y todo lo que se encuentra en la intersección entre las matemáticas y la programación. {jump} Llevo años programando y explorando las matemáticas, y cada día aprendo algo nuevo — ya sea autoalojando herramientas en mi homelab, experimentando con modelos de aprendizaje automático o profundizando en métodos estadísticos. {jump} Comparto mis conocimientos aquí porque sé lo valiosos que pueden ser los recursos claros, prácticos y accesibles, especialmente cuando uno está empezando o explorando temas técnicos en profundidad. {jump} Si tienes alguna pregunta o simplemente quieres charlar, no dudes en dejar un comentario abajo o contactarme por {linkedin} o {github}. {jump} Espero que este artículo te haya gustado y que hayas aprendido algo útil. Si es así, {comment} — ¡me ayuda mucho y significa mucho para mí!",
|
||||
"comment": "considera compartirlo",
|
||||
"name": "Arthur",
|
||||
"link": {
|
||||
"copied": "Link copiado",
|
||||
"copy": "Copiar link"
|
||||
118
app/pages/writings/index.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<script setup lang="ts">
|
||||
import { TAGS } from '~~/types'
|
||||
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Shelf',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const { data: writings } = await useAsyncData('all-writings', () => {
|
||||
return queryCollection('writings')
|
||||
.order('publishedAt', 'DESC')
|
||||
.all()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-12">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<UAlert
|
||||
v-if="locale !== 'en'"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<ul class="grid grid-cols-1 gap-4">
|
||||
<NuxtLink
|
||||
v-for="(writing, id) in writings"
|
||||
:key="id"
|
||||
:to="writing.path"
|
||||
>
|
||||
<li
|
||||
class=" h-full border p-4 border-neutral-200 rounded-md hover:border-neutral-500 dark:border-neutral-800 dark:hover:border-neutral-600 duration-300"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-black dark:text-white"
|
||||
>
|
||||
{{ writing.title }}
|
||||
</h1>
|
||||
<h3>
|
||||
{{ writing.description }}
|
||||
</h3>
|
||||
</article>
|
||||
<div class="flex justify-between items-center mt-2">
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(writing.publishedAt, 'DD MMMM YYYY').value }} </p>·
|
||||
<UIcon name="ph:timer-duotone" size="16" />
|
||||
<p>{{ writing.readingTime }}min long</p>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
v-for="tag in writing.tags.sort((a: any, b: any) => a.localeCompare(b))"
|
||||
:key="tag"
|
||||
:color="TAGS.find(color => color.label.toLowerCase() === tag)?.color as any"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
class="rounded-full"
|
||||
>
|
||||
<div class="flex gap-1 items-center">
|
||||
<UIcon :name="TAGS.find(icon => icon.label.toLowerCase() === tag)?.icon || ''" size="16" />
|
||||
<p>{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}</p>
|
||||
</div>
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "Writings on math, artificial intelligence, development, and my passions.",
|
||||
"description": "All my reflections on programming, mathematics, artificial intelligence design, etc., are organized chronologically.",
|
||||
"alert": {
|
||||
"title": "Attention to translations!",
|
||||
"description": "For time reasons, all article translations will only be available in English. Thank you for your understanding."
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"title": "Écrits sur les maths, l'intelligence artificielle, le développement et mes passions.",
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique.",
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"title": "Escritos sobre matemáticas, inteligencia artificial, desarrollo y mis pasiones.",
|
||||
"description": "Todas mis reflexiones sobre programación, matemáticas, diseño de inteligencia artificial, etc., están organizadas cronológicamente.",
|
||||
"alert": {
|
||||
"title": "¡Atención a las traducciones!",
|
||||
"description": "Por razones de tiempo, todas las traducciones de los artículos estarán disponibles solo en inglés. Gracias por su comprensión."
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<style scoped>
|
||||
.tablist > button {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
</style>
|
||||
@@ -5,9 +5,21 @@ export const collections = {
|
||||
type: 'page',
|
||||
source: 'home/*.md',
|
||||
}),
|
||||
portfolio: defineCollection({
|
||||
projects: defineCollection({
|
||||
type: 'page',
|
||||
source: 'portfolio/*.md',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.date(),
|
||||
tags: z.array(z.string()),
|
||||
cover: z.string(),
|
||||
}),
|
||||
}),
|
||||
writings: defineCollection({
|
||||
type: 'page',
|
||||
source: 'writings/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
slug: arthome
|
||||
title: ArtHome
|
||||
description: 🏡 Your personalised home page in your browser
|
||||
title: 🏡 ArtHome
|
||||
description: Your personalised home page in your browser
|
||||
publishedAt: 2024/09/04
|
||||
readingTime: 1
|
||||
cover: arthome/cover.png
|
||||
tags:
|
||||
- project
|
||||
- web
|
||||
---
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
slug: artsite
|
||||
title: ArtSite
|
||||
description: 🌍 My personal website, my portfolio, and my blog. 🚀
|
||||
title: 🌍 ArtSite
|
||||
description: My personal website, my portfolio, and my blog.
|
||||
publishedAt: 2024/06/01
|
||||
readingTime: 1
|
||||
cover: artsite/cover.png
|
||||
tags:
|
||||
- project
|
||||
- web
|
||||
---
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
slug: bikes-glm
|
||||
title: Generalized Linear Models for Bikes prediction
|
||||
description: 🚲 Predicting the number of bikes rented in a bike-sharing system using Generalized Linear Models.
|
||||
title: 🚲 Generalized Linear Models for Bikes prediction
|
||||
description: Predicting the number of bikes rented in a bike-sharing system using Generalized Linear Models.
|
||||
publishedAt: 2025/01/24
|
||||
readingTime: 1
|
||||
tags:
|
||||
- project
|
||||
- r
|
||||
- data
|
||||
- maths
|
||||
@@ -15,5 +14,5 @@ The project was done as part of the course `Generalised Linear Model` at the Par
|
||||
|
||||
You can find the code here: [GLM Bikes Code](https://github.com/ArthurDanjou/Studies/blob/master/M1/General%20Linear%20Models/Projet/GLM%20Code%20-%20DANJOU%20%26%20DUROUSSEAU.rmd)
|
||||
|
||||
<iframe src="/portfolio/bikes-glm/Report.pdf" width="100%" height="1000px">
|
||||
<iframe src="/projects/bikes-glm/Report.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
slug: monte-carlo-project
|
||||
title: Monte Carlo Methods Project
|
||||
title: 💻 Monte Carlo Methods Project
|
||||
description: A project to demonstrate the use of Monte Carlo methods in R.
|
||||
publishedAt: 2024/11/24
|
||||
readingTime: 3
|
||||
tags:
|
||||
- project
|
||||
- r
|
||||
- maths
|
||||
---
|
||||
@@ -22,5 +21,5 @@ Methods and algorithms implemented:
|
||||
|
||||
You can find the code here: [Monte Carlo Project Code](https://github.com/ArthurDanjou/Studies/blob/0c83e7e381344675e113c43b6f8d32e88a5c00a7/M1/Monte%20Carlo%20Methods/Project%201/003_rapport_DANJOU_DUROUSSEAU.rmd)
|
||||
|
||||
<iframe src="/portfolio/monte-carlo-project/Report.pdf" width="100%" height="1000px">
|
||||
<iframe src="/projects/monte-carlo-project/Report.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
slug: python-data-ml
|
||||
title: Python Data & ML
|
||||
description: 🧠 A repository dedicated to learning and practicing Python libraries for machine learning.
|
||||
title: 🐍 Python Data & ML
|
||||
description: A repository dedicated to learning and practicing Python libraries for machine learning.
|
||||
publishedAt: 2024/11/01
|
||||
readingTime: 1
|
||||
tags:
|
||||
- project
|
||||
- data
|
||||
- ai
|
||||
- python
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
slug: schelling-segregation-model
|
||||
title: Schelling Segregation Model
|
||||
description: 📊 A Python implementation of the Schelling Segregation Model using Statistics and Data Visualization.
|
||||
title: 📊 Schelling Segregation Model
|
||||
description: A Python implementation of the Schelling Segregation Model using Statistics and Data Visualization.
|
||||
publishedAt: 2024/05/03
|
||||
readingTime: 4
|
||||
tags:
|
||||
- project
|
||||
- python
|
||||
- maths
|
||||
---
|
||||
@@ -14,5 +13,5 @@ This is the French version of the report for the Schelling Segregation Model pro
|
||||
|
||||
You can find the code here: [Schelling Segregation Model Code](https://github.com/ArthurDanjou/Studies/blob/e1164f89bd11fc59fa79d94aa51fac69b425d68b/L3/Projet%20Num%C3%A9rique/Segregation.ipynb)
|
||||
|
||||
<iframe src="/portfolio/schelling/Projet.pdf" width="100%" height="1000px">
|
||||
<iframe src="/projects/schelling/Projet.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
slug: studies
|
||||
title: Studies projects
|
||||
description: 🎓 Studies projects - a collection of projects done during my studies.
|
||||
title: 🎓 Studies projects
|
||||
description: A collection of projects done during my studies.
|
||||
publishedAt: 2023/09/01
|
||||
readingTime: 1
|
||||
tags:
|
||||
- project
|
||||
- data
|
||||
- python
|
||||
- r
|
||||
@@ -5,7 +5,6 @@ description: My new website is using a fantastical stack and I am explaining how
|
||||
publishedAt: 2024/06/21
|
||||
readingTime: 5
|
||||
tags:
|
||||
- article
|
||||
- web
|
||||
---
|
||||
|
||||
@@ -13,37 +12,43 @@ My personal website is an overengineered playground where I tinker, explore new
|
||||
|
||||
While it's still fresh in my mind, I wanted to document how this version of the site works, the tools I used to build it, and the challenges I overcame to bring it to its current state.
|
||||
|
||||

|
||||
::prose-img
|
||||
---
|
||||
src: /writings/website-work/website.png
|
||||
label: Website
|
||||
caption: Website screenshot
|
||||
---
|
||||
::
|
||||
|
||||
## Ideas and Goals
|
||||
## 1 - Ideas and Goals
|
||||
|
||||
Most of the time, I work on my site for fun and without any profit motive. However, while building this latest version, I managed to keep a few key ideas and goals in mind:
|
||||
|
||||
### Reduce writing friction
|
||||
### 1.1 - Reduce writing friction
|
||||
|
||||
This new version of my website was built with the idea that I should be able to add, edit, and delete content directly from the front-end. This means that everything needs to be backed by a database or CMS, which quickly adds complexity. But at the end of the day, adding a bookmark should be a matter of pasting a URL and clicking save. Writing a blog post should be a matter of typing some Markdown and clicking publication.
|
||||
|
||||
Extra friction on these processes would make me less likely to keep things up to date or share new things.
|
||||
|
||||
### A playground for ideas
|
||||
### 1.2 - A playground for ideas
|
||||
|
||||
I want my website to be a playground where I can safely experiment with new technologies and packages, including testing frameworks (like the new Nuxt 3 stack), improving CSS styles with Tailwind, and discovering new technologies and frameworks, in a way that allows for easy isolation and deletion. This requirement made Nuxt.js an obvious choice, thanks to its support for hybrid page rendering strategies—static, server-rendered, or client-rendered. More on this below.
|
||||
|
||||
### Fast
|
||||
### 1.3 - Fast
|
||||
|
||||
The new version of my website is faster than the old one, thanks to the latest version of Nuxt. This improvement enhances the overall user experience and ensures that the site remains responsive and efficient.
|
||||
|
||||
## FrontEnd & BackEnd with Nuxt 3
|
||||
## 2 - FrontEnd & BackEnd with Nuxt 3
|
||||
|
||||
I wanted this version of my site to reflect my personality, especially because it seemed like a fun project! What would a 'personal application' look like, showcasing everything I've created? I aimed for a clean, monochrome design with plenty of 'Easter eggs' to keep things interesting.
|
||||
|
||||
### Nuxt 3
|
||||
### 2.1 - Nuxt 3
|
||||
|
||||
Nuxt.js is my front-end framework of choice. I particularly appreciate it for its comprehensive and complementary Vue and Nuxt ecosystem. The filesystem-based router provides an intuitive and powerful abstraction for building the route hierarchy. Nuxt.js also benefits from a large community that has thoroughly tested the framework, addressing edge cases and developing creative solutions for common Vue, data recovery, and performance issues. Whenever I encounter a problem, I turn to the Nuxt.js discussions on [GitHub](https://github.com/nuxt) or their [Discord server](https://go.nuxt.com/discord). Almost every time, I find that others have already come up with innovative solutions to similar challenges.
|
||||
|
||||
Nuxt.js is also fast. It optimizes performance by speeding up local builds, automatically compressing static assets, and ensuring quick deployment times. The regular project updates mean my site continually gets faster over time—at no extra cost!
|
||||
|
||||
### Styling
|
||||
### 2.2 - Styling
|
||||
|
||||
#### Tailwind CSS
|
||||
|
||||
@@ -59,11 +64,17 @@ Nuxt UI is a new tool I've been using since its release to enhance and streamlin
|
||||
|
||||
Nuxt UI aims to provide everything you need for the UI when building a Nuxt app, including components, icons, colors, dark mode, and keyboard shortcuts. It's an excellent tool for both beginners and experienced developers.
|
||||
|
||||
### Database & Deployment
|
||||
### 2.3 - Database & Deployment
|
||||
|
||||
#### NuxtHub & Cloudflare workers
|
||||
|
||||

|
||||
::prose-img
|
||||
---
|
||||
src: /writings/website-work/nuxt-hub.png
|
||||
label: NuxtHub
|
||||
caption: NuxtHub screenshot
|
||||
---
|
||||
::
|
||||
|
||||
NuxtHub is an innovative deployment and management platform tailored for Nuxt, leveraging the power of Cloudflare. Deploy your application effortlessly with database, key-value, and blob storage—all configured seamlessly within your Cloudflare account.
|
||||
|
||||
@@ -77,11 +88,17 @@ Drizzle isn't just a library; it's an exceptional journey 🤩. It empowers you
|
||||
|
||||
One word : `If you know SQL — you know Drizzle.`
|
||||
|
||||
### Writing
|
||||
### 2.4 - Writing
|
||||
|
||||
#### Nuxt Studio
|
||||
|
||||

|
||||
::prose-img
|
||||
---
|
||||
src: /writings/website-work/nuxt-studio.png
|
||||
label: Nuxt Studio
|
||||
caption: Nuxt Studio screenshot
|
||||
---
|
||||
::
|
||||
|
||||
Nuxt Studio introduces a fresh editing experience for your Nuxt Content website, providing limitless customization and a user-friendly interface. Edit your website effortlessly with our editor reminiscent of Notion, fostering seamless collaboration between developers and copywriters. It offers a rich text editor, markdown support, and a live preview, enabling you to create and edit content with ease.
|
||||
|
||||
@@ -93,7 +110,7 @@ The article you're currently reading is plain text stored in MySQL, rendered usi
|
||||
|
||||
Compromises are inevitable! I've chosen to sacrifice some features for simplicity and speed. I'm content with my decision, as it aligns with my goal of reducing friction in the writing process.
|
||||
|
||||
## How much everything costs
|
||||
## 3 - How much everything costs
|
||||
|
||||
I'm often asked how much it costs to run my website. Here's a breakdown of the costs:
|
||||
|
||||
@@ -103,6 +120,6 @@ I'm often asked how much it costs to run my website. Here's a breakdown of the c
|
||||
|
||||
Total: 0€ thanks to nuxt free plan and cloudflare free plan
|
||||
|
||||
## Thanks
|
||||
## 4 - Thanks
|
||||
|
||||
I want to thank the Nuxt team for their hard work and dedication to the project. I also want to thank the community for their support and for providing me with the tools I needed to build this site. I want to add a special thanks to [Estéban](https://x.com/soubiran_) for solving `All` my problems and for inspiring me to rewrite my website.
|
||||
@@ -5,7 +5,6 @@ description: This article introduces neural networks, explaining their structure
|
||||
readingTime: 3
|
||||
publishedAt: 2025/03/30
|
||||
tags:
|
||||
- article
|
||||
- ai
|
||||
- maths
|
||||
- python
|
||||
@@ -13,20 +12,20 @@ tags:
|
||||
|
||||
Neural networks are a class of machine learning algorithms inspired by the functioning of biological neurons. They are widely used in artificial intelligence for image recognition, natural language processing, time series forecasting, and many other fields. Thanks to their ability to model complex relationships in data, they have become one of the pillars of **deep learning**.
|
||||
|
||||
## 1. Basic Structure of a Neural Network
|
||||
## 1 - Basic Structure of a Neural Network
|
||||
|
||||
### 1.1 Neurons and Biological Inspiration
|
||||
### 1.1 - Neurons and Biological Inspiration
|
||||
|
||||
Neural networks are inspired by the way the human brain processes information. Each artificial neuron mimics a biological neuron, receiving inputs, applying a transformation, and passing the result to the next layer.
|
||||
|
||||
### 1.2 Layer Organization (Input, Hidden, Output)
|
||||
### 1.2 - Layer Organization (Input, Hidden, Output)
|
||||
|
||||
A neural network consists of layers:
|
||||
- **Input layer**: Receives raw data.
|
||||
- **Hidden layers**: Perform intermediate transformations and extract important features.
|
||||
- **Output layer**: Produces the final model prediction.
|
||||
|
||||
### 1.3 Weights and Biases
|
||||
### 1.3 - Weights and Biases
|
||||
|
||||
Each neuron connection has an associated **weight** $ w $, and each neuron has a **bias** $ b $. The transformation applied at each neuron before activation is given by:
|
||||
|
||||
@@ -34,11 +33,11 @@ $$
|
||||
z = W \cdot X + b
|
||||
$$
|
||||
|
||||
### 1.4 Neural Network Structure Visualization
|
||||
### 1.4 - Neural Network Structure Visualization
|
||||
|
||||
::prose-img
|
||||
---
|
||||
src: /portfolio/neural-network/neural-network-viz.png
|
||||
src: /writings/neural-network/neural-network-viz.png
|
||||
label: Neural Network Structure
|
||||
caption: Neural Network Structure
|
||||
---
|
||||
@@ -52,22 +51,22 @@ Starting from the left, we have:
|
||||
- The output layer (a.k.a. the prediction) of our model in green.
|
||||
- The arrows that connect the dots shows how all the neurons are interconnected and how data travels from the input layer all the way through to the output layer.
|
||||
|
||||
## 2. Information Propagation (Forward Pass)
|
||||
## 2 - Information Propagation (Forward Pass)
|
||||
|
||||
### 2.1 Linear Transformation $ z = W \cdot X + b $
|
||||
### 2.1 - Linear Transformation $ z = W \cdot X + b $
|
||||
|
||||
Each neuron computes a weighted sum of its inputs plus a bias term before applying an activation function.
|
||||
|
||||
### 2.2 Activation Functions (ReLU, Sigmoid, Softmax)
|
||||
### 2.2 - Activation Functions (ReLU, Sigmoid, Softmax)
|
||||
|
||||
Activation functions introduce **non-linearity**, enabling networks to learn complex patterns:
|
||||
- **ReLU**: $ f(z) = \max(0, z) $ (fast training, avoids vanishing gradients)
|
||||
- **Sigmoid**: $ \sigma(z) = \frac{1}{1 + e^{-z}} $ (useful for binary classification)
|
||||
- **Softmax**: Converts outputs into probability distributions for multi-class classification.
|
||||
|
||||
## 3. Learning and Backpropagation
|
||||
## 3 - Learning and Backpropagation
|
||||
|
||||
### 3.1 Cost Function (MSE, Cross-Entropy)
|
||||
### 3.1 - Cost Function (MSE, Cross-Entropy)
|
||||
|
||||
To measure error, different loss functions are used:
|
||||
- **Mean Squared Error (MSE)**:
|
||||
@@ -81,7 +80,7 @@ To measure error, different loss functions are used:
|
||||
|
||||
Where, $y$ represents the true values or labels, while $\hat{y}$represents the predicted values produced by the model. The goal is to minimize this difference during training.
|
||||
|
||||
### 3.2 Gradient Descent and Weight Updates
|
||||
### 3.2 - Gradient Descent and Weight Updates
|
||||
|
||||
Training consists of adjusting weights to minimize loss using **gradient descent**:
|
||||
|
||||
@@ -89,13 +88,13 @@ $$
|
||||
w := w - \alpha \frac{\partial L}{\partial w}, \quad b := b - \alpha \frac{\partial L}{\partial b}
|
||||
$$
|
||||
|
||||
### 3.3 Gradient Propagation via the Chain Rule
|
||||
### 3.3 - Gradient Propagation via the Chain Rule
|
||||
|
||||
Using **backpropagation**, the error is propagated backward through the network using the chain rule, adjusting each weight accordingly.
|
||||
|
||||
## 4. Optimization and Regularization
|
||||
## 4 - Optimization and Regularization
|
||||
|
||||
### 4.1 Optimization Algorithms (SGD, Adam)
|
||||
### 4.1 - Optimization Algorithms (SGD, Adam)
|
||||
|
||||
- **Stochastic Gradient Descent (SGD)**: Updates weights after each sample.
|
||||
- **Adam**: A more advanced optimizer that adapts learning rates per parameter.
|
||||
@@ -104,7 +103,7 @@ The gradient of a function is the vector whose elements are its partial derivati
|
||||
|
||||
::prose-img
|
||||
---
|
||||
src: /portfolio/neural-network/gradient-descent.png
|
||||
src: /writings/neural-network/gradient-descent.png
|
||||
label: Gradient Descent
|
||||
caption: Gradient Descent
|
||||
---
|
||||
@@ -114,13 +113,13 @@ caption: Gradient Descent
|
||||
2. Modify each parameter by an amount proportional to its gradient element and in the opposite direction of its gradient element. For example, if the partial derivative of our cost function with respect to B0 is positive but tiny and the partial derivative with respect to B1 is negative and large, then we want to decrease B0 by a tiny amount and increase B1 by a large amount to lower our cost function.
|
||||
3. Recompute the gradient using our new tweaked parameter values and repeat the previous steps until we arrive at the minimum.
|
||||
|
||||
### 4.2 Regularization to Avoid Overfitting (Dropout, L1/L2)
|
||||
### 4.2 - Regularization to Avoid Overfitting (Dropout, L1/L2)
|
||||
|
||||
To prevent overfitting:
|
||||
- **Dropout** randomly disables neurons during training.
|
||||
- **L1/L2 regularization** penalizes large weights to encourage simpler models.
|
||||
|
||||
## 5. Network Architectures
|
||||
## 5 - Network Architectures
|
||||
|
||||
Multi-Layer Perceptron (MLP)
|
||||
A standard feedforward neural network with multiple layers.
|
||||
@@ -134,26 +133,26 @@ Useful for time series and natural language tasks.
|
||||
Transformers for NLP and Vision
|
||||
State-of-the-art architecture for language understanding and vision tasks.
|
||||
|
||||
## 6. Training and Evaluation
|
||||
## 6 - Training and Evaluation
|
||||
|
||||
### 6.1 Data Splitting (Train/Test Split)
|
||||
### 6.1 - Data Splitting (Train/Test Split)
|
||||
|
||||
To evaluate performance, data is split into **training** and **test** sets.
|
||||
|
||||
### 6.2 Evaluation Metrics (Accuracy, Precision, Recall, RMSE, R²)
|
||||
### 6.2 - Evaluation Metrics (Accuracy, Precision, Recall, RMSE, R²)
|
||||
|
||||
Metrics depend on the task:
|
||||
- **Accuracy, Precision, Recall** for classification.
|
||||
- **RMSE, R²** for regression.
|
||||
|
||||
### 6.3 Hyperparameter Tuning
|
||||
### 6.3 - Hyperparameter Tuning
|
||||
|
||||
Choosing the right:
|
||||
- **Learning rate**
|
||||
- **Batch size**
|
||||
- **Number of layers and neurons**
|
||||
|
||||
## Example: A Neural Network with Two Hidden Layers
|
||||
## 7 - Example: A Neural Network with Two Hidden Layers
|
||||
|
||||
The following example demonstrates a simple **multi-layer perceptron (MLP)** with two hidden layers, trained to perform linear regression.
|
||||
|
||||
@@ -189,6 +188,6 @@ plt.legend()
|
||||
plt.show()
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
## 8 - Conclusion
|
||||
|
||||
Neural networks form the foundation of modern artificial intelligence. Their ability to learn from data and generalize to new situations makes them essential for applications like computer vision, automatic translation, and predictive medicine. 🚀
|
||||
125
content/writings/rag-ai-agents.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
slug: rag-ai-agents
|
||||
title: "Understanding AI Agents, LLMs, and RAG: A Powerful Synergy"
|
||||
description: Explore how AI agents, Large Language Models (LLMs), and Retrieval-Augmented Generation (RAG) combine to create intelligent, autonomous systems that reason, act, and interact with real-time data.
|
||||
readingTime: 5
|
||||
publishedAt: 2025/04/06
|
||||
tags:
|
||||
- ai
|
||||
---
|
||||
|
||||
In the rapidly evolving world of artificial intelligence, the combination of Large Language Models (LLMs), AI agents, and Retrieval-Augmented Generation (RAG) is driving new possibilities for autonomous systems. These components, when integrated, create intelligent systems capable of performing complex tasks, reasoning, and interacting with the world around them. In this post, we'll dive into each of these elements and explore their synergy.
|
||||
|
||||
## 1 - What is an Agent?
|
||||
|
||||
An **AI agent** is an autonomous entity capable of perceiving its environment, making decisions, and taking actions based on its observations. In simpler terms, an agent is a system that can autonomously interact with the world or other systems to achieve a specific goal.
|
||||
|
||||
Agents can be divided into two broad categories:
|
||||
|
||||
- **Reactive Agents**: These agents respond to specific stimuli without maintaining any internal state. They simply react based on their immediate environment.
|
||||
- **Proactive Agents**: These agents not only react to their environment but also plan and act based on future goals.
|
||||
|
||||
In modern AI, agents are often used in robotics, virtual assistants, and autonomous systems like **AutoGPT** or **LangChain**, which are capable of autonomously handling tasks like research, writing, and decision-making.
|
||||
|
||||
## 2 - What is a LLM?
|
||||
|
||||
A **Large Language Model (LLM)** is a machine learning model trained on vast amounts of text data, enabling it to understand and generate human-like language. These models, like **GPT (e.g., GPT-4)**, **Claude**, **Mistral**, and **LLaMA**, can produce coherent and contextually relevant responses to a wide range of queries.
|
||||
|
||||
LLMs work by predicting the next token (word or part of a word) based on the input they receive. This ability allows them to generate text, summarize documents, answer questions, and even carry on conversations that seem remarkably human.
|
||||
|
||||
However, LLMs have their limitations. They can sometimes generate **hallucinations** (incorrect or fabricated information), and their knowledge is **static**, meaning they can become outdated as they don’t automatically update from the web.
|
||||
|
||||
## 3 - Messages and Tokens
|
||||
|
||||
When interacting with LLMs or agents, information is transmitted through **messages** and **tokens**.
|
||||
|
||||
- **Messages** are the pieces of communication sent between the user and the system (or between different components of the AI system). These can be user queries, responses, or commands.
|
||||
|
||||
- **Tokens** are the basic units of text that an LLM processes. A token could be a word, part of a word, or even punctuation. In GPT models, a single token can represent a word like "dog" or even part of a word like "re-" in "reliable."
|
||||
|
||||
Managing tokens is essential because LLMs have a **token limit**, meaning they can only handle a fixed number of tokens in a single input/output sequence. This limit impacts performance and context retention. Long conversations or documents might require careful handling of token counts to maintain coherence.
|
||||
|
||||
## 4 - What Are Tools?
|
||||
|
||||
In the context of AI agents, **tools** refer to external resources or systems that the agent can access to help it accomplish tasks. These tools can include:
|
||||
|
||||
- **APIs** for data retrieval (like web scraping or querying databases)
|
||||
- **External software** (e.g., calculation engines, email clients)
|
||||
- **Search engines** to find up-to-date information
|
||||
|
||||
For example, an AI agent might use a **search engine** tool to retrieve the latest news articles, then generate a summary based on that real-time data. These tools allow agents to extend their capabilities beyond the static information contained within their training data.
|
||||
|
||||
## 5 - Thought - Actions - Observe
|
||||
|
||||
An AI agent typically follows a basic cycle of:
|
||||
|
||||
1. **Thought**: The agent processes its input and reasons through the task using models like LLMs.
|
||||
|
||||
| Type of Thought | Example |
|
||||
|---------------------|-------------------------------------------------------------------------|
|
||||
| Planning | “I need to break this task into three steps: 1) gather data, 2) analyze trends, 3) generate report” |
|
||||
| Analysis | “Based on the error message, the issue appears to be with the database connection parameters” |
|
||||
| Decision Making | “Given the user's budget constraints, I should recommend the mid-tier option” |
|
||||
| Problem Solving | “To optimize this code, I should first profile it to identify bottlenecks” |
|
||||
| Memory Integration | “The user mentioned their preference for Python earlier, so I'll provide examples in Python” |
|
||||
| Self-Reflection | “My last approach didn't work well, I should try a different strategy” |
|
||||
| Goal Setting | “To complete this task, I need to first establish the acceptance criteria” |
|
||||
| Prioritization | “The security vulnerability should be addressed before adding new features” |
|
||||
|
||||
2. **Action**: The agent performs an action, such as sending a query, interacting with an API, or initiating a task.
|
||||
|
||||
| Type of Action | Description |
|
||||
|---------------------|-----------------------------------------------------------------------|
|
||||
| Information Gathering| Performing web searches, querying databases, or retrieving documents. |
|
||||
| Tool Usage | Making API calls, running calculations, and executing code. |
|
||||
| Environment Interaction | Manipulating digital interfaces or controlling physical devices. |
|
||||
| Communication | Engaging with users via chat or collaborating with other agents. |
|
||||
|
||||
3. **Observe**: The agent receives feedback or new data from the environment, which informs its next set of decisions.
|
||||
|
||||
| Type of Observation | Example |
|
||||
|-----------------------|--------------------------------------------------------------------------|
|
||||
| System Feedback | Error messages, success notifications, status codes |
|
||||
| Data Changes | Database updates, file system modifications, state changes |
|
||||
| Environmental Data | Sensor readings, system metrics, resource usage |
|
||||
| Response Analysis | API responses, query results, computation outputs |
|
||||
| Time-based Events | Deadlines reached, scheduled tasks completed |
|
||||
|
||||
This cycle allows agents to learn, adapt, and improve over time. It also highlights the agent's ability to take actions in real-time, often guided by LLMs, which provide the reasoning and decision-making capabilities.
|
||||
|
||||
To better understand these steps, here's a breakdown of the types of **thought**, **action**, and **observation** an AI agent might engage in:
|
||||
|
||||
This **Thought → Action → Observe** cycle enables agents to make informed decisions, take meaningful actions, and adjust their behavior based on the results of those actions. Each step is critical for ensuring that the agent remains effective, adaptable, and responsive to its environment.
|
||||
|
||||
## 6 - What is RAG?
|
||||
|
||||
**Retrieval-Augmented Generation (RAG)** is a technique that enhances LLMs by integrating them with external data retrieval systems. In essence, RAG combines the power of **retrieval-based methods** (finding relevant information from external sources) with **generation-based methods** (creating text based on that information).
|
||||
|
||||
Here's how it works:
|
||||
1. The LLM retrieves relevant data or documents using a search engine or database query.
|
||||
2. The LLM then generates a response based on the retrieved information.
|
||||
|
||||
RAG solves a major problem with LLMs: the **outdated or incomplete information** they may have. By pulling in real-time data, RAG ensures that the generated content is relevant and grounded in current knowledge.
|
||||
|
||||
A classic example of RAG is when you ask an AI to summarize the latest research on a particular topic. Instead of relying on the model’s static knowledge base, the model can retrieve relevant papers or articles and generate an accurate summary.
|
||||
|
||||
## 7 - Synergy Between RAG and AI Agents
|
||||
|
||||
The combination of **RAG** and **AI agents** opens the door to highly autonomous systems capable of not only generating content but also acting on the information in real-time.
|
||||
|
||||
Here's how they complement each other:
|
||||
|
||||
- **RAG** acts as an external memory or knowledge source for AI agents, providing them with up-to-date information to improve their decision-making and outputs.
|
||||
- **AI agents**, powered by LLMs, can process this information and take actions based on it, whether it's generating a response, making a decision, or interacting with other systems.
|
||||
|
||||
For example, imagine an AI agent that's tasked with assisting a business in handling customer inquiries. It could use RAG to retrieve relevant customer information and FAQs, then generate a response based on that data. It might then take action by sending an email or updating a CRM system based on the customer’s query.
|
||||
|
||||
This synergy leads to **autonomous, efficient systems** that can process, reason, and act in a dynamic environment.
|
||||
|
||||
## 8 - Conclusion
|
||||
|
||||
The combination of **RAG**, **LLMs**, and **AI agents** represents a major leap forward in the field of AI. By merging data retrieval with powerful language models and autonomous agents, we can create intelligent systems that are more accurate, adaptable, and autonomous than ever before.
|
||||
|
||||
As these technologies evolve, we can expect even more sophisticated systems capable of transforming industries like healthcare, finance, customer service, and beyond. The future is bright for intelligent agents powered by this synergy!
|
||||
|
||||
If you have questions or want to dive deeper into any of the topics covered, feel free to comment below!
|
||||
@@ -5,12 +5,11 @@ description: An introduction to machine learning, exploring its main types, key
|
||||
readingTime: 3
|
||||
publishedAt: 2024/11/26
|
||||
tags:
|
||||
- article
|
||||
- ai
|
||||
- maths
|
||||
---
|
||||
|
||||
## Introduction
|
||||
## 1 - Introduction
|
||||
|
||||
Machine Learning (ML) is a key discipline in artificial intelligence (AI), enabling systems to learn from data to make predictions or discover patterns. It is the driving force behind many modern innovations, from personalised recommendations to autonomous vehicles.
|
||||
|
||||
@@ -21,7 +20,7 @@ In this article, we will cover:
|
||||
3. **The typical ML workflow**, exploring the essential steps for developing a model.
|
||||
4. **Model evaluation through the R² score**, an important metric for regression problems.
|
||||
|
||||
## The Types of Machine Learning
|
||||
## 2 - The Types of Machine Learning
|
||||
|
||||
To start, it is important to understand the three main categories of machine learning:
|
||||
|
||||
@@ -38,7 +37,7 @@ To start, it is important to understand the three main categories of machine lea
|
||||
|
||||
::prose-img
|
||||
---
|
||||
src: /portfolio/ML/types.png
|
||||
src: /writings/ML/types.png
|
||||
label: ML Model Types
|
||||
caption: The different types of machine learning models
|
||||
---
|
||||
@@ -46,7 +45,7 @@ caption: The different types of machine learning models
|
||||
|
||||
With this overview of ML types, let’s now focus on supervised learning, the most widely used approach, and explore how to choose the right model.
|
||||
|
||||
## Three Considerations for Choosing a Supervised Learning Model
|
||||
## 3 - Three Considerations for Choosing a Supervised Learning Model
|
||||
|
||||
Selecting the right supervised learning model is critical and depends on several factors:
|
||||
|
||||
@@ -64,7 +63,7 @@ Selecting the right supervised learning model is critical and depends on several
|
||||
|
||||
Once the model type is defined, the next step is to delve into the full workflow of developing an ML model.
|
||||
|
||||
## The Typical Workflow in Machine Learning
|
||||
## 4 - The Typical Workflow in Machine Learning
|
||||
|
||||
A machine learning project generally follows these steps:
|
||||
|
||||
@@ -80,7 +79,7 @@ A machine learning project generally follows these steps:
|
||||
|
||||
::prose-img
|
||||
---
|
||||
src: /portfolio/ML/model.png
|
||||
src: /writings/ML/model.png
|
||||
label: Modelization in Progress
|
||||
caption: Modelization in Progress
|
||||
---
|
||||
@@ -88,7 +87,7 @@ caption: Modelization in Progress
|
||||
|
||||
Evaluation is a crucial step to verify the performance of a model. For regression problems, the R² score is a key indicator.
|
||||
|
||||
## Evaluating Models: The R² Score
|
||||
## 5 - Evaluating Models: The R² Score
|
||||
|
||||
For regression problems, the **R² score** measures the proportion of the target’s variance explained by the model:
|
||||
|
||||
@@ -101,7 +100,7 @@ A $$R^2$$ close to 1 indicates a good fit.
|
||||
|
||||
::prose-img
|
||||
---
|
||||
src: /portfolio/ML/r2.png
|
||||
src: /writings/ML/r2.png
|
||||
label: R² Score
|
||||
caption: R² Score
|
||||
---
|
||||
@@ -109,6 +108,6 @@ caption: R² Score
|
||||
|
||||
With these concepts in mind, you are better equipped to understand and apply ML models in your projects.
|
||||
|
||||
## Conclusion
|
||||
## 6 - Conclusion
|
||||
|
||||
Machine learning is revolutionising how we solve complex problems using data. Supervised, unsupervised, and reinforcement learning approaches allow us to tackle a wide variety of use cases. In supervised learning, the model choice depends on the problem type, its complexity, and the appropriate algorithmic approach. Finally, a structured workflow and metrics like the R² score ensure the quality of predictions and analyses.
|
||||
19
package.json
@@ -18,35 +18,34 @@
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@iconify-json/twemoji": "^1.2.2",
|
||||
"@iconify-json/vscode-icons": "^1.2.18",
|
||||
"@intlify/message-compiler": "^11.1.2",
|
||||
"@intlify/message-compiler": "^11.1.3",
|
||||
"@nuxt/content": "3.4.0",
|
||||
"@nuxt/image": "^1.10.0",
|
||||
"@nuxt/ui": "3.0.2",
|
||||
"@nuxthub/core": "^0.8.22",
|
||||
"@nuxthub/core": "^0.8.23",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@nuxtjs/i18n": "9.4.0",
|
||||
"cobe": "^0.6.3",
|
||||
"@nuxtjs/i18n": "9.5.2",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"h3-zod": "^0.5.3",
|
||||
"nuxt": "^3.16.1",
|
||||
"nuxt": "^3.16.2",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"vue-use-spring": "^0.3.3",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.11.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^22.13.16",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/math": "^13.0.0",
|
||||
"@vueuse/nuxt": "^13.0.0",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"eslint": "^9.23.0",
|
||||
"typescript": "^5.8.2",
|
||||
"eslint": "^9.24.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vue-tsc": "^2.2.8",
|
||||
"wrangler": "^4.6.0"
|
||||
"wrangler": "^4.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
1473
pnpm-lock.yaml
generated
BIN
public/arthur.webp
Normal file
|
After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 302 KiB |
BIN
public/favicon.webp
Normal file
|
After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 316 KiB |
|
Before Width: | Height: | Size: 946 KiB After Width: | Height: | Size: 946 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
20
types.ts
@@ -32,6 +32,9 @@ interface LanyardActivity {
|
||||
timestamps: {
|
||||
start: number
|
||||
}
|
||||
assets?: {
|
||||
small_text: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
@@ -45,6 +48,7 @@ export const IDEs = [
|
||||
{ name: 'IntelliJ IDEA Ultimate', icon: 'i-logos:intellij-idea' },
|
||||
{ name: 'WebStorm', icon: 'i-logos:webstorm' },
|
||||
{ name: 'PyCharm Professional', icon: 'i-logos:pycharm' },
|
||||
{ name: 'Cursor', icon: 'i-vscode-icons-file-type-cursorrules' },
|
||||
]
|
||||
|
||||
export const activityMessages = {
|
||||
@@ -66,13 +70,13 @@ export const activityMessages = {
|
||||
invalid: '',
|
||||
},
|
||||
fr: {
|
||||
justNow: 'à l’instant',
|
||||
justNow: 'à l\'instant',
|
||||
past: (n: string) => (n.match(/\d/) ? `il y a ${n}` : n),
|
||||
future: (n: string) => (n.match(/\d/) ? `dans ${n}` : n),
|
||||
month: (n: number, past: boolean) =>
|
||||
n === 1 ? (past ? 'le mois dernier' : 'le mois prochain') : `${n} mois`,
|
||||
year: (n: number, past: boolean) =>
|
||||
n === 1 ? (past ? 'l’année dernière' : 'l’année prochaine') : `${n} ans`,
|
||||
n === 1 ? (past ? 'l\'année dernière' : 'l\'année prochaine') : `${n} ans`,
|
||||
day: (n: number, past: boolean) =>
|
||||
n === 1 ? (past ? 'hier' : 'demain') : `${n} jours`,
|
||||
week: (n: number, past: boolean) =>
|
||||
@@ -110,18 +114,6 @@ export interface Tag {
|
||||
}
|
||||
|
||||
export const TAGS: Array<Tag> = [
|
||||
{
|
||||
label: 'Article',
|
||||
icon: 'i-ph-pencil-line-duotone',
|
||||
color: 'red',
|
||||
translation: 'tags.article',
|
||||
},
|
||||
{
|
||||
label: 'Project',
|
||||
icon: 'i-ph-briefcase-duotone',
|
||||
color: 'blue',
|
||||
translation: 'tags.project',
|
||||
},
|
||||
{
|
||||
label: 'R',
|
||||
icon: 'i-vscode-icons-file-type-r',
|
||||
|
||||