271 lines
8.4 KiB
HTML
271 lines
8.4 KiB
HTML
|
<!DOCTYPE html>
|
|||
|
<html lang="ru">
|
|||
|
<head>
|
|||
|
<meta charset="UTF-8">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
<title>FutaClone</title>
|
|||
|
<!-- Добавляем библиотеку для визуализации графа -->
|
|||
|
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|||
|
<style>
|
|||
|
/* Добавляем новые стили */
|
|||
|
#network {
|
|||
|
width: 100%;
|
|||
|
height: 600px;
|
|||
|
border: 1px solid #800;
|
|||
|
background: white;
|
|||
|
}
|
|||
|
.hidden { display: none; }
|
|||
|
#greeting { text-align: center; padding: 20px; }
|
|||
|
#graphContainer { margin-top: 20px; }
|
|||
|
/* Стили в духе традиционных имиджборд */
|
|||
|
body {
|
|||
|
background-color: #F0E0D6;
|
|||
|
font-family: Arial, sans-serif;
|
|||
|
margin: 20px;
|
|||
|
max-width: 800px;
|
|||
|
margin-left: auto;
|
|||
|
margin-right: auto;
|
|||
|
}
|
|||
|
.post-form {
|
|||
|
background: #FFF;
|
|||
|
border: 1px solid #800;
|
|||
|
padding: 15px;
|
|||
|
margin-bottom: 20px;
|
|||
|
}
|
|||
|
.post {
|
|||
|
background: #FFF;
|
|||
|
border: 1px solid #800;
|
|||
|
padding: 15px;
|
|||
|
margin-bottom: 10px;
|
|||
|
}
|
|||
|
.post img {
|
|||
|
max-width: 200px;
|
|||
|
max-height: 200px;
|
|||
|
margin-top: 10px;
|
|||
|
}
|
|||
|
input, textarea, button {
|
|||
|
margin: 5px;
|
|||
|
padding: 5px;
|
|||
|
}
|
|||
|
.hidden {
|
|||
|
display: none;
|
|||
|
}
|
|||
|
.preview-image {
|
|||
|
max-width: 150px;
|
|||
|
margin: 10px 0;
|
|||
|
}
|
|||
|
</style>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<!-- Добавляем новые блоки -->
|
|||
|
<div id="greeting" class="hidden">
|
|||
|
<h2>Отправьте первое сообщение, чтобы начать</h2>
|
|||
|
</div>
|
|||
|
|
|||
|
<div id="graphContainer" class="hidden">
|
|||
|
<h2>Древо мыслей</h2>
|
|||
|
<div id="network"></div>
|
|||
|
</div>
|
|||
|
<!-- Форма создания поста -->
|
|||
|
<div class="post-form">
|
|||
|
<h2>Новый пост</h2>
|
|||
|
<form id="postForm">
|
|||
|
<textarea id="postText" rows="4" cols="50" placeholder="Текст поста"></textarea><br>
|
|||
|
<input type="file" id="imageInput" accept="image/*"><br>
|
|||
|
<div id="imagePreview" class="hidden"></div>
|
|||
|
<button type="submit">Отправить</button>
|
|||
|
</form>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- Форма поиска (только текст) -->
|
|||
|
<div class="post-form">
|
|||
|
<h2>Поиск</h2>
|
|||
|
<input type="text" id="searchText" placeholder="Текст для поиска">
|
|||
|
<button onclick="searchPosts()">Искать</button>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- Список постов -->
|
|||
|
<div id="postsContainer"></div>
|
|||
|
|
|||
|
<script>
|
|||
|
// Базовый URL бэкенда
|
|||
|
const API_URL = 'http://localhost:8000';
|
|||
|
|
|||
|
// Обработка отправки поста (оставляем без изменений)
|
|||
|
document.getElementById('postForm').addEventListener('submit', async (e) => {
|
|||
|
e.preventDefault();
|
|||
|
|
|||
|
const text = document.getElementById('postText').value;
|
|||
|
const fileInput = document.getElementById('imageInput');
|
|||
|
|
|||
|
const formData = new FormData();
|
|||
|
if (text) formData.append('text', text);
|
|||
|
if (fileInput.files[0]) formData.append('image', fileInput.files[0]);
|
|||
|
|
|||
|
try {
|
|||
|
const response = await fetch(`${API_URL}/posts/`, {
|
|||
|
method: 'POST',
|
|||
|
body: formData
|
|||
|
});
|
|||
|
|
|||
|
if (response.ok) {
|
|||
|
const postData = await response.json();
|
|||
|
// Сохраняем вектор в localStorage
|
|||
|
localStorage.setItem('userVector', JSON.stringify(postData.vector));
|
|||
|
checkUserVector();
|
|||
|
loadPosts();
|
|||
|
document.getElementById('postForm').reset();
|
|||
|
document.getElementById('imagePreview').classList.add('hidden');
|
|||
|
} else {
|
|||
|
console.error('Error:', error);
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error('Error:', error);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Превью изображения перед загрузкой
|
|||
|
document.getElementById('imageInput').addEventListener('change', function(e) {
|
|||
|
const preview = document.getElementById('imagePreview');
|
|||
|
if (this.files && this.files[0]) {
|
|||
|
const reader = new FileReader();
|
|||
|
reader.onload = function(e) {
|
|||
|
preview.innerHTML = `<img src="${e.target.result}" class="preview-image">`;
|
|||
|
preview.classList.remove('hidden');
|
|||
|
}
|
|||
|
reader.readAsDataURL(this.files[0]);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Загрузка и отображение постов (оставляем без изменений)
|
|||
|
async function loadPosts() {
|
|||
|
try {
|
|||
|
const response = await fetch(`${API_URL}/posts/`);
|
|||
|
const posts = await response.json();
|
|||
|
|
|||
|
const container = document.getElementById('postsContainer');
|
|||
|
container.innerHTML = posts.map(post => `
|
|||
|
<div class="post">
|
|||
|
<div class="post-meta">#${post.id} - ${new Date(post.created_at).toLocaleString()}</div>
|
|||
|
${post.text ? `<div class="post-text">${post.text}</div>` : ''}
|
|||
|
${post.image ? `<img src="${API_URL}/${post.image}" />` : ''}
|
|||
|
</div>
|
|||
|
`).join('');
|
|||
|
} catch (error) {
|
|||
|
console.error('Error:', error);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Обновлённая функция поиска, использующая GET-запрос
|
|||
|
async function searchPosts() {
|
|||
|
const text = document.getElementById('searchText').value;
|
|||
|
// Формируем строку запроса, если введён текст
|
|||
|
const query = text ? `?text=${encodeURIComponent(text)}` : '';
|
|||
|
|
|||
|
try {
|
|||
|
const response = await fetch(`${API_URL}/search/${query}`, {
|
|||
|
method: 'GET'
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('Запрос поиска завершился с ошибкой');
|
|||
|
}
|
|||
|
|
|||
|
const results = await response.json();
|
|||
|
alert('Найдено постов: ' + results.length);
|
|||
|
// Здесь можно добавить отображение результатов поиска в интерфейсе
|
|||
|
} catch (error) {
|
|||
|
console.error('Error during search:', error);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Первоначальная загрузка постов
|
|||
|
loadPosts();
|
|||
|
// Новая функция проверки вектора
|
|||
|
function checkUserVector() {
|
|||
|
const hasVector = localStorage.getItem('userVector') !== null;
|
|||
|
document.getElementById('greeting').classList.toggle('hidden', hasVector);
|
|||
|
document.getElementById('graphContainer').classList.toggle('hidden', !hasVector);
|
|||
|
|
|||
|
if (hasVector) {
|
|||
|
loadUserTree();
|
|||
|
} else {
|
|||
|
loadPosts();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Функция загрузки дерева
|
|||
|
async function loadUserTree() {
|
|||
|
const userVector = JSON.parse(localStorage.getItem('userVector'));
|
|||
|
if (!userVector) return;
|
|||
|
|
|||
|
try {
|
|||
|
const response = await fetch(`${API_URL}/posts/tree`, {
|
|||
|
method: 'POST',
|
|||
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
body: JSON.stringify({ vector: userVector }),
|
|||
|
});
|
|||
|
const posts = await response.json();
|
|||
|
renderGraph(posts);
|
|||
|
} catch (error) {
|
|||
|
console.error('Error loading tree:', error);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Визуализация графа
|
|||
|
function renderGraph(posts) {
|
|||
|
const container = document.getElementById('network');
|
|||
|
const nodes = [];
|
|||
|
const edges = [];
|
|||
|
|
|||
|
posts.forEach((post, index) => {
|
|||
|
nodes.push({
|
|||
|
id: post.id,
|
|||
|
label: post.text || '[Изображение]',
|
|||
|
image: post.image ? `${API_URL}/${post.image}` : 'https://via.placeholder.com/100',
|
|||
|
shape: 'circularImage',
|
|||
|
size: 25
|
|||
|
});
|
|||
|
|
|||
|
if (index > 0) {
|
|||
|
edges.push({
|
|||
|
from: posts[index-1].id,
|
|||
|
to: post.id,
|
|||
|
arrows: 'to',
|
|||
|
smooth: { type: 'curvedCW' }
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
const data = { nodes, edges };
|
|||
|
const options = {
|
|||
|
nodes: {
|
|||
|
borderWidth: 2,
|
|||
|
color: {
|
|||
|
border: '#800',
|
|||
|
background: '#F0E0D6'
|
|||
|
},
|
|||
|
font: { size: 14 }
|
|||
|
},
|
|||
|
edges: {
|
|||
|
color: '#800',
|
|||
|
width: 2
|
|||
|
},
|
|||
|
physics: {
|
|||
|
stabilization: true,
|
|||
|
barnesHut: {
|
|||
|
gravitationalConstant: -2000,
|
|||
|
springLength: 200
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
new vis.Network(container, data, options);
|
|||
|
}
|
|||
|
|
|||
|
// Инициализация при загрузке страницы
|
|||
|
checkUserVector();
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|