OpenRouter AI Chat Assistant

Удобный интерфейс для взаимодействия с AI моделями через OpenRouter API

Size

15.8 KB

Version

1.1.1

Created

Dec 4, 2025

Updated

3 months ago

1// ==UserScript==
2// @name		OpenRouter AI Chat Assistant
3// @description		Удобный интерфейс для взаимодействия с AI моделями через OpenRouter API
4// @version		1.1.1
5// @match		*://*/*
6// @icon		https://files.buildwithfern.com/openrouter.docs.buildwithfern.com/docs/9ea97c59357c47db235d0591b7ed936d40b94e3dd02030a36e26b406671f011d/content/assets/favicon.ico
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.xmlhttpRequest
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    const API_KEY = '111222333';
15    const API_URL = 'https://openrouter.ai/api/v1/chat/completions';
16
17    // Доступные AI модели
18    const AI_MODELS = [
19        { id: 'openai/gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
20        { id: 'openai/gpt-4', name: 'GPT-4' },
21        { id: 'openai/gpt-4-turbo', name: 'GPT-4 Turbo' },
22        { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus' },
23        { id: 'anthropic/claude-3-sonnet', name: 'Claude 3 Sonnet' },
24        { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku' },
25        { id: 'google/gemini-pro', name: 'Gemini Pro' },
26        { id: 'meta-llama/llama-3-70b-instruct', name: 'Llama 3 70B' },
27        { id: 'mistralai/mistral-large', name: 'Mistral Large' },
28        { id: 'cohere/command-r-plus', name: 'Command R+' }
29    ];
30
31    // Добавляем стили для интерфейса
32    TM_addStyle(`
33        #ai-chat-container {
34            position: fixed;
35            bottom: 20px;
36            right: 20px;
37            width: 400px;
38            height: 600px;
39            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
40            border-radius: 16px;
41            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
42            display: flex;
43            flex-direction: column;
44            z-index: 999999;
45            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
46            overflow: hidden;
47        }
48
49        #ai-chat-header {
50            background: rgba(255, 255, 255, 0.15);
51            backdrop-filter: blur(10px);
52            padding: 16px 20px;
53            display: flex;
54            justify-content: space-between;
55            align-items: center;
56            border-bottom: 1px solid rgba(255, 255, 255, 0.2);
57        }
58
59        #ai-chat-header h3 {
60            margin: 0;
61            color: white;
62            font-size: 18px;
63            font-weight: 600;
64        }
65
66        #ai-model-selector {
67            background: rgba(255, 255, 255, 0.9);
68            border: none;
69            color: #667eea;
70            padding: 8px 12px;
71            border-radius: 8px;
72            cursor: pointer;
73            font-size: 13px;
74            font-weight: 500;
75            outline: none;
76            transition: all 0.3s ease;
77            margin-right: 8px;
78        }
79
80        #ai-model-selector:hover {
81            background: white;
82            transform: scale(1.02);
83        }
84
85        #ai-chat-toggle {
86            background: rgba(255, 255, 255, 0.2);
87            border: none;
88            color: white;
89            width: 32px;
90            height: 32px;
91            border-radius: 8px;
92            cursor: pointer;
93            font-size: 18px;
94            display: flex;
95            align-items: center;
96            justify-content: center;
97            transition: all 0.3s ease;
98        }
99
100        #ai-chat-toggle:hover {
101            background: rgba(255, 255, 255, 0.3);
102            transform: scale(1.1);
103        }
104
105        #ai-chat-messages {
106            flex: 1;
107            overflow-y: auto;
108            padding: 20px;
109            display: flex;
110            flex-direction: column;
111            gap: 12px;
112        }
113
114        .ai-message {
115            max-width: 85%;
116            padding: 12px 16px;
117            border-radius: 12px;
118            word-wrap: break-word;
119            animation: slideIn 0.3s ease;
120        }
121
122        @keyframes slideIn {
123            from {
124                opacity: 0;
125                transform: translateY(10px);
126            }
127            to {
128                opacity: 1;
129                transform: translateY(0);
130            }
131        }
132
133        .ai-message.user {
134            align-self: flex-end;
135            background: white;
136            color: #333;
137            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
138        }
139
140        .ai-message.assistant {
141            align-self: flex-start;
142            background: rgba(255, 255, 255, 0.95);
143            color: #333;
144            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
145        }
146
147        .ai-message.system {
148            align-self: center;
149            background: rgba(255, 255, 255, 0.2);
150            color: white;
151            font-size: 13px;
152            font-style: italic;
153        }
154
155        #ai-chat-input-container {
156            padding: 16px 20px;
157            background: rgba(255, 255, 255, 0.15);
158            backdrop-filter: blur(10px);
159            border-top: 1px solid rgba(255, 255, 255, 0.2);
160            display: flex;
161            gap: 10px;
162        }
163
164        #ai-chat-input {
165            flex: 1;
166            padding: 12px 16px;
167            border: 2px solid rgba(255, 255, 255, 0.3);
168            border-radius: 12px;
169            background: rgba(255, 255, 255, 0.9);
170            color: #333;
171            font-size: 14px;
172            outline: none;
173            transition: all 0.3s ease;
174        }
175
176        #ai-chat-input:focus {
177            border-color: white;
178            background: white;
179        }
180
181        #ai-chat-send {
182            padding: 12px 20px;
183            background: white;
184            color: #667eea;
185            border: none;
186            border-radius: 12px;
187            cursor: pointer;
188            font-weight: 600;
189            font-size: 14px;
190            transition: all 0.3s ease;
191            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
192        }
193
194        #ai-chat-send:hover {
195            transform: translateY(-2px);
196            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
197        }
198
199        #ai-chat-send:disabled {
200            opacity: 0.5;
201            cursor: not-allowed;
202            transform: none;
203        }
204
205        #ai-chat-fab {
206            position: fixed;
207            bottom: 20px;
208            right: 20px;
209            width: 60px;
210            height: 60px;
211            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
212            border-radius: 50%;
213            display: none;
214            align-items: center;
215            justify-content: center;
216            cursor: pointer;
217            box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
218            z-index: 999999;
219            transition: all 0.3s ease;
220        }
221
222        #ai-chat-fab:hover {
223            transform: scale(1.1);
224            box-shadow: 0 6px 30px rgba(102, 126, 234, 0.6);
225        }
226
227        #ai-chat-fab svg {
228            width: 28px;
229            height: 28px;
230            fill: white;
231        }
232
233        .ai-chat-minimized #ai-chat-container {
234            display: none;
235        }
236
237        .ai-chat-minimized #ai-chat-fab {
238            display: flex;
239        }
240
241        #ai-chat-messages::-webkit-scrollbar {
242            width: 6px;
243        }
244
245        #ai-chat-messages::-webkit-scrollbar-track {
246            background: rgba(255, 255, 255, 0.1);
247            border-radius: 3px;
248        }
249
250        #ai-chat-messages::-webkit-scrollbar-thumb {
251            background: rgba(255, 255, 255, 0.3);
252            border-radius: 3px;
253        }
254
255        #ai-chat-messages::-webkit-scrollbar-thumb:hover {
256            background: rgba(255, 255, 255, 0.5);
257        }
258
259        .loading-dots {
260            display: inline-flex;
261            gap: 4px;
262        }
263
264        .loading-dots span {
265            width: 6px;
266            height: 6px;
267            background: #667eea;
268            border-radius: 50%;
269            animation: bounce 1.4s infinite ease-in-out both;
270        }
271
272        .loading-dots span:nth-child(1) {
273            animation-delay: -0.32s;
274        }
275
276        .loading-dots span:nth-child(2) {
277            animation-delay: -0.16s;
278        }
279
280        @keyframes bounce {
281            0%, 80%, 100% {
282                transform: scale(0);
283            }
284            40% {
285                transform: scale(1);
286            }
287        }
288    `);
289
290    // Создаем интерфейс чата
291    function createChatInterface() {
292        const container = document.createElement('div');
293        container.id = 'ai-chat-container';
294        
295        // Создаем опции для селектора моделей
296        const modelOptions = AI_MODELS.map(model => 
297            `<option value="${model.id}">${model.name}</option>`
298        ).join('');
299        
300        container.innerHTML = `
301            <div id="ai-chat-header">
302                <h3>🤖 AI Ассистент</h3>
303                <div style="display: flex; align-items: center; gap: 8px;">
304                    <select id="ai-model-selector">
305                        ${modelOptions}
306                    </select>
307                    <button id="ai-chat-toggle"></button>
308                </div>
309            </div>
310            <div id="ai-chat-messages"></div>
311            <div id="ai-chat-input-container">
312                <input type="text" id="ai-chat-input" placeholder="Напишите сообщение...">
313                <button id="ai-chat-send">Отправить</button>
314            </div>
315        `;
316
317        const fab = document.createElement('div');
318        fab.id = 'ai-chat-fab';
319        fab.innerHTML = `
320            <svg viewBox="0 0 24 24">
321                <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
322            </svg>
323        `;
324
325        document.body.appendChild(container);
326        document.body.appendChild(fab);
327
328        return { container, fab };
329    }
330
331    // Добавляем сообщение в чат
332    function addMessage(text, type = 'user') {
333        const messagesContainer = document.getElementById('ai-chat-messages');
334        const messageDiv = document.createElement('div');
335        messageDiv.className = `ai-message ${type}`;
336        messageDiv.textContent = text;
337        messagesContainer.appendChild(messageDiv);
338        messagesContainer.scrollTop = messagesContainer.scrollHeight;
339        
340        console.log(`[AI Chat] ${type}: ${text}`);
341    }
342
343    // Показываем индикатор загрузки
344    function showLoading() {
345        const messagesContainer = document.getElementById('ai-chat-messages');
346        const loadingDiv = document.createElement('div');
347        loadingDiv.className = 'ai-message assistant';
348        loadingDiv.id = 'loading-indicator';
349        loadingDiv.innerHTML = '<div class="loading-dots"><span></span><span></span><span></span></div>';
350        messagesContainer.appendChild(loadingDiv);
351        messagesContainer.scrollTop = messagesContainer.scrollHeight;
352    }
353
354    // Убираем индикатор загрузки
355    function hideLoading() {
356        const loadingIndicator = document.getElementById('loading-indicator');
357        if (loadingIndicator) {
358            loadingIndicator.remove();
359        }
360    }
361
362    // Отправляем сообщение в OpenRouter API
363    async function sendMessageToAI(message) {
364        try {
365            console.log('[AI Chat] Отправка запроса к OpenRouter API...');
366            
367            // Получаем выбранную модель
368            const modelSelector = document.getElementById('ai-model-selector');
369            const selectedModel = modelSelector ? modelSelector.value : 'openai/gpt-3.5-turbo';
370            
371            console.log('[AI Chat] Используется модель:', selectedModel);
372            
373            const response = await GM.xmlhttpRequest({
374                method: 'POST',
375                url: API_URL,
376                headers: {
377                    'Content-Type': 'application/json',
378                    'Authorization': `Bearer ${API_KEY}`,
379                    'HTTP-Referer': window.location.href,
380                    'X-Title': 'AI Chat Assistant'
381                },
382                data: JSON.stringify({
383                    model: selectedModel,
384                    messages: [
385                        {
386                            role: 'user',
387                            content: message
388                        }
389                    ]
390                }),
391                responseType: 'json'
392            });
393
394            console.log('[AI Chat] Получен ответ от API:', response.status);
395
396            if (response.status === 200) {
397                const data = response.response;
398                if (data.choices && data.choices.length > 0) {
399                    return data.choices[0].message.content;
400                } else {
401                    throw new Error('Некорректный формат ответа от API');
402                }
403            } else {
404                throw new Error(`Ошибка API: ${response.status} - ${response.statusText}`);
405            }
406        } catch (error) {
407            console.error('[AI Chat] Ошибка при отправке сообщения:', error);
408            throw error;
409        }
410    }
411
412    // Обработка отправки сообщения
413    async function handleSendMessage() {
414        const input = document.getElementById('ai-chat-input');
415        const sendButton = document.getElementById('ai-chat-send');
416        const message = input.value.trim();
417
418        if (!message) return;
419
420        // Добавляем сообщение пользователя
421        addMessage(message, 'user');
422        input.value = '';
423        sendButton.disabled = true;
424
425        // Показываем индикатор загрузки
426        showLoading();
427
428        try {
429            // Отправляем запрос к API
430            const aiResponse = await sendMessageToAI(message);
431            
432            // Убираем индикатор загрузки
433            hideLoading();
434            
435            // Добавляем ответ AI
436            addMessage(aiResponse, 'assistant');
437        } catch (error) {
438            hideLoading();
439            addMessage('Произошла ошибка при получении ответа. Проверьте API ключ и подключение.', 'system');
440            console.error('[AI Chat] Ошибка:', error);
441        } finally {
442            sendButton.disabled = false;
443            input.focus();
444        }
445    }
446
447    // Инициализация
448    async function init() {
449        console.log('[AI Chat] Инициализация расширения...');
450
451        // Создаем интерфейс
452        const { container, fab } = createChatInterface();
453
454        // Приветственное сообщение
455        addMessage('Привет! Я AI ассистент. Задайте мне любой вопрос!', 'system');
456
457        // Обработчики событий
458        const toggleButton = document.getElementById('ai-chat-toggle');
459        const input = document.getElementById('ai-chat-input');
460        const sendButton = document.getElementById('ai-chat-send');
461
462        // Сворачивание/разворачивание чата
463        toggleButton.addEventListener('click', () => {
464            document.body.classList.toggle('ai-chat-minimized');
465        });
466
467        fab.addEventListener('click', () => {
468            document.body.classList.remove('ai-chat-minimized');
469            input.focus();
470        });
471
472        // Отправка сообщения
473        sendButton.addEventListener('click', handleSendMessage);
474
475        input.addEventListener('keypress', (e) => {
476            if (e.key === 'Enter' && !e.shiftKey) {
477                e.preventDefault();
478                handleSendMessage();
479            }
480        });
481
482        console.log('[AI Chat] Расширение успешно загружено!');
483    }
484
485    // Запускаем после загрузки страницы
486    if (document.readyState === 'loading') {
487        document.addEventListener('DOMContentLoaded', init);
488    } else {
489        init();
490    }
491})();