Bug fixes to render react-flow using esbuild bundler instead of loading from cdn
This commit is contained in:
parent
bcd152e81f
commit
04a19b2508
|
|
@ -1,49 +1,72 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import esbuild from 'esbuild'
|
||||
import process from 'process'
|
||||
import builtins from 'builtin-modules'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
`
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
const prod = process.argv[2] === 'production'
|
||||
|
||||
const renameCssPlugin = () => ({
|
||||
name: 'rename-css',
|
||||
setup (build) {
|
||||
build.onEnd(() => {
|
||||
const outDir = path.dirname(build.initialOptions.outfile || 'main.js')
|
||||
const mainCss = path.join(outDir, 'main.css')
|
||||
const stylesCss = path.join(outDir, 'styles.css')
|
||||
|
||||
if (fs.existsSync(mainCss)) {
|
||||
fs.renameSync(mainCss, stylesCss)
|
||||
console.log(`Renamed ${mainCss} to ${stylesCss}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
js: banner
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: ['main.ts'],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
'obsidian',
|
||||
'electron',
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
...builtins
|
||||
],
|
||||
format: 'cjs',
|
||||
target: 'es2019', // Обновляем таргет до es2019
|
||||
logLevel: 'info',
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
outfile: 'main.js',
|
||||
minify: prod,
|
||||
});
|
||||
// ДОБАВЛЕНО: Загрузчик для CSS файлов
|
||||
loader: {
|
||||
'.css': 'css'
|
||||
},
|
||||
plugins: [renameCssPlugin()]
|
||||
})
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
await context.rebuild()
|
||||
process.exit(0)
|
||||
} else {
|
||||
await context.watch();
|
||||
await context.watch()
|
||||
}
|
||||
|
|
|
|||
2
main.ts
2
main.ts
|
|
@ -3,6 +3,8 @@ import { EventBus } from './src/utils/EventBus';
|
|||
import { GraphView, GRAPH_VIEW_TYPE } from './src/views/GraphView';
|
||||
import { ChatView, CHAT_VIEW_TYPE } from './src/views/ChatView';
|
||||
|
||||
import 'src/css/styles.css'; // Путь к твоему новому CSS файлу
|
||||
|
||||
// ----------------------------------------------- Settings ---------------------------------------------------------------
|
||||
|
||||
interface LLMAgentSettings {
|
||||
|
|
|
|||
270
old/src/App.css
Normal file
270
old/src/App.css
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/* Общие стили контейнера приложения */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh; /* Полная высота окна просмотра */
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f0f0f0; /* Светлый фон для всего приложения */
|
||||
}
|
||||
|
||||
/* Стили для HistoryPanel */
|
||||
.history-panel {
|
||||
width: 280px; /* Ширина боковой панели */
|
||||
background-color: #202123; /* Темный фон, как в ChatGPT */
|
||||
color: #ececec; /* Светлый текст */
|
||||
padding: 10px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2); /* Тень для выделения */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto; /* Прокрутка, если история длинная */
|
||||
flex-shrink: 0; /* Не позволяет панели сжиматься */
|
||||
}
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #3e3e40; /* Разделитель */
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.history-panel h2 {
|
||||
font-size: 1.1em;
|
||||
color: #f0f0f0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.new-chat-button {
|
||||
background-color: #3e3e40;
|
||||
color: #ececec;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.new-chat-button:hover {
|
||||
background-color: #555558;
|
||||
}
|
||||
|
||||
.chat-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background-color: #2d2e30; /* Фон элемента списка */
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
display: flex; /* Используем flexbox для внутреннего содержимого */
|
||||
align-items: center;
|
||||
min-height: 36px; /* Минимальная высота элемента */
|
||||
}
|
||||
|
||||
.chat-list-item:hover {
|
||||
background-color: #3e3e40; /* Фон при наведении */
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.chat-list-item.selected {
|
||||
background-color: #4a4a50; /* Фон для выбранного элемента (если будет реализовано) */
|
||||
}
|
||||
|
||||
.chat-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-item-text {
|
||||
flex-grow: 1; /* Позволяет тексту занимать все доступное пространство */
|
||||
min-width: 0; /* Важно для работы truncate */
|
||||
}
|
||||
|
||||
.chat-item-text .truncate {
|
||||
white-space: nowrap; /* Запрещает перенос строки */
|
||||
overflow: hidden; /* Скрывает содержимое, которое не помещается */
|
||||
text-overflow: ellipsis; /* Добавляет многоточие для скрытого текста */
|
||||
display: block; /* Важно для truncate */
|
||||
}
|
||||
|
||||
.chat-item-actions {
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0; /* Не позволяет кнопкам сжиматься */
|
||||
}
|
||||
|
||||
.chat-item-options-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #777777; /* Цвет иконки */
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
transition: color 0.2s ease, background-color 0.2s ease;
|
||||
display: flex; /* Для центрирования SVG */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-item-options-button:hover {
|
||||
color: #ffffff;
|
||||
background-color: #555558;
|
||||
}
|
||||
|
||||
/* Стили для GraphPanel */
|
||||
.graph-panel {
|
||||
flex-grow: 1;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* Стили для ChatPanel */
|
||||
.chat-panel {
|
||||
width: 300px; /* Фиксированная ширина для ChatPanel */
|
||||
flex-shrink: 0; /* Не позволяет панели сжиматься */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #343541; /* Темный фон для панели чата */
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chat-history {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px; /* Для отступа от скроллбара */
|
||||
padding-bottom: 10px; /* Сохраняем ваш padding-bottom */
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 15px; /* Увеличиваем отступ */
|
||||
padding: 10px 15px; /* Увеличиваем padding */
|
||||
border-radius: 8px; /* Скругляем углы */
|
||||
max-width: 70%; /* Ограничение ширины сообщения */
|
||||
word-break: break-word; /* Добавлено: Перенос слов, если строка слишком длинная */
|
||||
}
|
||||
|
||||
.message img {
|
||||
max-width: 100%; /* Максимальная ширина изображения - 100% от родительского элемента */
|
||||
height: auto; /* Автоматическая высота для сохранения пропорций */
|
||||
display: block; /* Убирает отступы снизу у inline элементов */
|
||||
margin-bottom: 5px; /* Небольшой отступ снизу каждого изображения */
|
||||
}
|
||||
|
||||
.message.user {
|
||||
background-color: #444654; /* Измененный фон */
|
||||
align-self: flex-end; /* Сообщения пользователя справа */
|
||||
margin-left: auto; /* Сдвигает сообщение вправо */
|
||||
text-align: left; /* Изменено: текст выравнивается по левому краю внутри блока */
|
||||
}
|
||||
|
||||
.message.assistant {
|
||||
background-color: #4e4f5a; /* Измененный фон */
|
||||
align-self: flex-start; /* Сообщения ассистента слева */
|
||||
margin-right: auto; /* Сдвигает сообщение влево */
|
||||
text-align: left; /* Сохраняем ваше выравнивание */
|
||||
}
|
||||
|
||||
.chat-input-form {
|
||||
display: flex;
|
||||
margin-top: 20px; /* Добавляем отступ сверху */
|
||||
gap: 10px; /* Промежуток между элементами формы */
|
||||
}
|
||||
|
||||
/* Стили для CommandInput */
|
||||
.command-input {
|
||||
position: relative;
|
||||
flex-grow: 1; /* Позволяет input занимать доступное пространство */
|
||||
}
|
||||
|
||||
.command-input input {
|
||||
width: 100%;
|
||||
padding: 10px 15px; /* Увеличенный padding */
|
||||
border: 1px solid #555; /* Темная рамка */
|
||||
border-radius: 5px;
|
||||
background-color: #444654; /* Темный фон */
|
||||
color: #e0e0e0; /* Светлый текст */
|
||||
font-size: 1em;
|
||||
outline: none; /* Убираем стандартный outline */
|
||||
}
|
||||
|
||||
.command-input input::placeholder {
|
||||
color: #999; /* Цвет placeholder */
|
||||
}
|
||||
|
||||
.chat-input-form button {
|
||||
background-color: #1a73e8; /* Синяя кнопка */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px; /* Увеличенный padding */
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-input-form button:hover {
|
||||
background-color: #0d47a1; /* Темнее при наведении */
|
||||
}
|
||||
|
||||
.react-flow-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.command-input .suggestions {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
bottom: 100%; /* Меню будет расположено выше элемента, к которому оно прикреплено */
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none; /* Убираем нижнюю границу, так как теперь верхняя часть будет примыкать к полю ввода */
|
||||
border-radius: 5px 5px 0 0; /* Верхние углы будут скруглены, нижние - прямые */
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); /* Тень сверху */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.command-input .suggestions li {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.command-input .suggestions li:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
|
||||
/* Стили для контекстного меню */
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.context-menu button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 15px;
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.context-menu button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
165
old/src/App.js
Normal file
165
old/src/App.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import ChatPanel from "./components/ChatPanel";
|
||||
import GraphPanel from "./components/GraphPanel";
|
||||
import HistoryPanel from "./components/HistoryPanel";
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
const [graphData, setGraphData] = useState({ nodes: [], edges: [] });
|
||||
const [chatHistory, setChatHistory] = useState([]);
|
||||
const [currentNode, setCurrentNode] = useState(null);
|
||||
const [graphsList, setGraphsList] = useState([]);
|
||||
const [selectedGraphId, setSelectedGraphId] = useState(null);
|
||||
|
||||
const API_BASE_URL = "http://localhost:5000/api"; // Укажи базовый URL для API
|
||||
|
||||
useEffect(() => {
|
||||
fetchGraphsList();
|
||||
if (selectedGraphId) {
|
||||
fetchGraphData(selectedGraphId);
|
||||
}
|
||||
}, [selectedGraphId]);
|
||||
|
||||
const fetchGraphsList = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/graphs`); // Используй базовый URL
|
||||
const data = await response.json();
|
||||
setGraphsList(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching graphs:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchGraphData = async (graphId) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/graphs/${graphId}`); // Используй базовый URL
|
||||
if (!response.ok) {
|
||||
// Если статус ответа не 200-299, выбрасываем ошибку
|
||||
if (response.status === 404) {
|
||||
console.error(`Graph with id ${graphId} not found.`);
|
||||
// Здесь можно добавить логику для отображения сообщения пользователю
|
||||
// Например, установить состояние ошибки и отобразить сообщение в UI
|
||||
// setErrorMessage(`Graph with id ${graphId} not found.`);
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("Fetched graph data:", data); // Добавлено для отладки
|
||||
setGraphData({
|
||||
nodes: data.graph_nodes.map((node) => ({
|
||||
id: node.id,
|
||||
data: { label: node.data.label },
|
||||
})),
|
||||
edges: data.graph_edges.map((edge) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
})),
|
||||
});
|
||||
setChatHistory(data.messages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching graph data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSendMessage = async (message) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/chat`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: message,
|
||||
graph_id: selectedGraphId,
|
||||
parent_node_id: currentNode,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Result from API:", result);
|
||||
|
||||
setGraphData({
|
||||
nodes: result.graph_visualization_data.nodes.map((node) => ({
|
||||
id: node.id,
|
||||
data: { label: node.data.label },
|
||||
})),
|
||||
edges: result.graph_visualization_data.edges.map((edge) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
})),
|
||||
});
|
||||
|
||||
// Обновляем выбранный ID графа
|
||||
setSelectedGraphId(result.graph_id);
|
||||
|
||||
// Устанавливаем текущий узел на тот, который был последним в ответе агента
|
||||
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
||||
setCurrentNode(newCurrentNodeId);
|
||||
|
||||
// Всегда загружаем сообщения от корня до нового текущего узла
|
||||
// Это гарантирует, что в чате отображается только текущая ветка
|
||||
if (newCurrentNodeId) {
|
||||
await fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||
} else {
|
||||
// Если почему-то current_node_id не пришел (что не должно быть при успешном ответе),
|
||||
// используем полную историю, хотя это менее предпочтительно.
|
||||
setChatHistory(result.messages);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error sending message:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Новая функция для получения сообщений от корня до выбранной ноды
|
||||
const fetchMessagesFromRootToNode = async (nodeId) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/messages/${selectedGraphId}/${nodeId}`); // Используй базовый URL
|
||||
const data = await response.json();
|
||||
setChatHistory(data); // Обновляем историю чата только сообщениями от корня до выбранной ноды
|
||||
} catch (error) {
|
||||
console.error("Error fetching messages:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNodeClick = (nodeId) => {
|
||||
setCurrentNode(nodeId);
|
||||
fetchMessagesFromRootToNode(nodeId); // Получаем сообщения при клике на ноду
|
||||
};
|
||||
|
||||
const handleGraphSelect = (graphId) => {
|
||||
setSelectedGraphId(graphId);
|
||||
};
|
||||
|
||||
// Новая функция для создания нового чата
|
||||
const handleNewChat = () => {
|
||||
setGraphData({ nodes: [], edges: [] }); // Очищаем данные графа
|
||||
setChatHistory([]); // Очищаем историю чата
|
||||
setCurrentNode(null); // Сбрасываем текущий выбранный узел
|
||||
setSelectedGraphId(null); // Сбрасываем выбранный ID графа (чтобы бэкенд создал новый)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
{/* Передаем handleNewChat в HistoryPanel */}
|
||||
<HistoryPanel
|
||||
graphs={graphsList}
|
||||
onGraphSelect={handleGraphSelect}
|
||||
onNewChat={handleNewChat}
|
||||
/>
|
||||
<GraphPanel graphData={graphData} onNodeClick={handleNodeClick} />
|
||||
<ChatPanel chatHistory={chatHistory} onSendMessage={handleSendMessage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
71
old/src/components/ChatPanel.js
Normal file
71
old/src/components/ChatPanel.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw'; // Импортируем rehype-raw для обработки сырого HTML
|
||||
import CommandInput from './CommandInput';
|
||||
|
||||
function ChatPanel({ chatHistory, onSendMessage }) {
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault(); // Предотвращаем стандартное действие отправки формы, которое перезагружает страницу
|
||||
if (message.trim()) { // Проверяем, что сообщение не пустое
|
||||
onSendMessage(message); // Вызываем функцию отправки сообщения, переданную из родителя
|
||||
setMessage(''); // Очищаем поле ввода после отправки
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const chatHistoryDiv = document.querySelector('.chat-history');
|
||||
if (chatHistoryDiv) {
|
||||
chatHistoryDiv.scrollTop = chatHistoryDiv.scrollHeight;
|
||||
}
|
||||
}, [chatHistory]);
|
||||
|
||||
return (
|
||||
<div className="chat-panel">
|
||||
<div className="chat-history">
|
||||
{chatHistory.map((msg, index) => (
|
||||
<div key={index} className={`message ${msg.role}`}>
|
||||
{/* Добавлено: обработка текста с Markdown */}
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]} // remarkHtml больше не нужен здесь
|
||||
rehypePlugins={[rehypeRaw]} // Добавляем rehypeRaw для парсинга и отображения сырого HTML
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a {...props} target="_blank" rel="noopener noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
),
|
||||
img: ({ node, ...props }) => (
|
||||
// Переопределяем стили изображения для адаптивности
|
||||
<img
|
||||
{...props}
|
||||
style={{
|
||||
...props.style, // Сохраняем другие инлайн-стили, переданные из бэкенда (например, max-height)
|
||||
maxWidth: '100%', // Гарантируем, что изображение не выходит за пределы ширины родителя
|
||||
height: 'auto' // Сохраняем пропорции изображения
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
children={msg.content} // Явное указание children
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="chat-input-form">
|
||||
<CommandInput
|
||||
value={message} // Передаем текущее значение поля ввода
|
||||
onChange={(e) => setMessage(e.target.value)} // Обновляем состояние при изменении поля ввода
|
||||
// onSendMessage больше НЕ передается сюда,
|
||||
// так как CommandInput не должен напрямую отправлять сообщения.
|
||||
// Отправка происходит через onSubmit формы.
|
||||
/>
|
||||
<button type="submit">Отправить</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatPanel;
|
||||
57
old/src/components/CommandInput.js
Normal file
57
old/src/components/CommandInput.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const CommandInput = ({ value, onChange, /* onSendMessage удален из пропсов */ }) => { // onSendMessage больше не передается как проп
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const commands = ['/help', '/imagine', '/analyze', '/subtitles_meet', '/subtitles_teams', '/summarize', '/chat'];
|
||||
|
||||
useEffect(() => {
|
||||
if (value.startsWith('/')) {
|
||||
const filteredSuggestions = commands.filter(command =>
|
||||
command.startsWith(value.toLowerCase())
|
||||
);
|
||||
setSuggestions(filteredSuggestions);
|
||||
} else {
|
||||
setSuggestions([]);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleSuggestionClick = (suggestion) => {
|
||||
onChange({ target: { value: suggestion } });
|
||||
setSuggestions([]);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (suggestions.length > 0) {
|
||||
// Если есть предложения, выбираем первое (или другое, если у вас есть логика выделения)
|
||||
handleSuggestionClick(suggestions[0]);
|
||||
// Предотвращаем стандартное поведение (отправку формы), так как мы обработали Enter
|
||||
event.preventDefault();
|
||||
}
|
||||
// Если предложений нет, то Enter должен просто вызвать стандартную отправку формы.
|
||||
// Ничего дополнительно делать здесь не нужно, т.к. родительская форма обработает submit.
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="command-input">
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
{suggestions.length > 0 && (
|
||||
<ul className="suggestions">
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li key={index} onClick={() => handleSuggestionClick(suggestion)}>
|
||||
{suggestion}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandInput;
|
||||
132
old/src/components/GraphPanel.js
Normal file
132
old/src/components/GraphPanel.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlowProvider
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import dagre from 'dagre'; // Импортируем библиотеку dagre
|
||||
|
||||
// Инициализируем объект Dagre Graph
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
// Определяем размеры узлов для корректного расположения
|
||||
const nodeWidth = 172; // Ширина узла, может потребоваться корректировка
|
||||
const nodeHeight = 36; // Высота узла, может потребоваться корректировка
|
||||
|
||||
/**
|
||||
* Применяет алгоритм расположения Dagre к узлам и рёбрам графа.
|
||||
* @param {Array} nodes Массив узлов React Flow.
|
||||
* @param {Array} edges Массив рёбер React Flow.
|
||||
* @param {string} direction Направление графа ('TB' - сверху вниз, 'LR' - слева направо).
|
||||
* @returns {Object} Объект с обновлёнными узлами и рёбрами, содержащими позиции.
|
||||
*/
|
||||
const getLayoutedElements = (nodes, edges, direction = 'TB') => {
|
||||
const isHorizontal = direction === 'LR'; // Проверяем, горизонтальное ли расположение
|
||||
dagreGraph.setGraph({ rankdir: direction }); // Устанавливаем направление графа
|
||||
|
||||
// Добавляем узлы в граф Dagre с их размерами
|
||||
nodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
|
||||
// Добавляем рёбра в граф Dagre
|
||||
edges.forEach((edge) => {
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
// Выполняем расчёт расположения графа
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
// Обновляем позиции узлов React Flow на основе расчётов Dagre
|
||||
const newNodes = nodes.map((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id); // Получаем данные узла из Dagre
|
||||
// Смещаем позицию узла Dagre (якорь в центре) к верхнему левому углу,
|
||||
// чтобы она соответствовала точке привязки узла React Flow (верхний левый угол).
|
||||
return {
|
||||
...node,
|
||||
targetPosition: isHorizontal ? 'left' : 'top',
|
||||
sourcePosition: isHorizontal ? 'right' : 'bottom',
|
||||
position: {
|
||||
x: nodeWithPosition.x - nodeWidth / 2,
|
||||
y: nodeWithPosition.y - nodeHeight / 2,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return { nodes: newNodes, edges }; // Возвращаем обновлённые узлы и рёбра
|
||||
};
|
||||
|
||||
const GraphPanel = ({ graphData, onNodeClick }) => {
|
||||
// Инициализируем состояния узлов и рёбер пустыми массивами
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
// Используем useEffect для применения Dagre layout при изменении данных графа
|
||||
useEffect(() => {
|
||||
// Проверяем, есть ли данные для построения графа
|
||||
if (graphData.nodes.length > 0 || graphData.edges.length > 0) {
|
||||
console.log("Applying Dagre layout with data:", graphData);
|
||||
|
||||
// Преобразуем входящие данные в формат, необходимый для Dagre и React Flow
|
||||
const initialNodesForLayout = graphData.nodes.map(node => ({
|
||||
id: node.id,
|
||||
data: { label: node.data.label },
|
||||
position: { x: 0, y: 0 } // Временная позиция, будет перезаписана Dagre
|
||||
}));
|
||||
const initialEdgesForLayout = graphData.edges.map(edge => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
}));
|
||||
|
||||
// Применяем Dagre layout
|
||||
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
|
||||
initialNodesForLayout,
|
||||
initialEdgesForLayout
|
||||
);
|
||||
|
||||
// Обновляем состояния узлов и рёбер React Flow
|
||||
setNodes(layoutedNodes);
|
||||
setEdges(layoutedEdges);
|
||||
} else {
|
||||
// Если данных нет, очищаем граф
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
}, [graphData.nodes, graphData.edges, setNodes, setEdges]); // Зависимости: изменения в узлах и рёбрах графа
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const nodeClickHandler = useCallback((event, node) => {
|
||||
onNodeClick(node.id);
|
||||
}, [onNodeClick]);
|
||||
|
||||
return (
|
||||
<div className="graph-panel">
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onNodeClick={nodeClickHandler}
|
||||
fitView // Автоматически центрирует и масштабирует граф
|
||||
attributionPosition="top-right"
|
||||
>
|
||||
<Controls /> {/* Добавляем элементы управления */}
|
||||
<Background variant="dots" gap={12} size={1} /> {/* Добавляем фон */}
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphPanel;
|
||||
127
old/src/components/HistoryPanel.js
Normal file
127
old/src/components/HistoryPanel.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
function HistoryPanel({ graphs, onGraphSelect, onNewChat }) {
|
||||
const [contextMenu, setContextMenu] = useState({
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
graphId: null,
|
||||
});
|
||||
const menuRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
setContextMenu((prev) => ({ ...prev, visible: false }));
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [menuRef]);
|
||||
|
||||
const handleContextMenu = (e, graphId) => {
|
||||
e.preventDefault();
|
||||
setContextMenu({
|
||||
visible: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
graphId: graphId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteGraph = async (graphId) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://localhost:5000/api/graphs/${graphId}`,
|
||||
{
|
||||
// Укажи URL для удаления графа
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// После успешного удаления обновите список графов
|
||||
// setGraphs(graphs.filter(graph => graph.id !== graphId));
|
||||
window.location.reload(); // Простейший способ обновить список графов
|
||||
|
||||
setContextMenu({ ...contextMenu, visible: false });
|
||||
} catch (error) {
|
||||
console.error("Error deleting graph:", error);
|
||||
// TODO: Обработка ошибок удаления графа
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="history-panel">
|
||||
<div className="history-header">
|
||||
<h2>История диалогов</h2>
|
||||
<button className="new-chat-button" onClick={onNewChat}>
|
||||
+ Новый чат
|
||||
</button>
|
||||
</div>
|
||||
<ul className="chat-list">
|
||||
{graphs.map((graph) => (
|
||||
<li
|
||||
key={graph.id}
|
||||
className="chat-list-item"
|
||||
onClick={() => onGraphSelect(graph.id)}
|
||||
>
|
||||
<div className="chat-item-content">
|
||||
<div className="chat-item-text">
|
||||
<span className="truncate">
|
||||
{graph.first_message
|
||||
? graph.first_message
|
||||
: `Граф ${graph.id}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="chat-item-actions">
|
||||
<button
|
||||
className="chat-item-options-button"
|
||||
aria-label="Открыть параметры обсуждения"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Предотвращаем выполнение родительского onClick
|
||||
handleContextMenu(e, graph.id); // Открываем контекстное меню
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M15.498 8.50159C16.3254 8.50159 16.9959 9.17228 16.9961 9.99963C16.9961 10.8271 16.3256 11.4987 15.498 11.4987C14.6705 11.4987 14 10.8271 14 9.99963C14.0002 9.17228 14.6706 8.50159 15.498 8.50159Z"></path>
|
||||
<path d="M4.49805 8.50159C5.32544 8.50159 5.99689 9.17228 5.99707 9.99963C5.99707 10.8271 5.32555 11.4987 4.49805 11.4987C3.67069 11.4985 3 10.827 3 9.99963C3.00018 9.17239 3.6708 8.50176 4.49805 8.50159Z"></path>
|
||||
<path d="M10.0003 8.50159C10.8276 8.50176 11.4982 9.17239 11.4984 9.99963C11.4984 10.827 10.8277 11.4985 10.0003 11.4987C9.17283 11.4987 8.50131 10.8271 8.50131 9.99963C8.50149 9.17228 9.17294 8.50159 10.0003 8.50159Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{contextMenu.visible && (
|
||||
<div
|
||||
className="context-menu"
|
||||
style={{
|
||||
top: contextMenu.y,
|
||||
left: contextMenu.x,
|
||||
}}
|
||||
ref={menuRef}
|
||||
>
|
||||
<button onClick={() => handleDeleteGraph(contextMenu.graphId)}>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HistoryPanel;
|
||||
19
old/src/index.css
Normal file
19
old/src/index.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
/* Общие стили для всего приложения */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
11
old/src/index.js
Normal file
11
old/src/index.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
658
package-lock.json
generated
658
package-lock.json
generated
|
|
@ -9,14 +9,21 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/dagre": "^0.7.52",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"dagre": "^0.8.5",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"reactflow": "^11.11.3",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
|
|
@ -537,6 +544,108 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/background": {
|
||||
"version": "11.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz",
|
||||
"integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/core": "11.11.4",
|
||||
"classcat": "^5.0.3",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/controls": {
|
||||
"version": "11.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
|
||||
"integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/core": "11.11.4",
|
||||
"classcat": "^5.0.3",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/core": {
|
||||
"version": "11.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
|
||||
"integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-drag": "^3.0.1",
|
||||
"@types/d3-selection": "^3.0.3",
|
||||
"@types/d3-zoom": "^3.0.1",
|
||||
"classcat": "^5.0.3",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/minimap": {
|
||||
"version": "11.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
|
||||
"integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/core": "11.11.4",
|
||||
"@types/d3-selection": "^3.0.3",
|
||||
"@types/d3-zoom": "^3.0.1",
|
||||
"classcat": "^5.0.3",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/node-resizer": {
|
||||
"version": "2.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
|
||||
"integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/core": "11.11.4",
|
||||
"classcat": "^5.0.4",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reactflow/node-toolbar": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
|
||||
"integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/core": "11.11.4",
|
||||
"classcat": "^5.0.3",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/codemirror": {
|
||||
"version": "5.60.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||
|
|
@ -546,12 +655,277 @@
|
|||
"@types/tern": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
|
||||
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/d3-axis": "*",
|
||||
"@types/d3-brush": "*",
|
||||
"@types/d3-chord": "*",
|
||||
"@types/d3-color": "*",
|
||||
"@types/d3-contour": "*",
|
||||
"@types/d3-delaunay": "*",
|
||||
"@types/d3-dispatch": "*",
|
||||
"@types/d3-drag": "*",
|
||||
"@types/d3-dsv": "*",
|
||||
"@types/d3-ease": "*",
|
||||
"@types/d3-fetch": "*",
|
||||
"@types/d3-force": "*",
|
||||
"@types/d3-format": "*",
|
||||
"@types/d3-geo": "*",
|
||||
"@types/d3-hierarchy": "*",
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-path": "*",
|
||||
"@types/d3-polygon": "*",
|
||||
"@types/d3-quadtree": "*",
|
||||
"@types/d3-random": "*",
|
||||
"@types/d3-scale": "*",
|
||||
"@types/d3-scale-chromatic": "*",
|
||||
"@types/d3-selection": "*",
|
||||
"@types/d3-shape": "*",
|
||||
"@types/d3-time": "*",
|
||||
"@types/d3-time-format": "*",
|
||||
"@types/d3-timer": "*",
|
||||
"@types/d3-transition": "*",
|
||||
"@types/d3-zoom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-axis": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
|
||||
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-brush": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
|
||||
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-chord": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
|
||||
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-contour": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
|
||||
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-dispatch": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
|
||||
"integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-drag": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-dsv": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
|
||||
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-fetch": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
|
||||
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-dsv": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-force": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
|
||||
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
|
||||
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-geo": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
|
||||
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-hierarchy": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
|
||||
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-polygon": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
|
||||
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-quadtree": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
|
||||
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-random": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
|
||||
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-scale-chromatic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-time-format": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
|
||||
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-transition": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dagre": {
|
||||
"version": "0.7.53",
|
||||
"resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz",
|
||||
"integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
|
|
@ -564,6 +938,31 @@
|
|||
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
|
||||
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
"version": "0.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||
|
|
@ -915,6 +1314,12 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/classcat": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -964,6 +1369,127 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/dagre": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
|
||||
"integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graphlib": "^2.1.8",
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
|
|
@ -1493,6 +2019,15 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/graphlib": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
|
||||
"integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -1605,6 +2140,12 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
|
|
@ -1679,6 +2220,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
|
@ -1686,6 +2233,18 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
|
@ -1921,6 +2480,49 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/reactflow": {
|
||||
"version": "11.11.4",
|
||||
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
|
||||
"integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@reactflow/background": "11.3.14",
|
||||
"@reactflow/controls": "11.2.14",
|
||||
"@reactflow/core": "11.11.4",
|
||||
"@reactflow/minimap": "11.7.14",
|
||||
"@reactflow/node-resizer": "2.2.14",
|
||||
"@reactflow/node-toolbar": "1.3.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/regexpp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
|
||||
|
|
@ -1993,6 +2595,15 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
|
|
@ -2156,16 +2767,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
|
|
@ -2178,6 +2789,15 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
|
|
@ -2230,6 +2850,34 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
package.json
13
package.json
|
|
@ -4,21 +4,28 @@
|
|||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"dev": "node esbuild.config.mjs && move main.css styles.css",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production && move main.css styles.css",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/dagre": "^0.7.52",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"dagre": "^0.8.5",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"reactflow": "^11.11.3",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* Управляет пользовательским интерфейсом чата, включая ввод сообщений,
|
||||
* отображение истории и подсказки команд для взаимодействия с пользователем.
|
||||
*/
|
||||
|
||||
import { ChatHistoryItem } from './models/ChatHistoryItem';
|
||||
|
||||
export class ChatPanel {
|
||||
|
|
@ -120,7 +125,7 @@ export class ChatPanel {
|
|||
historyContainer.innerHTML = this.chatHistory
|
||||
.map(
|
||||
(msg) => `
|
||||
<div class="message ${msg.role}">
|
||||
<div class="message ${msg.role} message-type-${msg.type || 'default'}">
|
||||
<div class="message-content">${this.processMessageContent(msg.content)}</div>
|
||||
</div>
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,138 +1,302 @@
|
|||
export class GraphPanel {
|
||||
container: HTMLElement;
|
||||
props: {
|
||||
graphData: { nodes: any[]; edges: any[] };
|
||||
onNodeClick: (nodeId: string) => void;
|
||||
};
|
||||
nodes: any[];
|
||||
edges: any[];
|
||||
// ----------------------------------------------- React & React Flow Imports ---------------------------------------------------
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: для createRoot нужен 'react-dom/client'
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlowProvider, // <-- ИСПРАВЛЕНО: импортируем прямо ReactFlowProvider без переименования
|
||||
Connection,
|
||||
Edge,
|
||||
BackgroundVariant // <-- ИСПРАВЛЕНО: используем BackgroundVariant для типизации
|
||||
} from 'reactflow';
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
props: {
|
||||
graphData: { nodes: any[]; edges: any[] };
|
||||
onNodeClick: (nodeId: string) => void;
|
||||
}
|
||||
) {
|
||||
this.container = container;
|
||||
this.props = props;
|
||||
this.nodes = [];
|
||||
this.edges = [];
|
||||
// <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов.
|
||||
// Обычно, при использовании esbuild для React-проектов, вы импортируете CSS прямо в JS/TS файл.
|
||||
// esbuild должен быть настроен для копирования или встраивания этого CSS.
|
||||
// Если стили не применяются, проверьте конфигурацию esbuild (например, через плагин esbuild-plugin-css-bundle).
|
||||
import 'reactflow/dist/style.css'; // Импортируем стили ReactFlow
|
||||
|
||||
this.init();
|
||||
}
|
||||
// Если вы используете какие-либо кастомные ноды, импортируйте их здесь
|
||||
// import CustomNode from './CustomNode';
|
||||
// const nodeTypes = { custom: CustomNode };
|
||||
|
||||
init(): void {
|
||||
// Создаем структуру для React Flow
|
||||
this.container.innerHTML = `
|
||||
<div class="llm-agent-graph-panel">
|
||||
<div id="reactflow-container" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
`;
|
||||
// ----------------------------------------------- Custom Nodes -----------------------------------------------------------------
|
||||
|
||||
// Загружаем React Flow динамически (если доступен)
|
||||
this.initReactFlow();
|
||||
}
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
async initReactFlow(): Promise<void> {
|
||||
try {
|
||||
// В среде Obsidian нужно будет подключить React Flow через CDN или bundler
|
||||
// Для демонстрации создаем упрощенную визуализацию
|
||||
this.createSimpleGraph();
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации React Flow:', error);
|
||||
this.createFallbackGraph();
|
||||
}
|
||||
}
|
||||
// Простой компонент для узла типа 'user'
|
||||
// Это базовый пример. Ты можешь стилизовать его по своему усмотрению.
|
||||
const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement('div', {
|
||||
className: 'react-flow__node-user'
|
||||
},
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
createSimpleGraph(): void {
|
||||
const container = this.container.querySelector('#reactflow-container') as HTMLElement;
|
||||
container.innerHTML = `
|
||||
<div class="simple-graph">
|
||||
<div class="graph-nodes" id="graph-nodes"></div>
|
||||
<svg class="graph-edges" id="graph-edges"></svg>
|
||||
</div>
|
||||
`;
|
||||
// Простой компонент для узла типа 'llm'
|
||||
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement('div', {
|
||||
className: 'react-flow__node-llm'
|
||||
},
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
this.renderNodes();
|
||||
this.renderEdges();
|
||||
}
|
||||
// Простой компонент для узла типа 'tool'
|
||||
const ToolNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement('div', {
|
||||
className: 'react-flow__node-tool'
|
||||
},
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
renderNodes(): void {
|
||||
const nodesContainer = this.container.querySelector('#graph-nodes') as HTMLElement;
|
||||
nodesContainer.innerHTML = '';
|
||||
// Простой компонент для узла типа 'error'
|
||||
const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement('div', {
|
||||
className: 'react-flow__node-error'
|
||||
},
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
this.nodes.forEach((node, index) => {
|
||||
const nodeEl = document.createElement('div');
|
||||
nodeEl.className = `graph-node node-${node.type || 'default'}`;
|
||||
nodeEl.style.cssText = `
|
||||
position: absolute;
|
||||
left: ${50 + index * 150}px;
|
||||
top: ${50 + Math.floor(index / 5) * 80}px;
|
||||
padding: 8px 12px;
|
||||
border: 2px solid #333;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
max-width: 120px;
|
||||
word-wrap: break-word;
|
||||
`;
|
||||
nodeEl.textContent = node.data?.label || `Node ${node.id}`;
|
||||
nodeEl.dataset.nodeId = node.id;
|
||||
const AssistantNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement('div', {
|
||||
className: 'react-flow__node-assistant' // Используем CSS класс для узла assistant
|
||||
},
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
nodeEl.addEventListener('click', () => {
|
||||
this.handleNodeClick(node.id);
|
||||
});
|
||||
// Регистрируем кастомные типы узлов
|
||||
const nodeTypes = {
|
||||
user: UserNode,
|
||||
llm: LLMNode,
|
||||
tool: ToolNode,
|
||||
error: ErrorNode,
|
||||
assistant: AssistantNode,
|
||||
// Если у тебя есть node.type === 'default', то можешь его тоже определить,
|
||||
// либо использовать встроенный 'default' React Flow.
|
||||
};
|
||||
|
||||
nodesContainer.appendChild(nodeEl);
|
||||
});
|
||||
}
|
||||
// ----------------------------------------------- Dagre Layout -----------------------------------------------------------------
|
||||
import dagre from 'dagre'; // Импортируем библиотеку dagre
|
||||
|
||||
renderEdges(): void {
|
||||
const edgesContainer = this.container.querySelector('#graph-edges') as HTMLElement;
|
||||
edgesContainer.innerHTML = '';
|
||||
// Упрощенная отрисовка связей - в реальной реализации нужно будет вычислять позиции
|
||||
}
|
||||
|
||||
createFallbackGraph(): void {
|
||||
const container = this.container.querySelector('#reactflow-container') as HTMLElement;
|
||||
container.innerHTML = `
|
||||
<div class="fallback-graph">
|
||||
<p>Упрощенное представление графа</p>
|
||||
<div id="nodes-list"></div>
|
||||
</div>
|
||||
`;
|
||||
this.renderFallbackNodes();
|
||||
}
|
||||
|
||||
renderFallbackNodes(): void {
|
||||
const nodesList = this.container.querySelector('#nodes-list') as HTMLElement;
|
||||
nodesList.innerHTML = '';
|
||||
|
||||
this.nodes.forEach((node) => {
|
||||
const nodeEl = document.createElement('div');
|
||||
nodeEl.className = 'fallback-node';
|
||||
nodeEl.textContent = `${node.id}: ${node.data?.label || 'No label'}`;
|
||||
nodeEl.addEventListener('click', () => this.handleNodeClick(node.id));
|
||||
nodesList.appendChild(nodeEl);
|
||||
});
|
||||
}
|
||||
|
||||
updateData(graphData: { nodes: any[]; edges: any[] }): void {
|
||||
this.nodes = graphData.nodes || [];
|
||||
this.edges = graphData.edges || [];
|
||||
this.renderNodes();
|
||||
this.renderEdges();
|
||||
}
|
||||
|
||||
handleNodeClick(nodeId: string): void {
|
||||
if (this.props.onNodeClick) {
|
||||
this.props.onNodeClick(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
// Очистка ресурсов
|
||||
this.container.innerHTML = '';
|
||||
}
|
||||
// Типы для Dagre (были уже корректны)
|
||||
interface DagreNode {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// Инициализируем объект Dagre Graph
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
// Определяем размеры узлов для корректного расположения.
|
||||
// Может потребоваться корректировка в зависимости от реального размера узла в CSS.
|
||||
const nodeWidth = 172;
|
||||
const nodeHeight = 36;
|
||||
|
||||
/**
|
||||
* Применяет алгоритм расположения Dagre к узлам и рёбрам графа.
|
||||
* @param {Array<any>} nodes - Массив узлов React Flow.
|
||||
* @param {Array<any>} edges - Массив рёбер React Flow.
|
||||
* @param {'TB' | 'LR'} [direction='TB'] - Направление графа ('TB' - сверху вниз, 'LR' - слева направо).
|
||||
* @returns {{nodes: any[], edges: any[]}} Объект с обновлёнными узлами и рёбрами, содержащими позиции.
|
||||
*/
|
||||
const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' = 'TB') => {
|
||||
const isHorizontal = direction === 'LR'; // Проверяем, горизонтальное ли расположение
|
||||
// ДОБАВЛЕНО: ranksep и nodesep для лучшего отступа между узлами
|
||||
dagreGraph.setGraph({ rankdir: direction, ranksep: 100, nodesep: 50 });
|
||||
|
||||
// Добавляем узлы в граф Dagre с их размерами
|
||||
nodes.forEach((node) => {
|
||||
// Убедимся, что узел имеет уникальный id и необходимые размеры для Dagre
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
|
||||
// Добавляем рёбра в граф Dagre
|
||||
edges.forEach((edge) => {
|
||||
// Dagre ожидает id, source, target
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
// Выполняем расчёт расположения графа
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
// Обновляем позиции узлов React Flow на основе расчётов Dagre
|
||||
const newNodes = nodes.map((node) => {
|
||||
const nodeWithPosition: DagreNode = dagreGraph.node(node.id); // Получаем данные узла из Dagre
|
||||
// Смещаем позицию узла Dagre (якорь в центре) к верхнему левому углу,
|
||||
// чтобы она соответствовала точке привязки узла React Flow (верхний левый угол).
|
||||
return {
|
||||
...node,
|
||||
// Обновляем targetPosition и sourcePosition в зависимости от направления для корректного отображения ручек
|
||||
targetPosition: isHorizontal ? 'left' : 'top',
|
||||
sourcePosition: isHorizontal ? 'right' : 'bottom',
|
||||
position: {
|
||||
x: nodeWithPosition.x - nodeWidth / 2, // Корректировка X
|
||||
y: nodeWithPosition.y - nodeHeight / 2, // Корректировка Y
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return { nodes: newNodes, edges }; // Возвращаем обновлённые узлы и рёбра
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} GraphPanelProps
|
||||
* @property {{ nodes: any[]; edges: any[]; }} graphData - Данные графа для отображения (узлы и ребра).
|
||||
* @property {(nodeId: string) => void} onNodeClick - Обработчик клика по узлу.
|
||||
*/
|
||||
interface GraphPanelProps {
|
||||
graphData: { nodes: any[]; edges: any[]; };
|
||||
onNodeClick: (nodeId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* GraphPanelComponent - React компонент для отображения графа диалогов с использованием React Flow и Dagre.
|
||||
* @param {GraphPanelProps} props - Свойства компонента.
|
||||
* @returns {React.ReactElement} Элемент React.
|
||||
*/
|
||||
export const GraphPanelComponent: React.FC<GraphPanelProps> = ({ graphData, onNodeClick }) => {
|
||||
// ----------------------------------------------- React Hooks ---------------------------------------------------------------
|
||||
// Инициализируем состояния для узлов и рёбер React Flow
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
// Используем useEffect для применения Dagre layout при изменении данных графа
|
||||
React.useEffect(() => {
|
||||
// Проверяем, есть ли данные для построения графа
|
||||
if (graphData.nodes.length > 0 || graphData.edges.length > 0) {
|
||||
console.log("Applying Dagre layout with data:", graphData);
|
||||
|
||||
// Преобразуем входящие данные в формат, необходимый для Dagre и React Flow
|
||||
// Убеждаемся, что каждый узел имеет данные.label для отображения
|
||||
const initialNodesForLayout = graphData.nodes.map(node => ({
|
||||
id: node.id,
|
||||
// ДОБАВЛЕНО: используем node.type, если он есть, иначе 'default'
|
||||
type: node.type || 'default',
|
||||
data: { label: node.data?.label || `Node ${node.id}` },
|
||||
position: { x: 0, y: 0 } // Временная позиция, будет перезаписана Dagre
|
||||
}));
|
||||
const initialEdgesForLayout = graphData.edges.map(edge => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
// Для рёбер можно добавить тип 'default' или ваш кастомный
|
||||
type: edge.type || 'default',
|
||||
}));
|
||||
|
||||
// Применяем Dagre layout
|
||||
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
|
||||
initialNodesForLayout,
|
||||
initialEdgesForLayout
|
||||
);
|
||||
|
||||
// Обновляем состояния узлов и рёбер React Flow
|
||||
setNodes(layoutedNodes);
|
||||
setEdges(layoutedEdges);
|
||||
} else {
|
||||
// Если данных нет, очищаем граф
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
}, [graphData.nodes, graphData.edges]); // Зависимости: изменения в узлах и рёбрах графа
|
||||
|
||||
// ----------------------------------------------- Event Handlers ------------------------------------------------------------
|
||||
/**
|
||||
* Обработчик добавления нового соединения между узлами (если разрешено).
|
||||
* @param {Connection | Edge} params - Параметры нового соединения.
|
||||
*/
|
||||
const onConnect = React.useCallback(
|
||||
(params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
/**
|
||||
* Обработчик клика по узлу.
|
||||
* @param {React.MouseEvent} event - Объект события клика.
|
||||
* @param {any} node - Данные узла, по которому был клик.
|
||||
*/
|
||||
const nodeClickHandler = React.useCallback((event: React.MouseEvent, node: any) => {
|
||||
onNodeClick(node.id);
|
||||
}, [onNodeClick]);
|
||||
|
||||
// ----------------------------------------------- Render --------------------------------------------------------------------
|
||||
return React.createElement('div', { className: 'graph-panel', style: { width: '100%', height: '100%' } },
|
||||
// Оборачиваем ReactFlowProvider вокруг ReactFlow для доступа к контексту React Flow
|
||||
React.createElement(ReactFlowProvider, null,
|
||||
React.createElement(ReactFlow, {
|
||||
nodes,
|
||||
edges,
|
||||
onNodesChange, // Обработчик изменения узлов
|
||||
onEdgesChange, // Обработчик изменения рёбер
|
||||
onConnect, // Обработчик подключения
|
||||
onNodeClick: nodeClickHandler, // Обработчик клика по узлу
|
||||
fitView: true, // Автоматически центрирует и масштабирует граф при загрузке
|
||||
attributionPosition: 'bottom-right', // Позиция атрибуции React Flow
|
||||
nodeTypes, // <-- ИСПРАВЛЕНО: Передаем зарегистрированные типы узлов
|
||||
},
|
||||
// Компоненты Controls и Background являются дочерними для ReactFlow
|
||||
React.createElement(Controls, null),
|
||||
React.createElement(Background, { variant: BackgroundVariant.Dots, gap: 12, size: 1 })
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* Управляет и отображает историю графов чата. Предоставляет функционал для выбора
|
||||
* прошлых разговоров, начала новых чатов и удаления записей графов.
|
||||
*/
|
||||
|
||||
export class HistoryPanel {
|
||||
container: HTMLElement;
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
/*
|
||||
* Определяет интерфейс для элемента истории чата, включающего роль участника
|
||||
* (пользователь/агент) и содержимое сообщения.
|
||||
*/
|
||||
|
||||
export interface ChatHistoryItem {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
type?: 'user' | 'llm' | 'tool' | 'error' | 'default';
|
||||
}
|
||||
104
src/css/styles.css
Normal file
104
src/css/styles.css
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* src/styles.css или куда ты добавляешь стили */
|
||||
|
||||
:root {
|
||||
/* Цвета сообщений чата */
|
||||
--llm-user-border-color: #78B0E3;
|
||||
--llm-user-background-color: #C5E1FF;
|
||||
--llm-llm-border-color: #A8D8AD;
|
||||
--llm-llm-background-color: #DBF5DD;
|
||||
--llm-tool-border-color: #FFD180;
|
||||
--llm-tool-background-color: #FFF0C9;
|
||||
--llm-error-border-color: #FF6666;
|
||||
--llm-error-background-color: #FFCCCC;
|
||||
--llm-assistant-border-color: #B0B0B0; /* Нейтральный серый для ассистента */
|
||||
--llm-assistant-background-color: #E0E0E0; /* Светло-серый фон */
|
||||
|
||||
/* Общие стили узлов графа */
|
||||
--node-padding: 10px;
|
||||
--node-border-radius: 5px;
|
||||
--node-font-size: 14px;
|
||||
--node-font-weight: bold;
|
||||
--node-text-color: #333;
|
||||
--node-text-align: center;
|
||||
}
|
||||
|
||||
.llm-agent-chat-panel .message-type-user .message-content {
|
||||
border: 1px solid var(--llm-user-border-color); /* Цвет для user */
|
||||
background-color: var(--llm-user-background-color); /* Светло-голубой фон для user */
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.llm-agent-chat-panel .message-type-llm .message-content {
|
||||
border: 1px solid var(--llm-llm-border-color); /* Цвет для llm */
|
||||
background-color: var(--llm-llm-background-color); /* Светло-зеленый фон для llm */
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.llm-agent-chat-panel .message-type-tool .message-content {
|
||||
border: 1px solid var(--llm-tool-border-color); /* Цвет для tool */
|
||||
background-color: var(--llm-tool-background-color); /* Светло-оранжевый фон для tool */
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.llm-agent-chat-panel .message-type-error .message-content {
|
||||
border: 1px solid var(--llm-error-border-color); /* Красный цвет для error */
|
||||
background-color: var(--llm-error-background-color); /* Светло-красный фон для error */
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.llm-agent-chat-panel .message-type-assistant .message-content {
|
||||
border: 1px solid var(--llm-assistant-border-color); /* Цвет для assistant */
|
||||
background-color: var(--llm-assistant-background-color); /* Светло-серый фон для assistant */
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Общие стили для сообщений */
|
||||
.llm-agent-chat-panel .message {
|
||||
margin-bottom: 10px;
|
||||
display: flex; /* Для выравнивания по сторонам */
|
||||
}
|
||||
|
||||
/* Стили для узлов графа */
|
||||
.react-flow__node-default,
|
||||
.react-flow__node-user,
|
||||
.react-flow__node-llm,
|
||||
.react-flow__node-tool,
|
||||
.react-flow__node-error,
|
||||
.react-flow__node-assistant {
|
||||
padding: var(--node-padding);
|
||||
border-radius: var(--node-border-radius);
|
||||
text-align: var(--node-text-align);
|
||||
font-size: var(--node-font-size);
|
||||
font-weight: var(--node-font-weight);
|
||||
color: var(--node-text-color);
|
||||
}
|
||||
|
||||
.react-flow__node-user {
|
||||
border: 1px solid var(--llm-user-border-color); /* Цвет для user */
|
||||
background-color: var(--llm-user-background-color); /* Светло-голубой фон для user */
|
||||
}
|
||||
|
||||
.react-flow__node-llm {
|
||||
border: 1px solid var(--llm-llm-border-color); /* Цвет для llm */
|
||||
background-color: var(--llm-llm-background-color); /* Светло-зеленый фон для llm */
|
||||
}
|
||||
|
||||
.react-flow__node-tool {
|
||||
border: 1px solid var(--llm-tool-border-color); /* Цвет для tool */
|
||||
background-color: var(--llm-tool-background-color); /* Светло-оранжевый фон для tool */
|
||||
}
|
||||
|
||||
.react-flow__node-error {
|
||||
border: 1px solid var(--llm-error-border-color); /* Красный цвет для error */
|
||||
background-color: var(--llm-error-background-color); /* Светло-красный фон для error */
|
||||
}
|
||||
|
||||
.react-flow__node-assistant {
|
||||
border: 1px solid var(--llm-assistant-border-color); /* Нейтральный серый для ассистента */
|
||||
background-color: var(--llm-assistant-background-color); /* Светло-серый фон */
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
// ----------------------------------------------- Event Bus ---------------------------------------------------------------
|
||||
/*
|
||||
* EventBus предоставляет централизованный механизм для подписки и публикации событий
|
||||
* между различными компонентами приложения, обеспечивая их слабую связанность.
|
||||
*/
|
||||
|
||||
export class EventBus {
|
||||
private events: { [key: string]: ((data?: any) => void)[] } = {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
/*
|
||||
* ChatView управляет пользовательским интерфейсом чата, отображает историю сообщений,
|
||||
* отправляет запросы LLM-агенту и обрабатывает ответы, обновляя состояние чата и графа.
|
||||
*/
|
||||
|
||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import { ChatPanel } from '../components/ChatPanel';
|
||||
import { HistoryPanel } from '../components/HistoryPanel';
|
||||
import LLMAgentPlugin from 'main';
|
||||
|
||||
export const CHAT_VIEW_TYPE = 'llm-agent-chat-view';
|
||||
|
|
@ -10,23 +14,18 @@ export const CHAT_VIEW_TYPE = 'llm-agent-chat-view';
|
|||
export class ChatView extends ItemView {
|
||||
plugin: LLMAgentPlugin;
|
||||
chatHistory: any[];
|
||||
graphsList: any[];
|
||||
selectedGraphId: string | null;
|
||||
currentNode: string | null;
|
||||
chatContainer: HTMLDivElement;
|
||||
historyContainer: HTMLDivElement;
|
||||
chatPanel: ChatPanel | null;
|
||||
historyPanel: HistoryPanel | null;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.chatHistory = [];
|
||||
this.graphsList = [];
|
||||
this.selectedGraphId = null;
|
||||
this.currentNode = null;
|
||||
this.chatPanel = null;
|
||||
this.historyPanel = null;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
|
|
@ -45,31 +44,20 @@ export class ChatView extends ItemView {
|
|||
const container = this.containerEl.children[1] as HTMLElement;
|
||||
container.empty();
|
||||
|
||||
// Создаем контейнеры для React компонентов
|
||||
// Создаем только контейнер для чата (без истории)
|
||||
this.chatContainer = container.createDiv() as HTMLDivElement;
|
||||
this.chatContainer.addClass('llm-agent-chat-container');
|
||||
|
||||
this.historyContainer = container.createDiv() as HTMLDivElement;
|
||||
this.historyContainer.addClass('llm-agent-history-container');
|
||||
|
||||
// Инициализируем React компоненты
|
||||
// Инициализируем только чат панель
|
||||
this.chatPanel = new ChatPanel(this.chatContainer, {
|
||||
chatHistory: this.chatHistory,
|
||||
onSendMessage: this.handleSendMessage.bind(this)
|
||||
});
|
||||
|
||||
this.historyPanel = new HistoryPanel(this.historyContainer, {
|
||||
graphs: this.graphsList,
|
||||
onGraphSelect: this.handleGraphSelect.bind(this),
|
||||
onNewChat: this.handleNewChat.bind(this)
|
||||
});
|
||||
|
||||
// Подписываемся на события
|
||||
this.plugin.eventBus.on('node-selected', this.handleNodeSelected.bind(this));
|
||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||
|
||||
// Загружаем список графов
|
||||
this.fetchGraphsList();
|
||||
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
||||
}
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
|
|
@ -77,145 +65,143 @@ export class ChatView extends ItemView {
|
|||
this.chatPanel.destroy();
|
||||
this.chatPanel = null;
|
||||
}
|
||||
if (this.historyPanel) {
|
||||
this.historyPanel.destroy();
|
||||
this.historyPanel = null;
|
||||
}
|
||||
this.plugin.eventBus.off('node-selected', this.handleNodeSelected);
|
||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
||||
this.plugin.eventBus.off('graph-selected', this.handleGraphSelected);
|
||||
}
|
||||
|
||||
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
||||
|
||||
async fetchGraphsList(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs`);
|
||||
const data = await response.json();
|
||||
this.graphsList = data;
|
||||
if (this.historyPanel) {
|
||||
this.historyPanel.updateGraphs(this.graphsList);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки списка графов:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGraphData(graphId: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
console.error(`Граф с ID ${graphId} не найден.`);
|
||||
}
|
||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// Обновляем граф
|
||||
this.plugin.eventBus.emit('graph-updated', {
|
||||
graphData: {
|
||||
nodes: data.graph_nodes || [],
|
||||
edges: data.graph_edges || []
|
||||
},
|
||||
currentNode: data.current_node_id
|
||||
});
|
||||
|
||||
// Обновляем историю чата
|
||||
this.chatHistory = data.messages || [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных графа:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMessagesFromRootToNode(nodeId: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/messages/${this.selectedGraphId}/${nodeId}`);
|
||||
const data = await response.json();
|
||||
this.chatHistory = data;
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки сообщений:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Загружает историю сообщений от корня до указанного узла графа.
|
||||
* Обновляет `chatHistory` и отображение в `chatPanel`.
|
||||
* @param {string} nodeId - ID конечного узла для загрузки истории.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async fetchMessagesFromRootToNode(nodeId: string): Promise<void> {
|
||||
try {
|
||||
// Убедимся, что selectedGraphId установлен
|
||||
if (!this.selectedGraphId) {
|
||||
console.warn('selectedGraphId не установлен. Невозможно загрузить сообщения.');
|
||||
return;
|
||||
}
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/messages/${this.selectedGraphId}/${nodeId}`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Ошибка загрузки сообщений: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
|
||||
this.chatHistory = data.map((msg: any) => ({
|
||||
...msg,
|
||||
type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
|
||||
})) || [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки сообщений:', error);
|
||||
// Optionally: show a notice to the user
|
||||
// new Notice(`Ошибка загрузки сообщений: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------- Event Handlers ---------------------------------------------------------------
|
||||
|
||||
async handleSendMessage(message: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: message,
|
||||
graph_id: this.selectedGraphId,
|
||||
parent_node_id: this.currentNode
|
||||
})
|
||||
});
|
||||
async handleSendMessage(message: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: message,
|
||||
graph_id: this.selectedGraphId,
|
||||
parent_node_id: this.currentNode,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = await response.json();
|
||||
|
||||
this.selectedGraphId = result.graph_id;
|
||||
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
||||
this.currentNode = newCurrentNodeId;
|
||||
|
||||
// Обновляем выбранный ID графа
|
||||
this.selectedGraphId = result.graph_id;
|
||||
// Обновляем граф
|
||||
this.plugin.eventBus.emit('graph-updated', {
|
||||
graphData: {
|
||||
nodes: result.graph_visualization_data.nodes || [],
|
||||
edges: result.graph_visualization_data.edges || []
|
||||
},
|
||||
currentNode: newCurrentNodeId
|
||||
});
|
||||
|
||||
// Устанавливаем текущий узел
|
||||
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
||||
this.currentNode = newCurrentNodeId;
|
||||
// Загружаем сообщения
|
||||
if (newCurrentNodeId) {
|
||||
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||
} else {
|
||||
// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
|
||||
this.chatHistory = result.messages.map((msg: any) => ({
|
||||
...msg,
|
||||
type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
|
||||
})) || [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем граф
|
||||
this.plugin.eventBus.emit('graph-updated', {
|
||||
graphData: {
|
||||
nodes: result.graph_visualization_data.nodes || [],
|
||||
edges: result.graph_visualization_data.edges || []
|
||||
},
|
||||
currentNode: newCurrentNodeId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка отправки сообщения:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем сообщения от корня до нового текущего узла
|
||||
if (newCurrentNodeId) {
|
||||
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||
} else {
|
||||
this.chatHistory = result.messages || [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Обрабатывает выбор узла на графе. Загружает историю сообщений до выбранного узла
|
||||
* и обновляет панель чата.
|
||||
* @param {{ nodeId: string }} data - Объект, содержащий ID выбранного узла.
|
||||
*/
|
||||
handleNodeSelected(data: { nodeId: string }): void {
|
||||
this.currentNode = data.nodeId;
|
||||
if (this.selectedGraphId) {
|
||||
this.fetchMessagesFromRootToNode(data.nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем список графов
|
||||
this.fetchGraphsList();
|
||||
} catch (error) {
|
||||
console.error('Ошибка отправки сообщения:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Обрабатывает событие начала нового чата. Сбрасывает текущее состояние чата
|
||||
* и очищает отображение истории.
|
||||
*/
|
||||
handleNewChat(): void {
|
||||
this.chatHistory = [];
|
||||
this.currentNode = null;
|
||||
this.selectedGraphId = null;
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
}
|
||||
|
||||
handleNodeSelected(data: { nodeId: string }): void {
|
||||
this.currentNode = data.nodeId;
|
||||
if (this.selectedGraphId) {
|
||||
this.fetchMessagesFromRootToNode(data.nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
handleGraphSelect(graphId: string): void {
|
||||
this.selectedGraphId = graphId;
|
||||
this.fetchGraphData(graphId);
|
||||
}
|
||||
|
||||
handleNewChat(): void {
|
||||
this.chatHistory = [];
|
||||
this.currentNode = null;
|
||||
this.selectedGraphId = null;
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Обрабатывает выбор графа из `GraphView`. Устанавливает выбранный граф и обновляет
|
||||
* историю чата, загружая сообщения, относящиеся к этому графу.
|
||||
* @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
||||
this.selectedGraphId = data.graphId;
|
||||
this.currentNode = data.currentNode;
|
||||
if (this.currentNode) {
|
||||
await this.fetchMessagesFromRootToNode(this.currentNode);
|
||||
} else {
|
||||
// Если текущего узла нет (например, пустой граф), очищаем историю
|
||||
this.chatHistory = [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,280 @@
|
|||
// ----------------------------------------------- Obsidian Imports ----------------------------------------------------------
|
||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import { GraphPanel } from '../components/GraphPanel';
|
||||
import LLMAgentPlugin from 'main';
|
||||
// ----------------------------------------------- Plugin Imports ------------------------------------------------------------
|
||||
import LLMAgentPlugin from 'main'; // Предполагаем, что main.ts экспортирует LLMAgentPlugin по умолчанию
|
||||
import { HistoryPanel } from '../components/HistoryPanel'; // Предполагаем, что HistoryPanel это JS-класс
|
||||
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: Импортируем 'react-dom/client' для createRoot
|
||||
|
||||
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
||||
|
||||
// ----------------------------------------------- Graph View ---------------------------------------------------------------
|
||||
|
||||
export class GraphView extends ItemView {
|
||||
plugin: LLMAgentPlugin;
|
||||
graphData: { nodes: any[]; edges: any[] };
|
||||
currentNode: string | null;
|
||||
graphPanel: GraphPanel | null;
|
||||
reactContainer: HTMLDivElement;
|
||||
plugin: LLMAgentPlugin;
|
||||
graphData: { nodes: any[], edges: any[] };
|
||||
currentNode: string | null;
|
||||
graphsList: any[];
|
||||
selectedGraphId: string | null;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.graphData = { nodes: [], edges: [] };
|
||||
this.currentNode = null;
|
||||
this.graphPanel = null;
|
||||
}
|
||||
// UI Elements
|
||||
historyPanel: HistoryPanel | null;
|
||||
historyContainer: HTMLDivElement;
|
||||
graphContainer: HTMLDivElement;
|
||||
reactRoot: ReactDOM.Root | null; // <-- ИСПРАВЛЕНО: Указываем тип для reactRoot
|
||||
|
||||
getViewType(): string {
|
||||
return GRAPH_VIEW_TYPE;
|
||||
}
|
||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.graphData = { nodes: [], edges: [] };
|
||||
this.currentNode = null;
|
||||
this.graphsList = [];
|
||||
this.selectedGraphId = null;
|
||||
this.historyPanel = null;
|
||||
this.reactRoot = null; // Инициализируем null
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return 'LLM Agent Graph';
|
||||
}
|
||||
/**
|
||||
* @returns {string} Тип представления.
|
||||
*/
|
||||
getViewType(): string {
|
||||
return GRAPH_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return 'brain-circuit';
|
||||
}
|
||||
/**
|
||||
* @returns {string} Отображаемое имя представления.
|
||||
*/
|
||||
getDisplayText(): string {
|
||||
return 'LLM Agent Graph';
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
const container = this.containerEl.children[1] as HTMLElement;
|
||||
container.empty();
|
||||
/**
|
||||
* @returns {string} Иконка представления.
|
||||
*/
|
||||
getIcon(): string {
|
||||
return 'brain-circuit';
|
||||
}
|
||||
|
||||
// Создаем контейнер для React компонента
|
||||
this.reactContainer = container.createDiv() as HTMLDivElement;
|
||||
this.reactContainer.addClass('llm-agent-graph-container');
|
||||
/**
|
||||
* Вызывается при открытии представления. Инициализирует UI и загружает данные.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async onOpen(): Promise<void> {
|
||||
const container = this.containerEl.children[1] as HTMLElement;
|
||||
container.empty();
|
||||
|
||||
// Инициализируем React компонент
|
||||
this.graphPanel = new GraphPanel(this.reactContainer, {
|
||||
graphData: this.graphData,
|
||||
onNodeClick: this.handleNodeClick.bind(this)
|
||||
});
|
||||
// Создаем основной контейнер с flexbox layout
|
||||
const mainContainer = container.createDiv('llm-agent-graph-main');
|
||||
mainContainer.style.cssText = `
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--background-primary); /* Убедимся, что фон установлен */
|
||||
`;
|
||||
|
||||
// Подписываемся на события
|
||||
this.plugin.eventBus.on('graph-updated', this.handleGraphUpdate.bind(this));
|
||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||
}
|
||||
// Левая панель для истории
|
||||
this.historyContainer = mainContainer.createDiv('llm-agent-history-sidebar') as HTMLDivElement;
|
||||
this.historyContainer.style.cssText = `
|
||||
width: 300px;
|
||||
min-width: 300px;
|
||||
border-right: 1px solid var(--background-modifier-border); /* ИСПРАВЛЕНО: Использование переменной Obsidian */
|
||||
overflow-y: auto;
|
||||
background-color: var(--background-secondary); /* Убедимся, что фон установлен */
|
||||
`;
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
if (this.graphPanel) {
|
||||
this.graphPanel.destroy();
|
||||
this.graphPanel = null;
|
||||
}
|
||||
this.plugin.eventBus.off('graph-updated', this.handleGraphUpdate);
|
||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
||||
}
|
||||
// Правая панель для графа
|
||||
this.graphContainer = mainContainer.createDiv('llm-agent-graph-container') as HTMLDivElement;
|
||||
this.graphContainer.style.cssText = `
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background-color: var(--background-primary); /* Убедимся, что фон установлен */
|
||||
`;
|
||||
|
||||
// ----------------------------------------------- Event Handlers ---------------------------------------------------------------
|
||||
// Инициализируем панель истории
|
||||
this.historyPanel = new HistoryPanel(this.historyContainer, {
|
||||
graphs: this.graphsList,
|
||||
onGraphSelect: this.handleGraphSelect.bind(this),
|
||||
onNewChat: this.handleNewChat.bind(this)
|
||||
});
|
||||
|
||||
handleNodeClick(nodeId: string): void {
|
||||
this.currentNode = nodeId;
|
||||
this.plugin.eventBus.emit('node-selected', { nodeId });
|
||||
}
|
||||
// Инициализируем React Flow
|
||||
this.initializeGraphPanel();
|
||||
|
||||
handleGraphUpdate(data: { graphData: { nodes: any[]; edges: any[] }; currentNode: string | null }): void {
|
||||
this.graphData = data.graphData;
|
||||
this.currentNode = data.currentNode;
|
||||
if (this.graphPanel) {
|
||||
this.graphPanel.updateData(this.graphData);
|
||||
}
|
||||
}
|
||||
// Подписываемся на события
|
||||
this.plugin.eventBus.on('graph-updated', this.handleGraphUpdate.bind(this));
|
||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||
|
||||
handleNewChat(): void {
|
||||
this.graphData = { nodes: [], edges: [] };
|
||||
this.currentNode = null;
|
||||
if (this.graphPanel) {
|
||||
this.graphPanel.updateData(this.graphData);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Загружаем список графов
|
||||
await this.fetchGraphsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается при закрытии представления. Очищает ресурсы.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async onClose(): Promise<void> {
|
||||
if (this.historyPanel) {
|
||||
this.historyPanel.destroy();
|
||||
this.historyPanel = null;
|
||||
}
|
||||
|
||||
// Отмонтируем React-компонент
|
||||
if (this.reactRoot) {
|
||||
this.reactRoot.unmount();
|
||||
this.reactRoot = null;
|
||||
}
|
||||
|
||||
this.plugin.eventBus.off('graph-updated', this.handleGraphUpdate);
|
||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
||||
}
|
||||
|
||||
// ----------------------------------------------- Graph Panel Integration --------------------------------------------------
|
||||
|
||||
/**
|
||||
* Инициализирует GraphPanelComponent (React компонент) в graphContainer.
|
||||
* Использует ReactDOM.createRoot для React 18.
|
||||
*/
|
||||
initializeGraphPanel(): void {
|
||||
// Создаем контейнер для React Flow
|
||||
// Убедимся, что id уникален или используем ref для React 18
|
||||
this.graphContainer.empty(); // Очищаем контейнер перед добавлением нового элемента
|
||||
const wrapper = this.graphContainer.createDiv();
|
||||
wrapper.style.cssText = 'width: 100%; height: 100%;';
|
||||
|
||||
|
||||
if (!wrapper) {
|
||||
console.error('Не удалось создать wrapper для монтирования React Flow.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем корневой элемент React 18
|
||||
if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
|
||||
this.reactRoot = ReactDOM.createRoot(wrapper);
|
||||
this.renderGraphPanel();
|
||||
} else {
|
||||
console.error('ReactDOM.createRoot не определен. Убедитесь, что "react-dom/client" правильно импортирован.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Рендерит или обновляет GraphPanelComponent в DOM.
|
||||
*/
|
||||
renderGraphPanel(): void {
|
||||
if (this.reactRoot) {
|
||||
this.reactRoot.render(
|
||||
React.createElement(GraphPanelComponent, {
|
||||
graphData: {
|
||||
nodes: this.graphData.nodes,
|
||||
edges: this.graphData.edges
|
||||
},
|
||||
onNodeClick: this.handleNodeClick.bind(this)
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.warn('React root не инициализирован для рендера GraphPanelComponent.');
|
||||
}
|
||||
}
|
||||
// ... (остальные методы API и Event Handlers без изменений)
|
||||
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Загружает список всех графов с бэкенда.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async fetchGraphsList(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Ошибка загрузки списка графов: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
this.graphsList = data;
|
||||
if (this.historyPanel) {
|
||||
this.historyPanel.updateGraphs(this.graphsList);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки списка графов:', error);
|
||||
// Optionally: show a notice to the user
|
||||
// new Notice(`Ошибка загрузки списка графов: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает данные конкретного графа по его ID.
|
||||
* @param {string} graphId - ID графа для загрузки.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async fetchGraphData(graphId: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Ошибка загрузки данных графа: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
this.graphData = {
|
||||
nodes: data.graph_nodes || [],
|
||||
edges: data.graph_edges || []
|
||||
};
|
||||
this.currentNode = data.current_node_id;
|
||||
|
||||
// Перерендериваем граф
|
||||
this.renderGraphPanel();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных графа:', error);
|
||||
// Optionally: show a notice to the user
|
||||
// new Notice(`Ошибка загрузки данных графа: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------- Event Handlers ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Обработчик клика по узлу графа.
|
||||
* Устанавливает текущий узел и генерирует событие 'node-selected'.
|
||||
* @param {string} nodeId - ID выбранного узла.
|
||||
*/
|
||||
handleNodeClick(nodeId: string): void {
|
||||
this.currentNode = nodeId;
|
||||
this.plugin.eventBus.emit('node-selected', { nodeId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик обновления данных графа (например, после нового сообщения).
|
||||
* Обновляет `graphData` и перерендеривает граф.
|
||||
* @param {{ graphData: { nodes: any[], edges: any[] }, currentNode: string | null }} data - Обновленные данные графа.
|
||||
*/
|
||||
handleGraphUpdate(data: { graphData: { nodes: any[], edges: any[] }, currentNode: string | null }): void {
|
||||
this.graphData = data.graphData;
|
||||
this.currentNode = data.currentNode;
|
||||
this.renderGraphPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик начала нового чата.
|
||||
* Очищает текущие данные графа и перерендеривает его.
|
||||
*/
|
||||
handleNewChat(): void {
|
||||
this.graphData = { nodes: [], edges: [] };
|
||||
this.currentNode = null;
|
||||
this.selectedGraphId = null;
|
||||
this.renderGraphPanel();
|
||||
this.fetchGraphsList(); // Обновить список графов, чтобы показать новый пустой чат
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик выбора графа из панели истории.
|
||||
* Устанавливает выбранный граф и загружает его данные.
|
||||
* @param {string} graphId - ID выбранного графа.
|
||||
*/
|
||||
async handleGraphSelect(graphId: string): Promise<void> {
|
||||
this.selectedGraphId = graphId;
|
||||
await this.fetchGraphData(graphId);
|
||||
// Также сообщаем ChatView, что выбран новый граф и он должен обновить свою историю
|
||||
this.plugin.eventBus.emit('graph-selected', { graphId, currentNode: this.currentNode });
|
||||
}
|
||||
}
|
||||
535
styles.css
535
styles.css
File diff suppressed because one or more lines are too long
|
|
@ -1,24 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES2019", // Обновляем таргет до ES2019
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ESNext" // Используем ESNext для более полного набора API
|
||||
],
|
||||
"jsx": "react-jsx", // Включаем поддержку JSX
|
||||
"esModuleInterop": true, // Включаем для лучшей совместимости импортов
|
||||
"skipLibCheck": true, // Пропускаем проверку типов в библиотеках
|
||||
"forceConsistentCasingInFileNames": true // Обеспечиваем единообразие в именах файлов
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx" // Добавляем поддержку TSX файлов
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user