Elysia-Suite's picture
Upload 25 files
0b194e5 verified
/*
ELYSIA MARKDOWN STUDIO v1.0 - AI Tools Module
Elysia's AI-powered features
*/
import Utils from "./utils.js";
import API from "./api.js";
import DB from "./db.js";
const AITools = {
isProcessing: false,
init() {
document.getElementById("btn-ai-tools").addEventListener("click", () => {
Utils.modal.open("modal-ai-tools");
});
document.querySelectorAll(".ai-tool-card").forEach(card => {
card.addEventListener("click", () => {
const tool = card.getAttribute("data-tool");
this.executeTool(tool);
});
});
},
async executeTool(tool) {
if (this.isProcessing) {
Utils.toast.warning("Please wait - Elysia is already working on something!");
return;
}
Utils.modal.close("modal-ai-tools");
this.isProcessing = true;
// Show loading indicator
const loadingToast = this.showLoadingState(tool);
try {
switch (tool) {
case "summarize":
await this.summarize();
break;
case "improve":
await this.improveWriting();
break;
case "merge":
await this.mergeDocuments();
break;
case "outline":
await this.extractOutline();
break;
case "duplicates":
await this.findDuplicates();
break;
case "organize":
await this.smartOrganize();
break;
}
} catch (err) {
console.error(`AI tool ${tool} failed:`, err);
// User-friendly error messages
let errorMsg = "AI tool failed";
if (err.message.includes("API key")) {
errorMsg = "API key not configured. Please add it in Settings ⚙️";
} else if (err.message.includes("network") || err.message.includes("fetch")) {
errorMsg = "Network error. Check your connection and try again.";
} else if (err.message.includes("rate limit")) {
errorMsg = "Rate limit reached. Please wait a moment and try again.";
} else {
errorMsg = err.message || "AI tool failed. Please try again.";
}
Utils.toast.error(errorMsg, 5000);
} finally {
this.isProcessing = false;
// Remove loading toast
if (loadingToast) loadingToast.remove();
}
},
showLoadingState(tool) {
const messages = {
summarize: "🧠 Elysia is reading and summarizing...",
improve: "✨ Elysia is polishing your writing...",
merge: "📚 Elysia is merging documents...",
outline: "🎯 Elysia is extracting outline...",
duplicates: "🔍 Elysia is analyzing duplicates...",
organize: "🏷️ Elysia is organizing content..."
};
const toast = Utils.toast.show(messages[tool] || "🧠 Elysia is thinking...", "loading", 0); // 0 = no auto-close
return toast;
},
async summarize() {
const content = window.app?.editor.getContent();
if (!content) {
Utils.toast.warning("No content to summarize");
return;
}
if (content.length < 100) {
Utils.toast.warning("Content too short. Add at least 100 characters for better summary.");
return;
}
const summary = await API.summarize(content);
// Create new document with summary
const doc = await DB.createDocument({
title: `Summary of ${window.app?.currentDoc?.title || "Document"}`,
content: summary
});
Utils.toast.success("Summary created!");
window.app?.loadDocument(doc.id);
},
async improveWriting() {
const content = window.app?.editor.getContent();
if (!content) {
Utils.toast.warning("No content to improve");
return;
}
if (content.length < 50) {
Utils.toast.warning("Content too short. Add at least 50 characters for improvement.");
return;
}
// 🎲 Show style selection modal (VS-inspired)
this.showImproveStyleModal(content);
},
// 🎲 VS-INSPIRED: Show style selection modal
showImproveStyleModal(content) {
// Add styles if not present
if (!document.getElementById("improve-styles-css")) {
const style = document.createElement("style");
style.id = "improve-styles-css";
style.textContent = `
.improve-style-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
margin: 20px 0;
}
.improve-style-card {
padding: 16px;
background: var(--bg-secondary);
border: 2px solid transparent;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.improve-style-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.improve-style-card.selected {
border-color: var(--accent);
background: rgba(167, 139, 250, 0.15);
}
.improve-style-icon {
font-size: 28px;
margin-bottom: 8px;
}
.improve-style-name {
font-weight: 600;
margin-bottom: 4px;
}
.improve-style-desc {
font-size: 11px;
color: var(--text-muted);
}
.improve-results {
margin-top: 16px;
max-height: 400px;
overflow-y: auto;
}
.improve-result-card {
padding: 16px;
margin-bottom: 12px;
background: var(--bg-secondary);
border-radius: 10px;
border-left: 4px solid var(--accent);
}
.improve-result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.improve-result-preview {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.6;
max-height: 150px;
overflow-y: auto;
padding: 10px;
background: var(--bg-tertiary);
border-radius: 6px;
}
`;
document.head.appendChild(style);
}
const modal = document.createElement("div");
modal.className = "modal active";
modal.id = "modal-improve-styles";
modal.innerHTML = `
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2>✨ Improve Writing — Choose Style</h2>
<button class="btn-icon" onclick="document.getElementById('modal-improve-styles').remove()">✕</button>
</div>
<div class="modal-body">
<p style="color: var(--text-secondary); margin-bottom: 16px;">
🎲 <strong>Verbalized Sampling:</strong> I'll generate 5 different styles and you choose your favorite!
</p>
<div class="improve-style-grid">
<div class="improve-style-card" data-style="concise">
<div class="improve-style-icon">📝</div>
<div class="improve-style-name">Concis</div>
<div class="improve-style-desc">Court et direct</div>
</div>
<div class="improve-style-card" data-style="creative">
<div class="improve-style-icon">🎨</div>
<div class="improve-style-name">Créatif</div>
<div class="improve-style-desc">Vivant et imagé</div>
</div>
<div class="improve-style-card" data-style="academic">
<div class="improve-style-icon">📚</div>
<div class="improve-style-name">Académique</div>
<div class="improve-style-desc">Formel et structuré</div>
</div>
<div class="improve-style-card" data-style="professional">
<div class="improve-style-icon">💼</div>
<div class="improve-style-name">Professionnel</div>
<div class="improve-style-desc">Business-friendly</div>
</div>
<div class="improve-style-card" data-style="engaging">
<div class="improve-style-icon">🔥</div>
<div class="improve-style-name">Engageant</div>
<div class="improve-style-desc">Capte l'attention</div>
</div>
</div>
<div id="improve-loading" style="display: none; text-align: center; padding: 20px;">
<p>✨ Elysia génère les 5 variantes...</p>
<div class="progress-bar" style="margin-top: 10px;"><div class="progress-fill" style="width: 0%"></div></div>
</div>
<div id="improve-results" class="improve-results" style="display: none;"></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="document.getElementById('modal-improve-styles').remove()">Annuler</button>
<button class="btn-primary" id="btn-generate-all" onclick="AITools.generateAllStyles()">
🎲 Générer les 5 styles
</button>
</div>
</div>
`;
document.body.appendChild(modal);
// Store content for later use
this.pendingContent = content;
// Add click handlers for style cards
modal.querySelectorAll(".improve-style-card").forEach(card => {
card.addEventListener("click", () => {
modal.querySelectorAll(".improve-style-card").forEach(c => c.classList.remove("selected"));
card.classList.add("selected");
});
});
},
// 🎲 Generate all 5 styles at once (VS approach)
async generateAllStyles() {
const content = this.pendingContent;
if (!content) return;
const loadingDiv = document.getElementById("improve-loading");
const resultsDiv = document.getElementById("improve-results");
const generateBtn = document.getElementById("btn-generate-all");
loadingDiv.style.display = "block";
generateBtn.disabled = true;
generateBtn.textContent = "⏳ Génération en cours...";
const styles = [
{ id: "concise", name: "📝 Concis", desc: "Version plus courte et directe, sans fioritures" },
{ id: "creative", name: "🎨 Créatif", desc: "Version vivante avec métaphores et images" },
{ id: "academic", name: "📚 Académique", desc: "Version formelle et bien structurée" },
{ id: "professional", name: "💼 Professionnel", desc: "Version adaptée au monde des affaires" },
{ id: "engaging", name: "🔥 Engageant", desc: "Version qui capte l'attention du lecteur" }
];
const results = [];
const progressFill = loadingDiv.querySelector(".progress-fill");
for (let i = 0; i < styles.length; i++) {
const style = styles[i];
try {
const improved = await API.improveWritingWithStyle(content, style.id);
results.push({
style: style,
content: improved,
success: true
});
} catch (err) {
results.push({
style: style,
content: null,
success: false,
error: err.message
});
}
progressFill.style.width = `${((i + 1) / styles.length) * 100}%`;
}
// Display results
loadingDiv.style.display = "none";
resultsDiv.style.display = "block";
generateBtn.style.display = "none";
let html = "<h3 style='margin-bottom: 12px;'>🎲 Choisissez votre version préférée:</h3>";
results.forEach((result, index) => {
if (result.success) {
const preview = result.content.substring(0, 300) + (result.content.length > 300 ? "..." : "");
html += `
<div class="improve-result-card">
<div class="improve-result-header">
<strong>${result.style.name}</strong>
<button class="btn-small" onclick="AITools.applyImprovement(${index})">✅ Utiliser</button>
</div>
<div class="improve-result-preview">${preview}</div>
</div>
`;
} else {
html += `
<div class="improve-result-card" style="border-left-color: var(--error);">
<strong>${result.style.name}</strong>
<p style="color: var(--error);">Erreur: ${result.error}</p>
</div>
`;
}
});
resultsDiv.innerHTML = html;
this.improveResults = results;
},
// Apply selected improvement
async applyImprovement(index) {
const result = this.improveResults?.[index];
if (!result || !result.success) return;
const replace = confirm("Remplacer le contenu actuel avec cette version?\n\n" + "OK = Remplacer | Annuler = Créer nouveau document");
if (replace) {
window.app?.editor.setContent(result.content);
Utils.toast.success(`✨ Style "${result.style.name}" appliqué!`);
} else {
const doc = await DB.createDocument({
title: `${result.style.name.replace(/[^\w\s]/g, "").trim()}: ${window.app?.currentDoc?.title || "Document"}`,
content: result.content
});
Utils.toast.success("Nouveau document créé avec ce style!");
window.app?.loadDocument(doc.id);
}
document.getElementById("modal-improve-styles")?.remove();
},
async mergeDocuments() {
const docs = await DB.getAllDocuments();
if (docs.length < 2) {
Utils.toast.warning("Need at least 2 documents to merge");
return;
}
// Simple selection (can be improved with checkboxes)
const count = prompt(`How many recent documents to merge? (2-${Math.min(docs.length, 10)})`);
if (!count || isNaN(count)) return;
const selectedDocs = docs.slice(0, parseInt(count));
const merged = await API.mergeDocuments(selectedDocs);
// Create new merged document
const doc = await DB.createDocument({
title: "Merged Document",
content: merged
});
Utils.toast.success("Documents merged!");
window.app?.loadDocument(doc.id);
},
async extractOutline() {
const content = window.app?.editor.getContent();
if (!content) {
Utils.toast.warning("No content to analyze");
return;
}
const outline = await API.extractOutline(content);
// Create new document with outline
const doc = await DB.createDocument({
title: `Outline of ${window.app?.currentDoc?.title || "Document"}`,
content: outline
});
Utils.toast.success("Outline extracted!");
window.app?.loadDocument(doc.id);
},
async findDuplicates() {
const docs = await DB.getAllDocuments();
if (docs.length < 2) {
Utils.toast.warning("Need at least 2 documents");
return;
}
Utils.toast.info("🔍 Finding duplicates...");
const result = await API.findDuplicates(docs);
// Show result in alert (can be improved with modal)
alert(`Duplicate Analysis:\n\n${result}`);
},
async smartOrganize() {
const content = window.app?.editor.getContent();
if (!content) {
Utils.toast.warning("No content to analyze");
return;
}
Utils.toast.info("🏷️ Analyzing content...");
const tags = await API.suggestTags(content);
// Update current document with suggested tags
if (window.app?.currentDoc) {
window.app.currentDoc.tags = tags;
await DB.updateDocument(window.app.currentDoc.id, { tags });
Utils.toast.success(`Tags suggested: ${tags.join(", ")}`);
}
}
};
export default AITools;