Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
81dbc658
Commit
81dbc658
authored
Apr 06, 2024
by
Timothy J. Baek
Browse files
refac: pdf generation
parent
d001d7af
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
104 additions
and
51 deletions
+104
-51
backend/apps/web/routers/utils.py
backend/apps/web/routers/utils.py
+56
-8
backend/requirements.txt
backend/requirements.txt
+2
-0
backend/static/fonts/NotoSans-Bold.ttf
backend/static/fonts/NotoSans-Bold.ttf
+0
-0
backend/static/fonts/NotoSans-Italic.ttf
backend/static/fonts/NotoSans-Italic.ttf
+0
-0
backend/static/fonts/NotoSans-Regular.ttf
backend/static/fonts/NotoSans-Regular.ttf
+0
-0
backend/static/fonts/NotoSansJP-Regular.ttf
backend/static/fonts/NotoSansJP-Regular.ttf
+0
-0
backend/static/fonts/NotoSansKR-Regular.ttf
backend/static/fonts/NotoSansKR-Regular.ttf
+0
-0
src/lib/apis/utils/index.ts
src/lib/apis/utils/index.ts
+26
-0
src/lib/components/layout/Navbar/Menu.svelte
src/lib/components/layout/Navbar/Menu.svelte
+20
-43
No files found.
backend/apps/web/routers/utils.py
View file @
81dbc658
from
fastapi
import
APIRouter
,
UploadFile
,
File
,
BackgroundTasks
from
fastapi
import
APIRouter
,
UploadFile
,
File
,
Response
from
fastapi
import
Depends
,
HTTPException
,
status
from
starlette.responses
import
StreamingResponse
,
FileResponse
from
pydantic
import
BaseModel
from
fpdf
import
FPDF
import
markdown
import
requests
import
os
import
aiohttp
import
json
from
utils.utils
import
get_admin_user
...
...
@@ -18,7 +13,7 @@ from utils.misc import calculate_sha256, get_gravatar_url
from
config
import
OLLAMA_BASE_URLS
,
DATA_DIR
,
UPLOAD_DIR
from
constants
import
ERROR_MESSAGES
from
typing
import
List
router
=
APIRouter
()
...
...
@@ -41,6 +36,59 @@ async def get_html_from_markdown(
return
{
"html"
:
markdown
.
markdown
(
form_data
.
md
)}
class
ChatForm
(
BaseModel
):
title
:
str
messages
:
List
[
dict
]
@
router
.
post
(
"/pdf"
)
async
def
download_chat_as_pdf
(
form_data
:
ChatForm
,
):
pdf
=
FPDF
()
pdf
.
add_page
()
STATIC_DIR
=
"./static"
FONTS_DIR
=
f
"
{
STATIC_DIR
}
/fonts"
pdf
.
add_font
(
"NotoSans"
,
""
,
f
"
{
FONTS_DIR
}
/NotoSans-Regular.ttf"
)
pdf
.
add_font
(
"NotoSans"
,
"b"
,
f
"
{
FONTS_DIR
}
/NotoSans-Bold.ttf"
)
pdf
.
add_font
(
"NotoSans"
,
"i"
,
f
"
{
FONTS_DIR
}
/NotoSans-Italic.ttf"
)
pdf
.
add_font
(
"NotoSansKR"
,
""
,
f
"
{
FONTS_DIR
}
/NotoSansKR-Regular.ttf"
)
pdf
.
add_font
(
"NotoSansJP"
,
""
,
f
"
{
FONTS_DIR
}
/NotoSansJP-Regular.ttf"
)
pdf
.
set_font
(
"NotoSans"
,
size
=
12
)
pdf
.
set_fallback_fonts
([
"NotoSansKR"
,
"NotoSansJP"
])
pdf
.
set_auto_page_break
(
auto
=
True
,
margin
=
15
)
# Adjust the effective page width for multi_cell
effective_page_width
=
(
pdf
.
w
-
2
*
pdf
.
l_margin
-
10
)
# Subtracted an additional 10 for extra padding
# Add chat messages
for
message
in
form_data
.
messages
:
role
=
message
[
"role"
]
content
=
message
[
"content"
]
pdf
.
set_font
(
"NotoSans"
,
"B"
,
size
=
12
)
# Bold for the role
pdf
.
multi_cell
(
effective_page_width
,
10
,
f
"
{
role
.
upper
()
}
"
,
0
,
"L"
)
pdf
.
ln
(
1
)
# Extra space between messages
pdf
.
set_font
(
"NotoSans"
,
size
=
10
)
# Regular for content
pdf
.
multi_cell
(
effective_page_width
,
10
,
content
,
0
,
"L"
)
pdf
.
ln
(
1
)
# Extra space between messages
# Save the pdf with name .pdf
pdf_bytes
=
pdf
.
output
()
return
Response
(
content
=
bytes
(
pdf_bytes
),
media_type
=
"application/pdf"
,
headers
=
{
"Content-Disposition"
:
f
"attachment;filename=chat.pdf"
},
)
@
router
.
get
(
"/db/download"
)
async
def
download_db
(
user
=
Depends
(
get_admin_user
)):
...
...
backend/requirements.txt
View file @
81dbc658
...
...
@@ -42,6 +42,8 @@ xlrd
opencv-python-headless
rapidocr-onnxruntime
fpdf2
faster-whisper
PyJWT
...
...
backend/static/fonts/NotoSans-Bold.ttf
0 → 100644
View file @
81dbc658
File added
backend/static/fonts/NotoSans-Italic.ttf
0 → 100644
View file @
81dbc658
File added
backend/static/fonts/NotoSans-Regular.ttf
0 → 100644
View file @
81dbc658
File added
backend/static/fonts/NotoSansJP-Regular.ttf
0 → 100644
View file @
81dbc658
File added
backend/static/fonts/NotoSansKR-Regular.ttf
0 → 100644
View file @
81dbc658
File added
src/lib/apis/utils/index.ts
View file @
81dbc658
...
...
@@ -22,6 +22,32 @@ export const getGravatarUrl = async (email: string) => {
return
res
;
};
export
const
downloadChatAsPDF
=
async
(
chat
:
object
)
=>
{
let
error
=
null
;
const
blob
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/utils/pdf`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
body
:
JSON
.
stringify
({
title
:
chat
.
title
,
messages
:
chat
.
messages
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
blob
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
return
blob
;
};
export
const
getHTMLFromMarkdown
=
async
(
md
:
string
)
=>
{
let
error
=
null
;
...
...
src/lib/components/layout/Navbar/Menu.svelte
View file @
81dbc658
...
...
@@ -11,6 +11,8 @@
import Dropdown from '$lib/components/common/Dropdown.svelte';
import Tags from '$lib/components/common/Tags.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import { downloadChatAsPDF } from '$lib/apis/utils';
export let shareEnabled: boolean = false;
export let shareHandler: Function;
...
...
@@ -25,7 +27,7 @@
export let onClose: Function = () => {};
const download
ChatAs
Txt = async () => {
const downloadTxt = async () => {
const _chat = chat.chat;
console.log('download', chat);
...
...
@@ -40,54 +42,29 @@
saveAs(blob, `chat-${_chat.title}.txt`);
};
const download
ChatAs
Pdf = async () => {
const downloadPdf = async () => {
const _chat = chat.chat;
console.log('download', chat);
const
doc = new jsPDF(
);
const
blob = await downloadChatAsPDF(_chat
);
// Initialize y-coordinate for text placement
let yPos = 10;
const pageHeight = doc.internal.pageSize.height;
// Create a URL for the blob
const url = window.URL.createObjectURL(blob);
// Function to check if new text exceeds the current page height
function checkAndAddNewPage() {
if (yPos > pageHeight - 10) {
doc.addPage();
yPos = 10; // Reset yPos for the new page
}
}
// Function to add text with specific style
function addStyledText(text, isTitle = false) {
// Set font style and size based on the parameters
doc.setFont('helvetica', isTitle ? 'bold' : 'normal');
doc.setFontSize(isTitle ? 12 : 10);
const textMargin = 7;
// Split text into lines to ensure it fits within the page width
const lines = doc.splitTextToSize(text, 180); // Adjust the width as needed
// Create a link element to trigger the download
const a = document.createElement('a');
a.href = url;
a.download = `chat-${_chat.title}.pdf`;
lines.forEach((line) => {
checkAndAddNewPage(); // Check if we need a new page before adding more text
doc.text(line, 10, yPos);
yPos += textMargin; // Increment yPos for the next line
});
// Append the link to the body and click it programmatically
document.body.appendChild(a);
a.click();
// Add extra space after a block of text
yPos += 2;
}
_chat.messages.forEach((message, i) => {
// Add user text in bold
doc.setFont('helvetica', 'normal', 'bold');
addStyledText(message.role.toUpperCase(), { isTitle: true });
addStyledText(message.content);
});
// Remove the link from the body
document.body.removeChild(a);
doc.save(`chat-${_chat.title}.pdf`);
// Revoke the URL to release memory
window.URL.revokeObjectURL(url);
};
</script>
...
...
@@ -193,7 +170,7 @@
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
on:click={() => {
download
ChatAs
Txt();
downloadTxt();
}}
>
<div class="flex items-center line-clamp-1">Plain text (.txt)</div>
...
...
@@ -202,7 +179,7 @@
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
on:click={() => {
download
ChatAs
Pdf();
downloadPdf();
}}
>
<div class="flex items-center line-clamp-1">PDF document (.pdf)</div>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment