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 (
);
}`,
"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) => (
))}
);
}`,
// ====== 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');