fix: leaner error handling by boxing large variants

This commit is contained in:
Per Stark
2026-06-06 07:59:57 +02:00
parent 4e20da538d
commit ac0d34bfbd
12 changed files with 238 additions and 83 deletions
+56 -6
View File
@@ -9,7 +9,7 @@ use crate::storage::types::file_info::FileError;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum EmbeddingError { pub enum EmbeddingError {
#[error("openai error: {0}")] #[error("openai error: {0}")]
OpenAI(#[from] OpenAIError), OpenAI(Box<OpenAIError>),
#[error("fastembed error: {0}")] #[error("fastembed error: {0}")]
FastEmbed(String), FastEmbed(String),
#[error("task join error: {0}")] #[error("task join error: {0}")]
@@ -24,6 +24,12 @@ pub enum EmbeddingError {
UnknownModel(String), UnknownModel(String),
} }
impl From<OpenAIError> for EmbeddingError {
fn from(err: OpenAIError) -> Self {
Self::OpenAI(Box::new(err))
}
}
impl EmbeddingError { impl EmbeddingError {
pub(crate) fn fastembed(err: impl std::fmt::Display) -> Self { pub(crate) fn fastembed(err: impl std::fmt::Display) -> Self {
Self::FastEmbed(err.to_string()) Self::FastEmbed(err.to_string())
@@ -39,9 +45,9 @@ impl EmbeddingError {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AppError { pub enum AppError {
#[error("database error: {0}")] #[error("database error: {0}")]
Database(#[from] surrealdb::Error), Database(Box<surrealdb::Error>),
#[error("openai error: {0}")] #[error("openai error: {0}")]
OpenAI(#[from] OpenAIError), OpenAI(Box<OpenAIError>),
#[error("embedding error: {0}")] #[error("embedding error: {0}")]
Embedding(#[from] EmbeddingError), Embedding(#[from] EmbeddingError),
#[error("file error: {0}")] #[error("file error: {0}")]
@@ -61,17 +67,47 @@ pub enum AppError {
#[error("io error: {0}")] #[error("io error: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("reqwest error: {0}")] #[error("reqwest error: {0}")]
Reqwest(#[from] reqwest::Error), Reqwest(Box<reqwest::Error>),
#[error("storage error: {0}")] #[error("storage error: {0}")]
Storage(#[from] object_store::Error), Storage(Box<object_store::Error>),
#[error("ingestion processing error: {0}")] #[error("ingestion processing error: {0}")]
Processing(String), Processing(String),
#[error("dom smoothie error: {0}")] #[error("dom smoothie error: {0}")]
DomSmoothie(#[from] dom_smoothie::ReadabilityError), DomSmoothie(Box<dom_smoothie::ReadabilityError>),
#[error("internal service error: {0}")] #[error("internal service error: {0}")]
InternalError(String), InternalError(String),
} }
impl From<surrealdb::Error> for AppError {
fn from(err: surrealdb::Error) -> Self {
Self::Database(Box::new(err))
}
}
impl From<OpenAIError> for AppError {
fn from(err: OpenAIError) -> Self {
Self::OpenAI(Box::new(err))
}
}
impl From<reqwest::Error> for AppError {
fn from(err: reqwest::Error) -> Self {
Self::Reqwest(Box::new(err))
}
}
impl From<object_store::Error> for AppError {
fn from(err: object_store::Error) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<dom_smoothie::ReadabilityError> for AppError {
fn from(err: dom_smoothie::ReadabilityError) -> Self {
Self::DomSmoothie(Box::new(err))
}
}
impl AppError { impl AppError {
/// Builds an [`AppError::InternalError`] from a displayable message. /// Builds an [`AppError::InternalError`] from a displayable message.
#[must_use] #[must_use]
@@ -79,3 +115,17 @@ impl AppError {
Self::InternalError(msg.to_string()) Self::InternalError(msg.to_string())
} }
} }
#[cfg(test)]
mod tests {
use super::AppError;
#[test]
fn app_error_is_reasonably_sized() {
assert!(
std::mem::size_of::<AppError>() <= 64,
"AppError is {} bytes",
std::mem::size_of::<AppError>()
);
}
}
+18 -6
View File
@@ -36,7 +36,7 @@ pub enum FileError {
/// Database operation on the file record failed. /// Database operation on the file record failed.
#[error("surrealdb error: {0}")] #[error("surrealdb error: {0}")]
SurrealError(#[from] surrealdb::Error), SurrealError(Box<surrealdb::Error>),
/// Failed to persist the temporary file to its final location. /// Failed to persist the temporary file to its final location.
#[error("failed to persist file: {0}")] #[error("failed to persist file: {0}")]
@@ -48,7 +48,19 @@ pub enum FileError {
/// The underlying object store operation failed. /// The underlying object store operation failed.
#[error("object store error: {0}")] #[error("object store error: {0}")]
ObjectStore(#[from] ObjectStoreError), ObjectStore(Box<ObjectStoreError>),
}
impl From<surrealdb::Error> for FileError {
fn from(err: surrealdb::Error) -> Self {
Self::SurrealError(Box::new(err))
}
}
impl From<ObjectStoreError> for FileError {
fn from(err: ObjectStoreError) -> Self {
Self::ObjectStore(Box::new(err))
}
} }
stored_object!(FileInfo, "file", { stored_object!(FileInfo, "file", {
@@ -163,7 +175,7 @@ impl FileInfo {
match db_client.get_item::<FileInfo>(id).await { match db_client.get_item::<FileInfo>(id).await {
Ok(Some(file_info)) => Ok(file_info), Ok(Some(file_info)) => Ok(file_info),
Ok(None) => Err(FileError::FileNotFound(id.to_string())), Ok(None) => Err(FileError::FileNotFound(id.to_string())),
Err(e) => Err(FileError::SurrealError(e)), Err(e) => Err(FileError::from(e)),
} }
} }
@@ -233,7 +245,7 @@ impl FileInfo {
if let Ok(existing) = Self::get_by_sha(&sha256, user_id, db_client).await { if let Ok(existing) = Self::get_by_sha(&sha256, user_id, db_client).await {
return Ok(existing); return Ok(existing);
} }
Err(FileError::SurrealError(e)) Err(FileError::from(e))
} }
} }
} }
@@ -263,7 +275,7 @@ impl FileInfo {
storage storage
.delete_prefix(&parent_prefix) .delete_prefix(&parent_prefix)
.await .await
.map_err(AppError::Storage)?; .map_err(AppError::from)?;
info!( info!(
"Removed object prefix {} and its contents via StorageManager", "Removed object prefix {} and its contents via StorageManager",
parent_prefix parent_prefix
@@ -286,7 +298,7 @@ impl FileInfo {
&self, &self,
storage: &StorageManager, storage: &StorageManager,
) -> Result<bytes::Bytes, AppError> { ) -> Result<bytes::Bytes, AppError> {
storage.get(&self.path).await.map_err(AppError::Storage) storage.get(&self.path).await.map_err(AppError::from)
} }
/// Persist bytes to storage using StorageManager. /// Persist bytes to storage using StorageManager.
+18 -18
View File
@@ -183,9 +183,9 @@ impl KnowledgeEntity {
.bind(("sources", source_ids.to_vec())) .bind(("sources", source_ids.to_vec()))
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.take(0) .take(0)
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(entities) Ok(entities)
} }
@@ -203,9 +203,9 @@ impl KnowledgeEntity {
.bind(("table", Self::table_name())) .bind(("table", Self::table_name()))
.bind(("source_id", source_id.to_owned())) .bind(("source_id", source_id.to_owned()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -248,9 +248,9 @@ impl KnowledgeEntity {
.bind(("entity", entity)) .bind(("entity", entity))
.bind(("emb", emb)) .bind(("emb", emb))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -289,11 +289,11 @@ impl KnowledgeEntity {
.bind(("embedding", query_embedding)) .bind(("embedding", query_embedding))
.bind(("user_id", user_id.to_string())) .bind(("user_id", user_id.to_string()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
response = response.check().map_err(AppError::Database)?; response = response.check().map_err(AppError::from)?;
let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::Database)?; let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::from)?;
Ok(rows Ok(rows
.into_iter() .into_iter()
@@ -321,7 +321,7 @@ impl KnowledgeEntity {
let entity: KnowledgeEntity = db_client let entity: KnowledgeEntity = db_client
.get_item(id) .get_item(id)
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.ok_or_else(|| AppError::NotFound(format!("entity {id} not found")))?; .ok_or_else(|| AppError::NotFound(format!("entity {id} not found")))?;
let settings = SystemSettings::get_current(db_client).await?; let settings = SystemSettings::get_current(db_client).await?;
@@ -355,9 +355,9 @@ impl KnowledgeEntity {
.bind(("emb", emb)) .bind(("emb", emb))
.bind(("description", description.to_string())) .bind(("description", description.to_string()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -453,9 +453,9 @@ impl KnowledgeEntity {
KnowledgeEntityEmbedding::table_name() KnowledgeEntityEmbedding::table_name()
)) ))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
db.client db.client
.query(format!( .query(format!(
@@ -463,9 +463,9 @@ impl KnowledgeEntity {
KnowledgeEntityEmbedding::table_name() KnowledgeEntityEmbedding::table_name()
)) ))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
// Perform DB updates in a single transaction // Perform DB updates in a single transaction
info!("Applying embedding updates in a transaction..."); info!("Applying embedding updates in a transaction...");
@@ -504,9 +504,9 @@ impl KnowledgeEntity {
db.client db.client
.query(transaction_query) .query(transaction_query)
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
info!("Re-embedding process for knowledge entities completed successfully."); info!("Re-embedding process for knowledge entities completed successfully.");
Ok(()) Ok(())
@@ -29,8 +29,8 @@ impl KnowledgeEntityEmbedding {
dimension, dimension,
); );
let res = db.client.query(query).await.map_err(AppError::Database)?; let res = db.client.query(query).await.map_err(AppError::from)?;
res.check().map_err(AppError::Database)?; res.check().map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -78,8 +78,8 @@ impl KnowledgeEntityEmbedding {
.query(query) .query(query)
.bind(("entity_id", entity_id.clone())) .bind(("entity_id", entity_id.clone()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let embeddings: Vec<Self> = result.take(0).map_err(AppError::Database)?; let embeddings: Vec<Self> = result.take(0).map_err(AppError::from)?;
Ok(embeddings.into_iter().next()) Ok(embeddings.into_iter().next())
} }
@@ -101,8 +101,8 @@ impl KnowledgeEntityEmbedding {
.query(query) .query(query)
.bind(("entity_ids", entity_ids.to_vec())) .bind(("entity_ids", entity_ids.to_vec()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let embeddings: Vec<Self> = result.take(0).map_err(AppError::Database)?; let embeddings: Vec<Self> = result.take(0).map_err(AppError::from)?;
Ok(embeddings Ok(embeddings
.into_iter() .into_iter()
@@ -123,9 +123,9 @@ impl KnowledgeEntityEmbedding {
.query(query) .query(query)
.bind(("entity_id", entity_id.clone())) .bind(("entity_id", entity_id.clone()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -142,9 +142,9 @@ impl KnowledgeEntityEmbedding {
.query(query) .query(query)
.bind(("source_id", source_id.to_owned())) .bind(("source_id", source_id.to_owned()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
} }
@@ -69,9 +69,9 @@ impl KnowledgeRelationship {
.bind(("source_id", self.metadata.source_id.clone())) .bind(("source_id", self.metadata.source_id.clone()))
.bind(("relationship_type", self.metadata.relationship_type.clone())) .bind(("relationship_type", self.metadata.relationship_type.clone()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -89,9 +89,9 @@ impl KnowledgeRelationship {
.bind(("source_id", source_id.to_owned())) .bind(("source_id", source_id.to_owned()))
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -109,9 +109,9 @@ impl KnowledgeRelationship {
.bind(("id", id.to_owned())) .bind(("id", id.to_owned()))
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let deleted: Vec<KnowledgeRelationship> = let deleted: Vec<KnowledgeRelationship> =
delete_result.take(0).map_err(AppError::Database)?; delete_result.take(0).map_err(AppError::from)?;
if !deleted.is_empty() { if !deleted.is_empty() {
return Ok(()); return Ok(());
@@ -122,9 +122,9 @@ impl KnowledgeRelationship {
.query("SELECT * FROM type::thing('relates_to', $id)") .query("SELECT * FROM type::thing('relates_to', $id)")
.bind(("id", id.to_owned())) .bind(("id", id.to_owned()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let existing: Option<KnowledgeRelationship> = let existing: Option<KnowledgeRelationship> =
exists_result.take(0).map_err(AppError::Database)?; exists_result.take(0).map_err(AppError::from)?;
if existing.is_some() { if existing.is_some() {
Err(AppError::Auth( Err(AppError::Auth(
+16 -16
View File
@@ -55,9 +55,9 @@ impl TextChunk {
.bind(("source_id", source_id.to_owned())) .bind(("source_id", source_id.to_owned()))
.bind(("table", Self::table_name())) .bind(("table", Self::table_name()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -97,9 +97,9 @@ impl TextChunk {
.bind(("chunk", chunk)) .bind(("chunk", chunk))
.bind(("emb", emb)) .bind(("emb", emb))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -140,11 +140,11 @@ impl TextChunk {
.bind(("embedding", query_embedding)) .bind(("embedding", query_embedding))
.bind(("user_id", user_id.to_string())) .bind(("user_id", user_id.to_string()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
response = response.check().map_err(AppError::Database)?; response = response.check().map_err(AppError::from)?;
let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::Database)?; let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::from)?;
Ok(rows Ok(rows
.into_iter() .into_iter()
@@ -208,11 +208,11 @@ impl TextChunk {
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.bind(("limit", limit)) .bind(("limit", limit))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
response = response.check().map_err(AppError::Database)?; response = response.check().map_err(AppError::from)?;
let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::Database)?; let rows: Vec<Row> = response.take::<Vec<Row>>(0).map_err(AppError::from)?;
Ok(rows Ok(rows
.into_iter() .into_iter()
@@ -314,16 +314,16 @@ impl TextChunk {
TextChunkEmbedding::table_name() TextChunkEmbedding::table_name()
)) ))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
db.client db.client
.query(format!("DELETE FROM {};", TextChunkEmbedding::table_name())) .query(format!("DELETE FROM {};", TextChunkEmbedding::table_name()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
// Perform DB updates in a single transaction against the embedding table // Perform DB updates in a single transaction against the embedding table
info!("Applying embedding updates in a transaction..."); info!("Applying embedding updates in a transaction...");
@@ -366,9 +366,9 @@ impl TextChunk {
db.client db.client
.query(transaction_query) .query(transaction_query)
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
info!("Re-embedding process for text chunks completed successfully."); info!("Re-embedding process for text chunks completed successfully.");
Ok(()) Ok(())
@@ -33,8 +33,8 @@ impl TextChunkEmbedding {
dimension, dimension,
); );
let res = db.client.query(query).await.map_err(AppError::Database)?; let res = db.client.query(query).await.map_err(AppError::from)?;
res.check().map_err(AppError::Database)?; res.check().map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -85,9 +85,9 @@ impl TextChunkEmbedding {
.query(query) .query(query)
.bind(("chunk_id", chunk_id.clone())) .bind(("chunk_id", chunk_id.clone()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let embeddings: Vec<Self> = result.take(0).map_err(AppError::Database)?; let embeddings: Vec<Self> = result.take(0).map_err(AppError::from)?;
Ok(embeddings.into_iter().next()) Ok(embeddings.into_iter().next())
} }
@@ -106,9 +106,9 @@ impl TextChunkEmbedding {
.query(query) .query(query)
.bind(("chunk_id", chunk_id.clone())) .bind(("chunk_id", chunk_id.clone()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
@@ -129,9 +129,9 @@ impl TextChunkEmbedding {
.query(query) .query(query)
.bind(("source_id", source_id.to_owned())) .bind(("source_id", source_id.to_owned()))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.check() .check()
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
+7 -7
View File
@@ -115,7 +115,7 @@ impl TextContent {
surrealdb::Datetime::from(now), surrealdb::Datetime::from(now),
)) ))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
if updated.is_none() { if updated.is_none() {
return Err(AppError::NotFound(format!("text content {id} not found"))); return Err(AppError::NotFound(format!("text content {id} not found")));
@@ -138,10 +138,10 @@ impl TextContent {
.bind(("file_id", file_id.to_owned())) .bind(("file_id", file_id.to_owned()))
.bind(("exclude_id", exclude_id.to_owned())) .bind(("exclude_id", exclude_id.to_owned()))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let existing: Option<surrealdb::sql::Thing> = let existing: Option<surrealdb::sql::Thing> =
response.take(0).map_err(AppError::Database)?; response.take(0).map_err(AppError::from)?;
Ok(existing.is_some()) Ok(existing.is_some())
} }
@@ -193,9 +193,9 @@ impl TextContent {
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.bind(("limit", limit)) .bind(("limit", limit))
.await .await
.map_err(AppError::Database)? .map_err(AppError::from)?
.take(0) .take(0)
.map_err(AppError::Database) .map_err(AppError::from)
} }
/// Builds a fallback display label for a source id when no matching content row exists. /// Builds a fallback display label for a source id when no matching content row exists.
@@ -239,9 +239,9 @@ impl TextContent {
.bind(("user_id", user_id.to_owned())) .bind(("user_id", user_id.to_owned()))
.bind(("record_ids", record_ids)) .bind(("record_ids", record_ids))
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
let contents: Vec<SourceLabelRow> = response.take(0).map_err(AppError::Database)?; let contents: Vec<SourceLabelRow> = response.take(0).map_err(AppError::from)?;
tracing::debug!( tracing::debug!(
source_id_count = source_ids.len(), source_id_count = source_ids.len(),
+1 -1
View File
@@ -688,7 +688,7 @@ impl User {
db.delete_item::<IngestionTask>(id) db.delete_item::<IngestionTask>(id)
.await .await
.map_err(AppError::Database)?; .map_err(AppError::from)?;
Ok(()) Ok(())
} }
+94
View File
@@ -44,6 +44,7 @@
--leading-snug: 1.375; --leading-snug: 1.375;
--leading-relaxed: 1.625; --leading-relaxed: 1.625;
--ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--default-transition-duration: 150ms; --default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -284,6 +285,37 @@
} }
} }
} }
.drawer-open {
> .drawer-side {
overflow-y: auto;
}
> .drawer-toggle {
display: none;
& ~ .drawer-side {
pointer-events: auto;
visibility: visible;
position: sticky;
display: block;
width: auto;
overscroll-behavior: auto;
opacity: 100%;
& > .drawer-overlay {
cursor: default;
background-color: transparent;
}
& > *:not(.drawer-overlay) {
translate: 0%;
[dir="rtl"] & {
translate: 0%;
}
}
}
&:checked ~ .drawer-side {
pointer-events: auto;
visibility: visible;
}
}
}
.drawer-toggle { .drawer-toggle {
position: fixed; position: fixed;
height: calc(0.25rem * 0); height: calc(0.25rem * 0);
@@ -1042,6 +1074,22 @@
grid-row-start: 1; grid-row-start: 1;
min-width: calc(0.25rem * 0); min-width: calc(0.25rem * 0);
} }
.chat-image {
grid-row: span 2 / span 2;
align-self: flex-end;
}
.chat-footer {
grid-row-start: 3;
display: flex;
gap: calc(0.25rem * 1);
font-size: 0.6875rem;
}
.chat-header {
grid-row-start: 1;
display: flex;
gap: calc(0.25rem * 1);
font-size: 0.6875rem;
}
.container { .container {
width: 100%; width: 100%;
@media (width >= 40rem) { @media (width >= 40rem) {
@@ -1748,6 +1796,9 @@
.w-10 { .w-10 {
width: calc(var(--spacing) * 10); width: calc(var(--spacing) * 10);
} }
.w-11 {
width: calc(var(--spacing) * 11);
}
.w-11\/12 { .w-11\/12 {
width: calc(11/12 * 100%); width: calc(11/12 * 100%);
} }
@@ -1811,6 +1862,9 @@
.flex-none { .flex-none {
flex: none; flex: none;
} }
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 { .flex-shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
@@ -1823,6 +1877,13 @@
.grow { .grow {
flex-grow: 1; flex-grow: 1;
} }
.border-collapse {
border-collapse: collapse;
}
.-translate-y-1 {
--tw-translate-y: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 { .-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1); --tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1895,6 +1956,9 @@
.justify-start { .justify-start {
justify-content: flex-start; justify-content: flex-start;
} }
.gap-0 {
gap: calc(var(--spacing) * 0);
}
.gap-0\.5 { .gap-0\.5 {
gap: calc(var(--spacing) * 0.5); gap: calc(var(--spacing) * 0.5);
} }
@@ -2027,6 +2091,9 @@
.border-base-200 { .border-base-200 {
border-color: var(--color-base-200); border-color: var(--color-base-200);
} }
.border-base-content {
border-color: var(--color-base-content);
}
.border-base-content\/10 { .border-base-content\/10 {
border-color: var(--color-base-content); border-color: var(--color-base-content);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -2063,6 +2130,9 @@
.bg-transparent { .bg-transparent {
background-color: transparent; background-color: transparent;
} }
.bg-warning {
background-color: var(--color-warning);
}
.bg-warning\/10 { .bg-warning\/10 {
background-color: var(--color-warning); background-color: var(--color-warning);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -2081,6 +2151,9 @@
.loading-spinner { .loading-spinner {
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
} }
.mask-repeat {
mask-repeat: repeat;
}
.fill-current { .fill-current {
fill: currentcolor; fill: currentcolor;
} }
@@ -2111,6 +2184,9 @@
.p-8 { .p-8 {
padding: calc(var(--spacing) * 8); padding: calc(var(--spacing) * 8);
} }
.px-1 {
padding-inline: calc(var(--spacing) * 1);
}
.px-1\.5 { .px-1\.5 {
padding-inline: calc(var(--spacing) * 1.5); padding-inline: calc(var(--spacing) * 1.5);
} }
@@ -2265,6 +2341,9 @@
--tw-tracking: var(--tracking-widest); --tw-tracking: var(--tracking-widest);
letter-spacing: var(--tracking-widest); letter-spacing: var(--tracking-widest);
} }
.text-wrap {
text-wrap: wrap;
}
.break-words { .break-words {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
@@ -2331,6 +2410,17 @@
.italic { .italic {
font-style: italic; font-style: italic;
} }
.underline {
text-decoration-line: underline;
}
.swap-active {
.swap-off {
opacity: 0%;
}
.swap-on {
opacity: 100%;
}
}
.opacity-0 { .opacity-0 {
opacity: 0%; opacity: 0%;
} }
@@ -2424,6 +2514,10 @@
--tw-duration: 300ms; --tw-duration: 300ms;
transition-duration: 300ms; transition-duration: 300ms;
} }
.ease-in-out {
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
.ease-out { .ease-out {
--tw-ease: var(--ease-out); --tw-ease: var(--ease-out);
transition-timing-function: var(--ease-out); transition-timing-function: var(--ease-out);
+1 -2
View File
@@ -233,8 +233,7 @@ fn plan_embedding_settings_update(
.as_deref() .as_deref()
.map(str::trim) .map(str::trim)
.filter(|value| !value.is_empty()) .filter(|value| !value.is_empty())
.map(ToOwned::to_owned) .map_or_else(|| current.embedding_model.clone(), ToOwned::to_owned);
.unwrap_or_else(|| current.embedding_model.clone());
if !is_valid_fastembed_model_code(&embedding_model) { if !is_valid_fastembed_model_code(&embedding_model) {
return Err(AppError::Validation(format!( return Err(AppError::Validation(format!(
@@ -77,7 +77,7 @@ pub async fn extract_text_from_file(
let file_bytes = storage let file_bytes = storage
.get(&file_info.path) .get(&file_info.path)
.await .await
.map_err(AppError::Storage)?; .map_err(AppError::from)?;
let local_path = resolve_existing_local_path(storage, &file_info.path).await; let local_path = resolve_existing_local_path(storage, &file_info.path).await;
match file_info.mime_type.as_str() { match file_info.mime_type.as_str() {