feature/insight-chat-improvements #83
@@ -1209,7 +1209,10 @@ pub(crate) fn apply_system_prompt_override(
|
|||||||
messages: &mut Vec<ChatMessage>,
|
messages: &mut Vec<ChatMessage>,
|
||||||
override_prompt: Option<&str>,
|
override_prompt: Option<&str>,
|
||||||
) -> Option<SystemPromptStash> {
|
) -> Option<SystemPromptStash> {
|
||||||
let prompt = override_prompt.map(str::trim).filter(|s| !s.is_empty())?.to_string();
|
let prompt = override_prompt
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|s| !s.is_empty())?
|
||||||
|
.to_string();
|
||||||
if let Some(first) = messages.first_mut()
|
if let Some(first) = messages.first_mut()
|
||||||
&& first.role == "system"
|
&& first.role == "system"
|
||||||
{
|
{
|
||||||
@@ -1505,10 +1508,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_override_no_op_when_none() {
|
fn apply_override_no_op_when_none() {
|
||||||
let mut msgs = vec![
|
let mut msgs = vec![ChatMessage::system("sys"), ChatMessage::user("hi")];
|
||||||
ChatMessage::system("sys"),
|
|
||||||
ChatMessage::user("hi"),
|
|
||||||
];
|
|
||||||
let stash = apply_system_prompt_override(&mut msgs, None);
|
let stash = apply_system_prompt_override(&mut msgs, None);
|
||||||
assert!(stash.is_none());
|
assert!(stash.is_none());
|
||||||
assert_eq!(msgs[0].content, "sys");
|
assert_eq!(msgs[0].content, "sys");
|
||||||
@@ -1524,13 +1524,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn restore_override_replaces_back() {
|
fn restore_override_replaces_back() {
|
||||||
let mut msgs = vec![
|
let mut msgs = vec![ChatMessage::system("new"), ChatMessage::user("hi")];
|
||||||
ChatMessage::system("new"),
|
|
||||||
ChatMessage::user("hi"),
|
|
||||||
];
|
|
||||||
restore_system_prompt_override(
|
restore_system_prompt_override(
|
||||||
&mut msgs,
|
&mut msgs,
|
||||||
Some(SystemPromptStash::Replaced { original: "original".to_string() }),
|
Some(SystemPromptStash::Replaced {
|
||||||
|
original: "original".to_string(),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
assert_eq!(msgs[0].content, "original");
|
assert_eq!(msgs[0].content, "original");
|
||||||
assert_eq!(msgs.len(), 2);
|
assert_eq!(msgs.len(), 2);
|
||||||
@@ -1538,10 +1537,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn restore_override_pops_synthetic() {
|
fn restore_override_pops_synthetic() {
|
||||||
let mut msgs = vec![
|
let mut msgs = vec![ChatMessage::system("new"), ChatMessage::user("hi")];
|
||||||
ChatMessage::system("new"),
|
|
||||||
ChatMessage::user("hi"),
|
|
||||||
];
|
|
||||||
restore_system_prompt_override(&mut msgs, Some(SystemPromptStash::Prepended));
|
restore_system_prompt_override(&mut msgs, Some(SystemPromptStash::Prepended));
|
||||||
assert_eq!(msgs.len(), 1);
|
assert_eq!(msgs.len(), 1);
|
||||||
assert_eq!(msgs[0].role, "user");
|
assert_eq!(msgs[0].role, "user");
|
||||||
|
|||||||
@@ -1773,8 +1773,7 @@ Return ONLY the summary, nothing else."#,
|
|||||||
let has_date = args.get("date").is_some()
|
let has_date = args.get("date").is_some()
|
||||||
|| args.get("start_ts").is_some()
|
|| args.get("start_ts").is_some()
|
||||||
|| args.get("end_ts").is_some();
|
|| args.get("end_ts").is_some();
|
||||||
let has_contact = args.get("contact").is_some()
|
let has_contact = args.get("contact").is_some() || args.get("contact_id").is_some();
|
||||||
|| args.get("contact_id").is_some();
|
|
||||||
if has_date || has_contact {
|
if has_date || has_contact {
|
||||||
return "Error: search_messages needs a 'query' (keywords/phrase). \
|
return "Error: search_messages needs a 'query' (keywords/phrase). \
|
||||||
To fetch messages around a date or from a contact without keywords, \
|
To fetch messages around a date or from a contact without keywords, \
|
||||||
@@ -1815,7 +1814,13 @@ Return ONLY the summary, nothing else."#,
|
|||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"tool_search_messages: query='{}', mode={}, contact_id={:?}, range=[{:?}, {:?}], user_limit={}, fetch_limit={}",
|
"tool_search_messages: query='{}', mode={}, contact_id={:?}, range=[{:?}, {:?}], user_limit={}, fetch_limit={}",
|
||||||
query, mode, contact_id, start_ts, end_ts, user_limit, fetch_limit
|
query,
|
||||||
|
mode,
|
||||||
|
contact_id,
|
||||||
|
start_ts,
|
||||||
|
end_ts,
|
||||||
|
user_limit,
|
||||||
|
fetch_limit
|
||||||
);
|
);
|
||||||
|
|
||||||
let hits = match self
|
let hits = match self
|
||||||
@@ -1857,7 +1862,11 @@ Return ONLY the summary, nothing else."#,
|
|||||||
"Found {} messages (mode: {}{}):\n\n",
|
"Found {} messages (mode: {}{}):\n\n",
|
||||||
filtered.len(),
|
filtered.len(),
|
||||||
mode,
|
mode,
|
||||||
if has_date_filter { ", date-filtered" } else { "" }
|
if has_date_filter {
|
||||||
|
", date-filtered"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
));
|
));
|
||||||
for h in filtered {
|
for h in filtered {
|
||||||
let date = chrono::DateTime::from_timestamp(h.date, 0)
|
let date = chrono::DateTime::from_timestamp(h.date, 0)
|
||||||
@@ -3006,7 +3015,7 @@ Return ONLY the summary, nothing else."#,
|
|||||||
Some(s) if !s.trim().is_empty() => s.trim().to_string(),
|
Some(s) if !s.trim().is_empty() => s.trim().to_string(),
|
||||||
_ => String::from(
|
_ => String::from(
|
||||||
"You are reconstructing a memory from a photo. Use the gathered \
|
"You are reconstructing a memory from a photo. Use the gathered \
|
||||||
context to write a thoughtful summary; you decide voice, length, and shape."
|
context to write a thoughtful summary; you decide voice, length, and shape.",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,10 +79,7 @@ pub trait DailySummaryDao: Sync + Send {
|
|||||||
/// Cheap presence check — returns true iff at least one daily summary row
|
/// Cheap presence check — returns true iff at least one daily summary row
|
||||||
/// exists. Used by gating logic that only needs "is the table empty?",
|
/// exists. Used by gating logic that only needs "is the table empty?",
|
||||||
/// avoiding a `COUNT(*)` full scan on large corpora.
|
/// avoiding a `COUNT(*)` full scan on large corpora.
|
||||||
fn has_any_summaries(
|
fn has_any_summaries(&mut self, context: &opentelemetry::Context) -> Result<bool, DbError>;
|
||||||
&mut self,
|
|
||||||
context: &opentelemetry::Context,
|
|
||||||
) -> Result<bool, DbError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqliteDailySummaryDao {
|
pub struct SqliteDailySummaryDao {
|
||||||
@@ -477,11 +474,10 @@ impl DailySummaryDao for SqliteDailySummaryDao {
|
|||||||
one: i32,
|
one: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
let rows: Vec<ProbeResult> = diesel::sql_query(
|
let rows: Vec<ProbeResult> =
|
||||||
"SELECT 1 as one FROM daily_conversation_summaries LIMIT 1",
|
diesel::sql_query("SELECT 1 as one FROM daily_conversation_summaries LIMIT 1")
|
||||||
)
|
.load(conn.deref_mut())
|
||||||
.load(conn.deref_mut())
|
.map_err(|e| anyhow::anyhow!("Failed to probe daily summaries: {}", e))?;
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to probe daily summaries: {}", e))?;
|
|
||||||
|
|
||||||
Ok(!rows.is_empty())
|
Ok(!rows.is_empty())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1718,7 +1718,12 @@ mod tests {
|
|||||||
// Mock — files.rs tests don't exercise the date-override endpoints.
|
// Mock — files.rs tests don't exercise the date-override endpoints.
|
||||||
// Returning a synthetic row keeps the trait satisfied without
|
// Returning a synthetic row keeps the trait satisfied without
|
||||||
// depending on private DbError constructors.
|
// depending on private DbError constructors.
|
||||||
Ok(mock_exif_row(library_id, rel_path, Some(date_taken), Some("manual".to_string())))
|
Ok(mock_exif_row(
|
||||||
|
library_id,
|
||||||
|
rel_path,
|
||||||
|
Some(date_taken),
|
||||||
|
Some("manual".to_string()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_manual_date_taken(
|
fn clear_manual_date_taken(
|
||||||
|
|||||||
@@ -995,10 +995,8 @@ async fn upload_image(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let perceptual = perceptual_hash::compute(&uploaded_path);
|
let perceptual = perceptual_hash::compute(&uploaded_path);
|
||||||
let resolved_date = date_resolver::resolve_date_taken(
|
let resolved_date =
|
||||||
&uploaded_path,
|
date_resolver::resolve_date_taken(&uploaded_path, exif_data.date_taken);
|
||||||
exif_data.date_taken,
|
|
||||||
);
|
|
||||||
let insert_exif = InsertImageExif {
|
let insert_exif = InsertImageExif {
|
||||||
library_id: target_library.id,
|
library_id: target_library.id,
|
||||||
file_path: relative_path.clone(),
|
file_path: relative_path.clone(),
|
||||||
@@ -1022,8 +1020,7 @@ async fn upload_image(
|
|||||||
size_bytes,
|
size_bytes,
|
||||||
phash_64: perceptual.map(|h| h.phash_64),
|
phash_64: perceptual.map(|h| h.phash_64),
|
||||||
dhash_64: perceptual.map(|h| h.dhash_64),
|
dhash_64: perceptual.map(|h| h.dhash_64),
|
||||||
date_taken_source: resolved_date
|
date_taken_source: resolved_date.map(|r| r.source.as_str().to_string()),
|
||||||
.map(|r| r.source.as_str().to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut dao) = exif_dao.lock() {
|
if let Ok(mut dao) = exif_dao.lock() {
|
||||||
|
|||||||
@@ -213,10 +213,7 @@ pub fn extract_date_from_filename(filename: &str) -> Option<DateTime<FixedOffset
|
|||||||
// dispatch on the source-app prefix instead.
|
// dispatch on the source-app prefix instead.
|
||||||
const NON_TIMESTAMP_PREFIXES: &[&str] = &["snapchat-"];
|
const NON_TIMESTAMP_PREFIXES: &[&str] = &["snapchat-"];
|
||||||
let lower = filename.to_ascii_lowercase();
|
let lower = filename.to_ascii_lowercase();
|
||||||
if NON_TIMESTAMP_PREFIXES
|
if NON_TIMESTAMP_PREFIXES.iter().any(|p| lower.starts_with(p)) {
|
||||||
.iter()
|
|
||||||
.any(|p| lower.starts_with(p))
|
|
||||||
{
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user