mirror of
https://github.com/perstarkse/minne.git
synced 2026-03-20 08:34:31 +01:00
feat: view graph entities, relations and visualization
This commit is contained in:
4058
assets/style.css
4058
assets/style.css
File diff suppressed because one or more lines are too long
@@ -5,7 +5,12 @@ use axum::{
|
||||
use axum_session::Session;
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use plotly::{Configuration, Layout, Plot, Scatter};
|
||||
use futures::SinkExt;
|
||||
use plotly::{
|
||||
common::{Line, Marker, Mode},
|
||||
layout::{Axis, Camera, LayoutScene, ProjectionType},
|
||||
Configuration, Layout, Plot, Scatter, Scatter3D,
|
||||
};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tokio::join;
|
||||
use tracing::info;
|
||||
@@ -54,46 +59,79 @@ pub async fn show_knowledge_page(
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
// In your handler function
|
||||
let mut plot = Plot::new();
|
||||
|
||||
// Create node positions (you might want to use a proper layout algorithm)
|
||||
let node_x: Vec<f64> = entities.iter().enumerate().map(|(i, _)| i as f64).collect();
|
||||
let node_y: Vec<f64> = vec![0.0; entities.len()];
|
||||
let node_text: Vec<String> = entities.iter().map(|e| e.description.clone()).collect();
|
||||
// Fibonacci sphere distribution
|
||||
let node_count = entities.len();
|
||||
let golden_ratio = (1.0 + 5.0_f64.sqrt()) / 2.0;
|
||||
let node_positions: Vec<(f64, f64, f64)> = (0..node_count)
|
||||
.map(|i| {
|
||||
let i = i as f64;
|
||||
let theta = 2.0 * std::f64::consts::PI * i / golden_ratio;
|
||||
let phi = (1.0 - 2.0 * (i + 0.5) / node_count as f64).acos();
|
||||
let x = phi.sin() * theta.cos();
|
||||
let y = phi.sin() * theta.sin();
|
||||
let z = phi.cos();
|
||||
(x, y, z)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Add nodes
|
||||
let nodes = Scatter::new(node_x.clone(), node_y.clone())
|
||||
.mode(plotly::common::Mode::Markers)
|
||||
.text_array(node_text)
|
||||
.name("Entities")
|
||||
.hover_template("%{text}");
|
||||
let node_x: Vec<f64> = node_positions.iter().map(|(x, _, _)| *x).collect();
|
||||
let node_y: Vec<f64> = node_positions.iter().map(|(_, y, _)| *y).collect();
|
||||
let node_z: Vec<f64> = node_positions.iter().map(|(_, _, z)| *z).collect();
|
||||
|
||||
// Add edges
|
||||
let mut edge_x = Vec::new();
|
||||
let mut edge_y = Vec::new();
|
||||
// Nodes trace
|
||||
let nodes = Scatter3D::new(node_x.clone(), node_y.clone(), node_z.clone())
|
||||
.mode(Mode::Markers)
|
||||
.marker(Marker::new().size(8).color("#1f77b4"))
|
||||
.text_array(
|
||||
entities
|
||||
.iter()
|
||||
.map(|e| e.description.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.hover_template("Entity: %{text}<br>");
|
||||
|
||||
// Edges traces
|
||||
for rel in &relationships {
|
||||
let from_idx = entities.iter().position(|e| e.id == rel.out).unwrap_or(0);
|
||||
let to_idx = entities.iter().position(|e| e.id == rel.in_).unwrap_or(0);
|
||||
|
||||
edge_x.extend_from_slice(&[from_idx as f64, to_idx as f64, std::f64::NAN]);
|
||||
edge_y.extend_from_slice(&[0.0, 0.0, std::f64::NAN]);
|
||||
let edge_x = vec![node_x[from_idx], node_x[to_idx]];
|
||||
let edge_y = vec![node_y[from_idx], node_y[to_idx]];
|
||||
let edge_z = vec![node_z[from_idx], node_z[to_idx]];
|
||||
|
||||
let edge_trace = Scatter3D::new(edge_x, edge_y, edge_z)
|
||||
.mode(Mode::Lines)
|
||||
.line(Line::new().color("#888").width(2.0))
|
||||
.hover_template(&format!(
|
||||
"Relationship: {}<br>",
|
||||
rel.metadata.relationship_type
|
||||
))
|
||||
.show_legend(false);
|
||||
|
||||
plot.add_trace(edge_trace);
|
||||
}
|
||||
|
||||
let edges = Scatter::new(edge_x, edge_y)
|
||||
.mode(plotly::common::Mode::Lines)
|
||||
.name("Relationships");
|
||||
|
||||
plot.add_trace(edges);
|
||||
plot.add_trace(nodes);
|
||||
|
||||
// Layout
|
||||
let layout = Layout::new()
|
||||
.title("Knowledge Graph")
|
||||
.scene(
|
||||
LayoutScene::new()
|
||||
.x_axis(Axis::new().visible(false))
|
||||
.y_axis(Axis::new().visible(false))
|
||||
.z_axis(Axis::new().visible(false))
|
||||
.camera(
|
||||
Camera::new()
|
||||
.projection(ProjectionType::Perspective.into())
|
||||
.eye((1.5, 1.5, 1.5).into()),
|
||||
),
|
||||
)
|
||||
.show_legend(false)
|
||||
.height(600);
|
||||
.paper_background_color("rbga(250,100,0,0)")
|
||||
.plot_background_color("rbga(0,0,0,0)");
|
||||
|
||||
plot.set_layout(layout);
|
||||
|
||||
// Convert to HTML
|
||||
let html = plot.to_html();
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
machines, with different resource requirements:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Server:</strong> Lightweight, using roughly 50MB of RAM. A minimum of 1 core and 256MB of RAM is
|
||||
recommended.
|
||||
<strong>Server:</strong> Lightweight. A minimum of 1 core and 256MB of RAM is recommended.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Worker:</strong> Handles content parsing, typically consuming about 60MB of RAM—occasionally peaking up
|
||||
to 1GB. We recommend allocating at least 2 cores and 1024MB of RAM.
|
||||
<strong>Worker:</strong> Handles content parsing and creation of database entities. It's recommended to allocate at
|
||||
least two cores and 1024 MB RAM. It will run on less but might run into constraints depending on the content being
|
||||
parsed.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
class="text-5xl sm:text-6xl py-4 pt-10 font-extrabold bg-linear-to-r from-primary to-secondary text-transparent bg-clip-text font-satoshi">
|
||||
Simplify Your Knowledge Management
|
||||
</h1>
|
||||
<p class="text-xl text-base-content/70">
|
||||
<p class="text-xl ">
|
||||
Capture, connect, and retrieve your knowledge effortlessly with Minne
|
||||
</p>
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
<div class="card bg-base-100 shadow-hover">
|
||||
<div class="card-body items-center">
|
||||
<div class="skeleton h-32 w-32 rounded-full"></div>
|
||||
<h3 class="card-title">Easy Capture</h3>
|
||||
<h3 class="card-title text-xl">Easy Capture</h3>
|
||||
<p>Save anything instantly - texts, links, images, and more</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-100 shadow-hover">
|
||||
<div class="card-body items-center">
|
||||
<div class="skeleton h-32 w-32 rounded-full"></div>
|
||||
<h3 class="card-title">Smart Analysis</h3>
|
||||
<h3 class="card-title text-xl">Smart Analysis</h3>
|
||||
<p>AI-powered content analysis and organization</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-100 shadow-hover">
|
||||
<div class="card-body items-center">
|
||||
<div class="skeleton h-32 w-32 rounded-full"></div>
|
||||
<h3 class="card-title">Knowledge Graph</h3>
|
||||
<h3 class="card-title text-xl">Knowledge Graph</h3>
|
||||
<p>Visualize connections between your ideas</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{% extends 'body_base.html' %}
|
||||
{% block main %}
|
||||
<main class="flex justify-center grow mt-2 sm:mt-4 gap-6">
|
||||
<main class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10">
|
||||
<div class="container">
|
||||
{{plot_html|safe}}
|
||||
<h2>Entities</h2>
|
||||
|
||||
{% for entity in entities %}
|
||||
<p>{{entity.id}} - {{entity.description}} </p>
|
||||
{% endfor %}
|
||||
<h2 class="text-2xl font-bold mb-2">Entities</h2>
|
||||
{% include "knowledge/entity_list.html" %}
|
||||
|
||||
|
||||
<h2 class="mt-10">Relationships</h2>
|
||||
<p>{{relationships}}</p>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-2 mt-10">Relationships</h2>
|
||||
{% include "knowledge/relationship_table.html" %}
|
||||
|
||||
<div class="rounded-box overflow-clip mt-10 shadow">
|
||||
{{plot_html|safe}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
24
templates/knowledge/entity_list.html
Normal file
24
templates/knowledge/entity_list.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-4" id="entity_list">
|
||||
{% for entity in entities %}
|
||||
<div class="card min-w-72 bg-base-100 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{entity.name}}
|
||||
<span class="badge badge-xs badge-primary">{{entity.entity_type}}</span>
|
||||
</h2>
|
||||
<p>{{entity.description}}</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<p>{{entity.updated_at | datetimeformat(format="short", tz=user.timezeone)}}</p>
|
||||
<div>
|
||||
<button hx-patch="/knowledge-entity/{{entity.id}}" class="btn btn-square btn-ghost btn-sm">
|
||||
{% include "icons/edit_icon.html" %}
|
||||
</button>
|
||||
<button hx-delete="/knowledge-entity/{{entity.id}}" hx-target="#entity_list" hx-swap="outerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
89
templates/knowledge/relationship_table.html
Normal file
89
templates/knowledge/relationship_table.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<div class="overflow-x-auto shadow rounded-box border border-base-content/5 bg-base-100 ">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Origin</th>
|
||||
<th>Target</th>
|
||||
<th>Type</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for relationship in relationships %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
|
||||
<!-- Origin column -->
|
||||
<td>
|
||||
{% for entity in entities if entity.id == relationship.in %}
|
||||
<span class="cursor-pointer tooltip tooltip-info" data-tip="Click for more details"
|
||||
hx-get="/knowledge-entity/{{entity.id}}" hx-trigger="click" hx-target="#entity_detail_modal"
|
||||
hx-swap="innerHTML">
|
||||
{{ entity.name }}
|
||||
</span>
|
||||
{% else %}
|
||||
{{ relationship.in }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
|
||||
<!-- Target column -->
|
||||
<td>
|
||||
{% for entity in entities if entity.id == relationship.out %}
|
||||
<span class="cursor-pointer tooltip tooltip-info" data-tip="Click for more details"
|
||||
hx-get="/knowledge-entity/{{entity.id}}" hx-trigger="click" hx-target="#entity_detail_modal"
|
||||
hx-swap="innerHTML">
|
||||
{{ entity.name }}
|
||||
</span>
|
||||
{% else %}
|
||||
{{ relationship.out }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
|
||||
<td>{{ relationship.metadata.relationship_type }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" hx-get="/relationship/{{ relationship.id }}/edit"
|
||||
hx-target="#modal_container" hx-swap="innerHTML">
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<!-- New linking row -->
|
||||
<tr id="new_relationship">
|
||||
<td></td>
|
||||
<td>
|
||||
<select name="origin_id" class="select select-bordered w-full new_relationship_input">
|
||||
<option disabled selected>Select Origin</option>
|
||||
{% for entity in entities %}
|
||||
<option value="{{ entity.id }}">{{ entity.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="target_id" class="select select-bordered w-full new_relationship_input">
|
||||
<option disabled selected>Select Target</option>
|
||||
{% for entity in entities %}
|
||||
<option value="{{ entity.id }}">{{ entity.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input name="relationship_type" type="text" placeholder="RelatedTo"
|
||||
class="input input-bordered w-full new_relationship_input" />
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-primary btn-sm" hx-post="/relationship/create"
|
||||
hx-target="#relationship_table" hx-swap="outerHTML" hx-include=".new_relationship_input">
|
||||
Save
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal containers for dynamic content -->
|
||||
<div id="entity_detail_modal" class="mt-4"></div>
|
||||
<div id="modal_container"></div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="navbar bg-base-200">
|
||||
<nav class="navbar bg-base-200 !p-0">
|
||||
<div class="container flex mx-auto">
|
||||
<div class="flex-1 flex items-center">
|
||||
<a class="text-2xl text-primary font-bold" href="/" hx-boost="true">Minne</a>
|
||||
|
||||
Reference in New Issue
Block a user