🚀 Auto-deploy: Camila atualizado em 26/05/2026 10:56:30
This commit is contained in:
+86
-22
@@ -491,7 +491,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
item.dataset.id = chat.id;
|
||||
|
||||
const pinIcon = chat.pinned
|
||||
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="pin-icon"><path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z"/></svg>`
|
||||
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="pin-icon"><line x1="12" y1="17" x2="12" y2="22" stroke="currentColor" stroke-width="2" stroke-linecap="round"></line><path d="M5 17h14v-1.76a2 2 0 0 0-.44-1.24l-2.78-3.47A2 2 0 0 1 15 9.29V5a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4.29a2 2 0 0 1-.78 1.24L5.44 14a2 2 0 0 0-.44 1.24z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`
|
||||
: '';
|
||||
|
||||
const tagBadges = (chat.tags || []).map(tag =>
|
||||
@@ -505,17 +505,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
${tagBadges ? `<div class="history-item-tags">${tagBadges}</div>` : ''}
|
||||
</div>
|
||||
<div class="history-item-actions">
|
||||
<button class="btn-history-action btn-pin-chat" aria-label="${chat.pinned ? 'Desafixar' : 'Fixar'}">
|
||||
<button class="btn-history-action btn-pin-chat" title="${chat.pinned ? 'Desafixar' : 'Fixar'}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="${chat.pinned ? 'currentColor' : 'none'}" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="16.689 16.689L19.3 19.3a1 1 0 0 0 1.414-1.414l-1.066-1.066a7.5 7.5 0 1 0-1.66 1.114l1.004 1.004A1 1 0 0 0 19.3 17.886l-.707-.707a1 1 0 0 0-1.414 1.414l.707.707a1 1 0 0 0 1.414-1.414l-.707-.707a1 1 0 0 0-1.414 1.414l.707.707a1 1 0 0 0 1.414-1.414l-.811-.811a7.5 7.5 0 0 0 .11 2.12l.796.796a1 1 0 1 0 1.414-1.414l-.707-.707a1 1 0 0 0-1.414 1.414l.707.707a1 1 0 0 0 1.414-1.414l-.72-.72a7.5 7.5 0 0 0-.47-2.12 1 1 0 0 0-1.89.574l.707.707a1 1 0 0 0 1.414-1.414l-.707-.707a1 1 0 0 0-1.414 1.414l.707.707a1 1 0 0 0 1.414-1.414l-.707-.707a1 1 0 0 0-1.414 1.414l.5.5a1 1 0 1 0 1.414-1.414l-.5-.5a1 1 0 0 0-1.414 1.414l.707.707a1 1 0 0 0-1.414-1.414l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-1.004 1.004a1 1 0 0 0 1.89-.574l-.796-.796a7.5 7.5 0 0 0-.11-2.12l.81.811a1 1 0 0 0-1.414-1.414l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707a1 1 0 0 0 1.414 1.414l.72-.72a7.5 7.5 0 0 0 .47 2.12l-.5-.5a1 1 0 1 0-1.414 1.414l.5.5a1 1 0 0 0-1.414-1.414l-.5.5a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-1.004 1.004a7.5 7.5 0 1 0 1.66-1.114l-1.004-1.004a1 1 0 1 0-1.414 1.414l1.066 1.066a1 1 0 0 0-1.414-1.414l-1.066 1.066a1 1 0 0 0 1.414-1.414l-3-3z"/>
|
||||
<line x1="12" y1="17" x2="12" y2="22"></line>
|
||||
<path d="M5 17h14v-1.76a2 2 0 0 0-.44-1.24l-2.78-3.47A2 2 0 0 1 15 9.29V5a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4.29a2 2 0 0 1-.78 1.24L5.44 14a2 2 0 0 0-.44 1.24z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-history-action btn-rename-chat" aria-label="Renomear">
|
||||
<button class="btn-history-action btn-tag-chat" title="Tags">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581a2.25 2.25 0 0 0 3.181 0l4.318-4.318a2.25 2.25 0 0 0 0-3.181l-9.58-9.581A2.25 2.25 0 0 0 9.568 3Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-history-action btn-rename-chat" title="Renomear">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-history-action btn-delete-chat" aria-label="Excluir">
|
||||
<button class="btn-history-action btn-delete-chat" title="Excluir">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
@@ -556,7 +563,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
item.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.btn-delete-chat')) { e.stopPropagation(); deleteChat(chat.id); return; }
|
||||
if (e.target.closest('.btn-pin-chat')) { e.stopPropagation(); togglePin(chat.id); return; }
|
||||
if (e.target.closest('.btn-rename-chat')) { e.stopPropagation(); renameChat(chat.id); return; }
|
||||
if (e.target.closest('.btn-rename-chat')) { e.stopPropagation(); renameChatInline(chat.id, item); return; }
|
||||
if (e.target.closest('.btn-tag-chat')) { e.stopPropagation(); editTags(chat.id); return; }
|
||||
selectChat(chat.id);
|
||||
toggleSidebar(false);
|
||||
});
|
||||
@@ -584,7 +592,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
item.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.btn-delete-chat')) { e.stopPropagation(); deleteChat(chat.id); return; }
|
||||
if (e.target.closest('.btn-pin-chat')) { e.stopPropagation(); togglePin(chat.id); return; }
|
||||
if (e.target.closest('.btn-rename-chat')) { e.stopPropagation(); renameChat(chat.id); return; }
|
||||
if (e.target.closest('.btn-rename-chat')) { e.stopPropagation(); renameChatInline(chat.id, item); return; }
|
||||
if (e.target.closest('.btn-tag-chat')) { e.stopPropagation(); editTags(chat.id); return; }
|
||||
selectChat(chat.id);
|
||||
toggleSidebar(false);
|
||||
});
|
||||
@@ -621,13 +630,69 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
renderHistory();
|
||||
};
|
||||
|
||||
// Renomear chat
|
||||
const renameChat = (id) => {
|
||||
// Renomear chat inline
|
||||
const renameChatInline = (id, itemElement) => {
|
||||
const chat = chats.find(c => c.id === id);
|
||||
if (!chat) return;
|
||||
const newTitle = prompt('Novo nome da conversa:', chat.title);
|
||||
if (newTitle && newTitle.trim()) {
|
||||
chat.title = newTitle.trim();
|
||||
|
||||
const titleDiv = itemElement.querySelector('.history-item-title');
|
||||
if (!titleDiv) return;
|
||||
|
||||
const originalTitle = chat.title;
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'rename-input';
|
||||
input.value = originalTitle;
|
||||
|
||||
// Prevenir clique e propagação para o item
|
||||
input.addEventListener('click', (e) => e.stopPropagation());
|
||||
|
||||
const finishRename = () => {
|
||||
const newTitle = input.value.trim();
|
||||
if (newTitle && newTitle !== originalTitle) {
|
||||
chat.title = newTitle;
|
||||
chat.updatedAt = Date.now();
|
||||
saveChats();
|
||||
}
|
||||
renderHistory();
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
finishRename();
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
renderHistory();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('blur', finishRename);
|
||||
|
||||
titleDiv.replaceWith(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
};
|
||||
|
||||
// Gerenciar tags da conversa
|
||||
const editTags = (id) => {
|
||||
const chat = chats.find(c => c.id === id);
|
||||
if (!chat) return;
|
||||
|
||||
const availableTags = ['relatorios', 'planejamento', 'mindlab', 'estudos'];
|
||||
const currentTags = chat.tags || [];
|
||||
|
||||
// Prompt amigável mostrando opções
|
||||
const promptText = `Atribua tags à conversa.\nOpções válidas: relatorios, planejamento, mindlab, estudos\n(Use vírgula para separar várias tags, ou deixe em branco para limpar):\n`;
|
||||
const tagsStr = prompt(promptText, currentTags.join(', '));
|
||||
|
||||
if (tagsStr !== null) {
|
||||
const newTags = tagsStr.split(',')
|
||||
.map(t => t.trim().toLowerCase())
|
||||
.filter(t => availableTags.includes(t));
|
||||
|
||||
chat.tags = newTags;
|
||||
chat.updatedAt = Date.now();
|
||||
saveChats();
|
||||
renderHistory();
|
||||
}
|
||||
@@ -892,24 +957,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const filesToSend = files || attachedFiles;
|
||||
let fullText = text || '';
|
||||
|
||||
// Processar anexos: imagens viram descrição em texto (o modelo não tem visão)
|
||||
// Processar anexos: evitar base64 gigantesco em imagens e erro de leitura binária em PDF/DOCX
|
||||
if (filesToSend.length > 0) {
|
||||
for (const file of filesToSend) {
|
||||
if (file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
const base64 = await new Promise((resolve) => {
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
fullText += `\n\n[Imagem anexada: ${file.name}]\n\`\`\`\n${base64}\n\`\`\``;
|
||||
} else {
|
||||
// Apenas anexa uma referência textual limpa da imagem para a IA saber que ela existe
|
||||
fullText += `\n\n*[Imagem anexada: ${file.name} (${Math.round(file.size/1024)} KB)]*`;
|
||||
} else if (file.name.endsWith('.txt') || file.name.endsWith('.md') || file.name.endsWith('.json') || file.name.endsWith('.csv') || file.name.endsWith('.js') || file.name.endsWith('.css') || file.name.endsWith('.html')) {
|
||||
const reader = new FileReader();
|
||||
const content = await new Promise((resolve) => {
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.readAsText(file);
|
||||
});
|
||||
const truncated = content.length > 2000 ? content.substring(0, 2000) + '...[arquivo truncado]' : content;
|
||||
fullText += `\n\n[Documento anexo: ${file.name}]\n\`\`\`\n${truncated}\n\`\`\``;
|
||||
const truncated = content.length > 3000 ? content.substring(0, 3000) + '... [conteúdo truncado]' : content;
|
||||
fullText += `\n\n*[Conteúdo do arquivo "${file.name}":]*\n\`\`\`\n${truncated}\n\`\`\``;
|
||||
} else {
|
||||
// Para arquivos binários como PDF/DOCX que não podem ser lidos diretamente como texto cru no frontend
|
||||
fullText += `\n\n*[Arquivo anexado: ${file.name} (${Math.round(file.size/1024)} KB) - formato não textual]*`;
|
||||
}
|
||||
}
|
||||
// Limpar anexos após envio
|
||||
|
||||
Reference in New Issue
Block a user