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 esbuild from 'esbuild'
|
||||||
import process from "process";
|
import process from 'process'
|
||||||
import builtins from "builtin-modules";
|
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
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||||
if you want to view the source, please visit the github repository of this plugin
|
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({
|
const context = await esbuild.context({
|
||||||
banner: {
|
banner: {
|
||||||
js: banner,
|
js: banner
|
||||||
},
|
},
|
||||||
entryPoints: ["main.ts"],
|
entryPoints: ['main.ts'],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
external: [
|
external: [
|
||||||
"obsidian",
|
'obsidian',
|
||||||
"electron",
|
'electron',
|
||||||
"@codemirror/autocomplete",
|
'@codemirror/autocomplete',
|
||||||
"@codemirror/collab",
|
'@codemirror/collab',
|
||||||
"@codemirror/commands",
|
'@codemirror/commands',
|
||||||
"@codemirror/language",
|
'@codemirror/language',
|
||||||
"@codemirror/lint",
|
'@codemirror/lint',
|
||||||
"@codemirror/search",
|
'@codemirror/search',
|
||||||
"@codemirror/state",
|
'@codemirror/state',
|
||||||
"@codemirror/view",
|
'@codemirror/view',
|
||||||
"@lezer/common",
|
'@lezer/common',
|
||||||
"@lezer/highlight",
|
'@lezer/highlight',
|
||||||
"@lezer/lr",
|
'@lezer/lr',
|
||||||
...builtins],
|
...builtins
|
||||||
format: "cjs",
|
],
|
||||||
target: "es2018",
|
format: 'cjs',
|
||||||
logLevel: "info",
|
target: 'es2019', // Обновляем таргет до es2019
|
||||||
sourcemap: prod ? false : "inline",
|
logLevel: 'info',
|
||||||
|
sourcemap: prod ? false : 'inline',
|
||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
outfile: "main.js",
|
outfile: 'main.js',
|
||||||
minify: prod,
|
minify: prod,
|
||||||
});
|
// ДОБАВЛЕНО: Загрузчик для CSS файлов
|
||||||
|
loader: {
|
||||||
|
'.css': 'css'
|
||||||
|
},
|
||||||
|
plugins: [renameCssPlugin()]
|
||||||
|
})
|
||||||
|
|
||||||
if (prod) {
|
if (prod) {
|
||||||
await context.rebuild();
|
await context.rebuild()
|
||||||
process.exit(0);
|
process.exit(0)
|
||||||
} else {
|
} 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 { GraphView, GRAPH_VIEW_TYPE } from './src/views/GraphView';
|
||||||
import { ChatView, CHAT_VIEW_TYPE } from './src/views/ChatView';
|
import { ChatView, CHAT_VIEW_TYPE } from './src/views/ChatView';
|
||||||
|
|
||||||
|
import 'src/css/styles.css'; // Путь к твоему новому CSS файлу
|
||||||
|
|
||||||
// ----------------------------------------------- Settings ---------------------------------------------------------------
|
// ----------------------------------------------- Settings ---------------------------------------------------------------
|
||||||
|
|
||||||
interface LLMAgentSettings {
|
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",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/dagre": "^0.7.52",
|
||||||
"@types/node": "^16.11.6",
|
"@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/eslint-plugin": "5.29.0",
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
|
"dagre": "^0.8.5",
|
||||||
"esbuild": "0.17.3",
|
"esbuild": "0.17.3",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"reactflow": "^11.11.3",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/state": {
|
"node_modules/@codemirror/state": {
|
||||||
|
|
@ -537,6 +544,108 @@
|
||||||
"node": ">= 8"
|
"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": {
|
"node_modules/@types/codemirror": {
|
||||||
"version": "5.60.8",
|
"version": "5.60.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||||
|
|
@ -546,12 +655,277 @@
|
||||||
"@types/tern": "*"
|
"@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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
|
@ -564,6 +938,31 @@
|
||||||
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
|
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/tern": {
|
||||||
"version": "0.23.9",
|
"version": "0.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||||
|
|
@ -915,6 +1314,12 @@
|
||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"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": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
|
@ -964,6 +1369,127 @@
|
||||||
"node": ">= 8"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
|
|
@ -1493,6 +2019,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": 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": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
|
@ -1605,6 +2140,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": 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": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
|
|
@ -1679,6 +2220,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
|
@ -1686,6 +2233,18 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": 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": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"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": {
|
"node_modules/regexpp": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
|
||||||
|
|
@ -1993,6 +2595,15 @@
|
||||||
"queue-microtask": "^1.2.2"
|
"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": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
|
@ -2156,16 +2767,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.7.4",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
|
|
@ -2178,6 +2789,15 @@
|
||||||
"punycode": "^2.1.0"
|
"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": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
|
@ -2230,6 +2850,34 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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)",
|
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs && move main.css styles.css",
|
||||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
"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"
|
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/dagre": "^0.7.52",
|
||||||
"@types/node": "^16.11.6",
|
"@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/eslint-plugin": "5.29.0",
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
|
"dagre": "^0.8.5",
|
||||||
"esbuild": "0.17.3",
|
"esbuild": "0.17.3",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"reactflow": "^11.11.3",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
/*
|
||||||
|
* Управляет пользовательским интерфейсом чата, включая ввод сообщений,
|
||||||
|
* отображение истории и подсказки команд для взаимодействия с пользователем.
|
||||||
|
*/
|
||||||
|
|
||||||
import { ChatHistoryItem } from './models/ChatHistoryItem';
|
import { ChatHistoryItem } from './models/ChatHistoryItem';
|
||||||
|
|
||||||
export class ChatPanel {
|
export class ChatPanel {
|
||||||
|
|
@ -120,7 +125,7 @@ export class ChatPanel {
|
||||||
historyContainer.innerHTML = this.chatHistory
|
historyContainer.innerHTML = this.chatHistory
|
||||||
.map(
|
.map(
|
||||||
(msg) => `
|
(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 class="message-content">${this.processMessageContent(msg.content)}</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -1,138 +1,302 @@
|
||||||
export class GraphPanel {
|
// ----------------------------------------------- React & React Flow Imports ---------------------------------------------------
|
||||||
container: HTMLElement;
|
import * as React from 'react';
|
||||||
props: {
|
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: для createRoot нужен 'react-dom/client'
|
||||||
graphData: { nodes: any[]; edges: any[] };
|
import ReactFlow, {
|
||||||
onNodeClick: (nodeId: string) => void;
|
addEdge,
|
||||||
};
|
useNodesState,
|
||||||
nodes: any[];
|
useEdgesState,
|
||||||
edges: any[];
|
Background,
|
||||||
|
Controls,
|
||||||
|
ReactFlowProvider, // <-- ИСПРАВЛЕНО: импортируем прямо ReactFlowProvider без переименования
|
||||||
|
Connection,
|
||||||
|
Edge,
|
||||||
|
BackgroundVariant // <-- ИСПРАВЛЕНО: используем BackgroundVariant для типизации
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
constructor(
|
// <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов.
|
||||||
container: HTMLElement,
|
// Обычно, при использовании esbuild для React-проектов, вы импортируете CSS прямо в JS/TS файл.
|
||||||
props: {
|
// esbuild должен быть настроен для копирования или встраивания этого CSS.
|
||||||
graphData: { nodes: any[]; edges: any[] };
|
// Если стили не применяются, проверьте конфигурацию esbuild (например, через плагин esbuild-plugin-css-bundle).
|
||||||
onNodeClick: (nodeId: string) => void;
|
import 'reactflow/dist/style.css'; // Импортируем стили ReactFlow
|
||||||
}
|
|
||||||
) {
|
|
||||||
this.container = container;
|
|
||||||
this.props = props;
|
|
||||||
this.nodes = [];
|
|
||||||
this.edges = [];
|
|
||||||
|
|
||||||
this.init();
|
// Если вы используете какие-либо кастомные ноды, импортируйте их здесь
|
||||||
}
|
// import CustomNode from './CustomNode';
|
||||||
|
// const nodeTypes = { custom: CustomNode };
|
||||||
|
|
||||||
init(): void {
|
// ----------------------------------------------- Custom Nodes -----------------------------------------------------------------
|
||||||
// Создаем структуру для React Flow
|
|
||||||
this.container.innerHTML = `
|
|
||||||
<div class="llm-agent-graph-panel">
|
|
||||||
<div id="reactflow-container" style="width: 100%; height: 100%;"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Загружаем React Flow динамически (если доступен)
|
import { Handle, Position } from 'reactflow';
|
||||||
this.initReactFlow();
|
|
||||||
}
|
|
||||||
|
|
||||||
async initReactFlow(): Promise<void> {
|
// Простой компонент для узла типа 'user'
|
||||||
try {
|
// Это базовый пример. Ты можешь стилизовать его по своему усмотрению.
|
||||||
// В среде Obsidian нужно будет подключить React Flow через CDN или bundler
|
const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
// Для демонстрации создаем упрощенную визуализацию
|
return React.createElement('div', {
|
||||||
this.createSimpleGraph();
|
className: 'react-flow__node-user'
|
||||||
} catch (error) {
|
},
|
||||||
console.error('Ошибка инициализации React Flow:', error);
|
React.createElement(Handle, {
|
||||||
this.createFallbackGraph();
|
type: "source",
|
||||||
}
|
position: Position.Bottom,
|
||||||
}
|
isConnectable: isConnectable,
|
||||||
|
}),
|
||||||
|
React.createElement(Handle, {
|
||||||
|
type: "target",
|
||||||
|
position: Position.Top,
|
||||||
|
isConnectable: isConnectable,
|
||||||
|
}),
|
||||||
|
data.label
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
createSimpleGraph(): void {
|
// Простой компонент для узла типа 'llm'
|
||||||
const container = this.container.querySelector('#reactflow-container') as HTMLElement;
|
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
container.innerHTML = `
|
return React.createElement('div', {
|
||||||
<div class="simple-graph">
|
className: 'react-flow__node-llm'
|
||||||
<div class="graph-nodes" id="graph-nodes"></div>
|
},
|
||||||
<svg class="graph-edges" id="graph-edges"></svg>
|
React.createElement(Handle, {
|
||||||
</div>
|
type: "source",
|
||||||
`;
|
position: Position.Bottom,
|
||||||
|
isConnectable: isConnectable,
|
||||||
|
}),
|
||||||
|
React.createElement(Handle, {
|
||||||
|
type: "target",
|
||||||
|
position: Position.Top,
|
||||||
|
isConnectable: isConnectable,
|
||||||
|
}),
|
||||||
|
data.label
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
this.renderNodes();
|
// Простой компонент для узла типа 'tool'
|
||||||
this.renderEdges();
|
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 {
|
// Простой компонент для узла типа 'error'
|
||||||
const nodesContainer = this.container.querySelector('#graph-nodes') as HTMLElement;
|
const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
nodesContainer.innerHTML = '';
|
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 AssistantNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
const nodeEl = document.createElement('div');
|
return React.createElement('div', {
|
||||||
nodeEl.className = `graph-node node-${node.type || 'default'}`;
|
className: 'react-flow__node-assistant' // Используем CSS класс для узла assistant
|
||||||
nodeEl.style.cssText = `
|
},
|
||||||
position: absolute;
|
React.createElement(Handle, {
|
||||||
left: ${50 + index * 150}px;
|
type: "source",
|
||||||
top: ${50 + Math.floor(index / 5) * 80}px;
|
position: Position.Bottom,
|
||||||
padding: 8px 12px;
|
isConnectable: isConnectable,
|
||||||
border: 2px solid #333;
|
}),
|
||||||
border-radius: 4px;
|
React.createElement(Handle, {
|
||||||
background: #fff;
|
type: "target",
|
||||||
cursor: pointer;
|
position: Position.Top,
|
||||||
max-width: 120px;
|
isConnectable: isConnectable,
|
||||||
word-wrap: break-word;
|
}),
|
||||||
`;
|
data.label
|
||||||
nodeEl.textContent = node.data?.label || `Node ${node.id}`;
|
);
|
||||||
nodeEl.dataset.nodeId = node.id;
|
};
|
||||||
|
|
||||||
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 {
|
// Типы для Dagre (были уже корректны)
|
||||||
const edgesContainer = this.container.querySelector('#graph-edges') as HTMLElement;
|
interface DagreNode {
|
||||||
edgesContainer.innerHTML = '';
|
x: number;
|
||||||
// Упрощенная отрисовка связей - в реальной реализации нужно будет вычислять позиции
|
y: number;
|
||||||
}
|
width: number;
|
||||||
|
height: number;
|
||||||
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 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 {
|
export class HistoryPanel {
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
props: {
|
props: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Определяет интерфейс для элемента истории чата, включающего роль участника
|
||||||
|
* (пользователь/агент) и содержимое сообщения.
|
||||||
|
*/
|
||||||
|
|
||||||
export interface ChatHistoryItem {
|
export interface ChatHistoryItem {
|
||||||
role: string;
|
role: string;
|
||||||
content: 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 {
|
export class EventBus {
|
||||||
private events: { [key: string]: ((data?: any) => void)[] } = {};
|
private events: { [key: string]: ((data?: any) => void)[] } = {};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
/*
|
||||||
|
* ChatView управляет пользовательским интерфейсом чата, отображает историю сообщений,
|
||||||
|
* отправляет запросы LLM-агенту и обрабатывает ответы, обновляя состояние чата и графа.
|
||||||
|
*/
|
||||||
|
|
||||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||||
import { ChatPanel } from '../components/ChatPanel';
|
import { ChatPanel } from '../components/ChatPanel';
|
||||||
import { HistoryPanel } from '../components/HistoryPanel';
|
|
||||||
import LLMAgentPlugin from 'main';
|
import LLMAgentPlugin from 'main';
|
||||||
|
|
||||||
export const CHAT_VIEW_TYPE = 'llm-agent-chat-view';
|
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 {
|
export class ChatView extends ItemView {
|
||||||
plugin: LLMAgentPlugin;
|
plugin: LLMAgentPlugin;
|
||||||
chatHistory: any[];
|
chatHistory: any[];
|
||||||
graphsList: any[];
|
|
||||||
selectedGraphId: string | null;
|
selectedGraphId: string | null;
|
||||||
currentNode: string | null;
|
currentNode: string | null;
|
||||||
chatContainer: HTMLDivElement;
|
chatContainer: HTMLDivElement;
|
||||||
historyContainer: HTMLDivElement;
|
|
||||||
chatPanel: ChatPanel | null;
|
chatPanel: ChatPanel | null;
|
||||||
historyPanel: HistoryPanel | null;
|
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.chatHistory = [];
|
this.chatHistory = [];
|
||||||
this.graphsList = [];
|
|
||||||
this.selectedGraphId = null;
|
this.selectedGraphId = null;
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.chatPanel = null;
|
this.chatPanel = null;
|
||||||
this.historyPanel = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType(): string {
|
getViewType(): string {
|
||||||
|
|
@ -45,31 +44,20 @@ export class ChatView extends ItemView {
|
||||||
const container = this.containerEl.children[1] as HTMLElement;
|
const container = this.containerEl.children[1] as HTMLElement;
|
||||||
container.empty();
|
container.empty();
|
||||||
|
|
||||||
// Создаем контейнеры для React компонентов
|
// Создаем только контейнер для чата (без истории)
|
||||||
this.chatContainer = container.createDiv() as HTMLDivElement;
|
this.chatContainer = container.createDiv() as HTMLDivElement;
|
||||||
this.chatContainer.addClass('llm-agent-chat-container');
|
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, {
|
this.chatPanel = new ChatPanel(this.chatContainer, {
|
||||||
chatHistory: this.chatHistory,
|
chatHistory: this.chatHistory,
|
||||||
onSendMessage: this.handleSendMessage.bind(this)
|
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('node-selected', this.handleNodeSelected.bind(this));
|
||||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||||
|
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
||||||
// Загружаем список графов
|
|
||||||
this.fetchGraphsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClose(): Promise<void> {
|
async onClose(): Promise<void> {
|
||||||
|
|
@ -77,145 +65,143 @@ export class ChatView extends ItemView {
|
||||||
this.chatPanel.destroy();
|
this.chatPanel.destroy();
|
||||||
this.chatPanel = null;
|
this.chatPanel = null;
|
||||||
}
|
}
|
||||||
if (this.historyPanel) {
|
|
||||||
this.historyPanel.destroy();
|
|
||||||
this.historyPanel = null;
|
|
||||||
}
|
|
||||||
this.plugin.eventBus.off('node-selected', this.handleNodeSelected);
|
this.plugin.eventBus.off('node-selected', this.handleNodeSelected);
|
||||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
||||||
|
this.plugin.eventBus.off('graph-selected', this.handleGraphSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
||||||
|
|
||||||
async fetchGraphsList(): Promise<void> {
|
/**
|
||||||
try {
|
* Загружает историю сообщений от корня до указанного узла графа.
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs`);
|
* Обновляет `chatHistory` и отображение в `chatPanel`.
|
||||||
const data = await response.json();
|
* @param {string} nodeId - ID конечного узла для загрузки истории.
|
||||||
this.graphsList = data;
|
* @returns {Promise<void>}
|
||||||
if (this.historyPanel) {
|
*/
|
||||||
this.historyPanel.updateGraphs(this.graphsList);
|
async fetchMessagesFromRootToNode(nodeId: string): Promise<void> {
|
||||||
}
|
try {
|
||||||
} catch (error) {
|
// Убедимся, что selectedGraphId установлен
|
||||||
console.error('Ошибка загрузки списка графов:', error);
|
if (!this.selectedGraphId) {
|
||||||
}
|
console.warn('selectedGraphId не установлен. Невозможно загрузить сообщения.');
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
async fetchGraphData(graphId: string): Promise<void> {
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/messages/${this.selectedGraphId}/${nodeId}`);
|
||||||
try {
|
if (!response.ok) {
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
|
const errorText = await response.text();
|
||||||
if (!response.ok) {
|
throw new Error(`Ошибка загрузки сообщений: ${response.status} - ${errorText}`);
|
||||||
if (response.status === 404) {
|
}
|
||||||
console.error(`Граф с ID ${graphId} не найден.`);
|
const data = await response.json();
|
||||||
}
|
// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
|
||||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
this.chatHistory = data.map((msg: any) => ({
|
||||||
}
|
...msg,
|
||||||
const data = await response.json();
|
type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
|
||||||
|
})) || [];
|
||||||
// Обновляем граф
|
if (this.chatPanel) {
|
||||||
this.plugin.eventBus.emit('graph-updated', {
|
this.chatPanel.updateHistory(this.chatHistory);
|
||||||
graphData: {
|
}
|
||||||
nodes: data.graph_nodes || [],
|
} catch (error) {
|
||||||
edges: data.graph_edges || []
|
console.error('Ошибка загрузки сообщений:', error);
|
||||||
},
|
// Optionally: show a notice to the user
|
||||||
currentNode: data.current_node_id
|
// new Notice(`Ошибка загрузки сообщений: ${error.message}`);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
// Обновляем историю чата
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------- Event Handlers ---------------------------------------------------------------
|
// ----------------------------------------------- Event Handlers ---------------------------------------------------------------
|
||||||
|
|
||||||
async handleSendMessage(message: string): Promise<void> {
|
async handleSendMessage(message: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat`, {
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message: message,
|
message: message,
|
||||||
graph_id: this.selectedGraphId,
|
graph_id: this.selectedGraphId,
|
||||||
parent_node_id: this.currentNode
|
parent_node_id: this.currentNode,
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// Обновляем выбранный ID графа
|
this.selectedGraphId = result.graph_id;
|
||||||
this.selectedGraphId = result.graph_id;
|
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
||||||
|
this.currentNode = newCurrentNodeId;
|
||||||
|
|
||||||
// Устанавливаем текущий узел
|
// Обновляем граф
|
||||||
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
this.plugin.eventBus.emit('graph-updated', {
|
||||||
this.currentNode = newCurrentNodeId;
|
graphData: {
|
||||||
|
nodes: result.graph_visualization_data.nodes || [],
|
||||||
|
edges: result.graph_visualization_data.edges || []
|
||||||
|
},
|
||||||
|
currentNode: newCurrentNodeId
|
||||||
|
});
|
||||||
|
|
||||||
// Обновляем граф
|
// Загружаем сообщения
|
||||||
this.plugin.eventBus.emit('graph-updated', {
|
if (newCurrentNodeId) {
|
||||||
graphData: {
|
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||||
nodes: result.graph_visualization_data.nodes || [],
|
} else {
|
||||||
edges: result.graph_visualization_data.edges || []
|
// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
|
||||||
},
|
this.chatHistory = result.messages.map((msg: any) => ({
|
||||||
currentNode: newCurrentNodeId
|
...msg,
|
||||||
});
|
type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
|
||||||
|
})) || [];
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем сообщения от корня до нового текущего узла
|
} catch (error) {
|
||||||
if (newCurrentNodeId) {
|
console.error('Ошибка отправки сообщения:', error);
|
||||||
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
}
|
||||||
} else {
|
}
|
||||||
this.chatHistory = result.messages || [];
|
|
||||||
if (this.chatPanel) {
|
|
||||||
this.chatPanel.updateHistory(this.chatHistory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обновляем список графов
|
/**
|
||||||
this.fetchGraphsList();
|
* Обрабатывает выбор узла на графе. Загружает историю сообщений до выбранного узла
|
||||||
} catch (error) {
|
* и обновляет панель чата.
|
||||||
console.error('Ошибка отправки сообщения:', error);
|
* @param {{ nodeId: string }} data - Объект, содержащий ID выбранного узла.
|
||||||
}
|
*/
|
||||||
}
|
handleNodeSelected(data: { nodeId: string }): void {
|
||||||
|
this.currentNode = data.nodeId;
|
||||||
|
if (this.selectedGraphId) {
|
||||||
|
this.fetchMessagesFromRootToNode(data.nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleNodeSelected(data: { nodeId: string }): void {
|
/**
|
||||||
this.currentNode = data.nodeId;
|
* Обрабатывает событие начала нового чата. Сбрасывает текущее состояние чата
|
||||||
if (this.selectedGraphId) {
|
* и очищает отображение истории.
|
||||||
this.fetchMessagesFromRootToNode(data.nodeId);
|
*/
|
||||||
}
|
handleNewChat(): void {
|
||||||
}
|
this.chatHistory = [];
|
||||||
|
this.currentNode = null;
|
||||||
|
this.selectedGraphId = null;
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleGraphSelect(graphId: string): void {
|
/**
|
||||||
this.selectedGraphId = graphId;
|
* Обрабатывает выбор графа из `GraphView`. Устанавливает выбранный граф и обновляет
|
||||||
this.fetchGraphData(graphId);
|
* историю чата, загружая сообщения, относящиеся к этому графу.
|
||||||
}
|
* @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
|
||||||
|
* @returns {Promise<void>}
|
||||||
handleNewChat(): void {
|
*/
|
||||||
this.chatHistory = [];
|
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
||||||
this.currentNode = null;
|
this.selectedGraphId = data.graphId;
|
||||||
this.selectedGraphId = null;
|
this.currentNode = data.currentNode;
|
||||||
if (this.chatPanel) {
|
if (this.currentNode) {
|
||||||
this.chatPanel.updateHistory(this.chatHistory);
|
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 { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||||
import { GraphPanel } from '../components/GraphPanel';
|
// ----------------------------------------------- Plugin Imports ------------------------------------------------------------
|
||||||
import LLMAgentPlugin from 'main';
|
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';
|
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
||||||
|
|
||||||
// ----------------------------------------------- Graph View ---------------------------------------------------------------
|
// ----------------------------------------------- Graph View ---------------------------------------------------------------
|
||||||
|
|
||||||
export class GraphView extends ItemView {
|
export class GraphView extends ItemView {
|
||||||
plugin: LLMAgentPlugin;
|
plugin: LLMAgentPlugin;
|
||||||
graphData: { nodes: any[]; edges: any[] };
|
graphData: { nodes: any[], edges: any[] };
|
||||||
currentNode: string | null;
|
currentNode: string | null;
|
||||||
graphPanel: GraphPanel | null;
|
graphsList: any[];
|
||||||
reactContainer: HTMLDivElement;
|
selectedGraphId: string | null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
// UI Elements
|
||||||
super(leaf);
|
historyPanel: HistoryPanel | null;
|
||||||
this.plugin = plugin;
|
historyContainer: HTMLDivElement;
|
||||||
this.graphData = { nodes: [], edges: [] };
|
graphContainer: HTMLDivElement;
|
||||||
this.currentNode = null;
|
reactRoot: ReactDOM.Root | null; // <-- ИСПРАВЛЕНО: Указываем тип для reactRoot
|
||||||
this.graphPanel = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewType(): string {
|
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||||
return GRAPH_VIEW_TYPE;
|
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;
|
* @returns {string} Иконка представления.
|
||||||
container.empty();
|
*/
|
||||||
|
getIcon(): string {
|
||||||
|
return 'brain-circuit';
|
||||||
|
}
|
||||||
|
|
||||||
// Создаем контейнер для React компонента
|
/**
|
||||||
this.reactContainer = container.createDiv() as HTMLDivElement;
|
* Вызывается при открытии представления. Инициализирует UI и загружает данные.
|
||||||
this.reactContainer.addClass('llm-agent-graph-container');
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async onOpen(): Promise<void> {
|
||||||
|
const container = this.containerEl.children[1] as HTMLElement;
|
||||||
|
container.empty();
|
||||||
|
|
||||||
// Инициализируем React компонент
|
// Создаем основной контейнер с flexbox layout
|
||||||
this.graphPanel = new GraphPanel(this.reactContainer, {
|
const mainContainer = container.createDiv('llm-agent-graph-main');
|
||||||
graphData: this.graphData,
|
mainContainer.style.cssText = `
|
||||||
onNodeClick: this.handleNodeClick.bind(this)
|
display: flex;
|
||||||
});
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--background-primary); /* Убедимся, что фон установлен */
|
||||||
|
`;
|
||||||
|
|
||||||
// Подписываемся на события
|
// Левая панель для истории
|
||||||
this.plugin.eventBus.on('graph-updated', this.handleGraphUpdate.bind(this));
|
this.historyContainer = mainContainer.createDiv('llm-agent-history-sidebar') as HTMLDivElement;
|
||||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
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.graphContainer = mainContainer.createDiv('llm-agent-graph-container') as HTMLDivElement;
|
||||||
this.graphPanel.destroy();
|
this.graphContainer.style.cssText = `
|
||||||
this.graphPanel = null;
|
flex: 1;
|
||||||
}
|
position: relative;
|
||||||
this.plugin.eventBus.off('graph-updated', this.handleGraphUpdate);
|
background-color: var(--background-primary); /* Убедимся, что фон установлен */
|
||||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------- 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 {
|
// Инициализируем React Flow
|
||||||
this.currentNode = nodeId;
|
this.initializeGraphPanel();
|
||||||
this.plugin.eventBus.emit('node-selected', { nodeId });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleGraphUpdate(data: { graphData: { nodes: any[]; edges: any[] }; currentNode: string | null }): void {
|
// Подписываемся на события
|
||||||
this.graphData = data.graphData;
|
this.plugin.eventBus.on('graph-updated', this.handleGraphUpdate.bind(this));
|
||||||
this.currentNode = data.currentNode;
|
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||||
if (this.graphPanel) {
|
|
||||||
this.graphPanel.updateData(this.graphData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNewChat(): void {
|
// Загружаем список графов
|
||||||
this.graphData = { nodes: [], edges: [] };
|
await this.fetchGraphsList();
|
||||||
this.currentNode = null;
|
}
|
||||||
if (this.graphPanel) {
|
|
||||||
this.graphPanel.updateData(this.graphData);
|
/**
|
||||||
}
|
* Вызывается при закрытии представления. Очищает ресурсы.
|
||||||
}
|
* @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": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES6",
|
"target": "ES2019", // Обновляем таргет до ES2019
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"DOM",
|
"DOM",
|
||||||
"ES5",
|
"ESNext" // Используем ESNext для более полного набора API
|
||||||
"ES6",
|
],
|
||||||
"ES7"
|
"jsx": "react-jsx", // Включаем поддержку JSX
|
||||||
]
|
"esModuleInterop": true, // Включаем для лучшей совместимости импортов
|
||||||
},
|
"skipLibCheck": true, // Пропускаем проверку типов в библиотеках
|
||||||
"include": [
|
"forceConsistentCasingInFileNames": true // Обеспечиваем единообразие в именах файлов
|
||||||
"**/*.ts"
|
},
|
||||||
]
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx" // Добавляем поддержку TSX файлов
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user