Spaces:
Running
Running
| /* | |
| 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; | |