/
Stils
/
SQLTranslator
Обзор
Документация
Войти
/
Stils
/
SQLTranslator
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/App.js
319 строк
14 KB
stilsman
pass
25 май 2025, 23:05
25 май 2025, 23:05
1085d74
Код
Авторство
О чём код?
import React, { useState, useRef, useEffect } from 'react'; import axios from 'axios'; import './App.css'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'; import { marked } from 'marked'; // Компонент индикатора загрузки const LoadingIndicator = () => ( <div className="loading-indicator"> <div className="loader"></div> <span>Обработка запроса...</span> </div> ); // Компонент "печатания" const TypingIndicator = () => ( <div className="typing-indicator"> <div className="typing-dot"></div> <div className="typing-dot"></div> <div className="typing-dot"></div> </div> ); // Компонент сообщения с содержимым const MessageContent = ({ text, isUser, isLoading, isLastMessage, hasSQLCode }) => { const sqlParts = text.match(/```sql[\s\S]*?```/g); const parts = text.split(/```sql[\s\S]*?```/g).filter(part => part); const sqlCode = sqlParts ? sqlParts.map(part => part.replace(/```sql\n|```/g, '')) : []; const handleCopy = (code) => { navigator.clipboard.writeText(code); }; return ( <div className={`MessageBubble ${isUser ? 'user' : ''}`}> {isLoading && isLastMessage ? ( <TypingIndicator /> ) : ( <> {parts.map((part, index) => ( <span key={index} dangerouslySetInnerHTML={{ __html: marked(part) }}></span> ))} {sqlCode.map((code, index) => ( <div key={index} style={{ position: 'relative' }}> <SyntaxHighlighter language="sql" style={vscDarkPlus} customStyle={{ width: 'auto', height: 'auto', padding: '10px', borderRadius: '10px', fontSize: '14px', margin: '10px 0', display: 'block' }} showLineNumbers={true} > {code} </SyntaxHighlighter> <div className="button-container"> <button onClick={() => handleCopy(code)} className="action-button"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> </svg> Копировать </button> </div> </div> ))} </> )} {isLoading && !isLastMessage && <div className="loading-animation">...</div>} </div> ); }; // Компонент приветственного сообщения const WelcomeMessage = () => ( <div className="WelcomeMessage"> <h1>Добро пожаловать в SQL Консультанта!</h1> <p>Введите запрос, для преобразования из <br /> TransactSQL в PostgreSQL 9.4.</p> <div className="feature-list"> <div className="feature-item"> <div className="feature-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon> </svg> </div> <span className="feature-text">Точное преобразование синтаксиса</span> </div> <div className="feature-item"> <div className="feature-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="16" x2="12" y2="12"></line> <line x1="12" y1="8" x2="12.01" y2="8"></line> </svg> </div> <span className="feature-text">Подробные пояснения</span> </div> </div> </div> ); function App() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [inputHeight, setInputHeight] = useState('auto'); const [isLoading, setIsLoading] = useState(false); const [isSidebarOpen, setSidebarOpen] = useState(true); const [selectedModel, setSelectedModel] = useState('GigaChat-Pro'); const [showModelMenu, setShowModelMenu] = useState(false); const messagesEndRef = useRef(null); const scrollToBottom = () => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }; const handleInputChange = (event) => { const { value } = event.target; setInput(value); const lineCount = value.split('\n').length; const maxLines = isSidebarOpen ? 5 : 8; setInputHeight(`${Math.min(lineCount, maxLines) * 1.2}em`); }; const handleKeyDown = (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); handleSubmit(event); } }; const handleFileUpload = async (event) => { const file = event.target.files[0]; if (!file || !file.name.endsWith('.sql')) { alert('Пожалуйста, загрузите файл с расширением .sql'); return; } const reader = new FileReader(); reader.onload = async (e) => { const fileContent = e.target.result; setMessages((prev) => [...prev, { text: `Отправка файла: ${file.name}`, isUser: true }]); setIsLoading(true); try { const response = await axios.post('http://localhost:8000/message/', { text: fileContent, model: selectedModel }); setMessages((prev) => [...prev, { text: response.data.response, isUser: false }]); } catch (error) { console.error('Ошибка при отправке файла:', error); setMessages((prev) => [...prev, { text: "Произошла ошибка при обработке файла. Пожалуйста, попробуйте еще раз.", isUser: false }]); } finally { setIsLoading(false); } }; reader.readAsText(file); }; const handleSubmit = async (event) => { event.preventDefault(); if (input.trim() === '' || isLoading) return; const messageToSend = { text: input, model: selectedModel }; setMessages((prev) => [...prev, { text: input, isUser: true }]); setInput(''); setInputHeight('auto'); setIsLoading(true); try { const response = await axios.post('http://localhost:8000/message/', messageToSend); setMessages((prev) => [...prev, { text: response.data.response, isUser: false }]); } catch (error) { console.error('Ошибка при отправке сообщения:', error); setMessages((prev) => [...prev, { text: "Произошла ошибка при обработке запроса. Пожалуйста, попробуйте еще раз.", isUser: false }]); } finally { setIsLoading(false); } }; const handleModelClick = (model) => { setSelectedModel(model); setShowModelMenu(false); }; useEffect(() => { scrollToBottom(); }, [messages]); return ( <Router> <div className="AppContainer"> <div className={`Sidebar ${isSidebarOpen ? '' : 'collapsed'}`}> <button className="ToggleButton" onClick={() => setSidebarOpen(!isSidebarOpen)}> {isSidebarOpen ? '←' : '→'} </button> {isSidebarOpen && ( <div className="nav-buttons"> <div> <button className="nav-button" onClick={() => setShowModelMenu(!showModelMenu)}> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M12 3h7a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-7m0-18H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7m0-18v18"></path> </svg> Модель: {selectedModel} </button> {showModelMenu && ( <div className="model-dropdown"> <button className="model-option" onClick={() => handleModelClick('GigaChat-2-Max')}>GigaChat-2-Max</button> <button className="model-option" onClick={() => handleModelClick('GigaChat-2-Pro')}>GigaChat-2-Pro</button> <button className="model-option" onClick={() => handleModelClick('GigaChat-Pro')}>GigaChat-Pro</button> <button className="model-option" onClick={() => handleModelClick('GigaChat-Max')}>GigaChat-Max</button> <button className="model-option" onClick={() => handleModelClick('GigaChat-2')}>GigaChat-2-Lite</button> <button className="model-option" onClick={() => handleModelClick('GigaChat')}>GigaChat-Lite</button> </div> )} </div> <Link to="/" className="nav-button" style={{ textDecoration: 'none', color: 'inherit' }}> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> </svg> Чат </Link> <a href="https://gitverse.ru/Stils/SQLTranslator" target="_blank" rel="noopener noreferrer" className="nav-button" style={{ textDecoration: 'none', color: 'inherit' }}> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path> </svg> GitVerse </a> </div> )} </div> <div className={`MessagesContainer ${isSidebarOpen ? '' : 'shifted'}`}> <Routes> <Route path="/" element={ <> {messages.length === 0 && <WelcomeMessage />} {messages.map((msg, index) => { const hasSQLCode = msg.text.includes('```sql') && msg.text.includes('```'); return ( <MessageContent key={index} text={msg.text} isUser={msg.isUser} isLoading={isLoading && index === messages.length - 1 && !msg.isUser} isLastMessage={index === messages.length - 1} hasSQLCode={hasSQLCode} /> ); })} {isLoading && !messages.some(msg => msg.isLoading) && ( <LoadingIndicator /> )} <div ref={messagesEndRef} /> </> } /> </Routes> </div> <div className={`InputForm ${isSidebarOpen ? '' : 'shifted'}`}> <form onSubmit={handleSubmit} className="InputContainer"> <label className="file-upload"> <input type="file" accept=".sql" onChange={handleFileUpload} disabled={isLoading} /> <div className="plus-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="12" y1="18" x2="12" y2="12"></line> <line x1="9" y1="15" x2="15" y2="15"></line> </svg> </div> </label> <textarea className="Input" value={input} onChange={handleInputChange} onKeyDown={handleKeyDown} placeholder="Введите TransactSQL-запрос для преобразования..." style={{ height: inputHeight }} disabled={isLoading} /> <button type="submit" className="Button" disabled={isLoading || input.trim() === ''} > <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: '4px' }}> <line x1="22" y1="2" x2="11" y2="13"></line> <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> </svg> Отправить </button> </form> </div> </div> </Router> ); } export default App;