ฉันสร้าง Live Web Editor สำหรับ RenderCV โดยใช้ Gemini ได้อย่างไร

เรซูเม่ในรูปแบบโค้ด: ความสง่างามของ RenderCV

หากคุณเป็นวิศวกรหรือนักวิชาการ การเขียนเรซูเม่ใน MS Word คือฝันร้ายของการจัดรูปแบบ และเทมเพลต LaTeX แบบกำหนดเองก็มักจะเวิ่นเว้อเกินกว่าจะดูแลรักษาได้ง่ายๆ

เมื่อไม่นานมานี้ ฉันบังเอิญไปพบกับโปรเจกต์ Python ที่ยอดเยี่ยมชื่อว่า RenderCV มันช่วยให้คุณเขียนเนื้อหาเรซูเม่ของคุณในไฟล์ YAML ที่สะอาดและมีโครงสร้างที่เป็นระเบียบ จากนั้นจะสร้าง PDF ที่จัดรูปแบบอย่างสวยงามด้วยไทโปกราฟีที่สมบูรณ์แบบโดยใช้ Typst เป็นตัวขับเคลื่อนเบื้องหลัง มันปฏิบัติกับเรซูเม่ของคุณเหมือนโค้ด ซึ่งหมายความว่าคุณสามารถควบคุมเวอร์ชัน (version-control) ได้ มุ่งเน้นไปที่เนื้อหาอย่างแท้จริง และปล่อยให้เครื่องมือจัดการเรื่องระยะขอบหน้า การจัดตำแหน่ง และสไตล์

อย่างไรก็ตาม RenderCV ถูกออกแบบมาเพื่อใช้งานผ่าน Command-line interface (CLI) เป็นหลัก ทุกครั้งที่คุณทำการเปลี่ยนแปลง คุณต้องรัน rendercv render John_Doe_CV.yaml ตรวจสอบผลลัพธ์ เปิดโปรแกรมดู PDF และเช็คการจัดตำแหน่ง หากคุณทำผิดพลาดในการตรวจสอบความถูกต้องหรือ स्कीमा (schema) คุณต้องนั่งอ่าน CLI compiler tracebacks ที่ชวนปวดหัว

ฉันต้องการเชื่อมช่องว่างนี้ด้วยการเพิ่ม Live Web Editor—เว็บแอปพลิเคชันหน้าเดียวแบบโลคัลที่มีโปรแกรมแก้ไข Monaco แบบเคียงข้างกัน พรีวิว PDF แบบเรียลไทม์ ตรวจสอบความถูกต้องของสคีมาทันที และการสลับธีม/ส่วนการทำงานแบบโต้ตอบได้

ด้วยความช่วยเหลือของ Gemini ฉันสร้างและรวมเว็บอินเตอร์เฟสนี้เข้ากับ RenderCV ได้โดยตรงสำเร็จแล้ว! และนี่คือวิธีที่เราทำกัน


เทคโนโลยีและสถาปัตยกรรม

เพื่อให้เว็บเอดิเตอร์นี้มีน้ำหนักเบา รวดเร็ว และพึ่งพาโมดูลภายนอกน้อยที่สุด เราจึงเลือก:

  1. Backend: FastAPI + Uvicorn — เหมาะอย่างยิ่งสำหรับการเขียน API ประสิทธิภาพสูงและให้บริการไฟล์สแตติก
  2. Frontend: Vanilla HTML5 + Modern CSS + Javascript — หลีกเลี่ยงขั้นตอนการบิลด์ที่ยุ่งยาก (เช่น Webpack หรือ Vite) สำหรับเครื่องมือช่วยในเครื่อง เราใช้ Monaco Editor (ผ่าน CDN) เพื่อประสบการณ์การเขียน YAML ระดับเดียวกับ VS Code และใช้ Lucide Icons สำหรับองค์ประกอบภาพที่ราบรื่น
  3. Rendering: RenderCV Python API — เรียกใช้โมเดลภายในของไลบรารีโดยตรงเพื่อแยกวิเคราะห์ ตรวจสอบความถูกต้อง และสร้างเอกสาร Typst และ PDF ในหน่วยความจำหรือในโฟลเดอร์ชั่วคราว

1. การออกแบบ FastAPI Backend (web_app.py)

