Claude AI Sidepanel Chat

Chat with Claude AI in a convenient sidepanel on any webpage

Size

18.6 KB

Version

1.0.1

Created

Mar 4, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		Claude AI Sidepanel Chat
3// @description		Chat with Claude AI in a convenient sidepanel on any webpage
4// @version		1.0.1
5// @match		*://*/*
6// @icon		https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // State management
12    let chatHistory = [];
13    let isPanelOpen = false;
14
15    // Initialize the extension
16    async function init() {
17        console.log('Claude AI Sidepanel initializing...');
18        
19        // Load chat history from storage
20        const savedHistory = await GM.getValue('claude_chat_history', '[]');
21        chatHistory = JSON.parse(savedHistory);
22        
23        // Load panel state
24        isPanelOpen = await GM.getValue('claude_panel_open', false);
25        
26        createToggleButton();
27        createSidePanel();
28        
29        if (isPanelOpen) {
30            openPanel();
31        }
32        
33        console.log('Claude AI Sidepanel initialized');
34    }
35
36    // Create the toggle button
37    function createToggleButton() {
38        const button = document.createElement('button');
39        button.id = 'claude-toggle-btn';
40        button.innerHTML = `
41            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
42                <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
43                <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
44                <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
45            </svg>
46        `;
47        button.title = 'Toggle Claude AI Chat';
48        
49        button.addEventListener('click', togglePanel);
50        document.body.appendChild(button);
51    }
52
53    // Create the side panel
54    function createSidePanel() {
55        const panel = document.createElement('div');
56        panel.id = 'claude-sidepanel';
57        panel.innerHTML = `
58            <div class="claude-panel-header">
59                <h3>Claude AI Chat</h3>
60                <div class="claude-header-actions">
61                    <button id="claude-clear-btn" title="Clear chat">
62                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
63                            <polyline points="3 6 5 6 21 6"></polyline>
64                            <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
65                        </svg>
66                    </button>
67                    <button id="claude-close-btn" title="Close panel">
68                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
69                            <line x1="18" y1="6" x2="6" y2="18"></line>
70                            <line x1="6" y1="6" x2="18" y2="18"></line>
71                        </svg>
72                    </button>
73                </div>
74            </div>
75            <div class="claude-chat-container" id="claude-chat-container">
76                <div class="claude-welcome-message">
77                    <h4>Welcome to Claude AI Chat</h4>
78                    <p>Ask me anything! I'm here to help you with information, analysis, or conversation.</p>
79                </div>
80            </div>
81            <div class="claude-input-container">
82                <textarea id="claude-input" placeholder="Type your message here..." rows="1"></textarea>
83                <button id="claude-send-btn" title="Send message">
84                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
85                        <line x1="22" y1="2" x2="11" y2="13"></line>
86                        <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
87                    </svg>
88                </button>
89            </div>
90        `;
91        
92        document.body.appendChild(panel);
93        
94        // Add event listeners
95        document.getElementById('claude-close-btn').addEventListener('click', closePanel);
96        document.getElementById('claude-clear-btn').addEventListener('click', clearChat);
97        document.getElementById('claude-send-btn').addEventListener('click', sendMessage);
98        
99        const input = document.getElementById('claude-input');
100        input.addEventListener('keydown', (e) => {
101            if (e.key === 'Enter' && !e.shiftKey) {
102                e.preventDefault();
103                sendMessage();
104            }
105        });
106        
107        // Auto-resize textarea
108        input.addEventListener('input', () => {
109            input.style.height = 'auto';
110            input.style.height = Math.min(input.scrollHeight, 150) + 'px';
111        });
112        
113        // Restore chat history
114        restoreChatHistory();
115    }
116
117    // Toggle panel open/close
118    async function togglePanel() {
119        if (isPanelOpen) {
120            closePanel();
121        } else {
122            openPanel();
123        }
124    }
125
126    // Open panel
127    async function openPanel() {
128        const panel = document.getElementById('claude-sidepanel');
129        panel.classList.add('open');
130        isPanelOpen = true;
131        await GM.setValue('claude_panel_open', true);
132        
133        // Focus input
134        setTimeout(() => {
135            document.getElementById('claude-input').focus();
136        }, 300);
137    }
138
139    // Close panel
140    async function closePanel() {
141        const panel = document.getElementById('claude-sidepanel');
142        panel.classList.remove('open');
143        isPanelOpen = false;
144        await GM.setValue('claude_panel_open', false);
145    }
146
147    // Send message to Claude
148    async function sendMessage() {
149        const input = document.getElementById('claude-input');
150        const message = input.value.trim();
151        
152        if (!message) return;
153        
154        // Clear input
155        input.value = '';
156        input.style.height = 'auto';
157        
158        // Add user message to chat
159        addMessageToChat('user', message);
160        
161        // Show loading indicator
162        const loadingId = addLoadingMessage();
163        
164        try {
165            // Call Claude AI
166            const response = await RM.aiCall(message);
167            
168            // Remove loading indicator
169            removeLoadingMessage(loadingId);
170            
171            // Add Claude's response to chat
172            addMessageToChat('assistant', response);
173            
174        } catch (error) {
175            console.error('Error calling Claude AI:', error);
176            removeLoadingMessage(loadingId);
177            addMessageToChat('error', 'Sorry, I encountered an error. Please try again.');
178        }
179    }
180
181    // Add message to chat
182    function addMessageToChat(role, content) {
183        const container = document.getElementById('claude-chat-container');
184        
185        // Remove welcome message if it exists
186        const welcomeMsg = container.querySelector('.claude-welcome-message');
187        if (welcomeMsg) {
188            welcomeMsg.remove();
189        }
190        
191        const messageDiv = document.createElement('div');
192        messageDiv.className = `claude-message claude-message-${role}`;
193        
194        const avatar = document.createElement('div');
195        avatar.className = 'claude-message-avatar';
196        avatar.textContent = role === 'user' ? 'You' : role === 'error' ? '⚠️' : 'AI';
197        
198        const contentDiv = document.createElement('div');
199        contentDiv.className = 'claude-message-content';
200        contentDiv.textContent = content;
201        
202        messageDiv.appendChild(avatar);
203        messageDiv.appendChild(contentDiv);
204        container.appendChild(messageDiv);
205        
206        // Scroll to bottom
207        container.scrollTop = container.scrollHeight;
208        
209        // Save to history (except error messages)
210        if (role !== 'error') {
211            chatHistory.push({ role, content, timestamp: Date.now() });
212            saveChatHistory();
213        }
214    }
215
216    // Add loading message
217    function addLoadingMessage() {
218        const container = document.getElementById('claude-chat-container');
219        const loadingId = 'loading-' + Date.now();
220        
221        const messageDiv = document.createElement('div');
222        messageDiv.className = 'claude-message claude-message-assistant claude-message-loading';
223        messageDiv.id = loadingId;
224        
225        const avatar = document.createElement('div');
226        avatar.className = 'claude-message-avatar';
227        avatar.textContent = 'AI';
228        
229        const contentDiv = document.createElement('div');
230        contentDiv.className = 'claude-message-content';
231        contentDiv.innerHTML = '<div class="claude-loading-dots"><span></span><span></span><span></span></div>';
232        
233        messageDiv.appendChild(avatar);
234        messageDiv.appendChild(contentDiv);
235        container.appendChild(messageDiv);
236        
237        container.scrollTop = container.scrollHeight;
238        
239        return loadingId;
240    }
241
242    // Remove loading message
243    function removeLoadingMessage(loadingId) {
244        const loadingMsg = document.getElementById(loadingId);
245        if (loadingMsg) {
246            loadingMsg.remove();
247        }
248    }
249
250    // Clear chat
251    async function clearChat() {
252        if (!confirm('Are you sure you want to clear the chat history?')) {
253            return;
254        }
255        
256        chatHistory = [];
257        await saveChatHistory();
258        
259        const container = document.getElementById('claude-chat-container');
260        container.innerHTML = `
261            <div class="claude-welcome-message">
262                <h4>Welcome to Claude AI Chat</h4>
263                <p>Ask me anything! I'm here to help you with information, analysis, or conversation.</p>
264            </div>
265        `;
266    }
267
268    // Save chat history
269    async function saveChatHistory() {
270        await GM.setValue('claude_chat_history', JSON.stringify(chatHistory));
271    }
272
273    // Restore chat history
274    function restoreChatHistory() {
275        if (chatHistory.length === 0) return;
276        
277        const container = document.getElementById('claude-chat-container');
278        const welcomeMsg = container.querySelector('.claude-welcome-message');
279        if (welcomeMsg) {
280            welcomeMsg.remove();
281        }
282        
283        chatHistory.forEach(msg => {
284            const messageDiv = document.createElement('div');
285            messageDiv.className = `claude-message claude-message-${msg.role}`;
286            
287            const avatar = document.createElement('div');
288            avatar.className = 'claude-message-avatar';
289            avatar.textContent = msg.role === 'user' ? 'You' : 'AI';
290            
291            const contentDiv = document.createElement('div');
292            contentDiv.className = 'claude-message-content';
293            contentDiv.textContent = msg.content;
294            
295            messageDiv.appendChild(avatar);
296            messageDiv.appendChild(contentDiv);
297            container.appendChild(messageDiv);
298        });
299        
300        container.scrollTop = container.scrollHeight;
301    }
302
303    // Add styles
304    TM_addStyle(`
305        #claude-toggle-btn {
306            position: fixed;
307            bottom: 20px;
308            right: 20px;
309            width: 56px;
310            height: 56px;
311            border-radius: 50%;
312            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
313            border: none;
314            color: white;
315            cursor: pointer;
316            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
317            z-index: 999999;
318            display: flex;
319            align-items: center;
320            justify-content: center;
321            transition: all 0.3s ease;
322        }
323        
324        #claude-toggle-btn:hover {
325            transform: scale(1.1);
326            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
327        }
328        
329        #claude-sidepanel {
330            position: fixed;
331            top: 0;
332            right: -400px;
333            width: 400px;
334            height: 100vh;
335            background: #ffffff;
336            box-shadow: -2px 0 20px rgba(0, 0, 0, 0.1);
337            z-index: 1000000;
338            display: flex;
339            flex-direction: column;
340            transition: right 0.3s ease;
341            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
342        }
343        
344        #claude-sidepanel.open {
345            right: 0;
346        }
347        
348        .claude-panel-header {
349            padding: 20px;
350            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
351            color: white;
352            display: flex;
353            justify-content: space-between;
354            align-items: center;
355            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
356        }
357        
358        .claude-panel-header h3 {
359            margin: 0;
360            font-size: 18px;
361            font-weight: 600;
362        }
363        
364        .claude-header-actions {
365            display: flex;
366            gap: 8px;
367        }
368        
369        .claude-header-actions button {
370            background: rgba(255, 255, 255, 0.2);
371            border: none;
372            color: white;
373            width: 32px;
374            height: 32px;
375            border-radius: 6px;
376            cursor: pointer;
377            display: flex;
378            align-items: center;
379            justify-content: center;
380            transition: background 0.2s ease;
381        }
382        
383        .claude-header-actions button:hover {
384            background: rgba(255, 255, 255, 0.3);
385        }
386        
387        .claude-chat-container {
388            flex: 1;
389            overflow-y: auto;
390            padding: 20px;
391            background: #f8f9fa;
392        }
393        
394        .claude-welcome-message {
395            text-align: center;
396            padding: 40px 20px;
397            color: #6c757d;
398        }
399        
400        .claude-welcome-message h4 {
401            margin: 0 0 10px 0;
402            color: #495057;
403            font-size: 20px;
404        }
405        
406        .claude-welcome-message p {
407            margin: 0;
408            font-size: 14px;
409            line-height: 1.6;
410        }
411        
412        .claude-message {
413            display: flex;
414            gap: 12px;
415            margin-bottom: 16px;
416            animation: fadeIn 0.3s ease;
417        }
418        
419        @keyframes fadeIn {
420            from {
421                opacity: 0;
422                transform: translateY(10px);
423            }
424            to {
425                opacity: 1;
426                transform: translateY(0);
427            }
428        }
429        
430        .claude-message-avatar {
431            width: 36px;
432            height: 36px;
433            border-radius: 50%;
434            display: flex;
435            align-items: center;
436            justify-content: center;
437            font-size: 11px;
438            font-weight: 600;
439            flex-shrink: 0;
440        }
441        
442        .claude-message-user .claude-message-avatar {
443            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
444            color: white;
445        }
446        
447        .claude-message-assistant .claude-message-avatar {
448            background: #e9ecef;
449            color: #495057;
450        }
451        
452        .claude-message-error .claude-message-avatar {
453            background: #fee;
454            color: #c00;
455        }
456        
457        .claude-message-content {
458            flex: 1;
459            padding: 12px 16px;
460            border-radius: 12px;
461            line-height: 1.6;
462            font-size: 14px;
463            word-wrap: break-word;
464        }
465        
466        .claude-message-user .claude-message-content {
467            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
468            color: white;
469        }
470        
471        .claude-message-assistant .claude-message-content {
472            background: white;
473            color: #212529;
474            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
475        }
476        
477        .claude-message-error .claude-message-content {
478            background: #fee;
479            color: #c00;
480        }
481        
482        .claude-loading-dots {
483            display: flex;
484            gap: 4px;
485            padding: 4px 0;
486        }
487        
488        .claude-loading-dots span {
489            width: 8px;
490            height: 8px;
491            border-radius: 50%;
492            background: #6c757d;
493            animation: bounce 1.4s infinite ease-in-out both;
494        }
495        
496        .claude-loading-dots span:nth-child(1) {
497            animation-delay: -0.32s;
498        }
499        
500        .claude-loading-dots span:nth-child(2) {
501            animation-delay: -0.16s;
502        }
503        
504        @keyframes bounce {
505            0%, 80%, 100% {
506                transform: scale(0);
507            }
508            40% {
509                transform: scale(1);
510            }
511        }
512        
513        .claude-input-container {
514            padding: 16px;
515            background: white;
516            border-top: 1px solid #e9ecef;
517            display: flex;
518            gap: 12px;
519            align-items: flex-end;
520        }
521        
522        #claude-input {
523            flex: 1;
524            padding: 12px;
525            border: 2px solid #e9ecef;
526            border-radius: 12px;
527            font-size: 14px;
528            font-family: inherit;
529            resize: none;
530            outline: none;
531            transition: border-color 0.2s ease;
532            max-height: 150px;
533        }
534        
535        #claude-input:focus {
536            border-color: #667eea;
537        }
538        
539        #claude-send-btn {
540            width: 44px;
541            height: 44px;
542            border-radius: 12px;
543            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
544            border: none;
545            color: white;
546            cursor: pointer;
547            display: flex;
548            align-items: center;
549            justify-content: center;
550            transition: all 0.2s ease;
551            flex-shrink: 0;
552        }
553        
554        #claude-send-btn:hover {
555            transform: scale(1.05);
556            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
557        }
558        
559        #claude-send-btn:active {
560            transform: scale(0.95);
561        }
562        
563        /* Scrollbar styling */
564        .claude-chat-container::-webkit-scrollbar {
565            width: 6px;
566        }
567        
568        .claude-chat-container::-webkit-scrollbar-track {
569            background: transparent;
570        }
571        
572        .claude-chat-container::-webkit-scrollbar-thumb {
573            background: #cbd5e0;
574            border-radius: 3px;
575        }
576        
577        .claude-chat-container::-webkit-scrollbar-thumb:hover {
578            background: #a0aec0;
579        }
580    `);
581
582    // Initialize when DOM is ready
583    if (document.readyState === 'loading') {
584        document.addEventListener('DOMContentLoaded', init);
585    } else {
586        init();
587    }
588})();
Claude AI Sidepanel Chat | Robomonkey