bookforge-cli 1.8.0

CLI-first EPUB translation engine with deterministic structure rebuild and review loop.
use std::sync::Arc;

use anyhow::Result;
use bookforge_core::{ResolvedRunSettings, scheduler::SchedulerConfig, segment::Segment};
use bookforge_llm::{LlmProvider, SegmentTranslation, TranslationRunConfig};
use bookforge_store::JobStore;

use crate::checkpoint::CheckpointWriter;

use super::{
    CheckpointContext, checkpointing::finalize_writer, translate_and_checkpoint,
    translate_and_checkpoint_batch,
};

pub(crate) struct CheckpointRunContext<'a> {
    pub store: &'a JobStore,
    pub job_id: &'a str,
    pub provider: &'a str,
    pub model: &'a str,
    pub prompt_version: &'a str,
}

pub(crate) fn batch_run_config(
    run_config: &TranslationRunConfig,
    settings: &ResolvedRunSettings,
) -> TranslationRunConfig {
    TranslationRunConfig {
        source_language: run_config.source_language.clone(),
        target_language: run_config.target_language.clone(),
        provider: run_config.provider.clone(),
        model: run_config.model.clone(),
        prompt_version: run_config.prompt_version.clone(),
        temperature: run_config.temperature,
        scheduler: SchedulerConfig {
            concurrency: run_config.scheduler.concurrency,
            max_attempts: settings.provider.provider_max_attempts,
        },
        profile: settings.profile,
        model_context_tokens: settings.provider.model_context_tokens,
        max_output_tokens: settings.provider.max_output_tokens,
        batch_max_output_tokens: settings.provider.batch_max_output_tokens,
        compact_prompts: settings.compact_prompts,
        glossary: run_config.glossary.clone(),
        context: run_config.context,
        context_registry: run_config.context_registry.clone(),
        style: run_config.style.clone(),
        entities: run_config.entities.clone(),
    }
}

pub(crate) async fn run_checkpointed_translation<P>(
    provider: P,
    pending_segments: &[Segment],
    run_config: &TranslationRunConfig,
    settings: &ResolvedRunSettings,
    checkpoint: CheckpointRunContext<'_>,
    progress: Arc<dyn bookforge_core::ProgressSink>,
    batch_enabled: bool,
) -> Result<Vec<SegmentTranslation>>
where
    P: LlmProvider,
{
    if pending_segments.is_empty() {
        return Ok(Vec::new());
    }

    let writer = CheckpointWriter::spawn(checkpoint.store.path().to_path_buf(), progress.clone());
    let sender = writer.sender();
    let checkpoint_context = CheckpointContext {
        store: checkpoint.store,
        job_id: checkpoint.job_id,
        provider: checkpoint.provider,
        model: checkpoint.model,
        prompt_version: checkpoint.prompt_version,
        sender: &sender,
    };
    let translation_result = if batch_enabled {
        let batch_config = batch_run_config(run_config, settings);
        translate_and_checkpoint_batch(
            provider,
            pending_segments,
            &batch_config,
            settings,
            checkpoint_context,
            progress,
        )
        .await
    } else {
        translate_and_checkpoint(provider, pending_segments, run_config, checkpoint_context).await
    };
    finalize_writer(translation_result, sender, writer).await
}