mirror of
https://github.com/Ladebeze66/devsite.git
synced 2026-05-11 16:56:26 +02:00
Compare commits
4 Commits
517eed915d
...
ba34820ffc
| Author | SHA1 | Date | |
|---|---|---|---|
| ba34820ffc | |||
| ee79d56d66 | |||
| 5021251dbf | |||
| 5c4abb2118 |
@ -1,8 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { askAI } from "../utils/askAI";
|
||||
import {
|
||||
clearGrasbotChatMessages,
|
||||
loadGrasbotChatMessages,
|
||||
newChatMessageId,
|
||||
saveGrasbotChatMessages,
|
||||
} from "../utils/grasbotChatStorage";
|
||||
|
||||
/**
|
||||
* GrasBot — UI du chatbot (Stitch).
|
||||
@ -18,6 +26,17 @@ import { askAI } from "../utils/askAI";
|
||||
* info si réponse générale faute de contexte pertinent).
|
||||
* - Timeout 45 s côté fetch (géré dans `askAI.js`) avec message éditorial.
|
||||
*
|
||||
* v3.2 (2026-04-26) :
|
||||
* - Réponses bot rendues en Markdown (`ReactMarkdown` + `remark-gfm`) : gras,
|
||||
* listes, liens cliquables. Texte justifié dans la bulle. Les messages
|
||||
* utilisateur restent en texte brut.
|
||||
*
|
||||
* v3.3 (2026-04-26) :
|
||||
* - Historique persisté en **localStorage** par `grasbot_user_id` (voir
|
||||
* `grasbotChatStorage.js`) : même navigateur / effacement cookies selon usage,
|
||||
* jusqu'à 80 messages récents. Aucune réinjection dans Ollama.
|
||||
* - Bouton pour effacer la conversation locale.
|
||||
*
|
||||
* Design :
|
||||
* - Fond `surface-container-lowest/95 backdrop-blur-vellum rounded-sheet shadow-ambient`.
|
||||
* - Bulles : user = `bg-primary text-white` à droite, bot = `bg-surface-container` à gauche.
|
||||
@ -27,10 +46,27 @@ import { askAI } from "../utils/askAI";
|
||||
export default function ChatBot({ onClose }) {
|
||||
const [question, setQuestion] = useState("");
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [isWaiting, setIsWaiting] = useState(false);
|
||||
const scrollRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setMessages(loadGrasbotChatMessages());
|
||||
setHydrated(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated) return;
|
||||
saveGrasbotChatMessages(messages);
|
||||
}, [messages, hydrated]);
|
||||
|
||||
const handleClearHistory = () => {
|
||||
setMessages([]);
|
||||
clearGrasbotChatMessages();
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
@ -44,7 +80,7 @@ export default function ChatBot({ onClose }) {
|
||||
const handleAsk = async () => {
|
||||
if (!question.trim() || isWaiting) return;
|
||||
|
||||
const userMessage = { sender: "user", text: question };
|
||||
const userMessage = { sender: "user", text: question, id: newChatMessageId() };
|
||||
setMessages((prev) => [...prev, userMessage]);
|
||||
setQuestion("");
|
||||
setIsWaiting(true);
|
||||
@ -59,6 +95,7 @@ export default function ChatBot({ onClose }) {
|
||||
sources: payload.sources || [],
|
||||
grounded: Boolean(payload.grounded),
|
||||
timeout: Boolean(payload._timeout),
|
||||
id: newChatMessageId(),
|
||||
},
|
||||
]);
|
||||
} catch (_error) {
|
||||
@ -70,6 +107,7 @@ export default function ChatBot({ onClose }) {
|
||||
sources: [],
|
||||
grounded: false,
|
||||
error: true,
|
||||
id: newChatMessageId(),
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
@ -102,20 +140,34 @@ export default function ChatBot({ onClose }) {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Fermer le chat"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full text-white transition-colors hover:bg-primary-container focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-fixed"
|
||||
>
|
||||
<span
|
||||
className="material-symbols-outlined"
|
||||
aria-hidden="true"
|
||||
translate="no"
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClearHistory}
|
||||
disabled={messages.length === 0}
|
||||
aria-label="Effacer l'historique de conversation"
|
||||
title="Effacer l'historique"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full text-white transition-colors hover:bg-primary-container focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-fixed disabled:pointer-events-none disabled:opacity-40"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<span className="material-symbols-outlined text-xl" aria-hidden="true" translate="no">
|
||||
delete_sweep
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Fermer le chat"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full text-white transition-colors hover:bg-primary-container focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-fixed"
|
||||
>
|
||||
<span
|
||||
className="material-symbols-outlined"
|
||||
aria-hidden="true"
|
||||
translate="no"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@ -129,10 +181,11 @@ export default function ChatBot({ onClose }) {
|
||||
)}
|
||||
|
||||
{messages.map((msg, index) => {
|
||||
const msgKey = msg.id ?? `legacy-${index}`;
|
||||
if (msg.sender === "user") {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
key={msgKey}
|
||||
className="ml-auto max-w-[80%] rounded-sheet bg-primary px-3 py-2 font-headline text-xs leading-relaxed text-white"
|
||||
>
|
||||
{msg.text}
|
||||
@ -140,9 +193,39 @@ export default function ChatBot({ onClose }) {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={index} className="mr-auto flex max-w-[85%] flex-col gap-1.5">
|
||||
<div className="rounded-sheet bg-surface-container px-3 py-2 font-headline text-xs leading-relaxed text-on-surface">
|
||||
{msg.text}
|
||||
<div key={msgKey} className="mr-auto flex max-w-[85%] flex-col gap-1.5">
|
||||
<div
|
||||
className="rounded-sheet bg-surface-container px-3 py-2 text-on-surface [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
||||
>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
className="prose prose-sm max-w-none text-justify font-body text-xs leading-relaxed text-on-surface
|
||||
prose-p:my-2 prose-p:text-xs prose-p:leading-relaxed
|
||||
prose-strong:font-semibold prose-strong:text-on-surface
|
||||
prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5 prose-li:text-xs
|
||||
prose-a:break-words prose-a:text-primary prose-a:no-underline hover:prose-a:underline
|
||||
prose-headings:font-headline prose-headings:text-sm prose-headings:text-on-surface prose-headings:my-2
|
||||
prose-code:rounded prose-code:bg-surface-container-low prose-code:px-1 prose-code:text-[11px]
|
||||
prose-pre:my-2 prose-pre:bg-surface-container-low prose-pre:text-[11px]"
|
||||
components={{
|
||||
a({ href, children, ...props }) {
|
||||
const external =
|
||||
typeof href === "string" && /^https?:\/\//i.test(href);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
{...props}
|
||||
target={external ? "_blank" : undefined}
|
||||
rel={external ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{msg.text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
{(msg.sources?.length > 0 || msg.grounded !== undefined) && !msg.error && !msg.timeout && (
|
||||
<BotFooter sources={msg.sources} grounded={msg.grounded} />
|
||||
|
||||
@ -212,7 +212,7 @@ export default function ContentSection({
|
||||
<div
|
||||
className="prose prose-sm mt-5 max-w-none font-body text-on-surface-variant sm:prose-base
|
||||
prose-headings:font-headline prose-headings:text-primary
|
||||
prose-p:font-body prose-p:text-on-surface-variant
|
||||
prose-p:font-body prose-p:text-left prose-p:text-on-surface-variant md:prose-p:text-justify
|
||||
prose-strong:text-on-surface
|
||||
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
|
||||
prose-li:marker:text-primary
|
||||
|
||||
@ -219,7 +219,7 @@ export default function ContentSectionCompetences({
|
||||
ref={contentRef}
|
||||
className="prose prose-sm mt-5 max-w-none font-body text-on-surface-variant sm:prose-base
|
||||
prose-headings:font-headline prose-headings:text-primary
|
||||
prose-p:font-body prose-p:text-on-surface-variant
|
||||
prose-p:font-body prose-p:text-left prose-p:text-on-surface-variant md:prose-p:text-justify
|
||||
prose-strong:text-on-surface
|
||||
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
|
||||
prose-li:marker:text-primary
|
||||
|
||||
13
app/page.tsx
13
app/page.tsx
@ -170,6 +170,19 @@ export default function HomePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="mt-1 text-center md:text-left">
|
||||
<a
|
||||
href="/cv/cv-fernand-grascalvet.pdf"
|
||||
download="cv-fernand-grascalvet.pdf"
|
||||
className="inline-flex items-center gap-1.5 font-headline text-xs font-semibold text-primary underline decoration-primary/40 underline-offset-2 transition-colors hover:text-primary-container hover:decoration-primary"
|
||||
>
|
||||
<span className="material-symbols-outlined text-base" aria-hidden="true" translate="no">
|
||||
picture_as_pdf
|
||||
</span>
|
||||
Télécharger le CV (PDF)
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div className="mt-2 flex flex-col items-stretch gap-3 sm:flex-row sm:justify-center sm:items-center md:justify-start">
|
||||
<Link
|
||||
href="/portfolio"
|
||||
|
||||
86
app/utils/grasbotChatStorage.js
Normal file
86
app/utils/grasbotChatStorage.js
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Persistance locale du fil GrasBot (sans envoi à Ollama).
|
||||
*
|
||||
* Clé : `grasbot_chat_v1:<user_id>` où `user_id` est celui de `grasbotIds.js`.
|
||||
* Limite : les derniers messages uniquement pour éviter quota localStorage (~5 Mo).
|
||||
*/
|
||||
|
||||
import { getGrasbotUserId } from "./grasbotIds";
|
||||
|
||||
export const GRASBOT_CHAT_STORAGE_VERSION = 1;
|
||||
export const GRASBOT_CHAT_MAX_MESSAGES = 80;
|
||||
|
||||
function storageKey(userId) {
|
||||
return `grasbot_chat_v${GRASBOT_CHAT_STORAGE_VERSION}:${userId}`;
|
||||
}
|
||||
|
||||
function safeRandomId() {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `m_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} m
|
||||
* @returns {object | null}
|
||||
*/
|
||||
function sanitizeMessage(m) {
|
||||
if (!m || typeof m !== "object") return null;
|
||||
if (m.sender !== "user" && m.sender !== "bot") return null;
|
||||
if (typeof m.text !== "string") return null;
|
||||
const out = { sender: m.sender, text: m.text };
|
||||
if (typeof m.id === "string") out.id = m.id;
|
||||
if (m.sender === "bot") {
|
||||
if (Array.isArray(m.sources)) out.sources = m.sources;
|
||||
if (typeof m.grounded === "boolean") out.grounded = m.grounded;
|
||||
if (typeof m.timeout === "boolean") out.timeout = m.timeout;
|
||||
if (typeof m.error === "boolean") out.error = m.error;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** @returns {object[]} */
|
||||
export function loadGrasbotChatMessages() {
|
||||
if (typeof window === "undefined") return [];
|
||||
try {
|
||||
const uid = getGrasbotUserId();
|
||||
if (!uid) return [];
|
||||
const raw = window.localStorage.getItem(storageKey(uid));
|
||||
if (!raw) return [];
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.map(sanitizeMessage).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {object[]} messages */
|
||||
export function saveGrasbotChatMessages(messages) {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
const uid = getGrasbotUserId();
|
||||
if (!uid) return;
|
||||
const trimmed = messages.slice(-GRASBOT_CHAT_MAX_MESSAGES);
|
||||
const serialized = trimmed.map(sanitizeMessage).filter(Boolean);
|
||||
window.localStorage.setItem(storageKey(uid), JSON.stringify(serialized));
|
||||
} catch {
|
||||
// quota / mode privé strict
|
||||
}
|
||||
}
|
||||
|
||||
export function clearGrasbotChatMessages() {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
const uid = getGrasbotUserId();
|
||||
if (!uid) return;
|
||||
window.localStorage.removeItem(storageKey(uid));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function newChatMessageId() {
|
||||
return safeRandomId();
|
||||
}
|
||||
@ -593,13 +593,15 @@ Ton rôle :
|
||||
|
||||
Règles de ton :
|
||||
- Réponds en français, ton sobre et précis, sans emojis.
|
||||
- Cite tes sources entre crochets carrés en utilisant le slug (ex. [push-swap], [ia]).
|
||||
- N'inclus **pas** de références entre crochets du type `[slug-de-la-note]` dans ta réponse : l'interface liste déjà les fiches du vault sous le message (liens cliquables). Tu peux nommer un projet ou une compétence dans la phrase si utile, sans notation `[slug]`.
|
||||
- Pour les **liens externes** (site personnel, GitHub, LinkedIn, etc.), utilise le Markdown `[libellé court](url)`. Ne répète pas l'URL en texte brut après le lien. Les listes à puces conviennent pour les coordonnées (téléphone, email, adresse).
|
||||
- **CV PDF** : le fichier officiel est servi sous `/cv/cv-fernand-grascalvet.pdf` (toujours ce chemin). Si le visiteur demande le CV, un PDF, ou « télécharger », inclus dans ta réponse un lien Markdown `[Télécharger le CV (PDF)](/cv/cv-fernand-grascalvet.pdf)`. Pour une question sur **alternance**, **embauche** ou **parcours** où un CV est naturellement utile, tu peux proposer ce lien une fois en fin de réponse (sans insister si hors contexte).
|
||||
- Reste concis (3 à 6 phrases en général), sauf demande explicite de détail.
|
||||
- Si la question est hors sujet (ex. question généraliste sans rapport avec Fernand), indique poliment ton rôle et invite à poser une question sur son parcours.
|
||||
|
||||
Règles de fidélité aux sources (important) :
|
||||
- Chaque source fournie est annotée `type=parcours | projet | moc | competence | glossaire`.
|
||||
- Pour toute question biographique (qui est Fernand, âge, situation, école, objectif, contact, localisation), appuie-toi **en priorité** sur les sources de `type=parcours` (ex. [bio-fernand], [cv-grascalvet-fernand]). Ne **déduis jamais** d'informations biographiques depuis une source `type=projet` ou `type=moc`.
|
||||
- Pour toute question biographique (qui est Fernand, âge, situation, école, objectif, contact, localisation), appuie-toi **en priorité** sur les sources de `type=parcours` visibles dans le contexte (bio, CV). Ne **déduis jamais** d'informations biographiques depuis une source `type=projet` ou `type=moc`.
|
||||
- Ne **jamais inventer** un fait factuel (âge, date, diplôme, école, entreprise, technologie utilisée) qui n'apparaît pas littéralement dans les sources. Si l'info n'est pas présente, écris « non précisé dans les notes » et oriente vers /portfolio, /competences ou /contact.
|
||||
- En cas de contradiction entre deux sources, privilégie la source de plus haut score, mentionne brièvement la divergence, et ne choisis jamais une valeur absente des deux.
|
||||
- Une note dont le body se termine par « note tronquée » a été résumée : signale-le si tu t'appuies dessus pour un point précis, ou invite à consulter la note complète."""
|
||||
|
||||
@ -1,60 +1,68 @@
|
||||
Gras-Calvet Fernand
|
||||
Gras-Calvet Fernand
|
||||
|
||||
Etudiant Informatique /
|
||||
Recherche alternance Data-IA
|
||||
04/11/1978he
|
||||
|
||||
Expérience pro
|
||||
Étudiant Informatique – Recherche
|
||||
alternance Data / IA
|
||||
|
||||
Ostréiculture 1999-2012
|
||||
(Automatisation & LLM)
|
||||
|
||||
Présentation Pendant plus de 12 ans, j’ai travaillé dans l’ostréiculture à Leucate,
|
||||
assurant la gestion complète de l’entreprise familiale (Port-Leucate).
|
||||
Ancien infirmier de 46 ans, actuellement
|
||||
étudiant en informatique à l’École 42 Infirmier 2014-2023
|
||||
Perpignan, je recherche une alternance de
|
||||
2 ans pour me spécialiser dans Suite à une reconversion professionnelle, j’ai exercé la profession
|
||||
l’automatisation agentique au sein des
|
||||
entreprises, en y apportant mon d’infirmier pendant près de 10 ans. Dernier poste clinique Supervaltech
|
||||
expérience sur le traitement de Data et les
|
||||
nouveaux process basés sur les LLM. Mon à St Estève en gériatrie de 2018-2023.
|
||||
parcours atypique reflète ma capacité
|
||||
d’adaptation, ma rigueur et mon esprit Etudiant Ecole 42 2023- 2025
|
||||
d’équipe.
|
||||
Après des problèmes de santé, bénéficiant d’une RQTH, j’ai entamé une
|
||||
Contact
|
||||
nouvelle reconversion professionnelle dans le domaine de
|
||||
06.12.01.01.72
|
||||
grascalvet.fernand@gmail.com l’informatique. Durant cette période j’ai validé le tronc commun de
|
||||
fernandgrascalvet.com
|
||||
https://github.com/Ladebeze66 l’école. Réalisant un stage dans un entreprise logiciel métier spécialisée
|
||||
Expérience pro
|
||||
|
||||
13 rue de Belfort 66600 dans le béton. L’objectif du stage étant basé sur l’élaboration d’un
|
||||
Etudiant Ecole 42 / Stage 2023- 2025
|
||||
|
||||
Rivesaltes chatbot, multi-agent, automatisation (interfaçage) entre les différents
|
||||
Après des problèmes de santé, bénéficiant d’une RQTH, j’ai entamé une
|
||||
|
||||
Expérience outils de l’entreprise (support, commercial, CRM ...).
|
||||
Présentation nouvelle reconversion professionnelle dans le domaine de
|
||||
|
||||
Objectifs Alternance
|
||||
Ancien infirmier en reconversion vers l’informatique. Lors de mon stage en entreprise logicielle spécialisée
|
||||
l’ingénierie informatique et l’intelligence
|
||||
artificielle, je me spécialise aujourd’hui dans dans le béton, j’ai conçu une architecture de chatbot multi-agent
|
||||
la conception de systèmes basés sur des
|
||||
LLM, l’automatisation de processus métier intégrant analyse de tickets support, traitement d’images techniques
|
||||
et le traitement de données techniques.
|
||||
Mon parcours atypique m’apporte rigueur, et pipeline RAG.
|
||||
adaptabilité et une forte capacité
|
||||
d’apprentissage autonome. Infirmier 2014-2023
|
||||
|
||||
Mes objectifs de stage sont d’approfondir mon expertise en
|
||||
programmation, tout en renforçant mes compétences en travail
|
||||
collaboratif et en gestion de projet. Je souhaite également maîtriser
|
||||
des outils et technologies avancés afin d’apporter une réelle valeur
|
||||
ajoutée aux équipes avec lesquelles je travaillerai.
|
||||
|
||||
Intérêts techniques et ambitions
|
||||
professionnelles
|
||||
|
||||
Python, PHP, HTML, C, C++ Passionné d’informatique depuis mon plus jeune âge, j’ai suivi
|
||||
Windows, Linux, VM, Server , Shell ... l’évolution des générations de PC et acquis une solide expertise en
|
||||
Docker assemblage, maintenance et dépannage matériel.
|
||||
Ollama, LLMs , VLMs open-source, API Également passionné par l’impression 3D, je possède plusieurs
|
||||
LangChain, Serveurs MCP ... machines et maîtrise des logiciels spécialisés tels que Fusion 360, ainsi
|
||||
que des slicers (PrusaSlicer, Orca) et firmwares (Klipper, Marlin).
|
||||
Hardware informatique, Impression 3d En parallèle, j’ai des notions en domotique, un domaine que j’aimerais
|
||||
approfondir. Enfin, je m’intéresse particulièrement à l’intelligence
|
||||
Language artificielle, dans laquelle je souhaite me spécialiser après mon stage à
|
||||
l’École 42.
|
||||
Anglais
|
||||
Espagnol
|
||||
Contact Suite à une reconversion professionnelle, j’ai exercé la profession
|
||||
d’infirmier pendant près de 10 ans. Dernier poste clinique Supervaltech
|
||||
06.12.01.01.72 à St Estève en gériatrie de 2018-2023.
|
||||
grascalvet.fernand@gmail.com
|
||||
Ostréiculture 1999-2012
|
||||
https://fernandgrascalvet.com
|
||||
https://github.com/Ladebeze66 Pendant plus de 12 ans, j’ai travaillé dans l’ostréiculture à Leucate,
|
||||
assurant la gestion complète de l’entreprise familiale (Port-Leucate).
|
||||
13 rue de Belfort 66600 Rivesaltes
|
||||
Objectifs Alternance
|
||||
Compétences techniques
|
||||
Intégrer une alternance de 2 ans orientée Data / IA appliquée afin de
|
||||
Python, PHP, HTML, C, C++ concevoir des outils intelligents pour l’automatisation des processus
|
||||
bases Node.js / FastAPI métier :
|
||||
Next, Strapi
|
||||
Windows, Linux, VM, Git agents IA et workflows LLM
|
||||
Server , Shell, Docker analyse documentaire et RAG
|
||||
Architecture serveur et self-hosting IA interfaçage avec systèmes existants (CRM, support, ERP)
|
||||
Ollama, LLMs , VLMs open-source, API
|
||||
LangChain, MCP Servers Projets personnels et environnement
|
||||
Ragflow technique
|
||||
Obsidian
|
||||
Hardware Passionné d’informatique et d’innovation technologique, je développe
|
||||
Impression 3D un environnement personnel orienté intelligence artificielle,
|
||||
automatisation et auto-hébergement. Je conçois et maintiens mes
|
||||
Language propres infrastructures (serveurs Windows/Linux, virtualisation,
|
||||
services cloud personnels, dépôt Git auto-hébergé, observabilité avec
|
||||
Anglais Langfuse, expérimentation d’outils IA comme Searxng, Firecrawl et
|
||||
Espagnol Ollama).
|
||||
J’explore activement les architectures RAG, en combinant LLM open-
|
||||
source, bases vectorielles et pipelines documentaires afin d’améliorer
|
||||
la recherche d’information et l’analyse technique.
|
||||
J’utilise également Obsidian comme un environnement de gestion de
|
||||
connaissances moderne et évolutif. Au-delà de la simple prise de notes,
|
||||
il me permet de structurer, relier et tracer l’information technique dans
|
||||
le temps. Couplé à des LLM et à des bases vectorielles, cet outil devient
|
||||
un véritable support d’ingénierie personnelle.
|
||||
En parallèle, je pratique l’impression 3D (Fusion 360, PrusaSlicer, Orca,
|
||||
Klipper, Marlin), ce qui renforce mon approche concrète de la
|
||||
conception et du prototypage technique.
|
||||
|
||||
68
strapi_extraction/docs/CV/Gras_Calvet_CV.pdf
Normal file
68
strapi_extraction/docs/CV/Gras_Calvet_CV.pdf
Normal file
@ -0,0 +1,68 @@
|
||||
Gras-Calvet Fernand
|
||||
|
||||
04/11/1978he
|
||||
|
||||
Étudiant Informatique – Recherche
|
||||
alternance Data / IA
|
||||
|
||||
(Automatisation & LLM)
|
||||
|
||||
Expérience pro
|
||||
|
||||
Etudiant Ecole 42 / Stage 2023- 2025
|
||||
|
||||
Après des problèmes de santé, bénéficiant d’une RQTH, j’ai entamé une
|
||||
|
||||
Présentation nouvelle reconversion professionnelle dans le domaine de
|
||||
|
||||
Ancien infirmier en reconversion vers l’informatique. Lors de mon stage en entreprise logicielle spécialisée
|
||||
l’ingénierie informatique et l’intelligence
|
||||
artificielle, je me spécialise aujourd’hui dans dans le béton, j’ai conçu une architecture de chatbot multi-agent
|
||||
la conception de systèmes basés sur des
|
||||
LLM, l’automatisation de processus métier intégrant analyse de tickets support, traitement d’images techniques
|
||||
et le traitement de données techniques.
|
||||
Mon parcours atypique m’apporte rigueur, et pipeline RAG.
|
||||
adaptabilité et une forte capacité
|
||||
d’apprentissage autonome. Infirmier 2014-2023
|
||||
|
||||
Contact Suite à une reconversion professionnelle, j’ai exercé la profession
|
||||
d’infirmier pendant près de 10 ans. Dernier poste clinique Supervaltech
|
||||
06.12.01.01.72 à St Estève en gériatrie de 2018-2023.
|
||||
grascalvet.fernand@gmail.com
|
||||
Ostréiculture 1999-2012
|
||||
https://fernandgrascalvet.com
|
||||
https://github.com/Ladebeze66 Pendant plus de 12 ans, j’ai travaillé dans l’ostréiculture à Leucate,
|
||||
assurant la gestion complète de l’entreprise familiale (Port-Leucate).
|
||||
13 rue de Belfort 66600 Rivesaltes
|
||||
Objectifs Alternance
|
||||
Compétences techniques
|
||||
Intégrer une alternance de 2 ans orientée Data / IA appliquée afin de
|
||||
Python, PHP, HTML, C, C++ concevoir des outils intelligents pour l’automatisation des processus
|
||||
bases Node.js / FastAPI métier :
|
||||
Next, Strapi
|
||||
Windows, Linux, VM, Git agents IA et workflows LLM
|
||||
Server , Shell, Docker analyse documentaire et RAG
|
||||
Architecture serveur et self-hosting IA interfaçage avec systèmes existants (CRM, support, ERP)
|
||||
Ollama, LLMs , VLMs open-source, API
|
||||
LangChain, MCP Servers Projets personnels et environnement
|
||||
Ragflow technique
|
||||
Obsidian
|
||||
Hardware Passionné d’informatique et d’innovation technologique, je développe
|
||||
Impression 3D un environnement personnel orienté intelligence artificielle,
|
||||
automatisation et auto-hébergement. Je conçois et maintiens mes
|
||||
Language propres infrastructures (serveurs Windows/Linux, virtualisation,
|
||||
services cloud personnels, dépôt Git auto-hébergé, observabilité avec
|
||||
Anglais Langfuse, expérimentation d’outils IA comme Searxng, Firecrawl et
|
||||
Espagnol Ollama).
|
||||
J’explore activement les architectures RAG, en combinant LLM open-
|
||||
source, bases vectorielles et pipelines documentaires afin d’améliorer
|
||||
la recherche d’information et l’analyse technique.
|
||||
J’utilise également Obsidian comme un environnement de gestion de
|
||||
connaissances moderne et évolutif. Au-delà de la simple prise de notes,
|
||||
il me permet de structurer, relier et tracer l’information technique dans
|
||||
le temps. Couplé à des LLM et à des bases vectorielles, cet outil devient
|
||||
un véritable support d’ingénierie personnelle.
|
||||
En parallèle, je pratique l’impression 3D (Fusion 360, PrusaSlicer, Orca,
|
||||
Klipper, Marlin), ce qui renforce mon approche concrète de la
|
||||
conception et du prototypage technique.
|
||||
|
||||
@ -26,6 +26,8 @@ answers:
|
||||
- "Quel est son profil ?"
|
||||
- "Cherche-t-il une alternance ?"
|
||||
- "A-t-il de l'expérience professionnelle ?"
|
||||
- "Où télécharger le CV ?"
|
||||
- "Je veux ton CV en PDF"
|
||||
priority: 10
|
||||
linked:
|
||||
- "[[bio-fernand]]"
|
||||
@ -57,6 +59,7 @@ visibility: public
|
||||
|
||||
## Contact
|
||||
|
||||
- **CV PDF** : téléchargeable sur le site à l’URL fixe `/cv/cv-fernand-grascalvet.pdf` (remplacer le fichier sur le serveur sans changer ce chemin pour mettre à jour le PDF).
|
||||
- **Téléphone** : 06.12.01.01.72
|
||||
- **Email** : grascalvet.fernand@gmail.com
|
||||
- **Adresse** : 13 rue de Belfort, 66600 Rivesaltes
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user