Skip to content

Commit eec0b1e

Browse files
skeptrunedevcdxker
authored andcommitted
feature: include cost estimate on pdf view
1 parent c30953e commit eec0b1e

File tree

4 files changed

+72
-18
lines changed

4 files changed

+72
-18
lines changed

pdf2md/server/src/operators/s3.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub async fn get_signed_url(bucket: &Bucket, key: &str) -> Result<String, Servic
4545
custom_queries.insert("response-content-encoding".into(), "utf-8".into());
4646

4747
let url = bucket
48-
.presign_get(key, 3600, Some(custom_queries))
48+
.presign_get(key, 86400, Some(custom_queries))
4949
.await
5050
.map_err(|e| {
5151
log::error!("Could not get signed url {:?}", e);

pdf2md/server/src/templates/demo-ui.html

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
id="upload-form-container"
2424
class="flex justify-center mt-[10vh] sm:mt-[20vh] items-center px-4"
2525
>
26-
<form class="max-w-lg mx-auto">
26+
<form class="max-w-2xl mx-auto">
2727
<div class="text-center">
2828
<h2
2929
class="text-balance text-4xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
3030
>
3131
OCR With Intelligence
3232
</h2>
3333
<p class="mt-2 text-pretty text-lg/8 text-gray-700">
34-
Convert any PDF to LLM-ready Markdown using vision models like GPT-4o.
34+
Convert any PDF to LLM-ready Markdown using thrifty vision models like GPT-4o-mini and Gemini-flash-1.5.
3535
</p>
3636
</div>
3737
<button
@@ -75,10 +75,15 @@
7575
name="model"
7676
class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 bg-white ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-magenta-600 sm:text-sm/6"
7777
>
78-
<option value="openai/gpt-4o-mini" selected>4o-mini</option>
79-
<option value="openai/chatgpt-4o-latest">4o</option>
80-
<option value="google/gemini-flash-1.5-8b">gemini-flash-1.5-8b</option>
81-
<option value="google/gemini-flash-1.5">gemini-flash-1.5</option>
78+
<option value="openai/gpt-4o-mini" selected>
79+
4o-mini ($0.15/M input)
80+
</option>
81+
<option value="google/gemini-flash-1.5-8b">
82+
gemini-flash-1.5-8b ($0.0375/M input)
83+
</option>
84+
<option value="google/gemini-flash-1.5">
85+
gemini-flash-1.5 ($0.075/M input)
86+
</option>
8287
</select>
8388
<label
8489
for="conversion-prompt"
@@ -134,11 +139,11 @@
134139
</div>
135140
</form>
136141
</div>
137-
<div class="px-4">
138-
<div
139-
id="result-container"
140-
class="mt-10 sm:mt-14 md:mt-24 grid grid-cols-2 gap-4 max-w-screen-2xl mx-auto"
141-
>
142+
<div class="px-4 mt-10 sm:mt-14 md:mt-24 max-w-screen-2xl mx-auto">
143+
<div class="col-span-2 flex justify-between">
144+
<p id="document-price" class="text-gray-600 text-sm/6"></p>
145+
</div>
146+
<div id="result-container" class="grid grid-cols-2 gap-4">
142147
<div id="my-pdf"></div>
143148
<div id="markdown-container" class="max-h-[75vh] overflow-y-auto"></div>
144149
</div>

pdf2md/server/static/output.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,10 @@ video {
570570
position: relative;
571571
}
572572

573+
.col-span-2 {
574+
grid-column: span 2 / span 2;
575+
}
576+
573577
.-mx-4 {
574578
margin-left: -1rem;
575579
margin-right: -1rem;
@@ -677,8 +681,8 @@ video {
677681
min-width: 100%;
678682
}
679683

680-
.max-w-lg {
681-
max-width: 32rem;
684+
.max-w-2xl {
685+
max-width: 42rem;
682686
}
683687

684688
.max-w-screen-2xl {

pdf2md/server/static/pdf2md.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
const costsPerM = {
2+
"openai/gpt-4o-mini": {
3+
input_price: 0.15,
4+
output_price: 0.6,
5+
},
6+
"google/gemini-flash-1.5-8b": {
7+
input_price: 0.0375,
8+
output_price: 0.15,
9+
},
10+
"google/gemini-flash-1.5": {
11+
input_price: 0.075,
12+
output_price: 0.3,
13+
},
14+
};
15+
116
const defaultTableRowStr = `
217
<td
318
class="task-id whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0"
@@ -83,6 +98,9 @@ const displayTask = (task) => {
8398
task.pages_processed
8499
);
85100

101+
let completionTokens = 0;
102+
let promptTokens = 0;
103+
86104
sortedPages.forEach((page) => {
87105
const pageContainer = document.createElement("div");
88106
pageContainer.classList.add("page-container");
@@ -91,6 +109,9 @@ const displayTask = (task) => {
91109
const spacerDiv = document.createElement("div");
92110
spacerDiv.classList.add(...["my-4", "h-1", "bg-gray-700"]);
93111
markdownContainer.appendChild(spacerDiv);
112+
113+
promptTokens += page?.usage?.prompt_tokens || 0;
114+
completionTokens += page?.usage?.completion_tokens || 0;
94115
});
95116
if (!sortedPages.length) {
96117
const pageContainer = document.createElement("div");
@@ -99,9 +120,33 @@ const displayTask = (task) => {
99120
"Your file is being converted. We are pinging the server every 5 seconds to check for status updates. See the table below for more detailed status information. Please be patient!";
100121
markdownContainer.appendChild(pageContainer);
101122
}
123+
124+
const modelSelect = document.getElementById("model");
125+
const model = modelSelect.options[modelSelect.selectedIndex].value;
126+
const costPerM = costsPerM[model];
127+
const completionCost = (completionTokens * costPerM.output_price) / 1000000;
128+
const promptCost = (promptTokens * costPerM.input_price) / 1000000;
129+
document.getElementById("document-price").innerText =
130+
`Cost: ${new Intl.NumberFormat("en-US", {
131+
style: "currency",
132+
currency: "USD",
133+
minimumFractionDigits: 5,
134+
}).format(promptCost)} (Prompt) + ${new Intl.NumberFormat("en-US", {
135+
style: "currency",
136+
currency: "USD",
137+
minimumFractionDigits: 5,
138+
}).format(completionCost)} (Completion) = ${new Intl.NumberFormat("en-US", {
139+
style: "currency",
140+
currency: "USD",
141+
minimumFractionDigits: 5,
142+
}).format(completionCost + promptCost)}`;
102143
};
103144

104145
const getTaskPages = async (taskId, taskIdToDisplay) => {
146+
if (!taskId) {
147+
return;
148+
}
149+
105150
try {
106151
let paginationToken = "";
107152
let task = null;
@@ -119,7 +164,7 @@ const getTaskPages = async (taskId, taskIdToDisplay) => {
119164
);
120165
const taskWithPages = await resp.json();
121166
task = taskWithPages;
122-
pages.push(...taskWithPages.pages);
167+
pages.push(...(taskWithPages.pages || []));
123168
paginationToken = taskWithPages.pagination_token;
124169
if (!paginationToken) {
125170
break;
@@ -243,13 +288,13 @@ const updateTaskStatusTable = () => {
243288
row.querySelector(".task-id").innerText = task.id;
244289
row.querySelector(".task-file-name").innerText = task.file_name;
245290
row.querySelector(".task-status").innerText =
246-
task.status.toLowerCase() === "completed"
291+
task.status?.toLowerCase() === "completed"
247292
? task.status
248293
: `${task.status} | Please wait. Checking for updates every 5 seconds.`;
249294
row
250295
.querySelector(".task-status")
251296
.classList.add(
252-
`status-${task.status.split(" ").join("-").toLowerCase()}`
297+
`status-${task.status?.split(" ").join("-").toLowerCase()}`
253298
);
254299
row.querySelector(".task-prompt-tokens").innerText = promptTokens;
255300
row.querySelector(".task-completion-tokens").innerText = completionTokens;
@@ -282,7 +327,7 @@ const refreshTasks = () => {
282327
const taskIdToDisplay = url.searchParams.get("taskId");
283328
tasks.forEach((task) => {
284329
if (
285-
task.status.toLowerCase() === "completed" &&
330+
task.status?.toLowerCase() === "completed" &&
286331
task.pages &&
287332
task.pages.length
288333
) {

0 commit comments

Comments
 (0)