Shakauthossain-NH commited on
Commit
daa9aa5
·
verified ·
1 Parent(s): acd8e82

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +18 -0
  2. apt.txt +1 -0
  3. main.py +155 -0
  4. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Copy files
7
+ COPY . .
8
+
9
+ # Install dependencies
10
+ RUN apt-get update && \
11
+ xargs -r -a apt.txt apt-get install -y && \
12
+ pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Expose port
15
+ EXPOSE 7860
16
+
17
+ # Run FastAPI using uvicorn
18
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
apt.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ pandoc
main.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, UploadFile, File
2
+ from fastapi.responses import StreamingResponse, JSONResponse
3
+ from bs4 import BeautifulSoup, Tag
4
+ from docx import Document
5
+ from docx.shared import Inches
6
+ from docx.enum.section import WD_ORIENT
7
+ from PIL import Image
8
+ from io import BytesIO
9
+ import markdown2
10
+ import io
11
+
12
+ app = FastAPI()
13
+
14
+ # === Markdown to HTML Utility Functions ===
15
+
16
+ def remove_empty_paragraphs_around(soup, tag_names):
17
+ for tag_name in tag_names:
18
+ for tag in soup.find_all(tag_name):
19
+ for prev in tag.find_all_previous():
20
+ if prev.name == "p" and not prev.text.strip():
21
+ prev.decompose()
22
+ break
23
+ elif prev.name not in ["p", "br", None]:
24
+ break
25
+ for next_ in tag.find_all_next():
26
+ if next_.name == "p" and not next_.text.strip():
27
+ next_.decompose()
28
+ break
29
+ elif next_.name not in ["p", "br", None]:
30
+ break
31
+
32
+ def clean_extra_spacing_around_tables(soup):
33
+ for p in soup.find_all("p"):
34
+ if not p.text.strip():
35
+ p.decompose()
36
+
37
+ for table in soup.find_all("table"):
38
+ next_sibling = table.find_next_sibling()
39
+ while next_sibling and (next_sibling.name == "br" or (next_sibling.name == "p" and not next_sibling.text.strip())):
40
+ temp = next_sibling.find_next_sibling()
41
+ next_sibling.decompose()
42
+ next_sibling = temp
43
+
44
+ def add_table_borders_to_html(html_content: str) -> str:
45
+ soup = BeautifulSoup(html_content, "html.parser")
46
+
47
+ for table in soup.find_all("table"):
48
+ table['border'] = "1"
49
+ table['style'] = "border: 1px solid black; border-collapse: collapse; width: 100%;"
50
+
51
+ first_row = table.find("tr")
52
+ if first_row:
53
+ col_count = len(first_row.find_all(["td", "th"]))
54
+ colgroup = soup.new_tag("colgroup")
55
+ for _ in range(col_count):
56
+ col = soup.new_tag("col")
57
+ col['style'] = "width: {}%;".format(round(100 / col_count))
58
+ colgroup.append(col)
59
+ table.insert(0, colgroup)
60
+
61
+ rows = table.find_all("tr")
62
+ if rows:
63
+ thead = soup.new_tag("thead")
64
+ thead.append(rows[0])
65
+ tbody = soup.new_tag("tbody")
66
+ for row in rows[1:]:
67
+ tbody.append(row)
68
+ table.append(thead)
69
+ table.append(tbody)
70
+
71
+ for row in table.find_all("tr"):
72
+ for cell in row.find_all(["th", "td"]):
73
+ existing_style = cell.get('style', '')
74
+ new_style = "border: 1px solid black; padding: 6px;"
75
+ cell['style'] = f"{existing_style} {new_style}".strip()
76
+
77
+ return str(soup)
78
+
79
+ # === API 1: Markdown to HTML ===
80
+
81
+ @app.post("/convert-md-to-html")
82
+ async def convert_md_to_html(request: Request):
83
+ data = await request.json()
84
+ md_text = data.get("markdown", "")
85
+ client_name = data.get("client_name", "Client").strip()
86
+
87
+ if not md_text:
88
+ return {"error": "No markdown text provided"}
89
+
90
+ html = markdown2.markdown(md_text, extras=[
91
+ "tables",
92
+ "fenced-code-blocks",
93
+ "cuddled-lists",
94
+ "footnotes"
95
+ ])
96
+
97
+ soup = BeautifulSoup(html, "html.parser")
98
+ remove_empty_paragraphs_around(soup, ["table", "img", "h1", "h2", "h3", "h4", "h5", "h6"])
99
+ clean_extra_spacing_around_tables(soup)
100
+
101
+ cleaned_html = add_table_borders_to_html(str(soup))
102
+
103
+ html_bytes = cleaned_html.encode("utf-8")
104
+ html_io = BytesIO(html_bytes)
105
+ html_io.seek(0)
106
+
107
+ safe_client_name = "".join(c for c in client_name if c.isalnum() or c in (" ", "_", "-")).strip()
108
+ filename = f"Proposal for {safe_client_name}.html"
109
+
110
+ headers = {
111
+ 'Content-Disposition': f'attachment; filename="{filename}"'
112
+ }
113
+
114
+ return StreamingResponse(
115
+ html_io,
116
+ media_type='text/html',
117
+ headers=headers
118
+ )
119
+
120
+ # === API 2: Merge Cover Image with DOCX ===
121
+
122
+ @app.post("/merge-cover-docx")
123
+ async def merge_cover_docx(
124
+ cover_image: UploadFile = File(...),
125
+ docx_file: UploadFile = File(...)
126
+ ):
127
+ try:
128
+ cover_bytes = await cover_image.read()
129
+ docx_bytes = await docx_file.read()
130
+
131
+ # Create DOCX and insert image
132
+ doc = Document()
133
+ section = doc.sections[0]
134
+ section.orientation = WD_ORIENT.PORTRAIT
135
+ width = section.page_width - section.left_margin - section.right_margin
136
+
137
+ doc.add_picture(BytesIO(cover_bytes), width=width)
138
+ doc.add_page_break()
139
+
140
+ # Load and append content
141
+ proposal = Document(BytesIO(docx_bytes))
142
+ for elem in proposal.element.body:
143
+ doc.element.body.append(elem)
144
+
145
+ output_stream = BytesIO()
146
+ doc.save(output_stream)
147
+ output_stream.seek(0)
148
+
149
+ return StreamingResponse(
150
+ output_stream,
151
+ media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
152
+ headers={"Content-Disposition": 'attachment; filename="Merged_Proposal_With_Cover.docx"'}
153
+ )
154
+ except Exception as e:
155
+ return JSONResponse(status_code=500, content={"error": str(e)})
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-docx
4
+ markdown2
5
+ html2docx
6
+ beautifulsoup4
7
+ python-multipart
8
+ Pillow