mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-23 17:28:34 +02:00
feat: ingress form in modal & delete relationship
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@plugin "daisyui";
|
|
||||||
|
@plugin "daisyui" {
|
||||||
|
exclude: rootscrollbargutter;
|
||||||
|
}
|
||||||
|
|
||||||
@plugin "@tailwindcss/typography";
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
@config '../tailwind.config.js';
|
@config '../tailwind.config.js';
|
||||||
@@ -22,6 +26,10 @@
|
|||||||
@apply font-satoshi;
|
@apply font-satoshi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
::after,
|
::after,
|
||||||
::before,
|
::before,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -34,10 +34,10 @@ use zettle_db::{
|
|||||||
show_privacy_policy,
|
show_privacy_policy,
|
||||||
},
|
},
|
||||||
gdpr::{accept_gdpr, deny_gdpr},
|
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},
|
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
||||||
knowledge::{
|
knowledge::{
|
||||||
delete_knowledge_entity, patch_knowledge_entity,
|
delete_knowledge_entity, delete_knowledge_relationship, patch_knowledge_entity,
|
||||||
show_edit_knowledge_entity_form, show_knowledge_page,
|
show_edit_knowledge_entity_form, show_knowledge_page,
|
||||||
},
|
},
|
||||||
search_result::search_result_handler,
|
search_result::search_result_handler,
|
||||||
@@ -176,6 +176,7 @@ fn html_routes(
|
|||||||
.route("/hide-ingress-form", get(hide_ingress_form))
|
.route("/hide-ingress-form", get(hide_ingress_form))
|
||||||
.route("/text-content/:id", delete(delete_text_content))
|
.route("/text-content/:id", delete(delete_text_content))
|
||||||
.route("/jobs/:job_id", delete(delete_job))
|
.route("/jobs/:job_id", delete(delete_job))
|
||||||
|
.route("/active-jobs", get(show_active_jobs))
|
||||||
.route("/knowledge", get(show_knowledge_page))
|
.route("/knowledge", get(show_knowledge_page))
|
||||||
.route(
|
.route(
|
||||||
"/knowledge-entity/:id",
|
"/knowledge-entity/:id",
|
||||||
@@ -183,6 +184,10 @@ fn html_routes(
|
|||||||
.delete(delete_knowledge_entity)
|
.delete(delete_knowledge_entity)
|
||||||
.patch(patch_knowledge_entity),
|
.patch(patch_knowledge_entity),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/knowledge-relationship/:id",
|
||||||
|
delete(delete_knowledge_relationship),
|
||||||
|
)
|
||||||
.route("/account", get(show_account_page))
|
.route("/account", get(show_account_page))
|
||||||
.route("/admin", get(show_admin_panel))
|
.route("/admin", get(show_admin_panel))
|
||||||
.route("/toggle-registrations", patch(toggle_registration_status))
|
.route("/toggle-registrations", patch(toggle_registration_status))
|
||||||
|
|||||||
@@ -179,8 +179,8 @@ async fn get_and_validate_text_content(
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ActiveJobsData {
|
pub struct ActiveJobsData {
|
||||||
active_jobs: Vec<Job>,
|
pub active_jobs: Vec<Job>,
|
||||||
user: User,
|
pub user: User,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_job(
|
pub async fn delete_job(
|
||||||
@@ -217,3 +217,31 @@ pub async fn delete_job(
|
|||||||
|
|
||||||
Ok(output.into_response())
|
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},
|
error::{AppError, HtmlError, IntoHtmlError},
|
||||||
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
||||||
page_data,
|
page_data,
|
||||||
server::AppState,
|
server::{
|
||||||
|
routes::html::{index::ActiveJobsData, render_block},
|
||||||
|
AppState,
|
||||||
|
},
|
||||||
storage::types::{file_info::FileInfo, user::User},
|
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()))?;
|
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||||
|
|
||||||
let output = render_template(
|
let output = render_template(
|
||||||
"ingress_form.html",
|
"index/signed_in/ingress_modal.html",
|
||||||
ShowIngressFormData { user_categories },
|
ShowIngressFormData { user_categories },
|
||||||
state.templates.clone(),
|
state.templates.clone(),
|
||||||
)?;
|
)?;
|
||||||
@@ -129,8 +132,22 @@ pub async fn process_ingress_form(
|
|||||||
.map_err(AppError::from)
|
.map_err(AppError::from)
|
||||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||||
|
|
||||||
Ok(Html(
|
// Update the active jobs page with the newly created job
|
||||||
"<a class='btn btn-primary' hx-get='/ingress-form' hx-swap='outerHTML'>Add Content</a>",
|
let active_jobs = state
|
||||||
)
|
.job_queue
|
||||||
.into_response())
|
.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())
|
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(())
|
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 %}
|
{% block active_jobs_section %}
|
||||||
<ul id="active_jobs_section" class="list ">
|
<ul id="active_jobs_section" class="list">
|
||||||
<li class="py-4 text-center font-bold tracking-wide">Active Jobs</li>
|
<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 %}
|
{% for item in active_jobs %}
|
||||||
<li class="list-row">
|
<li class="list-row">
|
||||||
<div class="bg-secondary rounded-box size-10 flex justify-center items-center text-secondary-content">
|
<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>
|
<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>
|
</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">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -39,9 +40,9 @@
|
|||||||
|
|
||||||
<td>{{ relationship.metadata.relationship_type }}</td>
|
<td>{{ relationship.metadata.relationship_type }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline" hx-get="/relationship/{{ relationship.id }}/edit"
|
<button class="btn btn-sm btn-outline" hx-delete="/knowledge-relationship/{{ relationship.id }}"
|
||||||
hx-target="#modal_container" hx-swap="innerHTML">
|
hx-target="#relationship_table_section" hx-swap="outerHTML">
|
||||||
Edit
|
{% include "icons/delete_icon.html" %}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user