mirror of
https://github.com/ArthurDanjou/artchat.git
synced 2026-02-02 23:31:29 +01:00
Compare commits
12 Commits
16a00b08c1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ed7228b60 | |||
| d88fd80aee | |||
| 8856e77ae1 | |||
| 48e6043205 | |||
| 22c93c509d | |||
| 6c5b561d49 | |||
| dbebcd23a5 | |||
| fa0421c51d | |||
| 6e648526d4 | |||
|
|
f586394f80 | ||
| 506152a986 | |||
| f73276df8f |
34
.github/workflows/nuxthub.yml
vendored
Normal file
34
.github/workflows/nuxthub.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Deploy to NuxtHub
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: "Deploy to NuxtHub"
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Ensure NuxtHub module is installed
|
||||
run: bunx nuxthub@latest ensure
|
||||
|
||||
- name: Build & Deploy to NuxtHub
|
||||
uses: nuxt-hub/action@v2
|
||||
with:
|
||||
project-key: artchat-vuju
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,4 +24,5 @@ logs
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.vercel
|
||||
|
||||
.wrangler
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import { Analytics } from '@vercel/analytics/nuxt'
|
||||
import { SpeedInsights } from '@vercel/speed-insights/nuxt'
|
||||
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/webp', href: '/favicon.webp' }],
|
||||
})
|
||||
@@ -31,7 +28,7 @@ const head = useLocaleHead()
|
||||
<ChatCommandPalette
|
||||
v-motion
|
||||
:active="messages.length > 0"
|
||||
:mode="route.path.includes('/projects') || route.path.includes('/writings') || route.path.includes('/canva') ? 'work' : 'chat'"
|
||||
:mode="route.path.includes('/projects') || route.path.includes('/writings') ? 'work' : 'chat'"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 200,
|
||||
@@ -48,8 +45,6 @@ const head = useLocaleHead()
|
||||
}"
|
||||
/>
|
||||
<NuxtPage />
|
||||
<SpeedInsights />
|
||||
<Analytics />
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const searchTerm = ref('')
|
||||
const openMessageModal = ref(false)
|
||||
const openClearModal = ref(false)
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
const { messages, submitMessage } = useChat()
|
||||
const { clearMessages, messages: storeMessages } = useChatStore()
|
||||
|
||||
@@ -72,7 +72,7 @@ const toolTipContent = {
|
||||
align: 'center',
|
||||
side: 'top',
|
||||
sideOffset: 0,
|
||||
}
|
||||
} as any
|
||||
|
||||
const router = useRouter()
|
||||
function goHome() {
|
||||
@@ -134,7 +134,7 @@ function isRoute(name: string): boolean {
|
||||
<div class="absolute inset-0 -m-1" />
|
||||
<div class="flex items-center gap-2.5">
|
||||
<UIcon :name="item.icon!" size="20" />
|
||||
<span>{{ t(item.label) }}</span>
|
||||
<span>{{ t(item.label || '') }}</span>
|
||||
</div>
|
||||
<div class="text-dimmed text-xs font-medium text-start">
|
||||
{{ t(item.prompt) }}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChatState } from '~~/types'
|
||||
const props = defineProps<{ messageId: number, fetchStates: ChatFetchState[] }>()
|
||||
const currentState = ref<ChatFetchState | undefined>(props.fetchStates[0] ?? undefined)
|
||||
const { setLoadingState } = useChatStore()
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
onMounted(() => {
|
||||
let index = 0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
||||
|
||||
const isArthur = computed(() => props.message.sender === ChatSender.ARTHUR)
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
const formatDate = computed(() => useDateFormat(props.message.createdAt, 'D MMMM YYYY, HH:mm', { locales: locale.value ?? 'en' }).value)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const props = defineProps<{
|
||||
message: ChatMessage
|
||||
}>()
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
const formatDate = computed(() => useDateFormat(props.message.createdAt, 'D MMMM YYYY, HH:mm', { locales: locale.value ?? 'en' }).value)
|
||||
|
||||
const componentMap: Record<ChatType, Component | undefined> = {
|
||||
@@ -66,9 +66,19 @@ const dynamicComponent = computed(() => componentMap[props.message.type])
|
||||
</UCard>
|
||||
<UCard
|
||||
v-else
|
||||
v-motion
|
||||
variant="soft"
|
||||
class="mt-1 w-full max-w-none bg-transparent"
|
||||
:ui="{ body: 'p-0 sm:p-0', header: 'p-0 sm:p-0', footer: 'p-0 sm:p-0' }"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { ease: 'easeInOut', duration: 300, delay: 500 },
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="dynamicComponent"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="mt-8 shadow-sm bg-white dark:bg-neutral-900">
|
||||
<NuxtImg
|
||||
src="/arthur pro.webp"
|
||||
src="/arthur-pro.webp"
|
||||
alt="Arthur Danjou"
|
||||
class="w-24 h-24 rounded-full float-left mr-4 mb-4"
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { UseTimeAgoMessages } from '@vueuse/core'
|
||||
import type { Activity } from '~~/types'
|
||||
import { activityMessages, IDEs } from '~~/types'
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch<Activity>('/api/activity'))
|
||||
|
||||
useIntervalFn(async () => await refresh(), 5000)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { socials } from '~~/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const year = ref(useNow().value.getFullYear())
|
||||
</script>
|
||||
@@ -17,12 +17,12 @@ const year = ref(useNow().value.getFullYear())
|
||||
class="px-0"
|
||||
/>
|
||||
</template>
|
||||
<template #vercel>
|
||||
<template #cloudflare>
|
||||
<UButton
|
||||
label="Vercel"
|
||||
trailing-icon="i-logos-vercel-icon"
|
||||
label="Cloudflare"
|
||||
trailing-icon="i-logos-cloudflare-icon"
|
||||
variant="link"
|
||||
to="https://vercel.com"
|
||||
to="https://cloudflare.com"
|
||||
target="_blank"
|
||||
class="px-0"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
const { data: experiences } = await useAsyncData('experiences', async () => await queryCollection('experiences').all())
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
const formatDate = (date: string) => useDateFormat(new Date(date), 'MMM YYYY', { locales: locale.value ?? 'en' }).value
|
||||
function getLanguageForText(text: { en: string, es: string, fr: string }) {
|
||||
return locale.value === 'en' ? text.en : locale.value === 'es' ? text.es : text.fr
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { en, es, fr } from '@nuxt/ui/locale'
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
const { changeLocale } = useLanguage()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="m-1 md:max-w-2/3 shadow-sm rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden relative z-10">
|
||||
<NuxtImg class="rounded-xl" src="/location.png" />
|
||||
<div class="size-12 rounded-full border-2 border-sky-500 absolute z-50 top-2/5 -translate-y-1/2 left-1/5 -translate-x-1/2 animate-bounce">
|
||||
<NuxtImg src="/arthur pro.webp" class="rounded-full" alt="Location of Arthur" />
|
||||
<NuxtImg src="/arthur-pro.webp" class="rounded-full" alt="Location of Arthur" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const { data: projects } = await useAsyncData('projects-index', async () => await queryCollection('projects').where('favorite', '=', true).select('title', 'description', 'id', 'publishedAt', 'tags', 'slug').all())
|
||||
const date = (date: string) => useDateFormat(new Date(date), 'DD MMMM YYYY', { locales: locale.value ?? 'en' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
interface ResumeFile {
|
||||
name: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
const { data: skills } = await useAsyncData('skills', async () => await queryCollection('skills').first())
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Stats } from '~~/types'
|
||||
|
||||
const { data: stats } = await useAsyncData<Stats>('stats', () => $fetch('/api/stats'))
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const time = useTimeAgo(new Date(stats.value!.coding.data.range.start) ?? new Date()).value.split(' ')[0]
|
||||
const date = useDateFormat(new Date(stats.value!.coding.data.range.start ?? new Date()), 'DD MMMM YYYY', { locales: locale.value ?? 'en' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
const { dark, toggleDark } = useTheme()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
const { data: items } = await useAsyncData(`uses-${props.category}`, async () => await queryCollection('uses').where('category', '=', props.category).all())
|
||||
const { data: categoryData } = await useAsyncData(`category-${props.category}`, async () => await queryCollection('usesCategories').where('slug', '=', props.category).first())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Weather } from '~~/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
const { data: weather } = await useAsyncData<Weather>('weather', () =>
|
||||
$fetch('/api/weather'))
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { locale, t } = useI18n()
|
||||
const { locale, t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const { data: writings } = await useAsyncData('writings-index', async () => await queryCollection('writings').order('publishedAt', 'DESC').select('title', 'description', 'id', 'publishedAt', 'tags', 'slug').limit(2).all())
|
||||
const formatDate = (date: string) => useDateFormat(new Date(date), 'DD MMMM YYYY', { locales: locale.value ?? 'en' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export function useLanguage() {
|
||||
const { setLocale } = useI18n()
|
||||
const { setLocale } = useI18n({ useScope: 'global' })
|
||||
|
||||
async function changeLocale(newLocale: string) {
|
||||
await setLocale(newLocale as 'en' | 'fr' | 'es')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,7 +10,7 @@ const { messages } = useChatStore()
|
||||
const parents = useTemplateRef('parents')
|
||||
const { height } = useElementBounding(parents)
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
const lastLang = ref(locale.value)
|
||||
watch(
|
||||
height,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useDateFormat } from '#imports'
|
||||
|
||||
const route = useRoute()
|
||||
const { data: project } = await useAsyncData(`projects/${route.params.slug}`, () =>
|
||||
queryCollection('projects').path(`/projects/${route.params.slug}`).first())
|
||||
@@ -16,7 +18,9 @@ useSeoMeta({
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
useSeoMeta(project.value.seo || {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
useSeoMeta({
|
||||
title: 'My Projects',
|
||||
description: t('projects.description'),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useDateFormat } from '#imports'
|
||||
|
||||
const route = useRoute()
|
||||
const { data: writing } = await useAsyncData(`writings/${route.params.slug}`, () =>
|
||||
queryCollection('writings').path(`/writings/${route.params.slug}`).first())
|
||||
@@ -10,13 +12,9 @@ if (!writing.value) {
|
||||
})
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: writing.value?.title,
|
||||
description: writing.value?.description,
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const { t } = useI18n()
|
||||
useSeoMeta(writing.value.seo || {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
useSeoMeta({
|
||||
title: 'My Shelf - Arthur DANJOU',
|
||||
description: t('writings.description'),
|
||||
|
||||
@@ -1,104 +1,111 @@
|
||||
import { defineCollection, z } from '@nuxt/content'
|
||||
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
|
||||
import { asSeoCollection } from '@nuxtjs/seo/content'
|
||||
|
||||
export const collections = {
|
||||
projects: defineCollection({
|
||||
type: 'page',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number().optional(),
|
||||
tags: z.array(z.string()),
|
||||
cover: z.string(),
|
||||
favorite: z.boolean().optional(),
|
||||
canva: z.object({
|
||||
height: z.number().default(270),
|
||||
width: z.number().default(480),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
writings: defineCollection({
|
||||
type: 'page',
|
||||
source: 'writings/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number(),
|
||||
cover: z.string().optional(),
|
||||
tags: z.array(z.string()),
|
||||
canva: z.object({
|
||||
height: z.number().default(270),
|
||||
width: z.number().default(480),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
usesCategories: defineCollection({
|
||||
type: 'data',
|
||||
source: 'uses/categories/*.json',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
name: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
uses: defineCollection({
|
||||
type: 'data',
|
||||
source: 'uses/*.json',
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
description: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
category: z.string(),
|
||||
}),
|
||||
}),
|
||||
skills: defineCollection({
|
||||
type: 'data',
|
||||
source: 'skills.json',
|
||||
schema: z.object({
|
||||
body: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
projects: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'page',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number().optional(),
|
||||
tags: z.array(z.string()),
|
||||
cover: z.string(),
|
||||
favorite: z.boolean().optional(),
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
}),
|
||||
),
|
||||
writings: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'page',
|
||||
source: 'writings/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number(),
|
||||
cover: z.string().optional(),
|
||||
tags: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
usesCategories: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'data',
|
||||
source: 'uses/categories/*.json',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
name: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
uses: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'data',
|
||||
source: 'uses/*.json',
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
experiences: defineCollection({
|
||||
type: 'data',
|
||||
source: 'experiences/*.json',
|
||||
schema: z.object({
|
||||
title: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
description: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
category: z.string(),
|
||||
}),
|
||||
}),
|
||||
company: z.string(),
|
||||
companyUrl: z.string().url().optional(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string().optional(),
|
||||
location: z.string(),
|
||||
description: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
),
|
||||
skills: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'data',
|
||||
source: 'skills.json',
|
||||
schema: z.object({
|
||||
body: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
tags: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
),
|
||||
experiences: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'data',
|
||||
source: 'experiences/*.json',
|
||||
schema: z.object({
|
||||
title: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
company: z.string(),
|
||||
companyUrl: z.string().url().optional(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string().optional(),
|
||||
location: z.string(),
|
||||
description: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
tags: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,11 +11,14 @@ tags:
|
||||
- r
|
||||
---
|
||||
|
||||
[ArtStudies](https://go.arthurdanjou.fr/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
|
||||
# ArtStudies
|
||||
|
||||
[ArtStudies Projects](https://github.com/ArthurDanjou/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
|
||||
|
||||
The projects are organized into two main sections:
|
||||
- **L3** – Third year of the Bachelor's degree in Mathematics
|
||||
- **M1** – First year of the Master's degree in Mathematics
|
||||
- **M2** – Second year of the Master's degree in Mathematics
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
@@ -38,6 +41,10 @@ The projects are organized into two main sections:
|
||||
- `Portfolio Management`
|
||||
- `Statistical Learning`
|
||||
|
||||
- `M2`
|
||||
- `Machine Learning`
|
||||
- `SQL`
|
||||
|
||||
## 🛠️ Technologies & Tools
|
||||
|
||||
- [Python](https://www.python.org): A high-level, interpreted programming language, widely used for data science, machine learning, and scientific computing.
|
||||
@@ -49,6 +56,8 @@ The projects are organized into two main sections:
|
||||
- [Scikit-learn](https://scikit-learn.org): A robust library offering simple and efficient tools for machine learning and statistical modeling, including classification, regression, and clustering.
|
||||
- [TensorFlow](https://www.tensorflow.org): A comprehensive open-source framework for building and deploying machine learning and deep learning models.
|
||||
- [Matplotlib](https://matplotlib.org): A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
|
||||
- [Plotly](https://plotly.com): An interactive graphing library for creating dynamic visualizations in Python and R.
|
||||
- [Seaborn](https://seaborn.pydata.org): A statistical data visualization library built on top of Matplotlib, providing a high-level interface for drawing attractive and informative graphics.
|
||||
- [RMarkdown](https://rmarkdown.rstudio.com): A dynamic tool for combining code, results, and narrative into high-quality documents and presentations.
|
||||
- [FactoMineR](https://factominer.free.fr/): An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
|
||||
- [ggplot2](https://ggplot2.tidyverse.org): A grammar-based graphics package for creating complex and elegant visualizations in R.
|
||||
|
||||
@@ -96,10 +96,6 @@
|
||||
"name": "Linux",
|
||||
"icon": "i-logos-linux-tux"
|
||||
},
|
||||
{
|
||||
"name": "Vercel",
|
||||
"icon": "i-logos-vercel-icon"
|
||||
},
|
||||
{
|
||||
"name": "CloudFlare",
|
||||
"icon": "i-logos-cloudflare-icon"
|
||||
@@ -141,7 +137,7 @@
|
||||
"name": {
|
||||
"en": "Python Frameworks",
|
||||
"fr": "Frameworks Python",
|
||||
"es": "Frameworks de Python"
|
||||
"es": "Frameworks Python"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "Theme and Font",
|
||||
"description": {
|
||||
"en": "My theme is Catppuccin Macchiato, a community-driven pastel theme that aims to be the middle ground between low and high contrast themes. My main fonts are Vercel Geist and JetBrains Mono",
|
||||
"fr": "Mon thème est Catppuccin Macchiato, un thème pastel piloté par la communauté qui vise à être le juste milieu entre les thèmes à faible et à fort contraste. Mes polices principales sont Vercel Geist et JetBrains Mono",
|
||||
"es": "Mi tema es Catppuccin Macchiato, un tema pastel impulsado por la comunidad que tiene como objetivo ser el punto intermedio entre los temas de bajo y alto contraste. Mis fuentes principales son Vercel Geist y JetBrains Mono"
|
||||
"en": "My theme is Catppuccin Macchiato, a community-driven pastel theme that aims to be the middle ground between low and high contrast themes. My main font is JetBrains Mono",
|
||||
"fr": "Mon thème est Catppuccin Macchiato, un thème pastel piloté par la communauté qui vise à être le juste milieu entre les thèmes à faible et à fort contraste. Ma police principale est JetBrains Mono",
|
||||
"es": "Mi tema es Catppuccin Macchiato, un tema pastel impulsado por la comunidad que tiene como objetivo ser el punto intermedio entre los temas de bajo y alto contraste. Mi fuente principal es JetBrains Mono"
|
||||
},
|
||||
"category": "ide"
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
},
|
||||
"hobbies": "Outside of programming and my technical projects, I dedicate much of my free time to my passions: sports, music, traveling, and spending time with friends. Sports teach me discipline and perseverance, music fuels my creativity, and traveling opens me up to new cultures and ways of thinking, which also nurtures my intellectual curiosity.\nThese passions help me maintain balance and strengthen the qualities I bring to both my studies and my career: curiosity, commitment, autonomy, and a constant desire to improve. They make me someone who is motivated, adaptable, and always ready to take on new challenges.",
|
||||
"credits": {
|
||||
"made": "This site was designed with {nuxt} and then deployed via {vercel}",
|
||||
"made": "This site was designed with {nuxt} and then deployed via {cloudflare}",
|
||||
"heart": "Made with ❤️ and a lot of reflection.",
|
||||
"chat": "A big thank you to {chat}, my personal assistant, always ready to answer your questions with clarity and speed.",
|
||||
"copyrights": "© {year} Arthur Danjou - All rights reserved.",
|
||||
@@ -230,9 +230,6 @@
|
||||
},
|
||||
"top": "Go to top"
|
||||
},
|
||||
"canva": {
|
||||
"title": "Loading the canva ..."
|
||||
},
|
||||
"writings": {
|
||||
"description": "All my reflections on programming, mathematics, the conception of artificial intelligence, etc., are put in chronological order.",
|
||||
"title": "Writings on math, artificial intelligence, development, and my passions.",
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
"wind": "Viento"
|
||||
},
|
||||
"credits": {
|
||||
"made": "Este sitio fue diseñado con {nuxt} y luego se implementó a través de {vercel}",
|
||||
"made": "Este sitio fue diseñado con {nuxt} y luego se implementó a través de {cloudflare}",
|
||||
"heart": "Hecho con ❤️ y mucho reflexión.",
|
||||
"chat": "Muchas gracias a {chat}, mi asistente personal, siempre listo para responder a sus preguntas con claridad y velocidad.",
|
||||
"copyrights": "© {year} Arthur Danjou - Todos los derechos reservados.",
|
||||
@@ -233,9 +233,6 @@
|
||||
"top": "Ir arriba"
|
||||
},
|
||||
"alert": "Por falta de tiempo, no tuve tiempo para traducir este contenido al francés. Gracias por su comprensión.",
|
||||
"canva": {
|
||||
"title": "Cargando el lienzo ..."
|
||||
},
|
||||
"writings": {
|
||||
"description": "Todas mis reflexiones sobre programación, matemáticas, la concepción de la inteligencia artificial, etc., están organizadas en orden cronológico.",
|
||||
"title": "Escritos sobre matemáticas, inteligencia artificial, desarrollo y mis pasiones.",
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
},
|
||||
"hobbies": "En dehors de la programmation et de mes projets techniques, je consacre une grande partie de mon temps libre à mes passions : le sport, la musique, les voyages et les moments partagés entre amis. Le sport m'apporte rigueur et persévérance, la musique stimule ma créativité, et voyager m'ouvre à d'autres cultures, à d'autres façons de penser, ce qui nourrit aussi ma curiosité intellectuelle. Ces passions m'aident à garder un bon équilibre et renforcent les qualités que je mobilise dans mes études et ma carrière: curiosité, engagement, autonomie et volonté constante de progresser. Elles font de moi quelqu'un de motivé, adaptable, et toujours prêt à relever de nouveaux défis.",
|
||||
"credits": {
|
||||
"made": "Ce site a été conçu avec {nuxt} et puis déployé via {vercel}",
|
||||
"made": "Ce site a été conçu avec {nuxt} et puis déployé via {cloudflare}",
|
||||
"heart": "Réalisé avec ❤️ et beaucoup de réflexion.",
|
||||
"chat": "Un grand merci à {chat}, mon assistant personnel, toujours prêt à répondre à vos questions avec clarté et rapidité.",
|
||||
"copyrights": "© {year} Arthur DANJOU — Tous droits réservés.",
|
||||
@@ -231,9 +231,6 @@
|
||||
},
|
||||
"top": "Remonter en haut"
|
||||
},
|
||||
"canva": {
|
||||
"title": "Chargement du canva..."
|
||||
},
|
||||
"writings": {
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique.",
|
||||
"title": "Écrits sur les maths, l'intelligence artificielle, le développement et mes passions.",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { definePerson } from 'nuxt-schema-org/schema'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-20',
|
||||
|
||||
@@ -17,22 +19,45 @@ export default defineNuxtConfig({
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
// Nuxt Modules
|
||||
modules: [
|
||||
'@nuxt/ui',
|
||||
'@nuxt/content',
|
||||
'@vueuse/nuxt',
|
||||
'@nuxtjs/google-fonts',
|
||||
'@nuxt/image',
|
||||
'@vueuse/motion/nuxt',
|
||||
'@pinia/nuxt',
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
modules: ['@nuxt/ui', '@nuxtjs/seo', '@nuxt/content', '@vueuse/nuxt', '@nuxtjs/google-fonts', '@nuxt/image', '@vueuse/motion/nuxt', '@pinia/nuxt', '@nuxtjs/i18n', 'nuxt-studio'],
|
||||
|
||||
ogImage: {
|
||||
enabled: false,
|
||||
},
|
||||
linkChecker: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
site: {
|
||||
url: 'https://arthurdanjou.fr',
|
||||
name: 'Developer enjoying Artificial Intelligence and Machine Learning. Mathematics Student at Paris Dauphine-PSL University specialised in Statistics and Data Science.',
|
||||
},
|
||||
|
||||
schemaOrg: {
|
||||
identity: definePerson({
|
||||
// Basic Information, if applicable
|
||||
name: 'Arthur Danjou',
|
||||
givenName: 'Arthur',
|
||||
familyName: 'Danjou',
|
||||
|
||||
// Profile Information, if applicable
|
||||
image: '/arthur-pro.webp',
|
||||
description: 'AI researcher and technical author specializing in machine learning and neural networks',
|
||||
jobTitle: 'Principal AI Researcher',
|
||||
|
||||
// Contact & Social, if applicable
|
||||
email: 'arthurdanjou@outlook.fr',
|
||||
url: 'https://go.arthurdanjou.fr/website',
|
||||
sameAs: [
|
||||
'https://go.arthurdanjou.fr/twitter',
|
||||
'https://go.arthurdanjou.fr/github',
|
||||
'https://go.arthurdanjou.fr/linkedin',
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
// Nuxt Content
|
||||
content: {
|
||||
preview: {
|
||||
api: 'https://api.nuxt.studio',
|
||||
},
|
||||
build: {
|
||||
markdown: {
|
||||
highlight: {
|
||||
@@ -51,6 +76,12 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
build: {
|
||||
sourcemap: false,
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt Color Mode
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
@@ -63,6 +94,17 @@ export default defineNuxtConfig({
|
||||
timeline: { enabled: true },
|
||||
},
|
||||
|
||||
// Nuxt Studio
|
||||
studio: {
|
||||
// GitHub repository configuration (owner and repo are required)
|
||||
repository: {
|
||||
provider: 'github', // only GitHub is currently supported
|
||||
owner: 'arthurdanjou', // your GitHub username or organization
|
||||
repo: 'artchat', // your repository name
|
||||
branch: 'main', // the branch to commit to (default: main)
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt I18N
|
||||
i18n: {
|
||||
strategy: 'no_prefix',
|
||||
@@ -108,9 +150,17 @@ export default defineNuxtConfig({
|
||||
// Nitro
|
||||
nitro: {
|
||||
experimental: {
|
||||
websocket: true,
|
||||
openAPI: true,
|
||||
},
|
||||
preset: 'cloudflare-module',
|
||||
cloudflare: {
|
||||
deployConfig: true,
|
||||
nodeCompat: true,
|
||||
},
|
||||
prerender: {
|
||||
routes: ['/'],
|
||||
crawlLinks: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt Env
|
||||
|
||||
35
package.json
35
package.json
@@ -13,38 +13,43 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@iconify-json/devicon": "^1.2.44",
|
||||
"@iconify-json/logos": "^1.2.9",
|
||||
"@iconify-json/devicon": "^1.2.46",
|
||||
"@iconify-json/logos": "^1.2.10",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@iconify-json/simple-icons": "^1.2.53",
|
||||
"@iconify-json/simple-icons": "^1.2.57",
|
||||
"@iconify-json/twemoji": "^1.2.4",
|
||||
"@iconify-json/vscode-icons": "^1.2.30",
|
||||
"@iconify-json/vscode-icons": "^1.2.33",
|
||||
"@nuxt/content": "3.7.1",
|
||||
"@nuxt/eslint": "1.9.0",
|
||||
"@nuxt/image": "^1.11.0",
|
||||
"@nuxt/ui": "^4.0.0",
|
||||
"@nuxt/ui": "4.0.1",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@nuxtjs/i18n": "10.1.0",
|
||||
"@pinia/nuxt": "^0.11.2",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"@nuxtjs/seo": "^3.2.2",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@vueuse/math": "13.9.0",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"eslint": "9.36.0",
|
||||
"nuxt": "4.1.2",
|
||||
"nuxt-studio": "1.0.0-alpha.1",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vue": "^3.5.21",
|
||||
"vue-router": "^4.5.1"
|
||||
"typescript": "5.9.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3",
|
||||
"wrangler": "^4.45.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/node": "24.6.2",
|
||||
"@vueuse/nuxt": "^13.9.0",
|
||||
"vue-tsc": "^3.0.8"
|
||||
}
|
||||
"vue-tsc": "^3.1.3"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@parcel/watcher",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 155 KiB |
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user