chat: scope insight lookup by library_id to fix regen-shadow bug
When a photo exists in more than one library and the user
regenerates its insight from library A's chat, the regenerate
streams cleanly, store_insight flips library A's old row to
is_current=false, and inserts a new is_current=true row tagged
(library A, rel_path). On the next history fetch the user sees
their old transcript — the regenerate appears to vanish.
The cause: get_insight(file_path) filters on rel_path + is_current
only, so library B's untouched is_current=true row for the same
rel_path satisfies the query and gets returned by SQLite's .first()
ahead of A's new row. Because get_insight is also what
chat_turn_stream uses to decide bootstrap vs. continuation, the
next chat turn after the shadow hit also routes against the
wrong insight, so update_training_messages corrupts library B's
transcript with library A's chat.
Fix: add get_current_insight_for_library(library_id, file_path)
filtered on (library_id, rel_path, is_current=true) and route the
chat surface (load_history, chat_turn{,_stream}, rewind_history)
through it. load_history falls back to the cross-library
get_insight when the scoped lookup misses — preserves the
"scalar data merges across libraries" intent for the case where
the active library has no insight but another does. The path-only
get_insight stays for callers that don't have library context
(populate_knowledge, the photo-grid metadata fetch).
chat_history_handler stops dropping the parsed library on the
floor and threads it through. Single-library deploys see no
behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -874,15 +874,21 @@ pub async fn chat_history_handler(
|
||||
query: web::Query<ChatHistoryQuery>,
|
||||
app_state: web::Data<AppState>,
|
||||
) -> impl Responder {
|
||||
// library param parsed for parity with other insight endpoints, even
|
||||
// though load_history currently keys on file_path alone (matches the
|
||||
// existing get_insight DAO contract).
|
||||
let _library = libraries::resolve_library_param(&app_state, query.library.as_deref())
|
||||
// library_id scopes the lookup so a regenerate on this library
|
||||
// isn't shadowed by an untouched is_current=true row in another
|
||||
// library for the same rel_path. load_history falls back to the
|
||||
// cross-library lookup when the scoped one misses, so a photo
|
||||
// with no insight in this library but one in another still
|
||||
// surfaces (the "show this photo's primary insight" merge case).
|
||||
let library = libraries::resolve_library_param(&app_state, query.library.as_deref())
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| app_state.primary_library());
|
||||
|
||||
match app_state.insight_chat.load_history(&query.path) {
|
||||
match app_state
|
||||
.insight_chat
|
||||
.load_history(library.id, &query.path)
|
||||
{
|
||||
Ok(view) => HttpResponse::Ok().json(ChatHistoryHttpResponse {
|
||||
messages: view
|
||||
.messages
|
||||
|
||||
Reference in New Issue
Block a user