Replace all console::style() calls in command modules with the centralized Theme API and render:: utility functions. This ensures consistent color behavior across the entire CLI, proper NO_COLOR/--color never support via the LoreRenderer singleton, and eliminates duplicated formatting code. Changes per module: - count.rs: Theme for table headers, render::format_number replacing local duplicate. Removed local format_number implementation. - doctor.rs: Theme::success/warning/error for check status symbols and messages. Unicode escapes for check/warning/cross symbols. - drift.rs: Theme::bold/error/success for drift detection headers and status messages. - embed.rs: Compact output format — headline with count, zero-suppressed detail lines, 'nothing to embed' short-circuit for no-op runs. - generate_docs.rs: Same compact pattern — headline + detail + hint for next step. No-op short-circuit when regenerated==0. - ingest.rs: Theme for project summaries, sync status, dry-run preview. All console::style -> Theme replacements. - list.rs: Replace comfy-table with render::LoreTable for issue/MR listing. Remove local colored_cell, colored_cell_hex, format_relative_time, truncate_with_ellipsis, and format_labels (all moved to render.rs). - list_tests.rs: Update test assertions to use render:: functions. - search.rs: Add render_snippet() for FTS5 <mark> tag highlighting via Theme::bold().underline(). Compact result layout with type badges. - show.rs: Theme for entity detail views, delegate format_date and wrap_text to render module. - stats.rs: Section-based layout using render::section_divider. Compact middle-dot format for document counts. Color-coded embedding coverage percentage (green >=95%, yellow >=50%, red <50%). - sync.rs: Compact sync summary — headline with counts and elapsed time, zero-suppressed detail lines, visually prominent error-only section. - sync_status.rs: Theme for run history headers, removed local format_number duplicate. - timeline.rs: Theme for headers/footers, render:: for date/truncate, standard format! padding replacing console::pad_str. - who.rs: Theme for all expert/workload/active/overlap/review output modes, render:: for relative time and truncation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
142 lines
4.1 KiB
Rust
142 lines
4.1 KiB
Rust
use crate::cli::render::Theme;
|
|
use serde::Serialize;
|
|
|
|
use crate::Config;
|
|
use crate::cli::robot::RobotMeta;
|
|
use crate::core::db::{LATEST_SCHEMA_VERSION, create_connection, get_schema_version};
|
|
use crate::core::error::{LoreError, Result};
|
|
use crate::core::paths::get_db_path;
|
|
use crate::core::shutdown::ShutdownSignal;
|
|
use crate::embedding::ollama::{OllamaClient, OllamaConfig};
|
|
use crate::embedding::pipeline::{DEFAULT_EMBED_CONCURRENCY, embed_documents};
|
|
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub struct EmbedCommandResult {
|
|
pub docs_embedded: usize,
|
|
pub chunks_embedded: usize,
|
|
pub failed: usize,
|
|
pub skipped: usize,
|
|
}
|
|
|
|
pub async fn run_embed(
|
|
config: &Config,
|
|
full: bool,
|
|
retry_failed: bool,
|
|
progress_callback: Option<Box<dyn Fn(usize, usize)>>,
|
|
signal: &ShutdownSignal,
|
|
) -> Result<EmbedCommandResult> {
|
|
let db_path = get_db_path(config.storage.db_path.as_deref());
|
|
let conn = create_connection(&db_path)?;
|
|
|
|
let schema_version = get_schema_version(&conn);
|
|
if schema_version < LATEST_SCHEMA_VERSION {
|
|
return Err(LoreError::MigrationFailed {
|
|
version: schema_version,
|
|
message: format!(
|
|
"Database is at schema version {schema_version} but {LATEST_SCHEMA_VERSION} is required. \
|
|
Run 'lore migrate' first."
|
|
),
|
|
source: None,
|
|
});
|
|
}
|
|
|
|
let ollama_config = OllamaConfig {
|
|
base_url: config.embedding.base_url.clone(),
|
|
model: config.embedding.model.clone(),
|
|
..OllamaConfig::default()
|
|
};
|
|
let client = OllamaClient::new(ollama_config);
|
|
|
|
client.health_check().await?;
|
|
|
|
if full {
|
|
conn.execute_batch(
|
|
"BEGIN;
|
|
DELETE FROM embedding_metadata;
|
|
DELETE FROM embeddings;
|
|
COMMIT;",
|
|
)?;
|
|
} else if retry_failed {
|
|
// DELETE (not UPDATE) so the LEFT JOIN in find_pending_documents returns NULL,
|
|
// making the doc appear pending again. UPDATE would leave a matching row that
|
|
// still satisfies the config-param check, making the doc permanently invisible.
|
|
conn.execute_batch(
|
|
"BEGIN;
|
|
DELETE FROM embeddings WHERE rowid / 1000 IN (
|
|
SELECT DISTINCT document_id FROM embedding_metadata
|
|
WHERE last_error IS NOT NULL
|
|
);
|
|
DELETE FROM embedding_metadata WHERE last_error IS NOT NULL;
|
|
COMMIT;",
|
|
)?;
|
|
}
|
|
|
|
let model_name = &config.embedding.model;
|
|
let concurrency = if config.embedding.concurrency > 0 {
|
|
config.embedding.concurrency as usize
|
|
} else {
|
|
DEFAULT_EMBED_CONCURRENCY
|
|
};
|
|
let result = embed_documents(
|
|
&conn,
|
|
&client,
|
|
model_name,
|
|
concurrency,
|
|
progress_callback,
|
|
signal,
|
|
)
|
|
.await?;
|
|
|
|
Ok(EmbedCommandResult {
|
|
docs_embedded: result.docs_embedded,
|
|
chunks_embedded: result.chunks_embedded,
|
|
failed: result.failed,
|
|
skipped: result.skipped,
|
|
})
|
|
}
|
|
|
|
pub fn print_embed(result: &EmbedCommandResult) {
|
|
if result.docs_embedded == 0 && result.failed == 0 && result.skipped == 0 {
|
|
println!(
|
|
"\n {} nothing to embed",
|
|
Theme::success().bold().render("Embedding")
|
|
);
|
|
return;
|
|
}
|
|
|
|
println!(
|
|
"\n {} {} documents ({} chunks)",
|
|
Theme::success().bold().render("Embedded"),
|
|
Theme::bold().render(&result.docs_embedded.to_string()),
|
|
result.chunks_embedded
|
|
);
|
|
if result.failed > 0 {
|
|
println!(
|
|
" {}",
|
|
Theme::error().render(&format!("{} failed", result.failed))
|
|
);
|
|
}
|
|
if result.skipped > 0 {
|
|
println!(
|
|
" {}",
|
|
Theme::dim().render(&format!("{} skipped", result.skipped))
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct EmbedJsonOutput<'a> {
|
|
ok: bool,
|
|
data: &'a EmbedCommandResult,
|
|
meta: RobotMeta,
|
|
}
|
|
|
|
pub fn print_embed_json(result: &EmbedCommandResult, elapsed_ms: u64) {
|
|
let output = EmbedJsonOutput {
|
|
ok: true,
|
|
data: result,
|
|
meta: RobotMeta { elapsed_ms },
|
|
};
|
|
println!("{}", serde_json::to_string(&output).unwrap());
|
|
}
|