|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Local LLM Chat</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<style> |
|
|
:root { |
|
|
--primary: #6366f1; |
|
|
--primary-dark: #4f46e5; |
|
|
--bg: #0f172a; |
|
|
--text: #e2e8f0; |
|
|
--text-light: #94a3b8; |
|
|
--border: #1e293b; |
|
|
} |
|
|
|
|
|
.dark { |
|
|
--bg: #0f172a; |
|
|
--text: #e2e8f0; |
|
|
--text-light: #94a3b8; |
|
|
--border: #1e293b; |
|
|
} |
|
|
|
|
|
.light { |
|
|
--bg: #f9fafb; |
|
|
--text: #111827; |
|
|
--text-light: #6b7280; |
|
|
--border: #e5e7eb; |
|
|
} |
|
|
|
|
|
body { |
|
|
width: 400px; |
|
|
height: 600px; |
|
|
font-family: 'Inter', sans-serif; |
|
|
background-color: var(--bg); |
|
|
color: var(--text); |
|
|
transition: background-color 0.3s, color 0.3s; |
|
|
} |
|
|
|
|
|
.chat-container { |
|
|
height: calc(100% - 60px); |
|
|
} |
|
|
|
|
|
.message-user { |
|
|
background-color: var(--primary); |
|
|
color: white; |
|
|
border-radius: 12px 12px 0 12px; |
|
|
} |
|
|
|
|
|
.message-ai { |
|
|
background-color: #1e293b; |
|
|
border: 1px solid var(--border); |
|
|
border-radius: 12px 12px 12px 0; |
|
|
} |
|
|
|
|
|
.rounded-12px { |
|
|
border-radius: 12px !important; |
|
|
} |
|
|
|
|
|
.rounded-none { |
|
|
border-radius: 0 !important; |
|
|
} |
|
|
|
|
|
.rounded-full { |
|
|
border-radius: 9999px !important; |
|
|
} |
|
|
|
|
|
.typing-indicator span { |
|
|
display: inline-block; |
|
|
width: 8px; |
|
|
height: 8px; |
|
|
background-color: var(--primary); |
|
|
border-radius: 50%; |
|
|
margin: 0 2px; |
|
|
opacity: 0.4; |
|
|
} |
|
|
|
|
|
.typing-indicator span:nth-child(1) { |
|
|
animation: pulse 1s infinite; |
|
|
} |
|
|
|
|
|
.typing-indicator span:nth-child(2) { |
|
|
animation: pulse 1s infinite 0.2s; |
|
|
} |
|
|
|
|
|
.typing-indicator span:nth-child(3) { |
|
|
animation: pulse 1s infinite 0.4s; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0%, 100% { |
|
|
opacity: 0.4; |
|
|
transform: scale(1); |
|
|
} |
|
|
50% { |
|
|
opacity: 1; |
|
|
transform: scale(1.2); |
|
|
} |
|
|
} |
|
|
|
|
|
.input-box { |
|
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); |
|
|
background-color: #1e293b; |
|
|
} |
|
|
|
|
|
.header-dark { |
|
|
background-color: #1e293b; |
|
|
border-color: #334155; |
|
|
} |
|
|
|
|
|
.modal-dark { |
|
|
background-color: #1e293b; |
|
|
color: #e2e8f0; |
|
|
} |
|
|
|
|
|
.modal-dark input, |
|
|
.modal-dark select { |
|
|
background-color: #334155; |
|
|
color: #e2e8f0; |
|
|
border-color: #475569; |
|
|
} |
|
|
|
|
|
.modal-dark .border-gray-200 { |
|
|
border-color: #334155; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="flex flex-col"> |
|
|
<header class="header-dark px-4 py-3 border-b border-gray-800 flex items-center justify-between sticky top-0 z-10"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center"> |
|
|
<i data-feather="cpu" class="text-indigo-500 w-4 h-4"></i> |
|
|
</div> |
|
|
<h1 class="font-semibold text-white">Local LLM</h1> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<button id="settings-btn" class="p-1 rounded-full hover:bg-gray-700"> |
|
|
<i data-feather="settings" class="w-5 h-5 text-white"></i> |
|
|
</button> |
|
|
<button id="clear-btn" class="p-1 rounded-full hover:bg-gray-700"> |
|
|
<i data-feather="trash-2" class="w-5 h-5 text-white"></i> |
|
|
</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
<div class="chat-container overflow-y-auto flex-1 px-4 py-3 space-y-3" id="chat-messages"> |
|
|
<div class="message-ai p-3 max-w-[80%]"> |
|
|
<p class="text-sm">Hello! I'm your local AI assistant. How can I help you today?</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="input-box bg-white px-4 py-3 border-t border-gray-200 sticky bottom-0"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<input |
|
|
type="text" |
|
|
id="message-input" |
|
|
placeholder="Type your message..." |
|
|
class="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm bg-white text-gray-900" |
|
|
autocomplete="off" |
|
|
> |
|
|
<button id="send-btn" class="bg-indigo-500 hover:bg-indigo-600 text-white rounded-full p-2 transition-colors"> |
|
|
<i data-feather="send" class="w-4 h-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex items-center justify-between mt-2 text-xs text-gray-500"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<button id="model-selector" class="flex items-center space-x-1 hover:text-indigo-500"> |
|
|
<span>Ollama</span> |
|
|
<i data-feather="chevron-down" class="w-3 h-3"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<button id="api-settings" class="hover:text-indigo-500 flex items-center"> |
|
|
<i data-feather="link" class="w-3 h-3 mr-1"></i> |
|
|
<span>API Settings</span> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> |
|
|
<div class="modal-dark bg-gray-900 rounded-lg w-full max-w-md mx-4"> |
|
|
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center"> |
|
|
<h3 class="text-lg font-medium text-gray-900">Settings</h3> |
|
|
<button id="close-settings" class="text-gray-400 hover:text-gray-500"> |
|
|
<i data-feather="x" class="w-5 h-5"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="px-6 py-4 space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">AI Role</label> |
|
|
<textarea id="ai-role" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm" placeholder="You are a helpful AI assistant..."></textarea> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Prompt Library</label> |
|
|
<select id="prompt-library" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm"> |
|
|
<option value="">Select a prompt...</option> |
|
|
<option value="creative">Creative Writer</option> |
|
|
<option value="coder">Code Assistant</option> |
|
|
<option value="analyst">Data Analyst</option> |
|
|
<option value="tutor">Learning Tutor</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Ollama API URL</label> |
|
|
<input type="text" id="api-url" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm" placeholder="http://localhost:11434"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Default Model</label> |
|
|
<select id="default-model" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm"> |
|
|
<option value="llama2">Llama 2</option> |
|
|
<option value="mistral">Mistral</option> |
|
|
<option value="gemma">Gemma</option> |
|
|
<option value="phi">Phi</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Theme</label> |
|
|
<select id="theme-selector" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm"> |
|
|
<option value="light">Light</option> |
|
|
<option value="dark">Dark</option> |
|
|
<option value="system">System</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Message Style</label> |
|
|
<select id="message-style" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm"> |
|
|
<option value="rounded">Rounded</option> |
|
|
<option value="square">Square</option> |
|
|
<option value="bubble">Bubble</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Font Size</label> |
|
|
<select id="font-size" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-sm"> |
|
|
<option value="small">Small</option> |
|
|
<option value="medium">Medium</option> |
|
|
<option value="large">Large</option> |
|
|
</select> |
|
|
</div> |
|
|
<div class="flex items-center justify-between"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Enable Markdown</label> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" id="markdown-toggle" class="sr-only peer"> |
|
|
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div> |
|
|
</label> |
|
|
</div> |
|
|
<div class="flex items-center justify-between"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Enable Animations</label> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" id="animations-toggle" class="sr-only peer"> |
|
|
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
<div class="px-6 py-4 border-t border-gray-200 flex justify-end"> |
|
|
<button id="save-settings" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded-md text-sm font-medium">Save</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
const messageInput = document.getElementById('message-input'); |
|
|
const sendBtn = document.getElementById('send-btn'); |
|
|
const chatMessages = document.getElementById('chat-messages'); |
|
|
const settingsBtn = document.getElementById('settings-btn'); |
|
|
const clearBtn = document.getElementById('clear-btn'); |
|
|
const settingsModal = document.getElementById('settings-modal'); |
|
|
const closeSettings = document.getElementById('close-settings'); |
|
|
const saveSettings = document.getElementById('save-settings'); |
|
|
const apiSettings = document.getElementById('api-settings'); |
|
|
const modelSelector = document.getElementById('model-selector'); |
|
|
|
|
|
|
|
|
document.documentElement.classList.add('dark'); |
|
|
|
|
|
|
|
|
chrome.storage.sync.get([ |
|
|
'apiUrl', |
|
|
'defaultModel', |
|
|
'theme', |
|
|
'messageStyle', |
|
|
'fontSize', |
|
|
'markdownEnabled', |
|
|
'animationsEnabled', |
|
|
'aiRole', |
|
|
'selectedPrompt' |
|
|
], (result) => { |
|
|
if (result.apiUrl) document.getElementById('api-url').value = result.apiUrl; |
|
|
if (result.defaultModel) document.getElementById('default-model').value = result.defaultModel; |
|
|
if (result.theme) applyTheme(result.theme); |
|
|
if (result.messageStyle) document.getElementById('message-style').value = result.messageStyle; |
|
|
if (result.fontSize) document.getElementById('font-size').value = result.fontSize; |
|
|
if (result.markdownEnabled) document.getElementById('markdown-toggle').checked = result.markdownEnabled; |
|
|
if (result.animationsEnabled) document.getElementById('animations-toggle').checked = result.animationsEnabled; |
|
|
|
|
|
applyMessageStyle(result.messageStyle || 'rounded'); |
|
|
applyFontSize(result.fontSize || 'medium'); |
|
|
}); |
|
|
|
|
|
|
|
|
sendBtn.addEventListener('click', sendMessage); |
|
|
messageInput.addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter') { |
|
|
sendMessage(); |
|
|
} |
|
|
}); |
|
|
|
|
|
settingsBtn.addEventListener('click', () => { |
|
|
settingsModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
closeSettings.addEventListener('click', () => { |
|
|
settingsModal.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
saveSettings.addEventListener('click', () => { |
|
|
const apiUrl = document.getElementById('api-url').value; |
|
|
const defaultModel = document.getElementById('default-model').value; |
|
|
const theme = document.getElementById('theme-selector').value; |
|
|
const messageStyle = document.getElementById('message-style').value; |
|
|
const fontSize = document.getElementById('font-size').value; |
|
|
const markdownEnabled = document.getElementById('markdown-toggle').checked; |
|
|
const animationsEnabled = document.getElementById('animations-toggle').checked; |
|
|
const aiRole = document.getElementById('ai-role').value; |
|
|
const selectedPrompt = document.getElementById('prompt-library').value; |
|
|
|
|
|
chrome.storage.sync.set({ |
|
|
apiUrl, |
|
|
defaultModel, |
|
|
theme, |
|
|
messageStyle, |
|
|
fontSize, |
|
|
markdownEnabled, |
|
|
animationsEnabled, |
|
|
aiRole, |
|
|
selectedPrompt |
|
|
}, () => { |
|
|
applyTheme(theme); |
|
|
applyMessageStyle(messageStyle); |
|
|
applyFontSize(fontSize); |
|
|
settingsModal.classList.add('hidden'); |
|
|
showNotification('Settings saved successfully'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
clearBtn.addEventListener('click', () => { |
|
|
chatMessages.innerHTML = ` |
|
|
<div class="message-ai p-3 max-w-[80%]"> |
|
|
<p class="text-sm">Hello! I'm your local AI assistant. How can I help you today?</p> |
|
|
</div> |
|
|
`; |
|
|
}); |
|
|
|
|
|
apiSettings.addEventListener('click', () => { |
|
|
settingsModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
|
|
|
function sendMessage() { |
|
|
const message = messageInput.value.trim(); |
|
|
if (!message) return; |
|
|
|
|
|
|
|
|
chrome.storage.sync.get(['aiRole', 'selectedPrompt'], (result) => { |
|
|
let fullMessage = message; |
|
|
|
|
|
if (result.aiRole) { |
|
|
fullMessage = `${result.aiRole}\n\n${message}`; |
|
|
} |
|
|
|
|
|
if (result.selectedPrompt) { |
|
|
const prompts = { |
|
|
'creative': 'You are a creative writer. Respond with imaginative and vivid descriptions.', |
|
|
'coder': 'You are a coding assistant. Respond with clean, efficient code examples.', |
|
|
'analyst': 'You are a data analyst. Respond with clear, structured analysis.', |
|
|
'tutor': 'You are a learning tutor. Explain concepts clearly with examples.' |
|
|
}; |
|
|
fullMessage = `${prompts[result.selectedPrompt]}\n\n${fullMessage}`; |
|
|
} |
|
|
|
|
|
|
|
|
addMessage(message, 'user'); |
|
|
messageInput.value = ''; |
|
|
|
|
|
|
|
|
const typingIndicator = document.createElement('div'); |
|
|
typingIndicator.className = 'typing-indicator p-3 max-w-[80%]'; |
|
|
typingIndicator.innerHTML = ` |
|
|
<div class="flex space-x-1"> |
|
|
<span></span> |
|
|
<span></span> |
|
|
<span></span> |
|
|
</div> |
|
|
`; |
|
|
chatMessages.appendChild(typingIndicator); |
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
|
|
|
|
|
|
chrome.storage.sync.get(['apiUrl', 'defaultModel'], async (result) => { |
|
|
const apiUrl = result.apiUrl || 'http://localhost:11434'; |
|
|
const model = result.defaultModel || 'llama2'; |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await fetch(`${apiUrl}/api/generate`, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
model: model, |
|
|
prompt: fullMessage, |
|
|
stream: false |
|
|
}) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`API request failed: ${response.status}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
chatMessages.removeChild(typingIndicator); |
|
|
|
|
|
|
|
|
addMessage(data.response, 'ai'); |
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
chatMessages.removeChild(typingIndicator); |
|
|
addMessage(`Error: ${error.message}`, 'ai'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function addMessage(text, sender) { |
|
|
const messageDiv = document.createElement('div'); |
|
|
messageDiv.className = `message-${sender} p-3 max-w-[80%] ${sender === 'user' ? 'ml-auto' : ''}`; |
|
|
|
|
|
|
|
|
chrome.storage.sync.get(['markdownEnabled'], (result) => { |
|
|
if (result.markdownEnabled) { |
|
|
|
|
|
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); |
|
|
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>'); |
|
|
text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" class="text-indigo-400 hover:underline" target="_blank">$1</a>'); |
|
|
} |
|
|
|
|
|
messageDiv.innerHTML = `<p class="text-sm">${text}</p>`; |
|
|
chatMessages.appendChild(messageDiv); |
|
|
|
|
|
|
|
|
chrome.storage.sync.get(['animationsEnabled'], (result) => { |
|
|
if (result.animationsEnabled) { |
|
|
messageDiv.style.opacity = '0'; |
|
|
messageDiv.style.transform = 'translateY(10px)'; |
|
|
messageDiv.style.transition = 'all 0.3s ease'; |
|
|
setTimeout(() => { |
|
|
messageDiv.style.opacity = '1'; |
|
|
messageDiv.style.transform = 'translateY(0)'; |
|
|
}, 10); |
|
|
} |
|
|
}); |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
}); |
|
|
} |
|
|
|
|
|
function applyMessageStyle(style) { |
|
|
const messages = document.querySelectorAll('.message-user, .message-ai'); |
|
|
|
|
|
messages.forEach(msg => { |
|
|
msg.classList.remove('rounded-12px', 'rounded-none', 'rounded-full'); |
|
|
|
|
|
switch(style) { |
|
|
case 'rounded': |
|
|
msg.classList.add('rounded-[12px]'); |
|
|
break; |
|
|
case 'square': |
|
|
msg.classList.add('rounded-none'); |
|
|
break; |
|
|
case 'bubble': |
|
|
msg.classList.add('rounded-full'); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function applyFontSize(size) { |
|
|
const messages = document.querySelectorAll('.message-user p, .message-ai p'); |
|
|
|
|
|
messages.forEach(msg => { |
|
|
msg.classList.remove('text-xs', 'text-sm', 'text-base'); |
|
|
|
|
|
switch(size) { |
|
|
case 'small': |
|
|
msg.classList.add('text-xs'); |
|
|
break; |
|
|
case 'medium': |
|
|
msg.classList.add('text-sm'); |
|
|
break; |
|
|
case 'large': |
|
|
msg.classList.add('text-base'); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function applyTheme(theme) { |
|
|
if (theme === 'system') { |
|
|
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; |
|
|
} |
|
|
|
|
|
document.documentElement.classList.remove('light', 'dark'); |
|
|
document.documentElement.classList.add(theme); |
|
|
|
|
|
|
|
|
const header = document.querySelector('header'); |
|
|
const modal = document.querySelector('#settings-modal > div'); |
|
|
|
|
|
if (theme === 'dark') { |
|
|
header.classList.add('header-dark'); |
|
|
header.classList.remove('bg-white'); |
|
|
modal.classList.add('modal-dark'); |
|
|
modal.classList.remove('bg-white'); |
|
|
} else { |
|
|
header.classList.remove('header-dark'); |
|
|
header.classList.add('bg-white'); |
|
|
modal.classList.remove('modal-dark'); |
|
|
modal.classList.add('bg-white'); |
|
|
} |
|
|
} |
|
|
|
|
|
function showNotification(message) { |
|
|
const notification = document.createElement('div'); |
|
|
notification.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-md text-sm shadow-lg'; |
|
|
notification.textContent = message; |
|
|
document.body.appendChild(notification); |
|
|
|
|
|
setTimeout(() => { |
|
|
notification.classList.add('opacity-0', 'transition-opacity', 'duration-300'); |
|
|
setTimeout(() => { |
|
|
document.body.removeChild(notification); |
|
|
}, 300); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('prompt-library').addEventListener('change', function() { |
|
|
if (this.value) { |
|
|
document.getElementById('ai-role').value = ''; |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('ai-role').addEventListener('input', function() { |
|
|
if (this.value) { |
|
|
document.getElementById('prompt-library').value = ''; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
chrome.storage.sync.get(['aiRole', 'selectedPrompt'], (result) => { |
|
|
if (result.aiRole) { |
|
|
document.getElementById('ai-role').value = result.aiRole; |
|
|
} |
|
|
if (result.selectedPrompt) { |
|
|
document.getElementById('prompt-library').value = result.selectedPrompt; |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|