mirror of
https://github.com/perstarkse/minne.git
synced 2026-03-17 23:14:08 +01:00
feat: ingress form in modal & delete relationship
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin "daisyui";
|
||||
|
||||
@plugin "daisyui" {
|
||||
exclude: rootscrollbargutter;
|
||||
}
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@config '../tailwind.config.js';
|
||||
@@ -22,6 +26,10 @@
|
||||
@apply font-satoshi;
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -34,10 +34,10 @@ use zettle_db::{
|
||||
show_privacy_policy,
|
||||
},
|
||||
gdpr::{accept_gdpr, deny_gdpr},
|
||||
index::{delete_job, delete_text_content, index_handler},
|
||||
index::{delete_job, delete_text_content, index_handler, show_active_jobs},
|
||||
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
||||
knowledge::{
|
||||
delete_knowledge_entity, patch_knowledge_entity,
|
||||
delete_knowledge_entity, delete_knowledge_relationship, patch_knowledge_entity,
|
||||
show_edit_knowledge_entity_form, show_knowledge_page,
|
||||
},
|
||||
search_result::search_result_handler,
|
||||
@@ -176,6 +176,7 @@ fn html_routes(
|
||||
.route("/hide-ingress-form", get(hide_ingress_form))
|
||||
.route("/text-content/:id", delete(delete_text_content))
|
||||
.route("/jobs/:job_id", delete(delete_job))
|
||||
.route("/active-jobs", get(show_active_jobs))
|
||||
.route("/knowledge", get(show_knowledge_page))
|
||||
.route(
|
||||
"/knowledge-entity/:id",
|
||||
@@ -183,6 +184,10 @@ fn html_routes(
|
||||
.delete(delete_knowledge_entity)
|
||||
.patch(patch_knowledge_entity),
|
||||
)
|
||||
.route(
|
||||
"/knowledge-relationship/:id",
|
||||
delete(delete_knowledge_relationship),
|
||||
)
|
||||
.route("/account", get(show_account_page))
|
||||
.route("/admin", get(show_admin_panel))
|
||||
.route("/toggle-registrations", patch(toggle_registration_status))
|
||||
|
||||
@@ -179,8 +179,8 @@ async fn get_and_validate_text_content(
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ActiveJobsData {
|
||||
active_jobs: Vec<Job>,
|
||||
user: User,
|
||||
pub active_jobs: Vec<Job>,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
pub async fn delete_job(
|
||||
@@ -217,3 +217,31 @@ pub async fn delete_job(
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
pub async fn show_active_jobs(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
let active_jobs = state
|
||||
.job_queue
|
||||
.get_unfinished_user_jobs(&user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let output = render_block(
|
||||
"index/signed_in/active_jobs.html",
|
||||
"active_jobs_section",
|
||||
ActiveJobsData {
|
||||
user: user.clone(),
|
||||
active_jobs,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ use crate::{
|
||||
error::{AppError, HtmlError, IntoHtmlError},
|
||||
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
||||
page_data,
|
||||
server::AppState,
|
||||
server::{
|
||||
routes::html::{index::ActiveJobsData, render_block},
|
||||
AppState,
|
||||
},
|
||||
storage::types::{file_info::FileInfo, user::User},
|
||||
};
|
||||
|
||||
@@ -38,7 +41,7 @@ pub async fn show_ingress_form(
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let output = render_template(
|
||||
"ingress_form.html",
|
||||
"index/signed_in/ingress_modal.html",
|
||||
ShowIngressFormData { user_categories },
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
@@ -129,8 +132,22 @@ pub async fn process_ingress_form(
|
||||
.map_err(AppError::from)
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
Ok(Html(
|
||||
"<a class='btn btn-primary' hx-get='/ingress-form' hx-swap='outerHTML'>Add Content</a>",
|
||||
)
|
||||
.into_response())
|
||||
// Update the active jobs page with the newly created job
|
||||
let active_jobs = state
|
||||
.job_queue
|
||||
.get_unfinished_user_jobs(&user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let output = render_block(
|
||||
"index/signed_in/active_jobs.html",
|
||||
"active_jobs_section",
|
||||
ActiveJobsData {
|
||||
user: user.clone(),
|
||||
active_jobs,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -263,3 +263,45 @@ pub async fn delete_knowledge_entity(
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RelationshipTableData {
|
||||
entities: Vec<KnowledgeEntity>,
|
||||
relationships: Vec<KnowledgeRelationship>,
|
||||
}
|
||||
|
||||
pub async fn delete_knowledge_relationship(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
// Early return if the user is not authenticated
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
KnowledgeRelationship::delete_relationship_by_id(&id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let entities = User::get_knowledge_entities(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let relationships = User::get_knowledge_relationships(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
// Render updated list
|
||||
let output = render_template(
|
||||
"knowledge/relationship_table.html",
|
||||
RelationshipTableData {
|
||||
entities,
|
||||
relationships,
|
||||
},
|
||||
state.templates,
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -73,4 +73,15 @@ impl KnowledgeRelationship {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_relationship_by_id(
|
||||
id: &str,
|
||||
db_client: &SurrealDbClient,
|
||||
) -> Result<(), AppError> {
|
||||
let query = format!("DELETE relates_to:`{}`", id);
|
||||
|
||||
db_client.query(query).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
5
templates/icons/refresh_icon.html
Normal file
5
templates/icons/refresh_icon.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
@@ -1,6 +1,11 @@
|
||||
{% block active_jobs_section %}
|
||||
<ul id="active_jobs_section" class="list ">
|
||||
<li class="py-4 text-center font-bold tracking-wide">Active Jobs</li>
|
||||
<ul id="active_jobs_section" class="list">
|
||||
<div class="flex justify-center items-center gap-4">
|
||||
<li class="py-4 text-center font-bold tracking-wide">Active Jobs</li>
|
||||
<button class="cursor-pointer scale-75" hx-get="/active-jobs" hx-target="#active_jobs_section" hx-swap="outerHTML">
|
||||
{% include "icons/refresh_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
{% for item in active_jobs %}
|
||||
<li class="list-row">
|
||||
<div class="bg-secondary rounded-box size-10 flex justify-center items-center text-secondary-content">
|
||||
|
||||
57
templates/index/signed_in/ingress_modal.html
Normal file
57
templates/index/signed_in/ingress_modal.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% extends "modal_base.html" %}
|
||||
|
||||
{% block form_attributes %}
|
||||
hx-post="/ingress-form"
|
||||
enctype="multipart/form-data"
|
||||
hx-target="#active_jobs_section"
|
||||
hx-swap="outerHTML"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold mb-4">Add new content</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Instructions</span>
|
||||
<textarea name="instructions" class="textarea w-full validator"
|
||||
placeholder="Enter instructions for the AI here, help it understand what its seeing or how it should relate to the database"
|
||||
required>{{ instructions }}</textarea>
|
||||
<div class="validator-hint hidden">Instructions are required</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="floating-label">
|
||||
<span>Content</span>
|
||||
<textarea name="content" class="textarea input-bordered w-full"
|
||||
placeholder="Enter the content you want to ingress, it can be an URL or a text snippet">{{ content }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="floating-label">
|
||||
<span>Category</span>
|
||||
<input type="text" name="category" class="input input-bordered validator w-full" value="{{ category }}"
|
||||
list="category-list" required />
|
||||
<datalist id="category-list">
|
||||
{% for category in user_categories %}
|
||||
<option value="{{ category }}" />
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<div class="validator-hint hidden">Category is required</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="label label-text">Files</label>
|
||||
<input type="file" name="files" multiple class="file-input file-input-bordered w-full" />
|
||||
</div>
|
||||
|
||||
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="flex gap-4 flex-col">
|
||||
<div class="flex gap-4 flex-col sm:flex-row">
|
||||
<a class="btn btn-secondary" href="/knowledge" hx-boost="true">View Knowledge</a>
|
||||
<button class="btn btn-primary" hx-get="/ingress-form" hx-swap="outerHTML">Add Content</button>
|
||||
<button class="btn btn-primary" hx-get="/ingress-form" hx-target="#modal" hx-swap="innerHTML">Add
|
||||
Content</button>
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
<form id="ingress-form" class="space-y-4 mt-2 w-full" hx-post="/ingress-form" enctype="multipart/form-data">
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Instructions</span>
|
||||
<textarea name="instructions" class="textarea w-full validator"
|
||||
placeholder="Enter instructions for the AI here, help it understand what its seeing or how it should relate to the database"
|
||||
required>{{ instructions }}</textarea>
|
||||
<div class="validator-hint hidden">Instructions are required</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Content</span>
|
||||
<textarea name="content" class="textarea input-bordered w-full"
|
||||
placeholder="Enter the content you want to ingress, it can be an URL or a text snippet">{{ content }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Category</span>
|
||||
<input type="text" name="category" class="input input-bordered validator w-full" value="{{ category }}"
|
||||
list="category-list" required />
|
||||
<datalist id="category-list">
|
||||
{% for category in user_categories %}
|
||||
<option value="{{ category }}" />
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<div class="validator-hint hidden">Category is required</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label label-text">Files</label>
|
||||
<input type="file" name="files" multiple class="file-input file-input-bordered w-full" />
|
||||
</div>
|
||||
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
||||
<div class="form-control mt-6 flex flex-col sm:flex-row gap-1">
|
||||
<button type="submit" class="btn btn-primary w-full sm:w-fit">Submit</button>
|
||||
<button hx-get="/hide-ingress-form" hx-target="#ingress-form" hx-swap="outerHTML"
|
||||
class="btn btn-outline w-full sm:w-fit">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="overflow-x-auto shadow rounded-box border border-base-content/5 bg-base-100">
|
||||
<div id="relationship_table_section"
|
||||
class="overflow-x-auto shadow rounded-box border border-base-content/5 bg-base-100">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -39,9 +40,9 @@
|
||||
|
||||
<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 class="btn btn-sm btn-outline" hx-delete="/knowledge-relationship/{{ relationship.id }}"
|
||||
hx-target="#relationship_table_section" hx-swap="outerHTML">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user