- TOC: Ajout indentation par niveau et correction des puces sur h6+ - TOC: Correction navigation avec scroll précis (TreeWalker) - TOC: Séparation scroll page/TOC avec position sticky - Preview: Correction bug ** apparaissant au retour en mode édition - Enhanced Mode: Nouveau format de réponse (blocs comment/document) - API: Ajout timeouts 120s et agent HTTPS pour éviter erreurs réseau - CSS: Ajout overflow-y sur sections pour scroll indépendant Fichiers modifiés : - assets/js/app.js (TOC, navigation, preview) - assets/css/style.css (scroll, sticky positioning) - routes/ai.js (timeouts, format réponse)
460 lines
13 KiB
JavaScript
460 lines
13 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const https = require('https');
|
|
require('dotenv').config({ path: './config/.env' });
|
|
|
|
// Mistral AI Configuration
|
|
const MISTRAL_API_KEY = process.env.MISTRAL_API_KEY;
|
|
const MISTRAL_BASE_URL = process.env.MISTRAL_BASE_URL || 'https://api.mistral.ai/v1';
|
|
const MISTRAL_MODEL = process.env.MISTRAL_MODEL || 'mistral-large-latest';
|
|
const AI_ENABLED = process.env.AI_ENABLED === 'true';
|
|
|
|
// Verification middleware
|
|
function checkAIEnabled(req, res, next) {
|
|
if (!AI_ENABLED) {
|
|
return res.status(503).json({
|
|
success: false,
|
|
error: 'AI features are disabled'
|
|
});
|
|
}
|
|
|
|
if (!MISTRAL_API_KEY) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Mistral API key not configured'
|
|
});
|
|
}
|
|
|
|
next();
|
|
}
|
|
|
|
// Create a custom HTTPS agent with longer timeouts
|
|
const httpsAgent = new https.Agent({
|
|
keepAlive: true,
|
|
timeout: 120000, // 120 seconds
|
|
maxSockets: 5
|
|
});
|
|
|
|
// Function to call Mistral API with timeout handling
|
|
async function callMistralAPI(messages, temperature = null) {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 120000); // 120 seconds timeout
|
|
|
|
try {
|
|
const response = await fetch(`${MISTRAL_BASE_URL}/chat/completions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${MISTRAL_API_KEY}`
|
|
},
|
|
body: JSON.stringify({
|
|
model: MISTRAL_MODEL,
|
|
messages: messages,
|
|
temperature: temperature !== null ? temperature : parseFloat(process.env.AI_TEMPERATURE) || 0.3,
|
|
max_tokens: parseInt(process.env.AI_MAX_TOKENS) || 35000,
|
|
top_p: parseFloat(process.env.AI_TOP_P) || 0.85
|
|
}),
|
|
signal: controller.signal,
|
|
agent: httpsAgent
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`Mistral API Error: ${response.status} - ${error}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.choices[0].message.content;
|
|
} catch (error) {
|
|
clearTimeout(timeoutId);
|
|
console.error('Mistral API Error:', error);
|
|
|
|
// Better error messages
|
|
if (error.name === 'AbortError') {
|
|
throw new Error('Request timeout: The API took too long to respond (>120s)');
|
|
}
|
|
if (error.cause && error.cause.code === 'UND_ERR_HEADERS_TIMEOUT') {
|
|
throw new Error('Connection timeout: Unable to connect to Mistral API. Check your network or API endpoint.');
|
|
}
|
|
if (error.cause && error.cause.code === 'ECONNREFUSED') {
|
|
throw new Error('Connection refused: Cannot reach Mistral API endpoint.');
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// POST /api/ai/rephrase - Rephrase text
|
|
router.post('/rephrase', checkAIEnabled, async (req, res) => {
|
|
try {
|
|
const { text, context = '' } = req.body;
|
|
|
|
if (!text || text.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Text to rephrase is required'
|
|
});
|
|
}
|
|
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content: `You are an assistant specialized in rephrasing technical and design texts.
|
|
|
|
STRICT RULES:
|
|
1. Rephrase the text to improve clarity, style, and fluency
|
|
2. Preserve the technical level and all important details
|
|
3. Respond ONLY with the final rephrased text
|
|
4. No introduction, conclusion, explanation, or commentary
|
|
5. Do not start with "Here is", "The rephrased text" or other preamble
|
|
6. Start directly with the rephrased content
|
|
|
|
${context ? `Document context: ${context}` : ''}`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `Rephrase this text: "${text}"`
|
|
}
|
|
];
|
|
|
|
const result = await callMistralAPI(messages, 0.2); // Precise rephrasing
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
original: text,
|
|
rephrased: result
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Rephrasing error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Error during rephrasing: ' + error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/ai/check-inconsistencies - Check for inconsistencies
|
|
router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => {
|
|
try {
|
|
const { content } = req.body;
|
|
|
|
if (!content || content.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Content to analyze is required'
|
|
});
|
|
}
|
|
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content: `You are an expert in technical design document analysis.
|
|
|
|
Analyze the following document and identify all potential inconsistencies:
|
|
- Contradictions in information
|
|
- Conflicting decisions
|
|
- Inconsistencies in terminology
|
|
- Logical issues in architecture or choices
|
|
|
|
Respond directly with your detailed analysis of the inconsistencies found.`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `Analyze this document to detect inconsistencies:\n\n${content}`
|
|
}
|
|
];
|
|
|
|
const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
analysis: result
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Inconsistencies analysis error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Error during analysis: ' + error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/ai/check-duplications - Check for duplications
|
|
router.post('/check-duplications', checkAIEnabled, async (req, res) => {
|
|
try {
|
|
const { content } = req.body;
|
|
|
|
if (!content || content.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Content to analyze is required'
|
|
});
|
|
}
|
|
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content: `You are a content analysis expert.
|
|
|
|
Analyze the following document to identify:
|
|
- Repeated or redundant information
|
|
- Sections that cover the same topic
|
|
- Duplicated explanations
|
|
- Concepts presented multiple times
|
|
|
|
Suggest ways to eliminate these duplications while preserving important information.
|
|
Respond directly with your analysis and suggestions.`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `Analyze this document to detect duplications:\n\n${content}`
|
|
}
|
|
];
|
|
|
|
const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
analysis: result
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Duplications analysis error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Error during analysis: ' + error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/ai/give-advice - Give advice
|
|
router.post('/give-advice', checkAIEnabled, async (req, res) => {
|
|
try {
|
|
const { content, domain = 'general' } = req.body;
|
|
|
|
if (!content || content.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Content to analyze is required'
|
|
});
|
|
}
|
|
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content: `You are an expert consultant in design and technical architecture in the domain: ${domain}.
|
|
|
|
Analyze the provided design document and give constructive advice to improve it.
|
|
|
|
Focus on:
|
|
- Documentation completeness
|
|
- Clarity of explanations
|
|
- Content organization
|
|
- Domain best practices
|
|
- Important missing points
|
|
- Concrete improvement suggestions
|
|
|
|
Be constructive and practical in your recommendations.
|
|
Respond directly with your advice and improvement suggestions.`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `Analyze this design document and provide advice to improve it:\n\n${content}`
|
|
}
|
|
];
|
|
|
|
const result = await callMistralAPI(messages, 0.4); // Balanced advice
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
advice: result
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Advice error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Error during analysis: ' + error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/ai/liberty-mode - Enhanced mode (iterative generation)
|
|
router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|
try {
|
|
const { content, iterations = 1, precision = 70, focus = 'design' } = req.body;
|
|
|
|
if (!content || content.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Base content is required'
|
|
});
|
|
}
|
|
|
|
const maxIterations = Math.min(parseInt(iterations), 10); // Limit to 10 iterations
|
|
const precisionPercent = Math.min(Math.max(parseInt(precision), 10), 100); // Between 10% and 100%
|
|
|
|
// Configure streaming for real-time responses
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive',
|
|
'Access-Control-Allow-Origin': '*'
|
|
});
|
|
|
|
let currentContent = content;
|
|
|
|
for (let i = 0; i < maxIterations; i++) {
|
|
try {
|
|
const messages = [
|
|
{
|
|
role: 'system',
|
|
content: `You are a technical design expert with "Enhanced Mode".
|
|
|
|
MISSION: Improve and enrich the document respecting EXACTLY this precision level: ${precisionPercent}%
|
|
|
|
PRECISION RULES:
|
|
- At ${precisionPercent}%: You can deduce and add content up to ${precisionPercent}% based on existing information
|
|
- At ${100 - precisionPercent}%: You can create logical and relevant content even without explicit info in the text
|
|
|
|
INSTRUCTIONS:
|
|
1. Enrich ALL of the document consistently
|
|
2. Add sections, details, explanations, conceptual diagrams
|
|
3. Develop existing ideas with the allowed creativity
|
|
4. Maintain logical structure
|
|
|
|
MANDATORY RESPONSE FORMAT - Use code blocks with triple backticks:
|
|
|
|
First, write your comment explaining the improvements inside a code block labeled "comment${i + 1}":
|
|
- What sections were added
|
|
- What was enhanced
|
|
- Reasoning behind changes
|
|
|
|
Then, write the complete improved markdown document inside a code block labeled "document".
|
|
|
|
Example format:
|
|
First block: comment${i + 1}
|
|
Second block: document
|
|
|
|
Focus: ${focus}
|
|
Precision: ${precisionPercent}%
|
|
Iteration: ${i + 1}/${maxIterations}`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `Document to improve (Iteration ${i + 1}):\n\n${currentContent}`
|
|
}
|
|
];
|
|
|
|
// Temperature based on precision (more creative = higher temperature)
|
|
const temperature = (100 - precisionPercent) / 100 * 0.8 + 0.1; // Between 0.1 and 0.9
|
|
|
|
const result = await callMistralAPI(messages, temperature);
|
|
|
|
// Extract code blocks
|
|
const commentRegex = /```comment\d+\s*([\s\S]*?)```/;
|
|
const documentRegex = /```document\s*([\s\S]*?)```/;
|
|
|
|
const commentMatch = result.match(commentRegex);
|
|
const documentMatch = result.match(documentRegex);
|
|
|
|
let explanation = '';
|
|
let newMarkdown = currentContent; // Default, keep old content
|
|
|
|
if (commentMatch && commentMatch[1]) {
|
|
explanation = commentMatch[1].trim();
|
|
}
|
|
|
|
if (documentMatch && documentMatch[1]) {
|
|
newMarkdown = documentMatch[1].trim();
|
|
// Update for next iteration
|
|
currentContent = newMarkdown;
|
|
}
|
|
|
|
// Fallback: if no code blocks found, try old format for compatibility
|
|
if (!commentMatch && !documentMatch) {
|
|
const parts = result.split('---SPLIT---');
|
|
if (parts.length >= 2) {
|
|
explanation = parts[0].trim();
|
|
newMarkdown = parts[1].trim();
|
|
currentContent = newMarkdown;
|
|
} else {
|
|
explanation = result;
|
|
}
|
|
}
|
|
|
|
// Send this iteration's response
|
|
const iterationData = {
|
|
iteration: i + 1,
|
|
explanation: explanation,
|
|
markdown: newMarkdown,
|
|
completed: false
|
|
};
|
|
|
|
res.write(`data: ${JSON.stringify(iterationData)}\n\n`);
|
|
|
|
// Small delay to allow client-side display
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
} catch (iterationError) {
|
|
console.error(`Iteration ${i + 1} error:`, iterationError);
|
|
|
|
const errorData = {
|
|
iteration: i + 1,
|
|
error: `Iteration ${i + 1} error: ${iterationError.message}`,
|
|
completed: true
|
|
};
|
|
|
|
res.write(`data: ${JSON.stringify(errorData)}\n\n`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// End signal
|
|
const finalData = {
|
|
completed: true,
|
|
totalIterations: maxIterations,
|
|
finalMarkdown: currentContent
|
|
};
|
|
|
|
res.write(`data: ${JSON.stringify(finalData)}\n\n`);
|
|
res.end();
|
|
|
|
} catch (error) {
|
|
console.error('Enhanced mode error:', error);
|
|
|
|
const errorData = {
|
|
error: 'Error during generation: ' + error.message,
|
|
completed: true
|
|
};
|
|
|
|
res.write(`data: ${JSON.stringify(errorData)}\n\n`);
|
|
res.end();
|
|
}
|
|
});
|
|
|
|
// GET /api/ai/status - AI status
|
|
router.get('/status', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
enabled: AI_ENABLED,
|
|
model: MISTRAL_MODEL,
|
|
configured: !!MISTRAL_API_KEY
|
|
}
|
|
});
|
|
});
|
|
|
|
module.exports = router; |