|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
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);
|
|
|
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);
|
|
|
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
|
|
|
this.showImproveStyleModal(content);
|
|
|
},
|
|
|
|
|
|
|
|
|
showImproveStyleModal(content) {
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
this.pendingContent = content;
|
|
|
|
|
|
|
|
|
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");
|
|
|
});
|
|
|
});
|
|
|
},
|
|
|
|
|
|
|
|
|
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}%`;
|
|
|
}
|
|
|
|
|
|
|
|
|
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;
|
|
|
},
|
|
|
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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;
|
|
|
|