use tuitbot_core::context::graph_expansion::{self, GraphNeighbor, GraphState};
use tuitbot_core::context::winning_dna;
use tuitbot_core::storage::vault_selections;
use crate::state::AppState;
pub(crate) async fn resolve_composer_rag_context(
state: &AppState,
account_id: &str,
selected_node_ids: Option<&[i64]>,
) -> Option<winning_dna::DraftContext> {
let config = match state.load_effective_config(account_id).await {
Ok(c) => c,
Err(e) => {
tracing::warn!("composer RAG: failed to load config: {e}");
return None;
}
};
let keywords = config.business.draft_context_keywords();
let has_selection = selected_node_ids.is_some_and(|ids| !ids.is_empty());
if keywords.is_empty() && !has_selection {
return None;
}
let draft_context = match winning_dna::build_draft_context_with_selection(
&state.db,
account_id,
&keywords,
winning_dna::MAX_ANCESTORS,
winning_dna::RECENCY_HALF_LIFE_DAYS,
selected_node_ids,
)
.await
{
Ok(ctx) => ctx,
Err(e) => {
tracing::warn!("composer RAG: failed to build draft context: {e}");
return None;
}
};
if draft_context.prompt_block.is_empty() {
None
} else {
Some(draft_context)
}
}
pub(crate) async fn resolve_selection_rag_context(
state: &AppState,
account_id: &str,
session_id: &str,
) -> Option<SelectionRagContext> {
let selection =
match vault_selections::get_selection_by_session(&state.db, account_id, session_id).await {
Ok(Some(sel)) => sel,
Ok(None) => {
tracing::debug!(session_id, "selection not found or expired");
return None;
}
Err(e) => {
tracing::warn!(session_id, "failed to fetch selection: {e}");
return None;
}
};
let node_ids = selection.resolved_node_id.map(|id| vec![id]);
let draft_context = resolve_composer_rag_context(state, account_id, node_ids.as_deref()).await;
Some(SelectionRagContext {
draft_context,
selected_text: if selection.selected_text.is_empty() {
None
} else {
Some(selection.selected_text)
},
})
}
pub(crate) async fn resolve_graph_suggestions(
state: &AppState,
account_id: &str,
node_id: i64,
max_neighbors: u32,
) -> GraphSuggestionResult {
match graph_expansion::expand_graph_neighbors(&state.db, account_id, node_id, max_neighbors)
.await
{
Ok(neighbors) if neighbors.is_empty() => GraphSuggestionResult {
neighbors: Vec::new(),
graph_state: GraphState::NoRelatedNotes,
},
Ok(neighbors) => GraphSuggestionResult {
graph_state: GraphState::Available,
neighbors,
},
Err(e) => {
tracing::warn!(node_id, "graph expansion failed: {e}");
GraphSuggestionResult {
neighbors: Vec::new(),
graph_state: GraphState::FallbackActive,
}
}
}
}
pub(crate) struct GraphSuggestionResult {
pub neighbors: Vec<GraphNeighbor>,
pub graph_state: GraphState,
}
pub(crate) struct SelectionRagContext {
pub draft_context: Option<winning_dna::DraftContext>,
pub selected_text: Option<String>,
}