import { writeFileSync, mkdirSync } from 'fs'; const files = { // ====== FICHIERS DE BASE ====== "src/app/globals.css": `@tailwind base; @tailwind components; @tailwind utilities; :root { --vortex-400: #7be0f7; --vortex-500: #3b82f6; --singularity-glow: #f59e0b; } body { background: #0a0a0a; color: #e5e5e5; } .gradient-text { background: linear-gradient(135deg, var(--vortex-400), var(--singularity-glow)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }`, "tailwind.config.js": `/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], theme: { extend: { colors: { vortex: { 400: '#7be0f7', 500: '#3b82f6', 600: '#2563eb' }, singularity: { glow: '#f59e0b' }, }, }, }, plugins: [], };`, "tsconfig.json": JSON.stringify({ compilerOptions: { target: "ES2017", lib: ["dom", "dom.iterable", "esnext"], allowJs: true, skipLibCheck: true, strict: false, noEmit: true, esModuleInterop: true, module: "esnext", moduleResolution: "bundler", resolveJsonModule: true, isolatedModules: true, jsx: "preserve", incremental: true, paths: { "@/*": ["./src/*"] } }, include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], exclude: ["node_modules"] }, null, 2), // ====== LAYOUT & PAGE ====== "src/app/layout.tsx": `import type { Metadata } from "next"; import "./globals.css"; import { VortexProvider } from "@/context/VortexContext"; export const metadata: Metadata = { title: "ATYPIQUE v3.0", description: "Binôme cognitif", }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); }`, "src/app/page.tsx": `"use client"; import { Header } from "@/components/Header"; import { Sidebar } from "@/components/Sidebar"; import { Chat } from "@/components/Chat"; export default function Home() { return (
); }`, // ====== CONTEXT ====== "src/context/VortexContext.tsx": `"use client"; import React, { createContext, useContext, useState, useCallback } from "react"; const API = process.env.NEXT_PUBLIC_API_URL || "https://rds777-atypique-api.hf.space"; type VortexState = { progress: number; activeTab: string; setActiveTab: (t: string) => void; sendMessage: (msg: string) => Promise; }; const Ctx = createContext({} as VortexState); export const useVortex = () => useContext(Ctx); export function VortexProvider({ children }: { children: React.ReactNode }) { const [progress] = useState(0); const [activeTab, setActiveTab] = useState("chat"); const sendMessage = useCallback(async (msg: string) => { try { const r = await fetch(API + "/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: msg }), }); const d = await r.json(); return d.response; } catch { return "Erreur de connexion"; } }, []); return ( {children} ); }`, // ====== COMPOSANTS ====== "src/components/Header.tsx": `"use client"; import { motion } from "framer-motion"; import { Zap } from "lucide-react"; export function Header() { return (

ATYPIQUE

); }`, "src/components/Sidebar.tsx": `"use client"; import { motion } from "framer-motion"; import { MessageCircle } from "lucide-react"; import { useVortex } from "@/context/VortexContext"; export function Sidebar() { const { activeTab, setActiveTab } = useVortex(); return ( ); }`, // ====== MIC BUTTON CORRIGÉ (détection client + Web Speech) ====== "src/components/MicButton.tsx": `"use client"; import { useState, useEffect, useRef } from "react"; import { Mic, MicOff, Loader } from "lucide-react"; interface MicButtonProps { onTranscription: (text: string) => void; disabled?: boolean; } export function MicButton({ onTranscription, disabled }: MicButtonProps) { const [isListening, setIsListening] = useState(false); const [isSupported, setIsSupported] = useState(false); const [error, setError] = useState(null); const recognitionRef = useRef(null); // Détection uniquement côté client useEffect(() => { const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; if (SpeechRecognition) { setIsSupported(true); const recognition = new SpeechRecognition(); recognition.lang = "fr-FR"; recognition.continuous = false; recognition.interimResults = false; recognition.maxAlternatives = 1; recognition.onresult = (event: any) => { const transcript = event.results[0][0].transcript; console.log("✅ Transcription:", transcript); onTranscription(transcript); setIsListening(false); }; recognition.onerror = (event: any) => { console.error("❌ Erreur micro:", event.error); setError(event.error); setIsListening(false); }; recognition.onend = () => { setIsListening(false); }; recognitionRef.current = recognition; } else { setIsSupported(false); } }, []); const toggleListening = () => { if (!recognitionRef.current) return; setError(null); if (isListening) { recognitionRef.current.stop(); setIsListening(false); } else { try { recognitionRef.current.start(); setIsListening(true); console.log("🎤 Écoute démarrée..."); } catch (e) { console.error("Erreur start:", e); } } }; if (!isSupported) { return ( 🚫 ); } return ( ); }`, // ====== CHAT (avec le mic intégré) ====== "src/components/Chat.tsx": `"use client"; import { useState } from "react"; import { motion } from "framer-motion"; import { Send } from "lucide-react"; import { useVortex } from "@/context/VortexContext"; import { MicButton } from "./MicButton"; export function Chat() { const [messages, setMessages] = useState([ { role: "assistant", content: "Bonjour ! Tapez @help pour voir les commandes." }, ]); const [input, setInput] = useState(""); const { sendMessage } = useVortex(); const handleSend = async () => { if (!input.trim()) return; setMessages((prev) => [...prev, { role: "user", content: input }]); setInput(""); const response = await sendMessage(input); setMessages((prev) => [...prev, { role: "assistant", content: response }]); }; return (
{messages.map((msg, i) => (

{msg.content}

))}
setInput(text)} /> setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSend()} placeholder="Votre message..." className="flex-1 bg-gray-800 text-gray-200 px-4 py-2 rounded-xl outline-none text-sm" />
); }`, // ====== PAGE DE TEST MIC (indépendante) ====== "public/micro.html": ` Micro ATYPIQUE

🎤 Micro ATYPIQUE

Enregistrez votre voix et obtenez une transcription + réponse

` }; // Écriture des fichiers for (const [path, content] of Object.entries(files)) { const dir = path.substring(0, path.lastIndexOf('/')); if (dir) mkdirSync(dir, { recursive: true }); writeFileSync(path, content, 'utf8'); console.log('✅ ' + path); } console.log('\n🎉 Tous les fichiers ont été créés (UI riche + micro corrigé).'); console.log('Lance maintenant :'); console.log(' npm install @tailwindcss/postcss tailwindcss framer-motion lucide-react'); console.log(' npm run build'); console.log(' npx vercel --prod --yes');