Dé-Replit-isation complète du projet
Supprimés : - replit.md — doc Replit obsolète - docs/GITEA_TUTO.md — tuto push Replit → Gitea (obsolète) - artifacts/api-server/ — serveur TypeScript mort, remplacé par Flask - artifacts/voix-du-peuple/vite.config.selfhost.ts — fusionné dans vite.config.ts Nettoyés : - ai_agent.py — fallback Replit AI supprimé (Mistral + OpenAI-compatible suffisent) - vite.config.ts — plugins @replit/* retirés, PORT optionnel (défaut 5173) - package.json + pnpm-workspace.yaml — @replit/* retirés du catalog et des deps - badge.tsx + button.tsx — commentaires // @replit supprimés - README.md, DEPLOIEMENT.md, DAT.md, DEX.md, WIKI.md — références Replit remplacées Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
kind = "api"
|
||||
previewPath = "/api"
|
||||
title = "API Server (Flask)"
|
||||
version = "1.0.0"
|
||||
id = "3B4_FFSkEVBkAeYMFRJ2e"
|
||||
|
||||
[[services]]
|
||||
localPort = 8080
|
||||
name = "API Server"
|
||||
paths = ["/api"]
|
||||
|
||||
[services.development]
|
||||
run = "PORT=8080 sh /home/runner/workspace/artifacts/flask-api/start.sh"
|
||||
|
||||
[services.production]
|
||||
|
||||
[services.production.build]
|
||||
args = ["echo", "no build step for Flask"]
|
||||
|
||||
[services.production.run]
|
||||
args = ["sh", "/home/runner/workspace/artifacts/flask-api/start.sh"]
|
||||
|
||||
[services.production.run.env]
|
||||
PORT = "8080"
|
||||
|
||||
[services.production.health.startup]
|
||||
path = "/api/healthz"
|
||||
@@ -1,126 +0,0 @@
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build as esbuild } from "esbuild";
|
||||
import esbuildPluginPino from "esbuild-plugin-pino";
|
||||
import { rm } from "node:fs/promises";
|
||||
|
||||
// Plugins (e.g. 'esbuild-plugin-pino') may use `require` to resolve dependencies
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
|
||||
const artifactDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function buildAll() {
|
||||
const distDir = path.resolve(artifactDir, "dist");
|
||||
await rm(distDir, { recursive: true, force: true });
|
||||
|
||||
await esbuild({
|
||||
entryPoints: [path.resolve(artifactDir, "src/index.ts")],
|
||||
platform: "node",
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
outdir: distDir,
|
||||
outExtension: { ".js": ".mjs" },
|
||||
logLevel: "info",
|
||||
// Some packages may not be bundleable, so we externalize them, we can add more here as needed.
|
||||
// Some of the packages below may not be imported or installed, but we're adding them in case they are in the future.
|
||||
// Examples of unbundleable packages:
|
||||
// - uses native modules and loads them dynamically (e.g. sharp)
|
||||
// - use path traversal to read files (e.g. @google-cloud/secret-manager loads sibling .proto files)
|
||||
external: [
|
||||
"*.node",
|
||||
"sharp",
|
||||
"better-sqlite3",
|
||||
"sqlite3",
|
||||
"canvas",
|
||||
"bcrypt",
|
||||
"argon2",
|
||||
"fsevents",
|
||||
"re2",
|
||||
"farmhash",
|
||||
"xxhash-addon",
|
||||
"bufferutil",
|
||||
"utf-8-validate",
|
||||
"ssh2",
|
||||
"cpu-features",
|
||||
"dtrace-provider",
|
||||
"isolated-vm",
|
||||
"lightningcss",
|
||||
"pg-native",
|
||||
"oracledb",
|
||||
"mongodb-client-encryption",
|
||||
"nodemailer",
|
||||
"handlebars",
|
||||
"knex",
|
||||
"typeorm",
|
||||
"protobufjs",
|
||||
"onnxruntime-node",
|
||||
"@tensorflow/*",
|
||||
"@prisma/client",
|
||||
"@mikro-orm/*",
|
||||
"@grpc/*",
|
||||
"@swc/*",
|
||||
"@aws-sdk/*",
|
||||
"@azure/*",
|
||||
"@opentelemetry/*",
|
||||
"@google-cloud/*",
|
||||
"@google/*",
|
||||
"googleapis",
|
||||
"firebase-admin",
|
||||
"@parcel/watcher",
|
||||
"@sentry/profiling-node",
|
||||
"@tree-sitter/*",
|
||||
"aws-sdk",
|
||||
"classic-level",
|
||||
"dd-trace",
|
||||
"ffi-napi",
|
||||
"grpc",
|
||||
"hiredis",
|
||||
"kerberos",
|
||||
"leveldown",
|
||||
"miniflare",
|
||||
"mysql2",
|
||||
"newrelic",
|
||||
"odbc",
|
||||
"piscina",
|
||||
"realm",
|
||||
"ref-napi",
|
||||
"rocksdb",
|
||||
"sass-embedded",
|
||||
"sequelize",
|
||||
"serialport",
|
||||
"snappy",
|
||||
"tinypool",
|
||||
"usb",
|
||||
"workerd",
|
||||
"wrangler",
|
||||
"zeromq",
|
||||
"zeromq-prebuilt",
|
||||
"playwright",
|
||||
"puppeteer",
|
||||
"puppeteer-core",
|
||||
"electron",
|
||||
],
|
||||
sourcemap: "linked",
|
||||
plugins: [
|
||||
// pino relies on workers to handle logging, instead of externalizing it we use a plugin to handle it
|
||||
esbuildPluginPino({ transports: ["pino-pretty"] })
|
||||
],
|
||||
// Make sure packages that are cjs only (e.g. express) but are bundled continue to work in our esm output file
|
||||
banner: {
|
||||
js: `import { createRequire as __bannerCrReq } from 'node:module';
|
||||
import __bannerPath from 'node:path';
|
||||
import __bannerUrl from 'node:url';
|
||||
|
||||
globalThis.require = __bannerCrReq(import.meta.url);
|
||||
globalThis.__filename = __bannerUrl.fileURLToPath(import.meta.url);
|
||||
globalThis.__dirname = __bannerPath.dirname(globalThis.__filename);
|
||||
`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
buildAll().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "@workspace/api-server",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "export NODE_ENV=development && pnpm run build && pnpm run start",
|
||||
"build": "node ./build.mjs",
|
||||
"start": "node --enable-source-maps ./dist/index.mjs",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@workspace/api-zod": "workspace:*",
|
||||
"@workspace/db": "workspace:*",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2",
|
||||
"drizzle-orm": "catalog:",
|
||||
"express": "^5",
|
||||
"openai": "^6.33.0",
|
||||
"pino": "^9",
|
||||
"pino-http": "^10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.10",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "catalog:",
|
||||
"esbuild": "^0.27.3",
|
||||
"esbuild-plugin-pino": "^2.3.3",
|
||||
"pino-pretty": "^13",
|
||||
"thread-stream": "3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import express, { type Express } from "express";
|
||||
import cors from "cors";
|
||||
import pinoHttp from "pino-http";
|
||||
import router from "./routes";
|
||||
import { logger } from "./lib/logger";
|
||||
|
||||
const app: Express = express();
|
||||
|
||||
app.use(
|
||||
pinoHttp({
|
||||
logger,
|
||||
serializers: {
|
||||
req(req) {
|
||||
return {
|
||||
id: req.id,
|
||||
method: req.method,
|
||||
url: req.url?.split("?")[0],
|
||||
};
|
||||
},
|
||||
res(res) {
|
||||
return {
|
||||
statusCode: res.statusCode,
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/api", router);
|
||||
|
||||
export default app;
|
||||
@@ -1,25 +0,0 @@
|
||||
import app from "./app";
|
||||
import { logger } from "./lib/logger";
|
||||
|
||||
const rawPort = process.env["PORT"];
|
||||
|
||||
if (!rawPort) {
|
||||
throw new Error(
|
||||
"PORT environment variable is required but was not provided.",
|
||||
);
|
||||
}
|
||||
|
||||
const port = Number(rawPort);
|
||||
|
||||
if (Number.isNaN(port) || port <= 0) {
|
||||
throw new Error(`Invalid PORT value: "${rawPort}"`);
|
||||
}
|
||||
|
||||
app.listen(port, (err) => {
|
||||
if (err) {
|
||||
logger.error({ err }, "Error listening on port");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
logger.info({ port }, "Server listening");
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
import OpenAI from "openai";
|
||||
import { logger } from "./logger";
|
||||
|
||||
const openai = new OpenAI({
|
||||
baseURL: process.env.AI_INTEGRATIONS_OPENAI_BASE_URL,
|
||||
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export interface FilterResult {
|
||||
accepted: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export async function filterIdea(content: string): Promise<FilterResult> {
|
||||
const systemPrompt = `Tu es un agent de filtrage éthique pour une plateforme démocratique citoyenne.
|
||||
Ta mission : analyser les idées politiques soumises et décider si elles respectent les valeurs démocratiques.
|
||||
|
||||
Critères d'ACCEPTATION :
|
||||
- L'idée promeut les droits fondamentaux, la liberté, l'égalité, la justice sociale
|
||||
- L'idée propose des améliorations concrètes pour la société
|
||||
- L'idée est constructive, même si critique du gouvernement ou des institutions
|
||||
- L'idée débat de politiques publiques de manière civile
|
||||
|
||||
Critères de REJET :
|
||||
- Contenu fasciste, totalitaire ou autoritaire
|
||||
- Appels à la haine, discrimination ou violence
|
||||
- Négation de droits fondamentaux pour des groupes de personnes
|
||||
- Propagande pour des idéologies qui détruisent la démocratie
|
||||
- Contenu raciste, sexiste, homophobe ou xénophobe
|
||||
- Appels au renversement violent de la démocratie
|
||||
|
||||
Réponds UNIQUEMENT avec un JSON valide, sans markdown, dans ce format exact :
|
||||
{"accepted": true} ou {"accepted": false, "reason": "explication courte en français"}`;
|
||||
|
||||
try {
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-5-mini",
|
||||
max_completion_tokens: 200,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: `Idée à analyser : "${content}"` },
|
||||
],
|
||||
});
|
||||
|
||||
const raw = response.choices[0]?.message?.content ?? '{"accepted": false, "reason": "Erreur d\'analyse"}';
|
||||
const result = JSON.parse(raw) as FilterResult;
|
||||
return result;
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Error filtering idea");
|
||||
return { accepted: false, reason: "Erreur interne de filtrage" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function synthesizeIdeas(ideas: string[]): Promise<string> {
|
||||
if (ideas.length === 0) {
|
||||
return "Aucune idée n'a encore été soumise. Soyez le premier à partager votre vision pour une société meilleure.";
|
||||
}
|
||||
|
||||
const systemPrompt = `Tu es un synthétiseur démocratique. Tu reçois une liste d'idées politiques citoyennes filtrées et validées.
|
||||
Ta mission : créer UN texte synthétique, éloquent et inspirant qui capture l'essence collective de ces idées.
|
||||
|
||||
Ce texte est "La Voix du Peuple" — il doit :
|
||||
- Être écrit à la première personne du pluriel (nous, notre, nos)
|
||||
- Capturer les thèmes communs et les aspirations partagées
|
||||
- Être poétique mais concret, inspirant mais ancré dans la réalité
|
||||
- Faire environ 3-5 paragraphes
|
||||
- Commencer par "Nous, le peuple, ..."
|
||||
- Respecter la diversité des idées sans en ignorer aucune
|
||||
- Être rédigé en français
|
||||
|
||||
NE PAS mentionner les idées individuellement, mais les fondre dans une vision collective cohérente.`;
|
||||
|
||||
const ideasText = ideas.map((idea, i) => `${i + 1}. ${idea}`).join("\n");
|
||||
|
||||
try {
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-5.2",
|
||||
max_completion_tokens: 1000,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{
|
||||
role: "user",
|
||||
content: `Voici les idées citoyennes à synthétiser :\n\n${ideasText}\n\nRédige "La Voix du Peuple".`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return response.choices[0]?.message?.content ?? "Synthèse en cours...";
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Error synthesizing ideas");
|
||||
return "La synthèse est temporairement indisponible. Vos idées ont été enregistrées.";
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import pino from "pino";
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL ?? "info",
|
||||
redact: [
|
||||
"req.headers.authorization",
|
||||
"req.headers.cookie",
|
||||
"res.headers['set-cookie']",
|
||||
],
|
||||
...(isProduction
|
||||
? {}
|
||||
: {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: { colorize: true },
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { HealthCheckResponse } from "@workspace/api-zod";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.get("/healthz", (_req, res) => {
|
||||
const data = HealthCheckResponse.parse({ status: "ok" });
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,100 +0,0 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { eq, count, and } from "drizzle-orm";
|
||||
import { db, ideasTable, synthesisTable } from "@workspace/db";
|
||||
import { SubmitIdeaBody } from "@workspace/api-zod";
|
||||
import { filterIdea, synthesizeIdeas } from "../lib/ai-agent";
|
||||
import { logger } from "../lib/logger";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.get("/ideas", async (_req, res): Promise<void> => {
|
||||
const ideas = await db
|
||||
.select()
|
||||
.from(ideasTable)
|
||||
.where(eq(ideasTable.accepted, true))
|
||||
.orderBy(ideasTable.createdAt);
|
||||
res.json(ideas);
|
||||
});
|
||||
|
||||
router.get("/ideas/stats", async (_req, res): Promise<void> => {
|
||||
const [totalRow] = await db.select({ value: count() }).from(ideasTable);
|
||||
const [acceptedRow] = await db
|
||||
.select({ value: count() })
|
||||
.from(ideasTable)
|
||||
.where(eq(ideasTable.accepted, true));
|
||||
const [rejectedRow] = await db
|
||||
.select({ value: count() })
|
||||
.from(ideasTable)
|
||||
.where(eq(ideasTable.accepted, false));
|
||||
|
||||
res.json({
|
||||
total: Number(totalRow?.value ?? 0),
|
||||
accepted: Number(acceptedRow?.value ?? 0),
|
||||
rejected: Number(rejectedRow?.value ?? 0),
|
||||
});
|
||||
});
|
||||
|
||||
router.post("/ideas", async (req, res): Promise<void> => {
|
||||
const parsed = SubmitIdeaBody.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ error: "validation_error", message: parsed.error.message });
|
||||
return;
|
||||
}
|
||||
|
||||
const { content, author } = parsed.data;
|
||||
|
||||
req.log.info({ contentLength: content.length }, "Filtering new idea");
|
||||
|
||||
const filterResult = await filterIdea(content);
|
||||
|
||||
const [idea] = await db
|
||||
.insert(ideasTable)
|
||||
.values({
|
||||
content,
|
||||
author: author ?? null,
|
||||
accepted: filterResult.accepted,
|
||||
rejectionReason: filterResult.reason ?? null,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (filterResult.accepted) {
|
||||
triggerSynthesisUpdate().catch((err) => {
|
||||
logger.error({ err }, "Background synthesis update failed");
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
id: idea!.id,
|
||||
accepted: filterResult.accepted,
|
||||
reason: filterResult.reason,
|
||||
idea: idea,
|
||||
});
|
||||
});
|
||||
|
||||
async function triggerSynthesisUpdate(): Promise<void> {
|
||||
const ideas = await db
|
||||
.select({ content: ideasTable.content })
|
||||
.from(ideasTable)
|
||||
.where(eq(ideasTable.accepted, true))
|
||||
.orderBy(ideasTable.createdAt);
|
||||
|
||||
const ideaTexts = ideas.map((i) => i.content);
|
||||
const synthesizedText = await synthesizeIdeas(ideaTexts);
|
||||
|
||||
const existing = await db.select().from(synthesisTable).limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(synthesisTable)
|
||||
.set({ text: synthesizedText, ideaCount: ideaTexts.length });
|
||||
} else {
|
||||
await db.insert(synthesisTable).values({
|
||||
text: synthesizedText,
|
||||
ideaCount: ideaTexts.length,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info({ ideaCount: ideaTexts.length }, "Synthesis updated");
|
||||
}
|
||||
|
||||
export default router;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import healthRouter from "./health";
|
||||
import ideasRouter from "./ideas";
|
||||
import synthesisRouter from "./synthesis";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.use(healthRouter);
|
||||
router.use(ideasRouter);
|
||||
router.use(synthesisRouter);
|
||||
|
||||
export default router;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { db, synthesisTable } from "@workspace/db";
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
router.get("/synthesis", async (_req, res): Promise<void> => {
|
||||
const [synthesis] = await db.select().from(synthesisTable).limit(1);
|
||||
|
||||
if (!synthesis) {
|
||||
res.json({
|
||||
text: "Aucune idée n'a encore été soumise. Soyez le premier à partager votre vision pour une société meilleure.",
|
||||
ideaCount: 0,
|
||||
updatedAt: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
text: synthesis.text,
|
||||
ideaCount: synthesis.ideaCount,
|
||||
updatedAt: synthesis.updatedAt,
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../lib/db"
|
||||
},
|
||||
{
|
||||
"path": "../../lib/api-zod"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@ La Voix du Peuple — Agent IA
|
||||
Copyright (C) 2026 billisdead — Licence EUPL-1.2
|
||||
|
||||
Agent IA pour le filtrage éthique et la synthèse démocratique.
|
||||
Supporte Mistral AI, OpenAI, et les intégrations Replit AI.
|
||||
Supporte Mistral AI (par défaut) et tout fournisseur compatible OpenAI.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -20,10 +20,9 @@ _client: OpenAI | None = None
|
||||
|
||||
def get_client() -> OpenAI:
|
||||
"""
|
||||
Supporte trois modes (par ordre de priorité) :
|
||||
1. Mistral AI : MISTRAL_API_KEY (+ MISTRAL_BASE_URL optionnel)
|
||||
2. OpenAI standard : OPENAI_API_KEY (+ OPENAI_BASE_URL optionnel)
|
||||
3. Replit AI Integration : AI_INTEGRATIONS_OPENAI_BASE_URL + AI_INTEGRATIONS_OPENAI_API_KEY
|
||||
Supporte deux modes (par ordre de priorité) :
|
||||
1. Mistral AI : MISTRAL_API_KEY (+ MISTRAL_BASE_URL optionnel)
|
||||
2. OpenAI-compatible : OPENAI_API_KEY (+ OPENAI_BASE_URL optionnel)
|
||||
"""
|
||||
global _client
|
||||
if _client is None:
|
||||
@@ -31,26 +30,20 @@ def get_client() -> OpenAI:
|
||||
mistral_base = os.environ.get("MISTRAL_BASE_URL", MISTRAL_BASE_URL)
|
||||
std_key = os.environ.get("OPENAI_API_KEY")
|
||||
std_base = os.environ.get("OPENAI_BASE_URL")
|
||||
replit_base = os.environ.get("AI_INTEGRATIONS_OPENAI_BASE_URL")
|
||||
replit_key = os.environ.get("AI_INTEGRATIONS_OPENAI_API_KEY")
|
||||
|
||||
if mistral_key:
|
||||
logger.info("Utilisation de l'API Mistral AI (%s)", mistral_base)
|
||||
_client = OpenAI(base_url=mistral_base, api_key=mistral_key)
|
||||
elif std_key:
|
||||
logger.info("Utilisation de l'API OpenAI")
|
||||
kwargs = {"api_key": std_key}
|
||||
logger.info("Utilisation d'une API compatible OpenAI")
|
||||
kwargs: dict = {"api_key": std_key}
|
||||
if std_base:
|
||||
kwargs["base_url"] = std_base
|
||||
_client = OpenAI(**kwargs)
|
||||
elif replit_base and replit_key:
|
||||
logger.info("Utilisation de l'intégration Replit AI")
|
||||
_client = OpenAI(base_url=replit_base, api_key=replit_key)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Aucune clé IA configurée. "
|
||||
"Définissez MISTRAL_API_KEY ou OPENAI_API_KEY dans le fichier .env, "
|
||||
"ou configurez les intégrations Replit AI."
|
||||
"Définissez MISTRAL_API_KEY (recommandé) ou OPENAI_API_KEY dans le fichier .env."
|
||||
)
|
||||
return _client
|
||||
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
"@radix-ui/react-toggle": "^1.1.3",
|
||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.0",
|
||||
"@replit/vite-plugin-cartographer": "catalog:",
|
||||
"@replit/vite-plugin-dev-banner": "catalog:",
|
||||
"@replit/vite-plugin-runtime-error-modal": "catalog:",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
|
||||
@@ -4,23 +4,17 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
// @replit
|
||||
// Whitespace-nowrap: Badges should never wrap.
|
||||
"whitespace-nowrap inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" +
|
||||
" hover-elevate ",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
// @replit shadow-xs instead of shadow, no hover because we use hover-elevate
|
||||
"border-transparent bg-primary text-primary-foreground shadow-xs",
|
||||
secondary:
|
||||
// @replit no hover because we use hover-elevate
|
||||
"border-transparent bg-secondary text-secondary-foreground",
|
||||
destructive:
|
||||
// @replit shadow-xs instead of shadow, no hover because we use hover-elevate
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow-xs",
|
||||
// @replit shadow-xs" - use badge outline variable
|
||||
outline: "text-foreground border [border-color:var(--badge-outline)]",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,24 +11,17 @@ const buttonVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
// @replit: no hover, and add primary border
|
||||
"bg-primary text-primary-foreground border border-primary-border",
|
||||
"bg-primary text-primary-foreground border border-primary-border",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm border-destructive-border",
|
||||
outline:
|
||||
// @replit Shows the background color of whatever card / sidebar / accent background it is inside of.
|
||||
// Inherits the current text color. Uses shadow-xs. no shadow on active
|
||||
// No hover state
|
||||
" border [border-color:var(--button-outline)] shadow-xs active:shadow-none ",
|
||||
secondary:
|
||||
// @replit border, no hover, no shadow, secondary border.
|
||||
"border bg-secondary text-secondary-foreground border border-secondary-border ",
|
||||
// @replit no hover, transparent border
|
||||
ghost: "border border-transparent",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
// @replit changed sizes
|
||||
default: "min-h-9 px-4 py-2",
|
||||
sm: "min-h-8 rounded-md px-3 text-xs",
|
||||
lg: "min-h-10 rounded-md px-8",
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
base: "/",
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(import.meta.dirname, "src"),
|
||||
},
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
root: path.resolve(import.meta.dirname),
|
||||
build: {
|
||||
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
});
|
||||
@@ -2,49 +2,15 @@ import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import path from "path";
|
||||
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
|
||||
|
||||
const rawPort = process.env.PORT;
|
||||
|
||||
if (!rawPort) {
|
||||
throw new Error(
|
||||
"PORT environment variable is required but was not provided.",
|
||||
);
|
||||
}
|
||||
|
||||
const port = Number(rawPort);
|
||||
|
||||
if (Number.isNaN(port) || port <= 0) {
|
||||
throw new Error(`Invalid PORT value: "${rawPort}"`);
|
||||
}
|
||||
|
||||
const basePath = process.env.BASE_PATH;
|
||||
|
||||
if (!basePath) {
|
||||
throw new Error(
|
||||
"BASE_PATH environment variable is required but was not provided.",
|
||||
);
|
||||
}
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 5173;
|
||||
const basePath = process.env.BASE_PATH ?? "/";
|
||||
|
||||
export default defineConfig({
|
||||
base: basePath,
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
runtimeErrorOverlay(),
|
||||
...(process.env.NODE_ENV !== "production" &&
|
||||
process.env.REPL_ID !== undefined
|
||||
? [
|
||||
await import("@replit/vite-plugin-cartographer").then((m) =>
|
||||
m.cartographer({
|
||||
root: path.resolve(import.meta.dirname, ".."),
|
||||
}),
|
||||
),
|
||||
await import("@replit/vite-plugin-dev-banner").then((m) =>
|
||||
m.devBanner(),
|
||||
),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
Reference in New Issue
Block a user