mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-19 15:31:23 +02:00
refactor: additional responsibilities to middleware, simplified handlers
fix
This commit is contained in:
@@ -25,6 +25,56 @@ pub struct CategoryResponse {
|
|||||||
category: String,
|
category: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Supported UI themes.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum Theme {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
WarmPaper,
|
||||||
|
ObsidianPrism,
|
||||||
|
#[default]
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Theme {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"light" => Ok(Self::Light),
|
||||||
|
"dark" => Ok(Self::Dark),
|
||||||
|
"warm-paper" => Ok(Self::WarmPaper),
|
||||||
|
"obsidian-prism" => Ok(Self::ObsidianPrism),
|
||||||
|
"system" => Ok(Self::System),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Light => "light",
|
||||||
|
Self::Dark => "dark",
|
||||||
|
Self::WarmPaper => "warm-paper",
|
||||||
|
Self::ObsidianPrism => "obsidian-prism",
|
||||||
|
Self::System => "system",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the theme that should be initially applied.
|
||||||
|
/// For "system", defaults to "light".
|
||||||
|
pub fn initial_theme(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::System => "light",
|
||||||
|
other => other.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stored_object!(
|
stored_object!(
|
||||||
#[allow(clippy::unsafe_derive_deserialize)]
|
#[allow(clippy::unsafe_derive_deserialize)]
|
||||||
User, "user", {
|
User, "user", {
|
||||||
@@ -36,7 +86,7 @@ stored_object!(
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
timezone: String,
|
timezone: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
theme: String
|
theme: Theme
|
||||||
});
|
});
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -73,11 +123,8 @@ fn validate_timezone(input: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures a theme string is valid, defaulting to "system" when invalid.
|
/// Ensures a theme string is valid, defaulting to "system" when invalid.
|
||||||
fn validate_theme(input: &str) -> String {
|
fn validate_theme(input: &str) -> Theme {
|
||||||
match input {
|
Theme::from_str(input).unwrap_or_default()
|
||||||
"light" | "dark" | "system" => input.to_owned(),
|
|
||||||
_ => "system".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -212,7 +259,7 @@ impl User {
|
|||||||
.bind(("created_at", surrealdb::Datetime::from(now)))
|
.bind(("created_at", surrealdb::Datetime::from(now)))
|
||||||
.bind(("updated_at", surrealdb::Datetime::from(now)))
|
.bind(("updated_at", surrealdb::Datetime::from(now)))
|
||||||
.bind(("timezone", validated_tz))
|
.bind(("timezone", validated_tz))
|
||||||
.bind(("theme", validated_theme))
|
.bind(("theme", validated_theme.as_str()))
|
||||||
.await?
|
.await?
|
||||||
.take(1)?;
|
.take(1)?;
|
||||||
|
|
||||||
@@ -490,7 +537,7 @@ impl User {
|
|||||||
let validated_theme = validate_theme(theme);
|
let validated_theme = validate_theme(theme);
|
||||||
db.query("UPDATE type::thing('user', $user_id) SET theme = $theme")
|
db.query("UPDATE type::thing('user', $user_id) SET theme = $theme")
|
||||||
.bind(("user_id", user_id.to_string()))
|
.bind(("user_id", user_id.to_string()))
|
||||||
.bind(("theme", validated_theme))
|
.bind(("theme", validated_theme.as_str()))
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1152,10 +1199,10 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_validate_theme() {
|
async fn test_validate_theme() {
|
||||||
assert_eq!(validate_theme("light"), "light");
|
assert_eq!(validate_theme("light"), Theme::Light);
|
||||||
assert_eq!(validate_theme("dark"), "dark");
|
assert_eq!(validate_theme("dark"), Theme::Dark);
|
||||||
assert_eq!(validate_theme("system"), "system");
|
assert_eq!(validate_theme("system"), Theme::System);
|
||||||
assert_eq!(validate_theme("invalid"), "system");
|
assert_eq!(validate_theme("invalid"), Theme::System);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1172,7 +1219,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create user");
|
.expect("Failed to create user");
|
||||||
|
|
||||||
assert_eq!(user.theme, "system");
|
assert_eq!(user.theme, Theme::System);
|
||||||
|
|
||||||
User::update_theme(&user.id, "dark", &db)
|
User::update_theme(&user.id, "dark", &db)
|
||||||
.await
|
.await
|
||||||
@@ -1183,7 +1230,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("get user")
|
.expect("get user")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(updated.theme, "dark");
|
assert_eq!(updated.theme, Theme::Dark);
|
||||||
|
|
||||||
// Invalid theme should default to system (but update_theme calls validate_theme)
|
// Invalid theme should default to system (but update_theme calls validate_theme)
|
||||||
User::update_theme(&user.id, "invalid", &db)
|
User::update_theme(&user.id, "invalid", &db)
|
||||||
@@ -1194,6 +1241,6 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("get user")
|
.expect("get user")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(updated2.theme, "system");
|
assert_eq!(updated2.theme, Theme::System);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use common::storage::{db::SurrealDbClient, types::user::User, types::StoredObject};
|
use common::storage::{
|
||||||
|
db::SurrealDbClient,
|
||||||
|
types::user::{Theme, User},
|
||||||
|
types::StoredObject,
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
@@ -212,7 +216,7 @@ pub(crate) async fn ensure_eval_user(db: &SurrealDbClient) -> Result<User> {
|
|||||||
api_key: None,
|
api_key: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
timezone: "UTC".to_string(),
|
timezone: "UTC".to_string(),
|
||||||
theme: "system".to_string(),
|
theme: Theme::System,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(existing) = db.get_item::<User>(user.get_id()).await? {
|
if let Some(existing) = db.get_item::<User>(user.get_id()).await? {
|
||||||
|
|||||||
@@ -97,11 +97,6 @@
|
|||||||
--border: 2px;
|
--border: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
THEME: Obsidian Prism
|
|
||||||
A forward-looking neobrutalist dark theme. Cool obsidian base,
|
|
||||||
prismatic violet shadows, dual-accent system (Signal + Ember).
|
|
||||||
========================================================================== */
|
|
||||||
[data-theme="obsidian-prism"] {
|
[data-theme="obsidian-prism"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
@@ -151,6 +146,54 @@
|
|||||||
--nb-shadow-hover: 6px 6px 0 0 oklch(6% 0.08 calc(var(--nb-shadow-hue) + 15));
|
--nb-shadow-hover: 6px 6px 0 0 oklch(6% 0.08 calc(var(--nb-shadow-hue) + 15));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="warm-paper"] {
|
||||||
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* --- Canvas & Surfaces: Warm cream paper (more yellow than light) --- */
|
||||||
|
--color-base-100: oklch(97% 0.025 85);
|
||||||
|
--color-base-200: oklch(93% 0.028 83);
|
||||||
|
--color-base-300: oklch(88% 0.032 80);
|
||||||
|
--color-base-content: oklch(18% 0.015 75);
|
||||||
|
|
||||||
|
/* --- Primary: Warm Amber/Gold (the landing page CTA color) --- */
|
||||||
|
--color-primary: oklch(72% 0.16 75);
|
||||||
|
--color-primary-content: oklch(18% 0.02 75);
|
||||||
|
|
||||||
|
/* --- Secondary: Warm Terracotta --- */
|
||||||
|
--color-secondary: oklch(55% 0.14 45);
|
||||||
|
--color-secondary-content: oklch(98% 0.01 85);
|
||||||
|
|
||||||
|
/* --- Accent: Deep Charcoal (for contrast buttons like "View on GitHub") --- */
|
||||||
|
--color-accent: oklch(22% 0.01 80);
|
||||||
|
--color-accent-content: oklch(98% 0.02 85);
|
||||||
|
|
||||||
|
/* --- Neutral: Warm Charcoal --- */
|
||||||
|
--color-neutral: oklch(20% 0.015 75);
|
||||||
|
--color-neutral-content: oklch(96% 0.015 85);
|
||||||
|
|
||||||
|
/* --- Semantic Colors (warmer variants) --- */
|
||||||
|
--color-info: oklch(58% 0.12 230);
|
||||||
|
--color-info-content: oklch(98% 0.01 230);
|
||||||
|
--color-success: oklch(62% 0.15 155);
|
||||||
|
--color-success-content: oklch(98% 0.01 155);
|
||||||
|
--color-warning: oklch(78% 0.16 70);
|
||||||
|
--color-warning-content: oklch(20% 0.04 70);
|
||||||
|
--color-error: oklch(58% 0.20 25);
|
||||||
|
--color-error-content: oklch(98% 0.02 25);
|
||||||
|
|
||||||
|
/* --- Radii (NB Law: Zero) --- */
|
||||||
|
--radius-selector: 0rem;
|
||||||
|
--radius-field: 0rem;
|
||||||
|
--radius-box: 0rem;
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
--border: 2px;
|
||||||
|
|
||||||
|
/* --- Classic Black Shadow --- */
|
||||||
|
--nb-shadow: 4px 4px 0 0 #000;
|
||||||
|
--nb-shadow-hover: 6px 6px 0 0 #000;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--color-base-100);
|
background-color: var(--color-base-100);
|
||||||
color: var(--color-base-content);
|
color: var(--color-base-content);
|
||||||
@@ -956,10 +999,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
OBSIDIAN PRISM: Component Overrides & Delight Features
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/* Prismatic shadow hue shift on hover */
|
/* Prismatic shadow hue shift on hover */
|
||||||
[data-theme="obsidian-prism"] .nb-panel:hover,
|
[data-theme="obsidian-prism"] .nb-panel:hover,
|
||||||
[data-theme="obsidian-prism"] .nb-card:hover,
|
[data-theme="obsidian-prism"] .nb-card:hover,
|
||||||
@@ -969,8 +1008,15 @@
|
|||||||
|
|
||||||
/* Focus state: breathing shadow pulse */
|
/* Focus state: breathing shadow pulse */
|
||||||
@keyframes shadow-breathe {
|
@keyframes shadow-breathe {
|
||||||
0%, 100% { box-shadow: 6px 6px 0 0 oklch(8% 0.08 305); }
|
|
||||||
50% { box-shadow: 7px 7px 0 0 oklch(10% 0.10 310); }
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 6px 6px 0 0 oklch(8% 0.08 305);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow: 7px 7px 0 0 oklch(10% 0.10 310);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="obsidian-prism"] .nb-btn:focus-visible,
|
[data-theme="obsidian-prism"] .nb-btn:focus-visible,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -65,3 +65,9 @@ impl ProvidesTemplateEngine for HtmlState {
|
|||||||
&self.templates
|
&self.templates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::middlewares::response_middleware::ProvidesHtmlState for HtmlState {
|
||||||
|
fn html_state(&self) -> &HtmlState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Request, State},
|
extract::{Request, State},
|
||||||
http::{HeaderName, StatusCode},
|
http::{HeaderName, StatusCode},
|
||||||
@@ -6,13 +8,24 @@ use axum::{
|
|||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use axum_htmx::{HxRequest, HX_TRIGGER};
|
use axum_htmx::{HxRequest, HX_TRIGGER};
|
||||||
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
|
use common::{
|
||||||
use minijinja::{context, Value};
|
error::AppError,
|
||||||
|
utils::template_engine::{ProvidesTemplateEngine, Value},
|
||||||
|
};
|
||||||
|
use minijinja::context;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::AuthSessionType;
|
use crate::{html_state::HtmlState, AuthSessionType};
|
||||||
|
use common::storage::types::{
|
||||||
|
conversation::Conversation,
|
||||||
|
user::{Theme, User},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait ProvidesHtmlState {
|
||||||
|
fn html_state(&self) -> &HtmlState;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum TemplateKind {
|
pub enum TemplateKind {
|
||||||
@@ -106,8 +119,10 @@ struct ContextWrapper<'a> {
|
|||||||
user_theme: &'a str,
|
user_theme: &'a str,
|
||||||
initial_theme: &'a str,
|
initial_theme: &'a str,
|
||||||
is_authenticated: bool,
|
is_authenticated: bool,
|
||||||
|
user: Option<&'a User>,
|
||||||
|
conversation_archive: Vec<Conversation>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
context: &'a Value,
|
context: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn with_template_response<S>(
|
pub async fn with_template_response<S>(
|
||||||
@@ -117,25 +132,23 @@ pub async fn with_template_response<S>(
|
|||||||
next: Next,
|
next: Next,
|
||||||
) -> Response
|
) -> Response
|
||||||
where
|
where
|
||||||
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
|
S: ProvidesTemplateEngine + ProvidesHtmlState + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
// Determine theme context
|
let mut user_theme = Theme::System.as_str();
|
||||||
let (user_theme, initial_theme, is_authenticated) =
|
let mut initial_theme = Theme::System.initial_theme();
|
||||||
|
let mut is_authenticated = false;
|
||||||
|
let mut current_user_id = None;
|
||||||
|
|
||||||
|
{
|
||||||
if let Some(auth) = req.extensions().get::<AuthSessionType>() {
|
if let Some(auth) = req.extensions().get::<AuthSessionType>() {
|
||||||
if let Some(user) = &auth.current_user {
|
if let Some(user) = &auth.current_user {
|
||||||
let theme = user.theme.as_str();
|
is_authenticated = true;
|
||||||
// For explicit themes (not "system"), use the theme directly as initial_theme
|
current_user_id = Some(user.id.clone());
|
||||||
let initial = match theme {
|
user_theme = user.theme.as_str();
|
||||||
"system" => "light",
|
initial_theme = user.theme.initial_theme();
|
||||||
other => other, // "light", "dark", "obsidian-prism", etc.
|
|
||||||
};
|
|
||||||
(theme.to_string(), initial.to_string(), true)
|
|
||||||
} else {
|
|
||||||
("system".to_string(), "light".to_string(), false)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
("system".to_string(), "light".to_string(), false)
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let response = next.run(req).await;
|
let response = next.run(req).await;
|
||||||
|
|
||||||
@@ -145,6 +158,20 @@ where
|
|||||||
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
|
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
|
||||||
let template_engine = state.template_engine();
|
let template_engine = state.template_engine();
|
||||||
|
|
||||||
|
let mut current_user = None;
|
||||||
|
let mut conversation_archive = Vec::new();
|
||||||
|
|
||||||
|
if let Some(user_id) = current_user_id {
|
||||||
|
let html_state = state.html_state();
|
||||||
|
if let Ok(Some(user)) = html_state.db.get_item::<User>(&user_id).await {
|
||||||
|
// Fetch conversation archive globally for authenticated users
|
||||||
|
if let Ok(archive) = User::get_user_conversations(&user.id, &html_state.db).await {
|
||||||
|
conversation_archive = archive;
|
||||||
|
}
|
||||||
|
current_user = Some(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to forward relevant headers
|
// Helper to forward relevant headers
|
||||||
fn forward_headers(from: &axum::http::HeaderMap, to: &mut axum::http::HeaderMap) {
|
fn forward_headers(from: &axum::http::HeaderMap, to: &mut axum::http::HeaderMap) {
|
||||||
for &header_name in HTMX_HEADERS_TO_FORWARD {
|
for &header_name in HTMX_HEADERS_TO_FORWARD {
|
||||||
@@ -156,11 +183,28 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert minijinja::Value to HashMap if it's a map, otherwise use empty HashMap
|
||||||
|
let context_map = if template_response.context.kind() == minijinja::value::ValueKind::Map {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
if let Ok(keys) = template_response.context.try_iter() {
|
||||||
|
for key in keys {
|
||||||
|
if let Ok(val) = template_response.context.get_item(&key) {
|
||||||
|
map.insert(key.to_string(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
let context = ContextWrapper {
|
let context = ContextWrapper {
|
||||||
user_theme: &user_theme,
|
user_theme: &user_theme,
|
||||||
initial_theme: &initial_theme,
|
initial_theme: &initial_theme,
|
||||||
is_authenticated,
|
is_authenticated,
|
||||||
context: &template_response.context,
|
user: current_user.as_ref(),
|
||||||
|
conversation_archive,
|
||||||
|
context: context_map,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &template_response.template_kind {
|
match &template_response.template_kind {
|
||||||
|
|||||||
@@ -9,40 +9,36 @@ use crate::{
|
|||||||
},
|
},
|
||||||
AuthSessionType,
|
AuthSessionType,
|
||||||
};
|
};
|
||||||
use common::storage::types::{conversation::Conversation, user::User};
|
use common::storage::types::user::{Theme, User};
|
||||||
|
|
||||||
use crate::html_state::HtmlState;
|
use crate::html_state::HtmlState;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct AccountPageData {
|
pub struct AccountPageData {
|
||||||
user: User,
|
|
||||||
timezones: Vec<String>,
|
timezones: Vec<String>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
theme_options: Vec<String>,
|
theme_options: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_account_page(
|
pub async fn show_account_page(
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(_user): RequireUser,
|
||||||
State(state): State<HtmlState>,
|
State(_state): State<HtmlState>,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let timezones = TZ_VARIANTS
|
let timezones = TZ_VARIANTS
|
||||||
.iter()
|
.iter()
|
||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
.collect();
|
.collect();
|
||||||
let theme_options = vec![
|
let theme_options = vec![
|
||||||
"light".to_string(),
|
Theme::Light.as_str().to_string(),
|
||||||
"dark".to_string(),
|
Theme::Dark.as_str().to_string(),
|
||||||
"obsidian-prism".to_string(),
|
Theme::WarmPaper.as_str().to_string(),
|
||||||
"system".to_string(),
|
Theme::ObsidianPrism.as_str().to_string(),
|
||||||
|
Theme::System.as_str().to_string(),
|
||||||
];
|
];
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"auth/account_settings.html",
|
"auth/account_settings.html",
|
||||||
AccountPageData {
|
AccountPageData {
|
||||||
user,
|
|
||||||
timezones,
|
timezones,
|
||||||
conversation_archive,
|
|
||||||
theme_options,
|
theme_options,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -54,25 +50,17 @@ pub async fn set_api_key(
|
|||||||
auth: AuthSessionType,
|
auth: AuthSessionType,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
// Generate and set the API key
|
// Generate and set the API key
|
||||||
let api_key = User::set_api_key(&user.id, &state.db).await?;
|
User::set_api_key(&user.id, &state.db).await?;
|
||||||
|
|
||||||
// Clear the cache so new requests have access to the user with api key
|
// Clear the cache so new requests have access to the user with api key
|
||||||
auth.cache_clear_user(user.id.to_string());
|
auth.cache_clear_user(user.id.to_string());
|
||||||
|
|
||||||
// Update the user's API key
|
|
||||||
let updated_user = User {
|
|
||||||
api_key: Some(api_key),
|
|
||||||
..user.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the API key section block
|
// Render the API key section block
|
||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
"auth/account_settings.html",
|
"auth/account_settings.html",
|
||||||
"api_key_section",
|
"api_key_section",
|
||||||
AccountPageData {
|
AccountPageData {
|
||||||
user: updated_user,
|
|
||||||
timezones: vec![],
|
timezones: vec![],
|
||||||
conversation_archive: vec![],
|
|
||||||
theme_options: vec![],
|
theme_options: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -108,12 +96,6 @@ pub async fn update_timezone(
|
|||||||
// Clear the cache
|
// Clear the cache
|
||||||
auth.cache_clear_user(user.id.to_string());
|
auth.cache_clear_user(user.id.to_string());
|
||||||
|
|
||||||
// Update the user's API key
|
|
||||||
let updated_user = User {
|
|
||||||
timezone: form.timezone,
|
|
||||||
..user.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let timezones = TZ_VARIANTS
|
let timezones = TZ_VARIANTS
|
||||||
.iter()
|
.iter()
|
||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
@@ -124,9 +106,7 @@ pub async fn update_timezone(
|
|||||||
"auth/account_settings.html",
|
"auth/account_settings.html",
|
||||||
"timezone_section",
|
"timezone_section",
|
||||||
AccountPageData {
|
AccountPageData {
|
||||||
user: updated_user,
|
|
||||||
timezones,
|
timezones,
|
||||||
conversation_archive: vec![],
|
|
||||||
theme_options: vec![],
|
theme_options: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -148,26 +128,19 @@ pub async fn update_theme(
|
|||||||
// Clear the cache
|
// Clear the cache
|
||||||
auth.cache_clear_user(user.id.to_string());
|
auth.cache_clear_user(user.id.to_string());
|
||||||
|
|
||||||
// Update the user's theme
|
|
||||||
let updated_user = User {
|
|
||||||
theme: form.theme,
|
|
||||||
..user.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let theme_options = vec![
|
let theme_options = vec![
|
||||||
"light".to_string(),
|
Theme::Light.as_str().to_string(),
|
||||||
"dark".to_string(),
|
Theme::Dark.as_str().to_string(),
|
||||||
"obsidian-prism".to_string(),
|
Theme::WarmPaper.as_str().to_string(),
|
||||||
"system".to_string(),
|
Theme::ObsidianPrism.as_str().to_string(),
|
||||||
|
Theme::System.as_str().to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
"auth/account_settings.html",
|
"auth/account_settings.html",
|
||||||
"theme_section",
|
"theme_section",
|
||||||
AccountPageData {
|
AccountPageData {
|
||||||
user: updated_user,
|
|
||||||
timezones: vec![],
|
timezones: vec![],
|
||||||
conversation_archive: vec![],
|
|
||||||
theme_options,
|
theme_options,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use common::{
|
|||||||
error::AppError,
|
error::AppError,
|
||||||
storage::types::{
|
storage::types::{
|
||||||
analytics::Analytics,
|
analytics::Analytics,
|
||||||
conversation::Conversation,
|
|
||||||
knowledge_entity::KnowledgeEntity,
|
knowledge_entity::KnowledgeEntity,
|
||||||
system_prompts::{
|
system_prompts::{
|
||||||
DEFAULT_IMAGE_PROCESSING_PROMPT, DEFAULT_INGRESS_ANALYSIS_SYSTEM_PROMPT,
|
DEFAULT_IMAGE_PROCESSING_PROMPT, DEFAULT_INGRESS_ANALYSIS_SYSTEM_PROMPT,
|
||||||
@@ -18,7 +17,6 @@ use common::{
|
|||||||
},
|
},
|
||||||
system_settings::SystemSettings,
|
system_settings::SystemSettings,
|
||||||
text_chunk::TextChunk,
|
text_chunk::TextChunk,
|
||||||
user::User,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
@@ -33,13 +31,11 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct AdminPanelData {
|
pub struct AdminPanelData {
|
||||||
user: User,
|
|
||||||
settings: SystemSettings,
|
settings: SystemSettings,
|
||||||
analytics: Option<Analytics>,
|
analytics: Option<Analytics>,
|
||||||
users: Option<i64>,
|
users: Option<i64>,
|
||||||
default_query_prompt: String,
|
default_query_prompt: String,
|
||||||
default_image_prompt: String,
|
default_image_prompt: String,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
available_models: Option<ListModelResponse>,
|
available_models: Option<ListModelResponse>,
|
||||||
current_section: AdminSection,
|
current_section: AdminSection,
|
||||||
}
|
}
|
||||||
@@ -64,7 +60,7 @@ pub struct AdminPanelQuery {
|
|||||||
|
|
||||||
pub async fn show_admin_panel(
|
pub async fn show_admin_panel(
|
||||||
State(state): State<HtmlState>,
|
State(state): State<HtmlState>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(_user): RequireUser,
|
||||||
Query(query): Query<AdminPanelQuery>,
|
Query(query): Query<AdminPanelQuery>,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let section = match query.section.as_deref() {
|
let section = match query.section.as_deref() {
|
||||||
@@ -72,10 +68,7 @@ pub async fn show_admin_panel(
|
|||||||
_ => AdminSection::Overview,
|
_ => AdminSection::Overview,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (settings, conversation_archive) = tokio::try_join!(
|
let settings = SystemSettings::get_current(&state.db).await?;
|
||||||
SystemSettings::get_current(&state.db),
|
|
||||||
User::get_user_conversations(&user.id, &state.db)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (analytics, users) = if section == AdminSection::Overview {
|
let (analytics, users) = if section == AdminSection::Overview {
|
||||||
let (analytics, users) = tokio::try_join!(
|
let (analytics, users) = tokio::try_join!(
|
||||||
@@ -103,14 +96,12 @@ pub async fn show_admin_panel(
|
|||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"admin/base.html",
|
"admin/base.html",
|
||||||
AdminPanelData {
|
AdminPanelData {
|
||||||
user,
|
|
||||||
settings,
|
settings,
|
||||||
analytics,
|
analytics,
|
||||||
available_models,
|
available_models,
|
||||||
users,
|
users,
|
||||||
default_query_prompt: DEFAULT_QUERY_SYSTEM_PROMPT.to_string(),
|
default_query_prompt: DEFAULT_QUERY_SYSTEM_PROMPT.to_string(),
|
||||||
default_image_prompt: DEFAULT_IMAGE_PROCESSING_PROMPT.to_string(),
|
default_image_prompt: DEFAULT_IMAGE_PROCESSING_PROMPT.to_string(),
|
||||||
conversation_archive,
|
|
||||||
current_section: section,
|
current_section: section,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_htmx::HxBoosted;
|
use axum_htmx::HxBoosted;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use common::storage::types::user::User;
|
use common::storage::types::user::{Theme, User};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
html_state::HtmlState,
|
html_state::HtmlState,
|
||||||
@@ -50,7 +50,7 @@ pub async fn process_signup_and_show_verification(
|
|||||||
form.password,
|
form.password,
|
||||||
&state.db,
|
&state.db,
|
||||||
form.timezone,
|
form.timezone,
|
||||||
"system".to_string(),
|
Theme::System.as_str().to_string(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ where
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ChatPageData {
|
pub struct ChatPageData {
|
||||||
user: User,
|
|
||||||
history: Vec<Message>,
|
history: Vec<Message>,
|
||||||
conversation: Option<Conversation>,
|
conversation: Option<Conversation>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_initialized_chat(
|
pub async fn show_initialized_chat(
|
||||||
@@ -76,16 +74,12 @@ pub async fn show_initialized_chat(
|
|||||||
state.db.store_item(ai_message.clone()).await?;
|
state.db.store_item(ai_message.clone()).await?;
|
||||||
state.db.store_item(user_message.clone()).await?;
|
state.db.store_item(user_message.clone()).await?;
|
||||||
|
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let messages = vec![user_message, ai_message];
|
let messages = vec![user_message, ai_message];
|
||||||
|
|
||||||
let mut response = TemplateResponse::new_template(
|
let mut response = TemplateResponse::new_template(
|
||||||
"chat/base.html",
|
"chat/base.html",
|
||||||
ChatPageData {
|
ChatPageData {
|
||||||
history: messages,
|
history: messages,
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
conversation: Some(conversation.clone()),
|
conversation: Some(conversation.clone()),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -100,17 +94,13 @@ pub async fn show_initialized_chat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_chat_base(
|
pub async fn show_chat_base(
|
||||||
State(state): State<HtmlState>,
|
State(_state): State<HtmlState>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(_user): RequireUser,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"chat/base.html",
|
"chat/base.html",
|
||||||
ChatPageData {
|
ChatPageData {
|
||||||
history: vec![],
|
history: vec![],
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
conversation: None,
|
conversation: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -126,8 +116,6 @@ pub async fn show_existing_chat(
|
|||||||
State(state): State<HtmlState>,
|
State(state): State<HtmlState>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let (conversation, messages) =
|
let (conversation, messages) =
|
||||||
Conversation::get_complete_conversation(conversation_id.as_str(), &user.id, &state.db)
|
Conversation::get_complete_conversation(conversation_id.as_str(), &user.id, &state.db)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -136,9 +124,7 @@ pub async fn show_existing_chat(
|
|||||||
"chat/base.html",
|
"chat/base.html",
|
||||||
ChatPageData {
|
ChatPageData {
|
||||||
history: messages,
|
history: messages,
|
||||||
user,
|
|
||||||
conversation: Some(conversation),
|
conversation: Some(conversation),
|
||||||
conversation_archive,
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -232,8 +218,6 @@ pub struct PatchConversationTitle {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct DrawerContext {
|
pub struct DrawerContext {
|
||||||
user: User,
|
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
edit_conversation_id: Option<String>,
|
edit_conversation_id: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -242,20 +226,19 @@ pub async fn show_conversation_editing_title(
|
|||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
Path(conversation_id): Path<String>,
|
Path(conversation_id): Path<String>,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
let conversation: Conversation = state
|
||||||
|
.db
|
||||||
|
.get_item(&conversation_id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| AppError::NotFound("Conversation not found".to_string()))?;
|
||||||
|
|
||||||
let owns = conversation_archive
|
if conversation.user_id != user.id {
|
||||||
.iter()
|
|
||||||
.any(|c| c.id == conversation_id && c.user_id == user.id);
|
|
||||||
if !owns {
|
|
||||||
return Ok(TemplateResponse::unauthorized().into_response());
|
return Ok(TemplateResponse::unauthorized().into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"sidebar.html",
|
"sidebar.html",
|
||||||
DrawerContext {
|
DrawerContext {
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
edit_conversation_id: Some(conversation_id),
|
edit_conversation_id: Some(conversation_id),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -270,13 +253,9 @@ pub async fn patch_conversation_title(
|
|||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
Conversation::patch_title(&conversation_id, &user.id, &form.title, &state.db).await?;
|
Conversation::patch_title(&conversation_id, &user.id, &form.title, &state.db).await?;
|
||||||
|
|
||||||
let updated_conversations = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"sidebar.html",
|
"sidebar.html",
|
||||||
DrawerContext {
|
DrawerContext {
|
||||||
user,
|
|
||||||
conversation_archive: updated_conversations,
|
|
||||||
edit_conversation_id: None,
|
edit_conversation_id: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -303,29 +282,21 @@ pub async fn delete_conversation(
|
|||||||
.delete_item::<Conversation>(&conversation_id)
|
.delete_item::<Conversation>(&conversation_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"sidebar.html",
|
"sidebar.html",
|
||||||
DrawerContext {
|
DrawerContext {
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
edit_conversation_id: None,
|
edit_conversation_id: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
pub async fn reload_sidebar(
|
pub async fn reload_sidebar(
|
||||||
State(state): State<HtmlState>,
|
State(_state): State<HtmlState>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(_user): RequireUser,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"sidebar.html",
|
"sidebar.html",
|
||||||
DrawerContext {
|
DrawerContext {
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
edit_conversation_id: None,
|
edit_conversation_id: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use axum_htmx::{HxBoosted, HxRequest, HxTarget};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use common::storage::types::{
|
use common::storage::types::{
|
||||||
conversation::Conversation, file_info::FileInfo, knowledge_entity::KnowledgeEntity,
|
file_info::FileInfo, knowledge_entity::KnowledgeEntity, text_chunk::TextChunk,
|
||||||
text_chunk::TextChunk, text_content::TextContent, user::User,
|
text_content::TextContent, user::User,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -26,18 +26,15 @@ const CONTENTS_PER_PAGE: usize = 12;
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ContentPageData {
|
pub struct ContentPageData {
|
||||||
user: User,
|
|
||||||
text_contents: Vec<TextContent>,
|
text_contents: Vec<TextContent>,
|
||||||
categories: Vec<String>,
|
categories: Vec<String>,
|
||||||
selected_category: Option<String>,
|
selected_category: Option<String>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
page_query: String,
|
page_query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct RecentTextContentData {
|
pub struct RecentTextContentData {
|
||||||
pub user: User,
|
|
||||||
pub text_contents: Vec<TextContent>,
|
pub text_contents: Vec<TextContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,13 +78,10 @@ pub async fn show_content_page(
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
let data = ContentPageData {
|
let data = ContentPageData {
|
||||||
user,
|
|
||||||
text_contents,
|
text_contents,
|
||||||
categories,
|
categories,
|
||||||
selected_category: params.category.clone(),
|
selected_category: params.category.clone(),
|
||||||
conversation_archive,
|
|
||||||
pagination,
|
pagination,
|
||||||
page_query,
|
page_query,
|
||||||
};
|
};
|
||||||
@@ -112,13 +106,12 @@ pub async fn show_text_content_edit_form(
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TextContentEditModal {
|
pub struct TextContentEditModal {
|
||||||
pub user: User,
|
|
||||||
pub text_content: TextContent,
|
pub text_content: TextContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"content/edit_text_content_modal.html",
|
"content/edit_text_content_modal.html",
|
||||||
TextContentEditModal { user, text_content },
|
TextContentEditModal { text_content },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +138,7 @@ pub async fn patch_text_content(
|
|||||||
|
|
||||||
return Ok(TemplateResponse::new_template(
|
return Ok(TemplateResponse::new_template(
|
||||||
"dashboard/recent_content.html",
|
"dashboard/recent_content.html",
|
||||||
RecentTextContentData {
|
RecentTextContentData { text_contents },
|
||||||
user,
|
|
||||||
text_contents,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,17 +149,14 @@ pub async fn patch_text_content(
|
|||||||
);
|
);
|
||||||
let text_contents = truncate_text_contents(page_contents);
|
let text_contents = truncate_text_contents(page_contents);
|
||||||
let categories = User::get_user_categories(&user.id, &state.db).await?;
|
let categories = User::get_user_categories(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
"content/base.html",
|
"content/base.html",
|
||||||
"main",
|
"main",
|
||||||
ContentPageData {
|
ContentPageData {
|
||||||
user,
|
|
||||||
text_contents,
|
text_contents,
|
||||||
categories,
|
categories,
|
||||||
selected_category: None,
|
selected_category: None,
|
||||||
conversation_archive,
|
|
||||||
pagination,
|
pagination,
|
||||||
page_query: String::new(),
|
page_query: String::new(),
|
||||||
},
|
},
|
||||||
@@ -209,16 +196,13 @@ pub async fn delete_text_content(
|
|||||||
);
|
);
|
||||||
let text_contents = truncate_text_contents(page_contents);
|
let text_contents = truncate_text_contents(page_contents);
|
||||||
let categories = User::get_user_categories(&user.id, &state.db).await?;
|
let categories = User::get_user_categories(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"content/content_list.html",
|
"content/content_list.html",
|
||||||
ContentPageData {
|
ContentPageData {
|
||||||
user,
|
|
||||||
text_contents,
|
text_contents,
|
||||||
categories,
|
categories,
|
||||||
selected_category: None,
|
selected_category: None,
|
||||||
conversation_archive,
|
|
||||||
pagination,
|
pagination,
|
||||||
page_query: String::new(),
|
page_query: String::new(),
|
||||||
},
|
},
|
||||||
@@ -234,13 +218,12 @@ pub async fn show_content_read_modal(
|
|||||||
let text_content = User::get_and_validate_text_content(&id, &user.id, &state.db).await?;
|
let text_content = User::get_and_validate_text_content(&id, &user.id, &state.db).await?;
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TextContentReadModalData {
|
pub struct TextContentReadModalData {
|
||||||
pub user: User,
|
|
||||||
pub text_content: TextContent,
|
pub text_content: TextContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"content/read_content_modal.html",
|
"content/read_content_modal.html",
|
||||||
TextContentReadModalData { user, text_content },
|
TextContentReadModalData { text_content },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +236,6 @@ pub async fn show_recent_content(
|
|||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"dashboard/recent_content.html",
|
"dashboard/recent_content.html",
|
||||||
RecentTextContentData {
|
RecentTextContentData { text_contents },
|
||||||
user,
|
|
||||||
text_contents,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,17 @@ use common::storage::types::user::DashboardStats;
|
|||||||
use common::{
|
use common::{
|
||||||
error::AppError,
|
error::AppError,
|
||||||
storage::types::{
|
storage::types::{
|
||||||
conversation::Conversation, file_info::FileInfo, ingestion_task::IngestionTask,
|
file_info::FileInfo, ingestion_task::IngestionTask, knowledge_entity::KnowledgeEntity,
|
||||||
knowledge_entity::KnowledgeEntity, knowledge_relationship::KnowledgeRelationship,
|
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
|
||||||
text_chunk::TextChunk, text_content::TextContent, user::User,
|
text_content::TextContent, user::User,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct IndexPageData {
|
pub struct IndexPageData {
|
||||||
user: Option<User>,
|
|
||||||
text_contents: Vec<TextContent>,
|
text_contents: Vec<TextContent>,
|
||||||
stats: DashboardStats,
|
stats: DashboardStats,
|
||||||
active_jobs: Vec<IngestionTask>,
|
active_jobs: Vec<IngestionTask>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn index_handler(
|
pub async fn index_handler(
|
||||||
@@ -44,7 +42,7 @@ pub async fn index_handler(
|
|||||||
return Ok(TemplateResponse::redirect("/signin"));
|
return Ok(TemplateResponse::redirect("/signin"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (text_contents, conversation_archive, stats, active_jobs) = try_join!(
|
let (text_contents, _conversation_archive, stats, active_jobs) = try_join!(
|
||||||
User::get_latest_text_contents(&user.id, &state.db),
|
User::get_latest_text_contents(&user.id, &state.db),
|
||||||
User::get_user_conversations(&user.id, &state.db),
|
User::get_user_conversations(&user.id, &state.db),
|
||||||
User::get_dashboard_stats(&user.id, &state.db),
|
User::get_dashboard_stats(&user.id, &state.db),
|
||||||
@@ -56,10 +54,8 @@ pub async fn index_handler(
|
|||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"dashboard/base.html",
|
"dashboard/base.html",
|
||||||
IndexPageData {
|
IndexPageData {
|
||||||
user: Some(user),
|
|
||||||
text_contents,
|
text_contents,
|
||||||
stats,
|
stats,
|
||||||
conversation_archive,
|
|
||||||
active_jobs,
|
active_jobs,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -68,7 +64,6 @@ pub async fn index_handler(
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct LatestTextContentData {
|
pub struct LatestTextContentData {
|
||||||
text_contents: Vec<TextContent>,
|
text_contents: Vec<TextContent>,
|
||||||
user: User,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_text_content(
|
pub async fn delete_text_content(
|
||||||
@@ -105,10 +100,7 @@ pub async fn delete_text_content(
|
|||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
"dashboard/recent_content.html",
|
"dashboard/recent_content.html",
|
||||||
"latest_content_section",
|
"latest_content_section",
|
||||||
LatestTextContentData {
|
LatestTextContentData { text_contents },
|
||||||
user: user.clone(),
|
|
||||||
text_contents,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +128,6 @@ async fn get_and_validate_text_content(
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ActiveJobsData {
|
pub struct ActiveJobsData {
|
||||||
pub active_jobs: Vec<IngestionTask>,
|
pub active_jobs: Vec<IngestionTask>,
|
||||||
pub user: User,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -161,7 +152,6 @@ struct TaskArchiveEntry {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct TaskArchiveData {
|
struct TaskArchiveData {
|
||||||
user: User,
|
|
||||||
tasks: Vec<TaskArchiveEntry>,
|
tasks: Vec<TaskArchiveEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,10 +167,7 @@ pub async fn delete_job(
|
|||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
"dashboard/active_jobs.html",
|
"dashboard/active_jobs.html",
|
||||||
"active_jobs_section",
|
"active_jobs_section",
|
||||||
ActiveJobsData {
|
ActiveJobsData { active_jobs },
|
||||||
user: user.clone(),
|
|
||||||
active_jobs,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,10 +179,7 @@ pub async fn show_active_jobs(
|
|||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"dashboard/active_jobs.html",
|
"dashboard/active_jobs.html",
|
||||||
ActiveJobsData {
|
ActiveJobsData { active_jobs },
|
||||||
user: user.clone(),
|
|
||||||
active_jobs,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +217,7 @@ pub async fn show_task_archive(
|
|||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"dashboard/task_archive_modal.html",
|
"dashboard/task_archive_modal.html",
|
||||||
TaskArchiveData {
|
TaskArchiveData { tasks: entries },
|
||||||
user,
|
|
||||||
tasks: entries,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,13 +120,12 @@ pub async fn process_ingress_form(
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct NewTasksData {
|
struct NewTasksData {
|
||||||
user: User,
|
|
||||||
tasks: Vec<IngestionTask>,
|
tasks: Vec<IngestionTask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"dashboard/current_task.html",
|
"dashboard/current_task.html",
|
||||||
NewTasksData { user, tasks },
|
NewTasksData { tasks },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use serde::{
|
|||||||
use common::{
|
use common::{
|
||||||
error::AppError,
|
error::AppError,
|
||||||
storage::types::{
|
storage::types::{
|
||||||
conversation::Conversation,
|
|
||||||
knowledge_entity::{KnowledgeEntity, KnowledgeEntityType},
|
knowledge_entity::{KnowledgeEntity, KnowledgeEntityType},
|
||||||
knowledge_relationship::KnowledgeRelationship,
|
knowledge_relationship::KnowledgeRelationship,
|
||||||
user::User,
|
user::User,
|
||||||
@@ -333,12 +332,10 @@ pub struct KnowledgeBaseData {
|
|||||||
entities: Vec<KnowledgeEntity>,
|
entities: Vec<KnowledgeEntity>,
|
||||||
visible_entities: Vec<KnowledgeEntity>,
|
visible_entities: Vec<KnowledgeEntity>,
|
||||||
relationships: Vec<RelationshipTableRow>,
|
relationships: Vec<RelationshipTableRow>,
|
||||||
user: User,
|
|
||||||
entity_types: Vec<String>,
|
entity_types: Vec<String>,
|
||||||
content_categories: Vec<String>,
|
content_categories: Vec<String>,
|
||||||
selected_entity_type: Option<String>,
|
selected_entity_type: Option<String>,
|
||||||
selected_content_category: Option<String>,
|
selected_content_category: Option<String>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
page_query: String,
|
page_query: String,
|
||||||
relationship_type_options: Vec<String>,
|
relationship_type_options: Vec<String>,
|
||||||
@@ -481,18 +478,15 @@ async fn build_knowledge_base_data(
|
|||||||
relationship_type_options,
|
relationship_type_options,
|
||||||
default_relationship_type,
|
default_relationship_type,
|
||||||
} = build_relationship_table_data(entities.clone(), filtered_relationships);
|
} = build_relationship_table_data(entities.clone(), filtered_relationships);
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(KnowledgeBaseData {
|
Ok(KnowledgeBaseData {
|
||||||
entities,
|
entities,
|
||||||
visible_entities,
|
visible_entities,
|
||||||
relationships,
|
relationships,
|
||||||
user: user.clone(),
|
|
||||||
entity_types,
|
entity_types,
|
||||||
content_categories,
|
content_categories,
|
||||||
selected_entity_type: params.entity_type.clone(),
|
selected_entity_type: params.entity_type.clone(),
|
||||||
selected_content_category: params.content_category.clone(),
|
selected_content_category: params.content_category.clone(),
|
||||||
conversation_archive,
|
|
||||||
pagination,
|
pagination,
|
||||||
page_query,
|
page_query,
|
||||||
relationship_type_options,
|
relationship_type_options,
|
||||||
@@ -861,7 +855,6 @@ pub async fn show_edit_knowledge_entity_form(
|
|||||||
pub struct EntityData {
|
pub struct EntityData {
|
||||||
entity: KnowledgeEntity,
|
entity: KnowledgeEntity,
|
||||||
entity_types: Vec<String>,
|
entity_types: Vec<String>,
|
||||||
user: User,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get entity types
|
// Get entity types
|
||||||
@@ -878,7 +871,6 @@ pub async fn show_edit_knowledge_entity_form(
|
|||||||
EntityData {
|
EntityData {
|
||||||
entity,
|
entity,
|
||||||
entity_types,
|
entity_types,
|
||||||
user,
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -895,7 +887,6 @@ pub struct PatchKnowledgeEntityParams {
|
|||||||
pub struct EntityListData {
|
pub struct EntityListData {
|
||||||
visible_entities: Vec<KnowledgeEntity>,
|
visible_entities: Vec<KnowledgeEntity>,
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
user: User,
|
|
||||||
entity_types: Vec<String>,
|
entity_types: Vec<String>,
|
||||||
content_categories: Vec<String>,
|
content_categories: Vec<String>,
|
||||||
selected_entity_type: Option<String>,
|
selected_entity_type: Option<String>,
|
||||||
@@ -943,7 +934,6 @@ pub async fn patch_knowledge_entity(
|
|||||||
EntityListData {
|
EntityListData {
|
||||||
visible_entities,
|
visible_entities,
|
||||||
pagination,
|
pagination,
|
||||||
user,
|
|
||||||
entity_types,
|
entity_types,
|
||||||
content_categories,
|
content_categories,
|
||||||
selected_entity_type: None,
|
selected_entity_type: None,
|
||||||
@@ -982,7 +972,6 @@ pub async fn delete_knowledge_entity(
|
|||||||
EntityListData {
|
EntityListData {
|
||||||
visible_entities,
|
visible_entities,
|
||||||
pagination,
|
pagination,
|
||||||
user,
|
|
||||||
entity_types,
|
entity_types,
|
||||||
content_categories,
|
content_categories,
|
||||||
selected_entity_type: None,
|
selected_entity_type: None,
|
||||||
|
|||||||
@@ -14,16 +14,13 @@ use crate::middlewares::{
|
|||||||
response_middleware::{HtmlError, TemplateResponse},
|
response_middleware::{HtmlError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use common::storage::types::{
|
use common::storage::types::{
|
||||||
conversation::Conversation, ingestion_payload::IngestionPayload, ingestion_task::IngestionTask,
|
ingestion_payload::IngestionPayload, ingestion_task::IngestionTask, scratchpad::Scratchpad,
|
||||||
scratchpad::Scratchpad, user::User,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ScratchpadPageData {
|
pub struct ScratchpadPageData {
|
||||||
user: User,
|
|
||||||
scratchpads: Vec<ScratchpadListItem>,
|
scratchpads: Vec<ScratchpadListItem>,
|
||||||
archived_scratchpads: Vec<ScratchpadArchiveItem>,
|
archived_scratchpads: Vec<ScratchpadArchiveItem>,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
new_scratchpad: Option<ScratchpadDetail>,
|
new_scratchpad: Option<ScratchpadDetail>,
|
||||||
}
|
}
|
||||||
@@ -38,9 +35,7 @@ pub struct ScratchpadListItem {
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ScratchpadDetailData {
|
pub struct ScratchpadDetailData {
|
||||||
user: User,
|
|
||||||
scratchpad: ScratchpadDetail,
|
scratchpad: ScratchpadDetail,
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
is_editing_title: bool,
|
is_editing_title: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +130,6 @@ pub async fn show_scratchpad_page(
|
|||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
||||||
@@ -149,10 +143,8 @@ pub async fn show_scratchpad_page(
|
|||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
"main",
|
"main",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
new_scratchpad: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -160,10 +152,8 @@ pub async fn show_scratchpad_page(
|
|||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
new_scratchpad: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -177,7 +167,6 @@ pub async fn show_scratchpad_modal(
|
|||||||
Query(query): Query<EditTitleQuery>,
|
Query(query): Query<EditTitleQuery>,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
let scratchpad = Scratchpad::get_by_id(&scratchpad_id, &user.id, &state.db).await?;
|
let scratchpad = Scratchpad::get_by_id(&scratchpad_id, &user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_detail = ScratchpadDetail::from(&scratchpad);
|
let scratchpad_detail = ScratchpadDetail::from(&scratchpad);
|
||||||
|
|
||||||
@@ -187,9 +176,7 @@ pub async fn show_scratchpad_modal(
|
|||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"scratchpad/editor_modal.html",
|
"scratchpad/editor_modal.html",
|
||||||
ScratchpadDetailData {
|
ScratchpadDetailData {
|
||||||
user,
|
|
||||||
scratchpad: scratchpad_detail,
|
scratchpad: scratchpad_detail,
|
||||||
conversation_archive,
|
|
||||||
is_editing_title,
|
is_editing_title,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -206,7 +193,6 @@ pub async fn create_scratchpad(
|
|||||||
|
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
||||||
@@ -219,10 +205,8 @@ pub async fn create_scratchpad(
|
|||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
"main",
|
"main",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: Some(ScratchpadDetail::from(&scratchpad)),
|
new_scratchpad: Some(ScratchpadDetail::from(&scratchpad)),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -257,14 +241,11 @@ pub async fn update_scratchpad_title(
|
|||||||
Scratchpad::update_title(&scratchpad_id, &user.id, &form.title, &state.db).await?;
|
Scratchpad::update_title(&scratchpad_id, &user.id, &form.title, &state.db).await?;
|
||||||
|
|
||||||
let scratchpad = Scratchpad::get_by_id(&scratchpad_id, &user.id, &state.db).await?;
|
let scratchpad = Scratchpad::get_by_id(&scratchpad_id, &user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"scratchpad/editor_modal.html",
|
"scratchpad/editor_modal.html",
|
||||||
ScratchpadDetailData {
|
ScratchpadDetailData {
|
||||||
user,
|
|
||||||
scratchpad: ScratchpadDetail::from(&scratchpad),
|
scratchpad: ScratchpadDetail::from(&scratchpad),
|
||||||
conversation_archive,
|
|
||||||
is_editing_title: false,
|
is_editing_title: false,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -279,7 +260,6 @@ pub async fn delete_scratchpad(
|
|||||||
|
|
||||||
// Return the updated main section content
|
// Return the updated main section content
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
@@ -293,10 +273,8 @@ pub async fn delete_scratchpad(
|
|||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
"main",
|
"main",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
new_scratchpad: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -350,7 +328,6 @@ pub async fn ingest_scratchpad(
|
|||||||
|
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
||||||
@@ -374,10 +351,8 @@ pub async fn ingest_scratchpad(
|
|||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
"main",
|
"main",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
new_scratchpad: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -399,7 +374,6 @@ pub async fn archive_scratchpad(
|
|||||||
|
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
||||||
@@ -411,15 +385,59 @@ pub async fn archive_scratchpad(
|
|||||||
Ok(TemplateResponse::new_template(
|
Ok(TemplateResponse::new_template(
|
||||||
"scratchpad/base.html",
|
"scratchpad/base.html",
|
||||||
ScratchpadPageData {
|
ScratchpadPageData {
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
scratchpads: scratchpad_list,
|
||||||
archived_scratchpads: archived_list,
|
archived_scratchpads: archived_list,
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
new_scratchpad: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn restore_scratchpad(
|
||||||
|
RequireUser(user): RequireUser,
|
||||||
|
State(state): State<HtmlState>,
|
||||||
|
Path(scratchpad_id): Path<String>,
|
||||||
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
Scratchpad::restore(&scratchpad_id, &user.id, &state.db).await?;
|
||||||
|
|
||||||
|
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
||||||
|
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
||||||
|
|
||||||
|
let scratchpad_list: Vec<ScratchpadListItem> =
|
||||||
|
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
||||||
|
let archived_list: Vec<ScratchpadArchiveItem> = archived_scratchpads
|
||||||
|
.iter()
|
||||||
|
.map(ScratchpadArchiveItem::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let trigger_payload = serde_json::json!({
|
||||||
|
"toast": {
|
||||||
|
"title": "Scratchpad restored",
|
||||||
|
"description": "The scratchpad is back in your active list.",
|
||||||
|
"type": "info"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let trigger_value = serde_json::to_string(&trigger_payload).unwrap_or_else(|_| {
|
||||||
|
r#"{"toast":{"title":"Scratchpad restored","description":"The scratchpad is back in your active list.","type":"info"}}"#.to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
let template_response = TemplateResponse::new_partial(
|
||||||
|
"scratchpad/base.html",
|
||||||
|
"main",
|
||||||
|
ScratchpadPageData {
|
||||||
|
scratchpads: scratchpad_list,
|
||||||
|
archived_scratchpads: archived_list,
|
||||||
|
new_scratchpad: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut response = template_response.into_response();
|
||||||
|
if let Ok(header_value) = HeaderValue::from_str(&trigger_value) {
|
||||||
|
response.headers_mut().insert(HX_TRIGGER, header_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -509,52 +527,3 @@ mod tests {
|
|||||||
assert_eq!(archive_item.ingested_at, None);
|
assert_eq!(archive_item.ingested_at, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_scratchpad(
|
|
||||||
RequireUser(user): RequireUser,
|
|
||||||
State(state): State<HtmlState>,
|
|
||||||
Path(scratchpad_id): Path<String>,
|
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
|
||||||
Scratchpad::restore(&scratchpad_id, &user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpads = Scratchpad::get_by_user(&user.id, &state.db).await?;
|
|
||||||
let archived_scratchpads = Scratchpad::get_archived_by_user(&user.id, &state.db).await?;
|
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let scratchpad_list: Vec<ScratchpadListItem> =
|
|
||||||
scratchpads.iter().map(ScratchpadListItem::from).collect();
|
|
||||||
let archived_list: Vec<ScratchpadArchiveItem> = archived_scratchpads
|
|
||||||
.iter()
|
|
||||||
.map(ScratchpadArchiveItem::from)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let trigger_payload = serde_json::json!({
|
|
||||||
"toast": {
|
|
||||||
"title": "Scratchpad restored",
|
|
||||||
"description": "The scratchpad is back in your active list.",
|
|
||||||
"type": "info"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let trigger_value = serde_json::to_string(&trigger_payload).unwrap_or_else(|_| {
|
|
||||||
r#"{"toast":{"title":"Scratchpad restored","description":"The scratchpad is back in your active list.","type":"info"}}"#.to_string()
|
|
||||||
});
|
|
||||||
|
|
||||||
let template_response = TemplateResponse::new_partial(
|
|
||||||
"scratchpad/base.html",
|
|
||||||
"main",
|
|
||||||
ScratchpadPageData {
|
|
||||||
user,
|
|
||||||
scratchpads: scratchpad_list,
|
|
||||||
archived_scratchpads: archived_list,
|
|
||||||
conversation_archive,
|
|
||||||
new_scratchpad: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut response = template_response.into_response();
|
|
||||||
if let Ok(header_value) = HeaderValue::from_str(&trigger_value) {
|
|
||||||
response.headers_mut().insert(HX_TRIGGER, header_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ use axum::{
|
|||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use common::storage::types::{
|
use common::storage::types::{
|
||||||
conversation::Conversation,
|
|
||||||
text_content::{deserialize_flexible_id, TextContent},
|
text_content::{deserialize_flexible_id, TextContent},
|
||||||
user::User,
|
|
||||||
StoredObject,
|
StoredObject,
|
||||||
};
|
};
|
||||||
use retrieval_pipeline::{RetrievalConfig, SearchResult, SearchTarget, StrategyOutput};
|
use retrieval_pipeline::{RetrievalConfig, SearchResult, SearchTarget, StrategyOutput};
|
||||||
@@ -194,10 +192,7 @@ pub async fn search_result_handler(
|
|||||||
pub struct AnswerData {
|
pub struct AnswerData {
|
||||||
search_result: Vec<SearchResultForTemplate>,
|
search_result: Vec<SearchResultForTemplate>,
|
||||||
query_param: String,
|
query_param: String,
|
||||||
user: User,
|
|
||||||
conversation_archive: Vec<Conversation>,
|
|
||||||
}
|
}
|
||||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
|
||||||
|
|
||||||
let (search_results_for_template, final_query_param_for_template) = if let Some(actual_query) =
|
let (search_results_for_template, final_query_param_for_template) = if let Some(actual_query) =
|
||||||
params.query
|
params.query
|
||||||
@@ -346,8 +341,6 @@ pub async fn search_result_handler(
|
|||||||
AnswerData {
|
AnswerData {
|
||||||
search_result: search_results_for_template,
|
search_result: search_results_for_template,
|
||||||
query_param: final_query_param_for_template,
|
query_param: final_query_param_for_template,
|
||||||
user,
|
|
||||||
conversation_archive,
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user