๐ฃ๏ธ ์์ด๊ณต๋ถ ํ์ค๋ถ ํ๋ฒ ํด๋ณด์ธ์~ https://english.huhsame.com
์๊ฐ
๊ณต๋ถํ๊ณ ์ถ์ ์์ด ์ ํ๋ธ๋, ์์ด ๊ธฐ์ฌ URL๋ฅผ ๊ฐ์ง๊ณ ์์ด ํ์ต ์๋ฃ๋ฅผ ๋ง๋ค์ด, ์์ดํจ๋์์ ๋ฐ๋ก ๊ณต๋ถํ ์ ์๊ฒ ์๋ํ๋ฅผ ํ์์ต๋๋ค.
ํ์์ ํ ์ต ํ ํ์ฑ
์ผ๋ก ๊ณต๋ถํ๊ธฐ๋ ์ซ๊ณ , ์ฐจ๋ผ๋ฆฌ ๊ด์ฌ์๋ ๋ถ์ผ์ ์์์ด๋ ๊ธฐ์ฌ๋ก ๊ณต๋ถํ์ ์ถ์์ง๋ง, ์๋ฌด๋๋ ์ฑ
์ด ์๋๋ค๋ณด๋ ๋จ์ด๋ ํํ ์ ๋ฆฌ๊ฐ ์๋์ด์์ด ํ๊ณ๊ฐ ์๋๋ผ๊ตฌ์. Claude์๊ฒ ๋งค๋ฒ ๋ฌผ์ด๊ฐ๋ฉฐํ๋ค ์ด๋ฒ ์คํฐ๋๋ฅผ ํตํด ์๋ํ๋ฅผ ์์ฑํ์์ต๋๋ค.
์งํ ๋ฐฉ๋ฒ
n8n ์ google Apps Script ํผํฉ ์ฌ์ฉ
n8n: AI agent๊ธฐ๋ฅ์ผ๋ก ๊ธฐ์ฌ์๋ฌธ์ด๋ ์ ํ๋ธ์คํฌ๋ฆฝํธ์์ ์ ์ฉํ ํํ ์ถ์ถ ๋ฐ ์๋ฌธ ์์ฑ
Apps Script: ๋ฐ์ดํฐ๋ฅผ HTML ํ ํ๋ฆฟ์ ์ ์ฉ ํ pdfํ์ผ ์์ฑ ๋ฐ ๊ตฟ๋ ธํธ ์ ๋ฌ
[n8n]
URL ๋ถ๋ฅ ํ ๋ถ๊ธฐ
์ ํ๋ธ์ธ์ง ์นํ์ด์ง์ธ์ง
์ ๋ชฉ๊ณผ ์๋ฌธ ์ถ์ถ
์ ํ๋ธ
์ปค๋ฎค๋ํฐ๋ ธ๋ youtube transcript ์ฌ์ฉ
์นํ์ด์ง
์ปค๋ฎค๋ํฐ๋ ธ๋ webpage content extracter ์ฌ์ฉ
๋ฌธ์ฅ ๋ณ ํํ ์ถ์ถ
AI Agent
Claude Sonnet 3.7
๋ฌธ์ฅ ๋ณ๋ก ์ ์ฉํ ํํ๋ค ์ถ์ถ
์ถ๋ ฅํฌ๋งท JSON
sentences์ ๋ฌธ์ฅ๋ค์ด ๋ฆฌ์คํธ๋ก ๋์ด
{ "sentences": [ { "original_sentence": "์๋ฌธ์ ์๋ ํ ๋ฌธ์ฅ", "original_meaning": "์๋ฌธ ๋ฌธ์ฅ ํด์", "expressions": "์๋ฌธ ๋ฌธ์ฅ์ ์๋ ์ ์ฉํ ํํ๋ค (์ฌ๋ฌ๊ฐ์ผ ๊ฒฝ์ฐ ๋ฆฌ์คํธ ๋์ ๊ตฌ๋ถ์๋ฅผ ๋ฃ๊ณ ํ๋์ ๋ฌธ์์ด๋ก)" } ] }
๋ฐ์ดํฐ ๋ถํ ๋ฐ ์ ์ก
ํ ๋ฌธ์ฅ ์ฉ ๋๋์ด ๋ค์ ์ํฌํ๋ก์ฐ๋ก ์์ฒญ
์์ฒญ์ 5๋ถ๋ง๋ค 5๊ฐ์ฉ ๋ฐฐ์น
POST https://n8n.huhsame.com/webhook/sentence
body
title, url, original_sentence. original_meaning, expressions
ํํ ๋ณ ์๋ฌธ ์์ฑ
์นํ ์ผ๋ก 1๋ฌธ์ฅ์ฉ ์์ฒญ ๋ฐ์
AI Agent
Claude Sonnet 3.7
๋ฌธ์ฅ์์ ์ถ์ถํ ํํ๋ค ๋ง๋ค ๋์ ๋ง์ถคํ ์ํฉ๋ณ ์๋ฌธ 4๊ฐ์ฉ ์์ฑ
์ถ๋ ฅ ํฌ๋งท JSON
{ "expressions": [{ "expression": "์ ์ฉํ ํํ", "meaning": "์ ์ฉํ ํํ ํ๊ตญ์ด ๋ป", "situation_description": "์ด ํํํ๋ ์ํฉ ์ค๋ช ", "original_sentence": "์๋ฌธ์ ์๋ ๋ฌธ์ฅ", "original_meaning": "์๋ฌธ ๋ฌธ์ฅ ํด์", "example1": "AI๋ ์๋ํ์ ๊ด๋ จ๋ ์ํฉ ์๋ฌธ", "example2": "๊ฐ์๋ ๋คํธ์ํน์ ๊ด๋ จ๋ ์ํฉ ์๋ฌธ", "example3": "์ผ์, ์ทจ๋ฏธ์ ๊ด๋ จ๋ ์ํฉ ์๋ฌธ", "example4": "์ฌํ, ๋ฏธ๋์ ๊ด๋ จ๋ ์ํฉ ์๋ฌธ" }] }
๋ฐ์ดํฐ ์ ์ฅ
Google Sheets
Apps Script ํธ์ถ
๋ฌธ์ฅ๋ณ ์ํฌํ๋ก์ฐ๊ฐ ๋ค ์งํ ๋ ๋ ๊น์ง ๋๊ธฐ (์ฝ 1์๊ฐ)
Apps Script ์นํ
POST https://script.google.com/macros/s/AKfycbyKL-HrFJs69ngy4M9ABjIfd86Xwv-TvxTCP29F4iuc52zQg5UE9WJb1158CL6q24o/exec
body
url
[Apps Script]
์์ด ํํ ๋ฐ์ดํฐ ์ฝ์ด์ค๊ธฐ
webhook์ผ๋ก ๋ฐ์ URL ์ถ์ถ
๊ตฌ๊ธ์ํธ์์ ํด๋น URL์ ์์ด ํํ ๋ฐ์ดํฐ๋ค ์ฝ์ด์ค๊ธฐ
// JSON ๋ฐ์ดํฐ์์ URL ํ๋ผ๋ฏธํฐ ์ถ์ถ (ํํ ํ์ต ๋ฐ์ดํฐ๊ฐ ์๋ ์์/๋ฌธ์ URL) const data = JSON.parse(e.postData.contents || '{}'); const inputUrl = data.url; if (!inputUrl) return ContentService.createTextOutput('No URL received.'); // 'ํํ๊ณต๋ถ์ํธ' ์คํ๋ ๋์ํธ ์ด๊ธฐ - ์์ด ํํ๊ณผ ์๋ฌธ์ด ์ ์ฅ๋ ์ํธ const sheet = SpreadsheetApp.openById('๋ฐ์ดํฐ ์ ์ฅ๋ ๊ตฌ๊ธ์ํธ ์์ด๋') .getSheetByName('ํํ๊ณต๋ถ์ํธ'); // ๋ชจ๋ ํํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ const rows = sheet.getDataRange().getValues(); // ์ ๋ ฅ๋ URL๊ณผ ์ผ์นํ๋ ์์ด ํํ ๋ฐ์ดํฐ ์ฐพ๊ธฐ (2๋ฒ์งธ ์ด[์ธ๋ฑ์ค 1]์ ์๋ณธ URL ์ ์ฅ) const matched = rows.filter((row, i) => i > 0 && row[1] === inputUrl); // ํด๋น URL์ ๊ด๋ จ๋ ์์ด ํํ์ด ์์ผ๋ฉด ๋ฉ์์ง ๋ฐํ if (matched.length === 0) { return ContentService.createTextOutput('No matching rows.'); }
HTML ํ ํ๋ฆฟ ์ ์ฉ
Claude๋ก HTML ๊ตฌ์ฑ
์์ดํจ๋์์ ํ๊ธฐํ๊ธฐ ์ํด ํ๊ธฐ๊ณต๊ฐ ์ถ๊ฐ
// =================================================================== // buildStyledHtml ํจ์: ์์ด ํํ ํ์ต ์นด๋ HTML ์์ฑ // =================================================================== function buildStyledHtml(rows) { // ์์ด ํํ ํ์ต ์นด๋์ HTML ๊ตฌ์กฐ์ ์คํ์ผ ์ ์ let html = ` <!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8"> <title>์์ด ํํ ํ์ต ์นด๋</title> <style> @font-face { font-family: 'Pretendard'; src: url(data:font/ttf;base64,${FONT_REGULAR}) format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Pretendard'; src: url(data:font/ttf;base64,${FONT_BOLD}) format('truetype'); font-weight: 700; font-style: normal; } * { box-sizing: border-box; margin: 0; padding: 0; } @page { size: A4 portrait; margin: 40px 80px 20px 20px; /* top right bottom left */ } body { font-family: 'Noto Sans KR', sans-serif; background-color: white; color: #333; line-height: 1.6; padding: 0px; } .container { width: 500px; /* A4 - ์ฌ๋ฐฑ ๊ณ ๋ คํ ๋๋น */ margin-left: 0; /* ์ผ์ชฝ ์ ๋ ฌ */ margin-right: auto; /* ์ค๋ฅธ์ชฝ ์ฌ๋ฐฑ ํ๋ณด */ } .expression-card { background-color: white; padding: 30px; margin-bottom: 60px; page-break-after: always; /* ๊ฐ ํํ ์นด๋๋ง๋ค ํ์ด์ง ๋๋ */ } .expression-header { margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 8px; line-height: 1.2; } .expression-original-meaning, .expression-meaning, .expression-situation { font-family: 'Pretendard', sans-serif; } .expression-title { font-size: 24px; font-weight: 700; color: #4f46e5; margin-bottom: 8x; } .expression-meaning { font-size: 18px; font-weight: 700; color: #4b5563; padding: 8px 0px; } .expression-situation { background-color: #f3f4f6; padding: 5px; border-radius: 8px; margin: 8px 0px; font-size: 15px; color: #4b5563; } .expression-original { font-style: italic; color: #6366f1; font-weight: 400; padding: 4px 0px; } .expression-original-meaning { font-weight: 400; font-size: 8px padding: 4px 0px; margin-bottom: 10px; } .examples-section { margin-top: 30px; } .examples-section h3 { font-size: 16px; margin-bottom: 15px; color: #4b5563; } .example-item { background-color: #f9fafb; padding: 0px 12px; border-radius: 8px; margin-bottom: 30px; border-left: 3px solid #a5b4fc; font-size: 15px; line-height: 2; } .highlight { font-weight: 700; background: none; } </style> </head><body><div class="container"> `; // ๊ฐ ์์ด ํํ๋ง๋ค ํ์ต ์นด๋ ์์ฑ rows.forEach(row => { // ์คํ๋ ๋์ํธ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ถํด ํ ๋น // [์ ๋ชฉ, URL, ํํ, ์๋ฏธ, ์ฌ์ฉ์ํฉ, ์๋ฌธ, ์๋ฌธ์๋ฏธ, ์๋ฌธ1, ์๋ฌธ2, ์๋ฌธ3, ์๋ฌธ4] const [ , , expression, meaning, situation, original, original_meaning, ex1, ex2, ex3, ex4 ] = row; // ์์ด ํํ ํ์ต ์นด๋ HTML ๊ตฌ์กฐ ์์ฑ html += ` <div class="expression-card"> <div class="expression-header"> <h2 class="expression-title">${expression}</h2> <div class="expression-meaning">${meaning}</div> </div> <div class="expression-situation">${situation}</div> <div class="expression-original">${highlight(expression, original)}</div> <div class="expression-original-meaning">${original_meaning}</div> <div class="examples-section"> ${[ex1, ex2, ex3, ex4].filter(Boolean).map(e => `<div class="example-item">${highlight(expression, e)}</div>`).join('')} </div> </div>`; }); // HTML ๋ง๋ฌด๋ฆฌ ํ๊ทธ ์ถ๊ฐ html += `</div></body></html>`; return html; }
PDF ํ์ผ ์์ฑ ๋ฐ ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ์ ์ฅ
// ์์ด ํํ ํ์ต ์๋ฃ๋ฅผ ์ ์ฅํ ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ํด๋์ PDF ํ์ผ ์ ์ฅ const folder = DriveApp.getFolderById('1DTJFVOAnrq5mS1l3nQwcWfw5rVazEc1b'); const file = folder.createFile(blob.setName(filename));
์์ดํจ๋ Goodnotes ์ฑ์ ์ถ๊ฐ
์๋ํ ์ ์ฉ ์ด๋ฉ์ผ๋ก ๋ฐ์ก
์ฑ์ pdfํ์ผ ์๋ ์ํฌํธ
// ์์ฑ๋ PDF ํ์ต ์๋ฃ๋ฅผ GoodNotes๋ก ๊ฐ์ ธ์ค๊ธฐ ์ํด ์ด๋ฉ์ผ๋ก ๋ฐ์ก GmailApp.sendEmail( "[email protected]", // GoodNotes ์๋ ๊ฐ์ ธ์ค๊ธฐ์ฉ ์ด๋ฉ์ผ ์ฃผ์ `๐ ํ์ต์๋ฃ ์์ฑ: ${title}`, // ํ์ต ์๋ฃ ์ ๋ชฉ "์ฒจ๋ถ๋ PDF๋ฅผ ํ์ธํด์ฃผ์ธ์.", // ์ด๋ฉ์ผ ๋ณธ๋ฌธ { attachments: [file.getAs(MimeType.PDF)], // ํ์ต ์๋ฃ PDF ์ฒจ๋ถ cc: "[email protected]", // ์ฐธ์กฐ ์์ ์ (์๋ฃ ์๋ฆผ์ฉ) name: "ํ์ธ์AI" // ๋ฐ์ ์ ์ด๋ฆ } );
๊ฒฐ๊ณผ
ํธ๋ฌ๋ธ์ํ
n8n์ execution data ํฌ๊ธฐ๊ฐ ํฐ ๊ฒฝ์ฐ ํ ๋ ธ๋์ฉ ์คํ ๋ถ๊ฐ โก๏ธ data๋ฅผ pin ํด๋๊ณ ์ ์ฒด ํ๋ก์ฐ ์คํ
n8n์ output parser๋ ์๊ฐ๋ณด๋ค ์๋ฌ๊ฐ ๋ง์ด ๋จ โก๏ธ ๊ทธ๋ฅ OpenAI JSON๋ชจ๋๋ก ๋ค์ ํ๋ฒ ๊ฑฐ์น๋ ๊ฒ์ด ๊ฐ๋จ
ํ๋ฒ์ ๋ชจ๋ ๋ฌธ์ฅ์ ๋ํ ํํ๊ณผ ์๋ฌธ๋ค์ ๋ค ์๋ต๋ฐ๊ธฐ์ ์ต๋ ํ ํฐ๋์ด ๋ชจ์๋ โก๏ธ ํ ๋ฌธ์ฅ๋ง๋ค ๋ฐ๋ก OpenAI ์ฝ
30๋ฌธ์ฅ์ ๊ฒฝ์ฐ ๋์์ 30๊ฐ์ OpenAI ์์ฒญ์ผ๋ก TPM(Token Per Minute) ์ด๊ณผ โก๏ธ 5๋ถ๋ง๋ค 5๊ฐ ์์ฒญ์ฉ ๋๋์ด ์ํ
Google Apps Script์์ PDF ๋ณํ์ HTML์ ์ ์ฉํ ์ด์ํจ๊ณผ๋ค ์ฌ๋ผ์ง โก๏ธ ๋ฐฐ๊ฒฝ์๋์ ๊ตต๊ธฐ, ๊ธ์จ์์ผ๋ก ์กฐ์
Google Apps Script์์ PDF ๋ณํ์ CSS์ importํ ํฐํธ ๋ฌด์ โก๏ธ ์ง์ base64๋ก ๋ฃ์ (๊ธฐ๋ณธ ๋ด์ฅ ํฐํธ๋ ํ๊ธ์ด ์ฐธ์ ์ ์์ด ๋ชป์๊น)
๋๋์
์๊ฐ๋ณด๋ค ์ค๋๊ฑธ๋ ธ๋ค.
ํนํ ๋์๊ฒ ๋ง๋ ํํ ์ถ์ถ, ๊ทธ๋ฆฌ๊ณ ๋์๊ฒ ๋ง๋ ์๋ฌธ ์์ฑํด์ฃผ๋ ํ๋กฌํํธ ๊ณ ๋ํ์ ์๊ฐ์ ๋ง์ด ์ผ๋ค.
ํฅํ ๋ ํ๊ณ ์ถ์ ๊ฒ
์์ดํฐ ๋จ์ถ์ด, ๊ณต์ ํ๊ธฐ, ํฌ๋กฌํ์ฅํ๋ก๊ทธ๋จ
์น์๋น์ค ๋ฐฐํฌ
ํ๋ก ํธ์๋ ์์ ํ์
๋ง์ถค ์๋ฌธ ์์ฑ์ ์ํ ์ฌ์ฉ์ ์ ๋ณด ์ ๋ ฅ ๋จ๊ณ ํ์
๊ท์ฐฎ์์ ๊ทธ๋ฅ ๋๋ง ์ฐ๊ณ ๋๋ผ์๋ ์๋ค.โก๏ธ sonnet 4 ํ ์คํธ๊ฒธ cursor๋ก ๋ง๋ค์ด๋ดค๋๋ฐ ๋๋ํฉ๋๋ค!MyZample https://english.huhsame.com
์์ค์ฝ๋
n8n workflow: https://drive.google.com/file/d/1w3C5YEptkOnG49GeDbmIM-FsWkGHpM3B/view?usp=drive_link
google app script: https://drive.google.com/file/d/1_jQMyW39pxOhzL-5r4xwnCrlVcAGnfhu/view?usp=drive_link