cloudiful_docling_convert/api/
source.rs1use std::collections::BTreeMap;
2
3use serde::Serialize;
4use serde_json::Value;
5
6use crate::document::{ChunkerKind, ChunkingOptions, InputKind, PipelineKind};
7
8use super::docling::DoclingConvertRequest;
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(tag = "kind")]
12pub enum HttpSource {
13 #[serde(rename = "http")]
14 Http {
15 url: String,
16 #[serde(default)]
17 headers: BTreeMap<String, Value>,
18 },
19}
20
21impl HttpSource {
22 pub fn new(url: impl Into<String>) -> Self {
23 Self::Http {
24 url: url.into(),
25 headers: BTreeMap::new(),
26 }
27 }
28}
29
30#[derive(Debug, Clone, Serialize)]
31#[serde(tag = "kind")]
32pub enum TargetKind {
33 #[serde(rename = "inbody")]
34 Inbody {},
35 #[serde(rename = "zip")]
36 Zip {},
37}
38
39#[derive(Debug, Clone, Serialize)]
40pub(crate) struct ConvertDocumentsOptions {
41 pub from_formats: Vec<String>,
42 pub to_formats: Vec<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub pipeline: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub page_range: Option<[u64; 2]>,
47 #[serde(skip_serializing_if = "Option::is_none")]
51 pub picture_description_preset: Option<String>,
52}
53
54#[derive(Debug, Clone, Serialize)]
55pub(crate) struct ConvertSourcesRequest {
56 pub options: ConvertDocumentsOptions,
57 pub sources: Vec<HttpSource>,
58 pub target: TargetKind,
59}
60
61#[derive(Debug, Clone, Serialize)]
62pub(crate) struct ChunkSourcesRequest {
63 pub convert_options: ConvertDocumentsOptions,
64 pub sources: Vec<HttpSource>,
65 pub include_converted_doc: bool,
66 pub target: TargetKind,
67 pub chunking_options: ChunkingRequestOptions,
68}
69
70#[derive(Debug, Clone, Serialize)]
71#[serde(tag = "chunker")]
72pub(crate) enum ChunkingRequestOptions {
73 #[serde(rename = "hybrid")]
74 Hybrid {
75 use_markdown_tables: bool,
76 use_markdown_images: bool,
77 image_placeholder: String,
78 include_raw_text: bool,
79 max_tokens: Option<u32>,
80 tokenizer: String,
81 merge_peers: bool,
82 },
83 #[serde(rename = "hierarchical")]
84 Hierarchical {
85 use_markdown_tables: bool,
86 use_markdown_images: bool,
87 image_placeholder: String,
88 include_raw_text: bool,
89 },
90}
91
92pub(crate) fn convert_options(
93 input_kind: InputKind,
94 request: &DoclingConvertRequest,
95) -> ConvertDocumentsOptions {
96 ConvertDocumentsOptions {
97 from_formats: vec![input_kind.from_formats_value().to_string()],
98 to_formats: request
99 .output_formats
100 .iter()
101 .map(|format| format.as_api_value().to_string())
102 .collect(),
103 pipeline: request.pipeline.map(|pipeline| pipeline.to_string()),
104 page_range: request
105 .page_range
106 .map(|(start, end)| [start as u64, end as u64]),
107 picture_description_preset: request.picture_description_preset.clone(),
108 }
109}
110
111pub(crate) fn target_for(request: &DoclingConvertRequest) -> TargetKind {
112 if request
113 .output_formats
114 .iter()
115 .any(|format| format.is_archive())
116 {
117 TargetKind::Zip {}
118 } else {
119 TargetKind::Inbody {}
120 }
121}
122
123pub(crate) fn chunking_options(
124 chunker: ChunkerKind,
125 options: &ChunkingOptions,
126) -> crate::error::Result<ChunkingRequestOptions> {
127 match chunker {
128 ChunkerKind::Hybrid => {
129 let defaults = ChunkingOptions::hybrid_defaults();
130 Ok(ChunkingRequestOptions::Hybrid {
131 use_markdown_tables: options.use_markdown_tables,
132 use_markdown_images: options.use_markdown_images,
133 image_placeholder: if options.image_placeholder.is_empty() {
134 defaults.image_placeholder
135 } else {
136 options.image_placeholder.clone()
137 },
138 include_raw_text: options.include_raw_text,
139 max_tokens: options.max_tokens,
140 tokenizer: options
141 .tokenizer
142 .clone()
143 .unwrap_or_else(|| defaults.tokenizer.unwrap_or_default()),
144 merge_peers: options.merge_peers,
145 })
146 }
147 ChunkerKind::Hierarchical => Ok(ChunkingRequestOptions::Hierarchical {
148 use_markdown_tables: options.use_markdown_tables,
149 use_markdown_images: options.use_markdown_images,
150 image_placeholder: if options.image_placeholder.is_empty() {
151 ChunkingOptions::hierarchical_defaults().image_placeholder
152 } else {
153 options.image_placeholder.clone()
154 },
155 include_raw_text: options.include_raw_text,
156 }),
157 ChunkerKind::None => Err(crate::error::PdfConvertError::validation_error(
158 "chunker",
159 "chunking options require hybrid or hierarchical",
160 )),
161 }
162}
163
164pub(crate) fn source_request(
165 url: impl Into<String>,
166 input_kind: InputKind,
167 request: &DoclingConvertRequest,
168) -> ConvertSourcesRequest {
169 ConvertSourcesRequest {
170 options: convert_options(input_kind, request),
171 sources: vec![HttpSource::new(url)],
172 target: target_for(request),
173 }
174}
175
176pub(crate) fn chunk_source_request(
177 url: impl Into<String>,
178 input_kind: InputKind,
179 request: &DoclingConvertRequest,
180) -> crate::error::Result<ChunkSourcesRequest> {
181 Ok(ChunkSourcesRequest {
182 convert_options: convert_options(input_kind, request),
183 sources: vec![HttpSource::new(url)],
184 include_converted_doc: false,
185 target: target_for(request),
186 chunking_options: chunking_options(request.chunker, &request.chunking)?,
187 })
188}
189
190impl std::fmt::Display for PipelineKind {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 let value = match self {
193 PipelineKind::Legacy => "legacy",
194 PipelineKind::Standard => "standard",
195 PipelineKind::Vlm => "vlm",
196 PipelineKind::Asr => "asr",
197 };
198 f.write_str(value)
199 }
200}