mirror of
https://github.com/ArthurDanjou/artsite.git
synced 2026-01-31 16:29:33 +01:00
feat: integrate new AI tools and enhance API functionality
- Added '@nuxtjs/mdc' module to nuxt.config.ts for improved UI components. - Configured MDC settings to disable anchor links for headings. - Updated API proxy settings to include '/api/uses' and a general '/api/' endpoint. - Introduced new dependencies for AI SDKs and tools in package.json. - Created a new chat API endpoint to handle AI interactions with various tools. - Implemented utility functions for activity tracking, resource reading, resume retrieval, statistics, status monitoring, and categorized tool usage. - Updated social links in types/index.ts to redirect through a custom domain. - Updated worker configuration to include AI binding for Cloudflare.
This commit is contained in:
@@ -4,8 +4,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--animate-wave: wave 3s infinite;
|
--animate-wave: wave 3s infinite;
|
||||||
|
|
||||||
--ui-black: #000000;
|
|
||||||
--ui-white: #ffffff;
|
|
||||||
--ui-bg: #f8f8f8;
|
--ui-bg: #f8f8f8;
|
||||||
|
|
||||||
--ui-font-family: 'DM Sans', sans-serif;
|
--ui-font-family: 'DM Sans', sans-serif;
|
||||||
@@ -15,9 +13,7 @@
|
|||||||
.dark {
|
.dark {
|
||||||
--animate-wave: wave 3s infinite;
|
--animate-wave: wave 3s infinite;
|
||||||
|
|
||||||
--ui-black: #000000;
|
--ui-bg: #0f0f0f;
|
||||||
--ui-white: #ffffff;
|
|
||||||
--ui-bg: #141414;
|
|
||||||
|
|
||||||
--ui-font-family: 'DM Sans', sans-serif;
|
--ui-font-family: 'DM Sans', sans-serif;
|
||||||
transition-duration: 0.7s;
|
transition-duration: 0.7s;
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1
|
|
||||||
class="text-3xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100"
|
|
||||||
>
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
<p class="mt-4 text-base">
|
|
||||||
{{ description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,13 +1,148 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { getTextFromMessage } from '@nuxt/ui/utils/ai'
|
||||||
|
import { DefaultChatTransport } from 'ai'
|
||||||
|
import { Chat } from '@ai-sdk/vue'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const input = ref('')
|
||||||
|
|
||||||
|
const { t, locale } = useI18n({ useScope: 'local' })
|
||||||
|
|
||||||
|
const chat = new Chat({
|
||||||
|
transport: new DefaultChatTransport({
|
||||||
|
api: '/api/chat',
|
||||||
|
body: () => ({
|
||||||
|
messages: chat.messages,
|
||||||
|
lang: locale.value
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
onError(error) {
|
||||||
|
toast.add({
|
||||||
|
title: 'Error',
|
||||||
|
description: error.message,
|
||||||
|
color: 'red'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleSubmit(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (input.value.trim()) {
|
||||||
|
chat.sendMessage({ text: input.value })
|
||||||
|
input.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clipboard = useClipboard()
|
||||||
|
const actions = ref([
|
||||||
|
{
|
||||||
|
label: 'Copy to clipboard',
|
||||||
|
icon: 'i-lucide-copy',
|
||||||
|
onClick: (message: never) => clipboard.copy(getTextFromMessage(message))
|
||||||
|
}
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
CHAT
|
<UDashboardPanel
|
||||||
</div>
|
:ui="{ body: 'p-0 sm:p-0' }"
|
||||||
|
class="max-h-[calc(100vh-28rem)]! min-h-[calc(100vh-28rem)]! border-none"
|
||||||
|
:resizable="false"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<UContainer>
|
||||||
|
<UChatMessages
|
||||||
|
:actions
|
||||||
|
:messages="chat.messages"
|
||||||
|
:status="chat.status"
|
||||||
|
:user="{
|
||||||
|
variant: 'solid'
|
||||||
|
}"
|
||||||
|
:assistant="{
|
||||||
|
variant: 'naked'
|
||||||
|
}"
|
||||||
|
:auto-scroll="{
|
||||||
|
color: 'neutral',
|
||||||
|
variant: 'outline'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #indicator>
|
||||||
|
<UButton
|
||||||
|
class="px-0"
|
||||||
|
color="neutral"
|
||||||
|
variant="link"
|
||||||
|
loading
|
||||||
|
loading-icon="i-lucide-loader"
|
||||||
|
label="Thinking..."
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #content="{ message }">
|
||||||
|
<MDC
|
||||||
|
:value="getTextFromMessage(message)"
|
||||||
|
:cache-key="message.id"
|
||||||
|
class="*:first:mt-0 *:last:mb-0"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</UChatMessages>
|
||||||
|
</UContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UContainer>
|
||||||
|
<ClientOnly>
|
||||||
|
<UCard
|
||||||
|
variant="outline"
|
||||||
|
class="rounded-xl"
|
||||||
|
:ui="{ body: 'p-2 sm:p-2' }"
|
||||||
|
>
|
||||||
|
<UChatPrompt
|
||||||
|
v-model="input"
|
||||||
|
:placeholder="t('placeholder')"
|
||||||
|
:error="chat.error"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
>
|
||||||
|
<UChatPromptSubmit
|
||||||
|
:status="chat.status"
|
||||||
|
color="neutral"
|
||||||
|
submitted-color="neutral"
|
||||||
|
submitted-variant="subtle"
|
||||||
|
submitted-icon="i-lucide-square"
|
||||||
|
streaming-color="neutral"
|
||||||
|
streaming-variant="subtle"
|
||||||
|
streaming-icon="i-lucide-square"
|
||||||
|
error-color="red"
|
||||||
|
error-variant="soft"
|
||||||
|
error-icon="i-lucide-rotate-ccw"
|
||||||
|
@stop="chat.stop()"
|
||||||
|
@reload="chat.regenerate()"
|
||||||
|
/>
|
||||||
|
</UChatPrompt>
|
||||||
|
</UCard>
|
||||||
|
</ClientOnly>
|
||||||
|
</UContainer>
|
||||||
|
</template>
|
||||||
|
</UDashboardPanel>
|
||||||
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<i18n lang="json">
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"placeholder": "Type your message...",
|
||||||
|
"thinking": "Thinking..."
|
||||||
|
},
|
||||||
|
"fr": {
|
||||||
|
"placeholder": "Tapez votre message...",
|
||||||
|
"thinking": "Réflexion..."
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"placeholder": "Escribe tu mensaje...",
|
||||||
|
"thinking": "Pensando..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
'@nuxt/ui',
|
'@nuxt/ui',
|
||||||
|
'@nuxtjs/mdc',
|
||||||
'@nuxt/content',
|
'@nuxt/content',
|
||||||
'@nuxthub/core',
|
'@nuxthub/core',
|
||||||
'@nuxt/eslint',
|
'@nuxt/eslint',
|
||||||
@@ -33,6 +34,12 @@ export default defineNuxtConfig({
|
|||||||
fallback: 'light'
|
fallback: 'light'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mdc: {
|
||||||
|
headings: {
|
||||||
|
anchorLinks: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
theme: {
|
theme: {
|
||||||
colors: [
|
colors: [
|
||||||
@@ -72,6 +79,12 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
'/api/stats': {
|
'/api/stats': {
|
||||||
proxy: `${process.env.NUXT_API_URL}/api/stats`
|
proxy: `${process.env.NUXT_API_URL}/api/stats`
|
||||||
|
},
|
||||||
|
'/api/uses': {
|
||||||
|
proxy: `${process.env.NUXT_API_URL}/api/uses`
|
||||||
|
},
|
||||||
|
'/api/': {
|
||||||
|
proxy: `${process.env.NUXT_API_URL}/api/`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -11,20 +11,26 @@
|
|||||||
"cf-typegen": "wrangler types"
|
"cf-typegen": "wrangler types"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ai-sdk/mcp": "^0.0.12",
|
||||||
|
"@ai-sdk/vue": "^2.0.115",
|
||||||
|
"@libsql/client": "^0.15.15",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||||
"@nuxt/content": "3.9.0",
|
"@nuxt/content": "3.9.0",
|
||||||
"@nuxt/eslint": "1.12.1",
|
"@nuxt/eslint": "1.12.1",
|
||||||
"@nuxt/ui": "4.3.0",
|
"@nuxt/ui": "^4.3.0",
|
||||||
"@nuxthub/core": "0.10.3",
|
"@nuxthub/core": "^0.10.3",
|
||||||
"@nuxtjs/i18n": "10.2.1",
|
"@nuxtjs/mdc": "^0.19.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"@vueuse/math": "^14.1.0",
|
"@vueuse/math": "^14.1.0",
|
||||||
|
"ai": "5.0.115",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"nuxt": "4.2.2",
|
"nuxt": "4.2.2",
|
||||||
"nuxt-studio": "1.0.0-alpha.4",
|
"nuxt-studio": "1.0.0-alpha.4",
|
||||||
"vue": "3.5.25",
|
"vue": "3.5.26",
|
||||||
"vue-router": "4.6.4",
|
"vue-router": "4.6.4",
|
||||||
"zod": "4.2.1"
|
"workers-ai-provider": "^2.0.0",
|
||||||
|
"zod": "^4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/devicon": "1.2.53",
|
"@iconify-json/devicon": "1.2.53",
|
||||||
|
|||||||
89
server/api/chat.post.ts
Normal file
89
server/api/chat.post.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { defineEventHandler, readValidatedBody } from 'h3'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import {
|
||||||
|
convertToModelMessages,
|
||||||
|
createUIMessageStream,
|
||||||
|
createUIMessageStreamResponse,
|
||||||
|
streamText
|
||||||
|
} from 'ai'
|
||||||
|
import type { UIMessage } from 'ai'
|
||||||
|
import { createWorkersAI } from 'workers-ai-provider'
|
||||||
|
import { statsTool } from '~~/shared/utils/tools/stats'
|
||||||
|
import { activityTool } from '~~/shared/utils/tools/activity'
|
||||||
|
import { resumesTool } from '~~/shared/utils/tools/read-resume'
|
||||||
|
import { resourceTool } from '~~/shared/utils/tools/read-resources'
|
||||||
|
import { statusPageTool } from '~~/shared/utils/tools/status-page'
|
||||||
|
import { usesByCategoryTool } from '~~/shared/utils/tools/uses-by-category'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const { messages, lang } = await readValidatedBody(event, z.object({
|
||||||
|
messages: z.array(z.custom<UIMessage>()),
|
||||||
|
lang: z.string().optional()
|
||||||
|
}).parse)
|
||||||
|
|
||||||
|
const { AI } = event.context.cloudflare.env || {}
|
||||||
|
|
||||||
|
if (!AI) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Cloudflare AI Binding not found. Check wrangler.json.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const validCategories = ['contact', 'education', 'experiences', 'hobbies', 'languages', 'profile', 'projects', 'skills', 'uses'].join(', ')
|
||||||
|
|
||||||
|
const workersAI = createWorkersAI({ binding: AI })
|
||||||
|
|
||||||
|
const stream = createUIMessageStream({
|
||||||
|
execute: async ({ writer }) => {
|
||||||
|
const result = streamText({
|
||||||
|
model: workersAI('@cf/meta/llama-3-8b-instruct'),
|
||||||
|
system: `You are Arthur Danjou's personal portfolio assistant (Data Science Student).
|
||||||
|
|
||||||
|
### TOOL USAGE GUIDE - FOLLOW STRICTLY:
|
||||||
|
|
||||||
|
1. **readResources(category)**: YOUR PRIMARY BRAIN.
|
||||||
|
- Use this for ANY question about Arthur's life, background, or work.
|
||||||
|
- Categories: ${validCategories}.
|
||||||
|
- Example: "What are his skills?" -> call readResources('skills').
|
||||||
|
- Example: "Where did he study?" -> call readResources('education').
|
||||||
|
|
||||||
|
2. **readResume()**: THE FILE DISPENSER.
|
||||||
|
- **WARNING:** This tool ONLY returns a direct DOWNLOAD LINK (URL). It does NOT contain text.
|
||||||
|
- **WHEN TO USE:** ONLY when the user explicitly asks to "download", "get", "see", or "have" the CV/Resume file.
|
||||||
|
- **DO NOT USE:** If the user asks "What is on his resume?" or "Describe his experience", DO NOT use this. Use 'readResources' instead.
|
||||||
|
|
||||||
|
3. **stats()**: GITHUB METRICS.
|
||||||
|
- Use this for questions about coding volume, commit streaks, languages used percentages, or GitHub activity.
|
||||||
|
|
||||||
|
4. **activity()**: LIVE STATUS.
|
||||||
|
- Use this to know what Arthur is doing RIGHT NOW (e.g., "Is he coding?", "What is he listening to on Spotify?").
|
||||||
|
|
||||||
|
5. **statusPage()**: INFRASTRUCTURE HEALTH.
|
||||||
|
- Use this if the user asks about the website's uptime, server status, or if services are down.
|
||||||
|
|
||||||
|
6. **usesByCategory()**: TECH STACK & GEAR.
|
||||||
|
- Use this for specific questions about his hardware (Mac vs PC), software, developer tools, or desk setup.
|
||||||
|
|
||||||
|
### GENERAL RULES:
|
||||||
|
- If you don't call a tool, you know NOTHING. Do not hallucinate.
|
||||||
|
- Always answer in ${lang || 'English'}.
|
||||||
|
- Be concise and professional.
|
||||||
|
`,
|
||||||
|
tools: {
|
||||||
|
stats: statsTool,
|
||||||
|
activity: activityTool,
|
||||||
|
readResume: resumesTool,
|
||||||
|
readResources: resourceTool,
|
||||||
|
statusPage: statusPageTool,
|
||||||
|
usesByCategory: usesByCategoryTool
|
||||||
|
},
|
||||||
|
messages: await convertToModelMessages(messages)
|
||||||
|
})
|
||||||
|
|
||||||
|
writer.merge(result.toUIMessageStream())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return createUIMessageStreamResponse({ stream })
|
||||||
|
})
|
||||||
6
shared/utils/index.ts
Normal file
6
shared/utils/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './tools/activity'
|
||||||
|
export * from './tools/read-resources'
|
||||||
|
export * from './tools/read-resume'
|
||||||
|
export * from './tools/stats'
|
||||||
|
export * from './tools/status-page'
|
||||||
|
export * from './tools/uses-by-category'
|
||||||
12
shared/utils/tools/activity.ts
Normal file
12
shared/utils/tools/activity.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
import type { Activity } from '~~/types'
|
||||||
|
|
||||||
|
export type ActivityUIToolInvocation = UIToolInvocation<typeof activityTool>
|
||||||
|
|
||||||
|
export const activityTool = tool({
|
||||||
|
description: 'Real-time current activity and status of Arthur Danjou, including what he\'s currently working on',
|
||||||
|
execute: async () => {
|
||||||
|
return await $fetch<Activity>('/api/activity')
|
||||||
|
}
|
||||||
|
})
|
||||||
25
shared/utils/tools/read-resources.ts
Normal file
25
shared/utils/tools/read-resources.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export type resourceUIToolInvocation = UIToolInvocation<typeof resourceTool>
|
||||||
|
|
||||||
|
export const resourceTool = tool({
|
||||||
|
description: 'Read a resource from the server API',
|
||||||
|
inputSchema: z.object({
|
||||||
|
resource: z.enum([
|
||||||
|
'contact',
|
||||||
|
'education',
|
||||||
|
'experiences',
|
||||||
|
'hobbies',
|
||||||
|
'languages',
|
||||||
|
'profile',
|
||||||
|
'projects',
|
||||||
|
'skills',
|
||||||
|
'uses'
|
||||||
|
]).describe('resource name')
|
||||||
|
}),
|
||||||
|
execute: async ({ resource }) => {
|
||||||
|
return await $fetch(`/api/${resource}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
15
shared/utils/tools/read-resume.ts
Normal file
15
shared/utils/tools/read-resume.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export type ResumesUIToolInvocation = UIToolInvocation<typeof resumesTool>
|
||||||
|
|
||||||
|
export const resumesTool = tool({
|
||||||
|
description: 'Retrieves a direct download link to Arthur Danjou\'s professional resume in the specified language. Supports both English and French versions.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
lang: z.enum(['en', 'fr']).describe('The language for the resume: \'en\' for English or \'fr\' for French.')
|
||||||
|
}),
|
||||||
|
execute: async ({ lang }) => {
|
||||||
|
return `/api/resumes/${lang}`
|
||||||
|
}
|
||||||
|
})
|
||||||
12
shared/utils/tools/stats.ts
Normal file
12
shared/utils/tools/stats.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
import type { Stats } from '~~/types'
|
||||||
|
|
||||||
|
export type StatsUIToolInvocation = UIToolInvocation<typeof statsTool>
|
||||||
|
|
||||||
|
export const statsTool = tool({
|
||||||
|
description: 'Detailed coding statistics and analytics from WakaTime, including programming languages, time spent coding, and productivity metrics',
|
||||||
|
execute: async () => {
|
||||||
|
return await $fetch<Stats>('/api/stats')
|
||||||
|
}
|
||||||
|
})
|
||||||
11
shared/utils/tools/status-page.ts
Normal file
11
shared/utils/tools/status-page.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
|
||||||
|
export type StatusPageUIToolInvocation = UIToolInvocation<typeof statusPageTool>
|
||||||
|
|
||||||
|
export const statusPageTool = tool({
|
||||||
|
description: 'Real-time status, uptime monitoring, and incident reports for Arthur Danjou\'s homelab infrastructure, powered by UptimeKuma',
|
||||||
|
execute: async () => {
|
||||||
|
return await $fetch('/api/status-page')
|
||||||
|
}
|
||||||
|
})
|
||||||
17
shared/utils/tools/uses-by-category.ts
Normal file
17
shared/utils/tools/uses-by-category.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { UIToolInvocation } from 'ai'
|
||||||
|
import { tool } from 'ai'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export type UsesByCategoryUIToolInvocation = UIToolInvocation<typeof usesByCategoryTool>
|
||||||
|
|
||||||
|
export const usesByCategoryTool = tool({
|
||||||
|
description: 'Retrieves a filtered list of tools, software, and hardware used by Arthur Danjou based on a specific category. Available categories: homelab, IDE, hardware, and software.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
categoryName: z.enum(['homelab', 'ide', 'hardware', 'software']).describe('The category to filter by: \'homelab\', \'ide\', \'hardware\', or \'software\'.')
|
||||||
|
}),
|
||||||
|
execute: async ({ categoryName }: { categoryName: 'homelab' | 'ide' | 'hardware' | 'software' }) => {
|
||||||
|
const uses = await $fetch<{ category: string }[]>('/api/uses')
|
||||||
|
|
||||||
|
return uses.filter(use => use.category === categoryName)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -124,10 +124,10 @@ export const activityMessages: Record<'en' | 'fr' | 'es', ActivityMessages> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const socials = [
|
export const socials = [
|
||||||
{ icon: 'i-ph-x-logo-duotone', label: 'Twitter', to: 'https://twitter.com/ArthurDanj' },
|
{ icon: 'i-ph-x-logo-duotone', label: 'Twitter', to: 'https://go.arthurdanjou.fr/twitter' },
|
||||||
{ icon: 'i-ph-github-logo-duotone', label: 'GitHub', to: 'https://github.com/ArthurDanjou' },
|
{ icon: 'i-ph-github-logo-duotone', label: 'GitHub', to: 'https://go.arthurdanjou.fr/github' },
|
||||||
{ icon: 'i-ph-linkedin-logo-duotone', label: 'LinkedIn', to: 'https://www.linkedin.com/in/arthurdanjou/' },
|
{ icon: 'i-ph-linkedin-logo-duotone', label: 'LinkedIn', to: 'https://go.arthurdanjou.fr/linkedin' },
|
||||||
{ icon: 'i-ph-discord-logo-duotone', label: 'Discord', to: 'https://discordapp.com/users/179635349100691456' }
|
{ icon: 'i-ph-discord-logo-duotone', label: 'Discord', to: 'https://go.arthurdanjou.fr/discord' }
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type Locale = 'en' | 'fr' | 'es'
|
type Locale = 'en' | 'fr' | 'es'
|
||||||
|
|||||||
3
worker-configuration.d.ts
vendored
3
worker-configuration.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// Generated by Wrangler by running `wrangler types` (hash: 9ebceff030e512b05e46edbb174bea44)
|
// Generated by Wrangler by running `wrangler types` (hash: 7333ef017a5016b51354dce96a893062)
|
||||||
// Runtime types generated with workerd@1.20251217.0 2025-12-13
|
// Runtime types generated with workerd@1.20251217.0 2025-12-13
|
||||||
declare namespace Cloudflare {
|
declare namespace Cloudflare {
|
||||||
interface Env {
|
interface Env {
|
||||||
@@ -9,6 +9,7 @@ declare namespace Cloudflare {
|
|||||||
STUDIO_GITHUB_CLIENT_ID: string;
|
STUDIO_GITHUB_CLIENT_ID: string;
|
||||||
STUDIO_GITHUB_CLIENT_SECRET: string;
|
STUDIO_GITHUB_CLIENT_SECRET: string;
|
||||||
DB: D1Database;
|
DB: D1Database;
|
||||||
|
AI: Ai;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interface Env extends Cloudflare.Env {}
|
interface Env extends Cloudflare.Env {}
|
||||||
|
|||||||
Reference in New Issue
Block a user