หัวใจของเซิร์ฟเวอร์คือเอนด์พอยต์ /api/render เมื่อผู้ใช้พิมพ์ YAML ในเว็บ UI ส่วนหน้าจะส่งข้อมูลไปยังเอนด์พอยต์นี้ แบ็คเอนด์จะจัดการงานหลักสามงาน:

  1. การเรนเดอร์ PDF: เราคอมไพล์สตริง YAML โดยใช้ฟังก์ชันการคอมไพล์ภายในของ RenderCV และส่งคืนไฟล์ไบต์ PDF ดิบ
  2. การสลับเปิด/ปิดส่วนการแสดงผล: RenderCV ช่วยให้คุณสามารถซ่อนหรือแสดงส่วนต่างๆ ของ CV ได้ เราจะแยกวิเคราะห์ YAML ที่ส่งเข้ามา ระบุส่วนของ CV ที่มีอยู่ทั้งหมด ตัดส่วนที่ผู้ใช้เลือกซ่อนในดรอปดาวน์ UI ออก และคอมไพล์เอกสารสุดท้าย
  3. การจับคู่ข้อผิดพลาดอย่างมีโครงสร้าง: เมื่อการตรวจสอบความถูกต้องล้มเหลว RenderCV จะส่งข้อผิดพลาด RenderCVUserValidationError ออกมา เราจะดักจับข้อผิดพลาดนี้ ดึงหมายเลขบรรทัดและคอลัมน์ที่แน่นอนของฟิลด์ YAML ที่ไม่ถูกต้อง และส่งคืนการตอบกลับข้อผิดพลาด JSON ที่สะอาด เพื่อให้ Monaco แสดงเส้นหยักสีแดงในตำแหน่งที่เกิดข้อผิดพลาดได้อย่างแม่นยำ!

นี่คือหน้าตาของเอนด์พอยต์ /api/render ในภาษาไพธอน:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@app.post("/api/render")
def render_pdf(request: RenderRequest):
"""คอมไพล์ YAML ที่กำหนดให้เป็น PDF และส่งคืนไบต์ไฟล์ PDF"""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = pathlib.Path(tmpdir)
try:
# แยกวิเคราะห์ YAML ลงในพจนานุกรมภายในของ RenderCV
input_dict, overlay_sources = build_rendercv_dictionary(
request.yaml,
output_folder=tmp_path,
dont_generate_png=True,
dont_generate_markdown=True,
dont_generate_html=True,
)

# ดึงส่วนทั้งหมดเพื่อส่งคืนไปยัง UI
all_sections = []
if "cv" in input_dict and "sections" in input_dict["cv"] and input_dict["cv"]["sections"]:
all_sections = list(input_dict["cv"]["sections"].keys())

# ลบส่วนที่เลือกซ่อนออกโดยใช้โค้ด
if request.hide_sections:
for sec in request.hide_sections:
if "cv" in input_dict and "sections" in input_dict["cv"] and input_dict["cv"]["sections"]:
input_dict["cv"]["sections"].pop(sec, None)

# ตรวจสอบความถูกต้องและสร้างโมเดล
model = build_rendercv_model_from_commented_map(input_dict, tmp_path, overlay_sources)

# สร้างซอร์สโค้ด Typst และคอมไพล์เป็น PDF
typst_path = generate_typst(model)
pdf_path = generate_pdf(model, typst_path)
pdf_bytes = pdf_path.read_bytes()

# ส่งรายการส่วนต่างๆ ในส่วนหัว (URL-encoded JSON)
headers = {
"X-RenderCV-Sections": urllib.parse.quote(json.dumps(all_sections)),
"Access-Control-Expose-Headers": "X-RenderCV-Sections"
}
return Response(content=pdf_bytes, media_type="application/pdf", headers=headers)

except RenderCVUserValidationError as e:
# จับคู่ข้อผิดพลาดการตรวจสอบความถูกต้องของ Pydantic กลับไปยังพิกัดของเอดิเตอร์ YAML
errors = []
for err in e.validation_errors:
start_line = err.yaml_location[0][0] if err.yaml_location else None
start_col = err.yaml_location[0][1] if err.yaml_location else None
errors.append({
"message": err.message,
"line": start_line,
"col": start_col,
"schema_location": err.schema_location
})
return JSONResponse(
status_code=422,
content={"type": "validation_error", "detail": "การตรวจสอบความถูกต้องของ YAML ล้มเหลว", "errors": errors}
)
# ... การจัดการข้อผิดพลาดอื่นๆ

2. เว็บอินเตอร์เฟสระดับพรีเมียม (index.html)

