feat: ingress form in modal & delete relationship

This commit is contained in:
Per Stark
2025-02-12 13:59:32 +01:00
parent e8e7f610de
commit e7c666c054
13 changed files with 200 additions and 61 deletions

View File

@@ -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

View File

@@ -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))

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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(())
}
}

View 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

View File

@@ -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">

View 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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>