สำหรับส่วนติดต่อผู้ใช้ เราต้องการบางสิ่งที่ให้ความรู้สึกระดับพรีเมียม ตอบสนองรวดเร็ว และใช้งานง่ายสุดๆ Gemini ช่วยฉันโค้ดเลย์เอาต์ที่ทันสมัยและสวยงามซึ่งมีคุณสมบัติ:

  • HSL Dark Theme: พื้นหลังสีเทาเข้ม, โอเวอร์เลย์กึ่งโปร่งใส (backdrop-filter: blur), และลูกเล่นไล่โทนสีม่วงสดใส
  • แบ่งหน้าจอเคียงข้างกัน: Monaco Editor อยู่ทางซ้าย, หน้าจอพรีวิว PDF สด (ใช้ <iframe> ที่โหลดด้วย Blob URL ท้องถิ่น) อยู่ทางขวา
  • การควบคุมแบบไดนามิก:
    • ตัวเลือกสลับธีมที่จะโหลดเทมเพลต (classic, engineering, moderncv, ฯลฯ) และรวมข้อมูล CV ปัจจุบันของคุณเข้ากับเทมเพลตเหล่านั้นโดยอัตโนมัติ
    • เมนูดรอปดาวน์ของส่วน CV ซึ่งสืบค้นส่วนหัวจากแบ็คเอนด์และสร้างเช็คลิสต์เพื่อซ่อน/แสดงส่วนต่างๆ ของเรซูเม่แบบไดนามิก (เช่น ซ่อนส่วน “Projects” หรือ “References” เมื่อต้องการส่งออกเวอร์ชันที่ปรับให้เหมาะกับเป้าหมายที่เฉพาะเจาะจง)
    • คอนโซลบันทึกการตรวจสอบความถูกต้องที่ด้านล่างของเอดิเตอร์ซึ่งขยายเพื่อแสดงรายละเอียดข้อผิดพลาดได้ การคลิกที่ข้อผิดพลาดจะโฟกัสไปที่บรรทัดนั้นใน Monaco Editor โดยอัตโนมัติ

ในการคอมไพล์ ผู้ใช้สามารถคลิกปุ่ม “Render PDF” หรือใช้คีย์ลัด Cmd + Enter PDF คอมไพล์เสร็จในเวลาน้อยกว่าหนึ่งวินาที!


3. การรวมเข้ากับ CLI (web_command.py)

เพื่อให้ผู้ใช้เริ่มต้นใช้งานได้ง่ายที่สุด เราจึงเพิ่มคำสั่งใหม่ให้กับ RenderCV CLI ที่ทำงานด้วย Typer:

1
rendercv web

นี่จะเปิดใช้งานเซิร์ฟเวอร์ Uvicorn FastAPI ในเครื่อง และเปิดเว็บเบราว์เซอร์ของคุณโดยอัตโนมัติ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.command(
name="web",
help="เริ่มเว็บเอดิเตอร์ของ RenderCV เพื่อแก้ไขแบบสดและคอมไพล์ PDF"
)
@handle_user_errors
def cli_command_web(
host: str = "127.0.0.1",
port: int = 8000,
no_browser: bool = False,
):
typer.echo(f"กำลังเริ่มเว็บเอดิเตอร์ RenderCV ที่ http://{host}:{port}")

if not no_browser:
def open_browser():
time.sleep(1.2)
webbrowser.open(f"http://{host}:{port}")

threading.Thread(target=open_browser, daemon=True).start()

uvicorn.run(web_app, host=host, port=port)

สะท้อนความคิดจากการโค้ดร่วมกับ Gemini

การสร้างฟีเจอร์นี้ด้วย Gemini ทำได้รวดเร็วอย่างเหลือเชื่อ สิ่งที่ปกติแล้วอาจต้องใช้เวลาหลายวันในการอ่านซอร์สโค้ดภายในและกำหนดค่า Monaco API สามารถแก้ไขได้ภายในไม่กี่ชั่วโมง:

  1. การทำความเข้าใจไลบรารี AST/YAML: Gemini ระบุอย่างถูกต้องว่าเราควรใช้ ruamel.yaml ในการแยกวิเคราะห์ไฟล์ YAML เพื่อรักษาเครื่องหมายคำพูด ความคิดเห็น และโครงสร้างเดิมของผู้ใช้เมื่อสลับธีม
  2. การจับคู่พิกัดข้อผิดพลาด: Gemini รู้วิธีการเข้าถึงพิกัดข้อผิดพลาดของ RenderCVUserValidationError และสร้างโครงสร้างสำหรับ Monaco model markers (monaco.editor.setModelMarkers) ทำให้ตัวเอดิเตอร์ทำงานประสานกับการตรวจสอบความถูกต้องของ Pydantic ได้อย่างสมบูรณ์แบบ
  3. ขัดเกลา UX: มันออกแบบตัวแปร CSS และการจัดวางที่สวยงาม ปรับขนาดเข้ากับหลายความละเอียดหน้าจอได้อย่างยอดเยี่ยม และตอบสนองได้อย่างไหลลื่น

หากคุณต้องเขียนเรซูเม่ ลองไปศึกษา RenderCV และทดลองใช้คำสั่ง rendercv web เพื่อสัมผัสประสบการณ์แก้เรซูเม่แบบสดๆ ด้วยตัวคุณเองได้เลย!

ขอให้สนุกกับการเขียนโค้ดนะ!