EDITORCLASSBLOGLOGINimg default descriptionimg default descriptionimg default description표현한다는 것의무한한 가능성,새로운 형태로
담아내다.
img default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default description새로운 형태의 콘텐츠Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.
옵클 에디터 개발기: Valkey 큐 서버의 기본 구조dev7옵클 에디터 개발기: Worker loop와 AI 결과 검증dev8옵클 에디터 개발기: 미리보기가 그대로 코드로dev9옵클 에디터 개발기: 그래픽 에디터도 결국 직접 만들기로 했다dev10옵클 에디터 개발기: Rust 공장을 그래픽 엔진으로 다듬다dev117891011...9 옵클 에디터 개발기: 미리보기가 그대로 코드로큐 서버가 안정적으로 돌아가게 된 다음에는, AI가 옵클 에디터의 작업 방식을 익히게 해야 했습니다. 모델이 문장을 잘 쓰는 것과 에디터 안에서 제대로 일하는 것은 다른 문제입니다. 에디터 안에서는 사용자가 렌더링된 EPUB 미리보기를 보고 있고, 실제 저장되는 것은 XHTML과 CSS입니다. 사용자가 눈앞의 문단을 고쳤는데 뒤의 코드가 다르게 남으면 안 됩니다. AI도 마찬가지였습니다. 화면에서 한 일과 EPUB 안에 들어가는 코드가 같은 방향으로 움직여야 했습니다.제가 만들고 싶었던 것은 “HTML을 써주는 AI”가 아니었습니다. 사용자는 코드 편집기를 보고 있지 않습니다. 문단을 선택하고, 이미지를 누르고, 서식을 바꾸고, 미리보기 위에서 바로 명령을 내립니다. 그러면 에디터는 선택된 대상의 현재 clean HTML, 적용 중인 general CSS, chapter local CSS, target id, 사용자 명령을 묶어 AI에게 넘깁니다. AI는 그 상태를 읽고, 실제 EPUB에 들어갈 수 있는 outerHTML과 stylesheet patch를 돌려줘야 합니다.여기서 중요한 것은 그럴듯한 화면을 만드는 게 아니었습니다. 다시 적용할 수 있는 수정 결과를 만드는 것이었습니다. 미리보기에서 바뀐 모습과 저장되는 XHTML/CSS가 같아야 하고, CM6 문자열과 document state, IndexedDB 저장 상태까지 같은 변경으로 이어져야 합니다. 그래서 AI에게 자유롭게 설명하게 하지 않고, `edits`와 `commands`를 가진 JSON 결과만 받도록 했습니다. AI가 할 수 있는 일은 선택 대상의 HTML/CSS를 고치거나, 에디터가 아는 UI command를 실행하는 것뿐입니다.Preview as source옵클 에디터에서 미리보기는 단순히 결과를 보는 화면이 아닙니다. 실제로 편집이 일어나는 표면입니다. 사용자는 EPUB 코드를 직접 보지 않아도 렌더링된 문단과 이미지와 표를 보면서 작업합니다. 그렇지만 EPUB은 결국 XHTML과 CSS로 만들어집니다. 그래서 미리보기에서 일어난 편집은 반드시 코드로 되돌아갈 수 있어야 합니다. 이 연결이 느슨하면 미리보기는 편집기가 아니라 미리보기로만 남습니다.그래서 선택된 block은 단순 DOM node가 아니라 코드 주소를 가진 대상이어야 했습니다. 사용자가 어떤 문단을 선택하면, 에디터는 그 문단의 id와 현재 outerHTML, 적용 중인 CSS context를 같이 잡습니다. AI에게도 똑같이 넘깁니다. “이 문장을 조금 더 크게 해줘”라는 말만 보내면 부족합니다. 지금 대상의 HTML이 무엇인지, 어떤 general rule과 local rule이 영향을 주고 있는지까지 같이 줘야 합니다.이렇게 해두면 미리보기와 코드 편집기의 역할이 분명해집니다. 미리보기는 사람이 만지는 표면이고, XHTML/CSS는 저장과 export의 기준입니다. AI는 미리보기 DOM을 직접 휘젓는 것이 아니라, 미리보기에서 선택된 대상에 적용할 수 있는 코드 patch를 만들어야 합니다. 그래야 AI로 바꾼 결과가 화면에만 남지 않고 EPUB 파일 안에도 남습니다.Shadow DOM 기반 미리보기와도 이 방식은 잘 맞았습니다. Shadow DOM 안의 렌더링 결과는 독립적인 스타일 환경을 갖지만, 에디터는 그 안에서 선택된 block을 document state의 node와 연결할 수 있습니다. AI에게 전달되는 것은 화면에 보이는 텍스트 한 조각이 아니라, 다시 저장 가능한 target context입니다. 미리보기는 사람에게는 편집 화면이고, AI에게는 구조화된 작업 대상이 됩니다.Selector modeAI가 미리보기 편집에 들어오려면 일반 writer mode와 다른 경로가 필요했습니다. writer mode는 책에 들어갈 문장을 쓰거나 다듬습니다. selector mode는 선택된 대상의 HTML/CSS를 고칩니다. 그래서 selector system prompt는 처음부터 선택 대상에 대한 action engine으로 잡았습니다. 사용자가 문서 안에서 하나 이상의 target을 선택했고, 그 target들의 현재 clean HTML과 적용 CSS가 주어졌다는 전제로 움직입니다.selector mode의 입력은 대상별 outerHTML과 CSS context입니다. HTML은 현재 node를 통째로 대체할 수 있는 전체 outerHTML이어야 하고, CSS는 book-wide stylesheet인 general과 chapter stylesheet에 가까운 local로 나뉩니다. AI는 이 정보를 보고 사용자의 요청을 해석합니다. 결과는 선택 대상 자체를 바꾸는 `edits`와 에디터 UI를 움직이는 `commands`로 나뉩니다.이 구분이 생각보다 중요했습니다. 사용자가 “이 문단을 파란색으로 바꿔줘”라고 하면 selector는 HTML inline style이나 general CSS override를 반환해야 합니다. 그런데 “서식 팝업 열어줘”라고 하면 HTML을 건드리면 안 됩니다. command만 반환하면 됩니다. 또 “이 문단을 강조하고 서식 팝업도 열어줘”처럼 두 일이 섞이면 `edits`와 `commands`가 같이 나올 수 있습니다. 한 입력 안에서도 코드 수정과 UI 제어가 동시에 존재할 수 있는 구조입니다.그래서 selector output은 JSON 하나로 고정했습니다. `{"edits":[{"id":"...","html":"...","css":{"general":"...","local":""}}],"commands":["..."]}` 형태입니다. 설명도, markdown도, code fence도 없어야 합니다. 에디터가 이 결과를 그대로 parse해서 적용하기 때문입니다. AI가 친절하게 설명하는 것보다, 에디터가 바로 실행할 수 있는 결과를 주는 것이 더 중요했습니다.OuterHTML contractselector mode에서 HTML은 항상 full outerHTML로 받게 했습니다. 부분 HTML이나 innerHTML만 받으면 적용 위치가 애매해집니다. 선택된 node를 통째로 교체할 수 있어야 하므로, AI는 해당 target의 전체 element를 반환해야 합니다. 요청이 tag 변경을 요구하지 않는다면 기존 tag 구조를 유지하고, 필요한 attribute와 inline style만 바꾸는 것이 기본입니다.id는 절대 바꾸면 안 됩니다. target id는 에디터가 patch를 다시 꽂는 주소입니다. AI가 id를 빼먹거나 새 id를 만들면 결과를 적용할 수 없습니다. 그래서 system prompt에서도 id를 정확히 유지하라고 고정했고, sanitize 단계에서도 id가 없는 edit은 버리게 했습니다. 되돌려 꽂을 주소가 없는 patch는 아무리 좋아 보여도 쓸 수 없습니다.CSS만 바꾸는 경우에도 html field는 필요했습니다. general stylesheet에 override rule만 추가하는 작업이어도, selector 결과의 shape는 같아야 합니다. 그래야 적용 경로가 단순해집니다. 대신 실제로 바꾼 target만 `edits`에 포함합니다. 바꾸지 않은 대상까지 매번 echo하면 불필요한 DOM replace가 일어나고, selection state나 undo stack이 괜히 흔들릴 수 있습니다.이 contract를 두면 AI가 만든 HTML은 미리보기 DOM과 document state 사이에서 안정적인 patch 단위가 됩니다. 사용자는 “이 문단 좀 더 잘 보이게 해줘”라고 말하지만, 서버는 target id와 outerHTML을 받고, AI는 수정된 outerHTML을 반환하고, 에디터는 해당 node를 교체합니다. 그 다음 serialize된 XHTML, CM6 문자열, IndexedDB 저장 상태가 같은 변경으로 따라옵니다.CSS channelsCSS는 더 조심해서 다뤘습니다. AI에게 stylesheet를 마음대로 다시 쓰게 하면 EPUB 전체가 흔들릴 수 있습니다. 그래서 selector mode에서 스타일을 바꾸는 채널은 두 개로 제한했습니다. 하나는 inline style이고, 다른 하나는 general stylesheet append입니다. local CSS는 읽기 전용 context로만 줍니다. AI가 local CSS를 수정해서 반환하는 것은 막았습니다.inline style은 해당 element 하나에만 적용되는 변경에 씁니다. 선택된 문단 하나의 글자색을 바꾸거나, 특정 이미지 하나의 margin을 조정하는 경우에는 outerHTML 안의 style attribute에 직접 넣는 것이 안정적입니다. patch 범위가 분명합니다. target 하나에만 영향을 주기 때문에 다른 문단이나 서식이 예상치 않게 바뀌지 않습니다.general stylesheet는 공유 서식이나 공통 selector를 바꿔야 할 때 씁니다. 모든 `

`에 적용되는 규칙이나 특정 class의 스타일을 바꿔야 한다면 `css.general`에 새 rule만 넣습니다. 여기서 중요한 것은 append-only입니다. 기존 stylesheet를 다시 쓰거나 그대로 echo하지 않습니다. 뒤에 붙는 rule이 cascade에서 이기므로, 필요한 override rule만 반환하면 됩니다. 기존 CSS를 부수지 않고 변경을 쌓는 방식입니다.

local CSS는 항상 빈 문자열로 돌려주게 했습니다. local context는 AI가 현재 상태를 이해하기 위한 배경 정보일 뿐, 수정 채널이 아닙니다. local rule이 어떤 값을 설정하고 있더라도, 바꿔야 한다면 inline style이나 general override로 처리합니다. 채널을 이렇게 제한해두면 CSS patch가 어디에 들어가는지 분명해지고, EPUB export 시 stylesheet 구조도 예측할 수 있습니다.Command catalog미리보기 편집에서 모든 요청이 HTML/CSS patch로 끝나는 것은 아닙니다. 사용자는 같은 입력창에서 “미리보기 열어줘”, “선택 모드 켜줘”, “챕터 추가해줘”, “서식 팝업 열어줘”, “페이지뷰로 가줘” 같은 UI 제어도 말할 수 있습니다. 이런 요청을 HTML 수정으로 오해하면 안 됩니다. 그래서 helper command catalog가 필요했습니다.helper command catalog는 에디터가 실제로 실행할 수 있는 command id와 한국어 설명의 목록입니다. `open_preview_view`, `select_mode_activate`, `chapter_add`, `open_stylePopup_base`, `open_template_popup`, `click_button_extract`, `click_button_import`, `click_button_page_view`, `click_button_ai` 같은 command가 들어갑니다. AI는 새 명령 이름을 만들 수 없고, catalog 안의 id만 반환해야 합니다.이 catalog는 selector mode에서도 그대로 사용됩니다. selector system prompt는 HTML/CSS edit뿐 아니라 UI command도 반환할 수 있게 되어 있습니다. 순수 UI 요청이면 `edits`는 빈 배열이고 `commands`에 command id가 들어갑니다. 선택 대상 편집과 UI 명령이 섞인 요청이면 둘 다 반환됩니다. 덕분에 미리보기 위에서 사용자가 하는 말이 code patch인지 UI action인지 같은 pipeline 안에서 처리됩니다.command id를 catalog로 제한한 이유는 단순합니다. 실행할 수 없는 명령은 에디터에게 의미가 없습니다. 자연어 요청은 유연하게 받아도, 실행은 deterministic command id로 고정해야 합니다. command catalog를 system prompt에 넣고, normalize 단계에서 unknown을 걸러내면 실행 가능한 명령만 남습니다. AI가 판단하더라도 마지막 출력은 에디터의 언어로 내려와야 했습니다.Helper routinghelperCommands 쪽에서 특히 신경 쓴 것은, AI를 부르기 전에 확정할 수 있는 요청을 먼저 처리하는 일이었습니다. “미리보기”, “새 프로젝트”, “챕터 추가”, “서식 팝업”처럼 에디터 UI 명령으로 바로 매칭되는 입력은 모델까지 갈 필요가 없습니다. 그래서 phrase, shortcut, typo fix, compact matching, fuzzy matching, contained shortcut matching을 단계적으로 둔 rule-based router를 만들었습니다.사용자 입력은 그대로 믿지 않습니다. 먼저 prompt bar tag prefix를 떼고, 공백과 문장부호를 정리하고, 자주 틀리는 표현을 보정합니다. “탬플릿”은 “템플릿”으로, “프리뷰”는 “미리보기”로, “챕타”는 “챕터”로, “실헹취소”는 “실행취소”로 정리합니다. 띄어쓰기 차이도 compact form으로 흡수합니다. 한국어 UI 명령은 사용자가 항상 정확히 입력하지 않기 때문에, 이 정리 단계가 꽤 중요했습니다.다만 넓게 잡는 만큼 방어선도 필요했습니다. “본문을 써줘”를 “본문 서식” 명령으로 착각하면 안 됩니다. 그래서 writing veto를 두었습니다. 쓰기, 작성, 다듬기, 교정, 번역, 요약, 이어쓰기, 문체 유지 같은 의도가 섞이면 helper UI 명령 매칭을 포기하고 secretary나 writer 계열로 넘깁니다. 반대로 짧은 UI 단어는 exact-only set으로 관리해서 긴 문장 안에 우연히 들어갔다고 바로 command로 보내지 않게 했습니다.selector 앞에서는 더 보수적인 gate를 사용했습니다. 선택된 대상이 있을 때는 사용자의 말이 대상 편집인지 UI 명령인지 애매할 수 있습니다. 그래서 pre-selector helper command는 phrase 완전 일치와 shortcut 앞쪽의 핵심 표현만 통과시킵니다. 퍼지, 포함, 넓은 typo match는 일부러 배제했습니다. 애매하면 selector가 선택 context를 보고 판단하는 편이 더 낫습니다. 미리보기 편집에서는 이 보수성이 중요했습니다.Normalize output모델 응답도 그대로 믿지 않았습니다. helper 응답은 bare command id 하나만 나와야 하지만, 실제로는 따옴표, code fence, JSON 배열, JSON 객체, 마침표, 문장 속 설명이 섞일 수 있습니다. 그래서 normalizeHelperCommand는 여러 형태를 흡수합니다. JSON이면 문자열 값을 걸어 다니며 후보를 모으고, 문장 안에 command id가 하나만 박혀 있으면 그것도 잡습니다. 복수 id가 보이면 모호하므로 unknown으로 떨어뜨립니다.selector 응답은 더 까다롭습니다. HTML과 CSS 값 안에는 중괄호가 들어갈 수 있기 때문에 단순 substring으로 JSON을 자르면 안 됩니다. 그래서 균형 중괄호 scanner를 두었습니다. 문자열 리터럴 내부의 중괄호는 무시하면서 최상위 JSON 객체 후보를 추출하고, 그중 `edits`나 `commands` key를 가진 후보를 우선 파싱합니다. 앞뒤에 설명이 붙어도 JSON 객체만 꺼낼 수 있어야 했습니다.파싱된 edit 항목도 다시 정리합니다. id가 비어 있으면 버리고, html이 문자열이 아니면 빈 문자열로 보정합니다. css가 객체가 아니면 빈 객체로 보고, `general`과 `local`은 문자열만 받습니다. commands는 normalizeHelperCommand를 통과시켜 unknown을 제거합니다. JSON 파싱 자체가 불가능하면 null을 반환해 queue worker가 retry하도록 합니다.이 layer가 있어야 AI가 조금 흐트러져도 에디터의 적용 규칙은 유지됩니다. AI에게 strict output을 요구하되, 서버에서는 현실적인 복구를 합니다. 다만 복구할 수 없는 경우에는 억지로 적용하지 않습니다. id 없는 edit, unknown command, parse 불가능한 JSON은 버리거나 재시도합니다. 미리보기와 EPUB 코드의 일치를 지키려면 애매한 patch를 적용하는 것보다 적용하지 않는 편이 낫습니다.Same EPUB이 구조가 잡히면 사용자는 코드를 보지 않고도 EPUB을 편집할 수 있습니다. 미리보기에서 문단을 선택하고 “이 문단을 더 작게”, “이 이미지를 가운데로”, “이 표를 모바일에서 보기 좋게”, “서식 팝업 열어줘”처럼 말하면 됩니다. 에디터는 그 말이 code patch인지 UI command인지 판단하고, selector context를 구성하고, AI 응답을 JSON patch로 정리하고, 적용 가능한 것만 document state에 반영합니다.중요한 것은 화면에서만 바뀌지 않는다는 점입니다. outerHTML이 바뀌고, general stylesheet append가 일어나고, command가 실행되고, 그 결과가 CM6의 XHTML/CSS 문자열과 IndexedDB 저장 상태로 이어집니다. 미리보기 DOM, 코드 편집기, 로컬 저장소가 같은 변경을 공유합니다. AI는 따로 붙은 장식 기능이 아니라 에디터의 serialization pipeline 안에서 움직입니다.helper routing과 selector mode는 서로 보완합니다. helper는 확정적인 UI 명령을 빠르게 처리하고, selector는 선택된 target의 HTML/CSS patch를 만듭니다. secretary escape rule까지 붙으면 본문 작성 경로로 들어간 요청이라도 실제로는 UI command라고 판단될 때 command id로 빠져나올 수 있습니다. 사용자는 자연어 입력 하나를 쓰지만, 내부에서는 writing, UI routing, selection patch가 분리됩니다.결과적으로 AI는 옵클 에디터의 EPUB 작성법을 따르는 작업자가 되었습니다. 어떤 CSS는 inline으로 들어가야 하고, 어떤 CSS는 general stylesheet 뒤에 append되어야 하며, local CSS는 읽기 전용으로만 봐야 합니다. 어떤 요청은 outerHTML을 바꾸고, 어떤 요청은 command catalog를 실행해야 합니다. id는 유지해야 하고, HTML은 full outerHTML이어야 하며, JSON은 strict shape를 지켜야 합니다. 이 규칙들이 맞물리면서 미리보기 편집과 실제 EPUB 코드가 같은 결과를 바라보게 되었습니다.이 단계도 안정적으로 완성되었습니다. 미리보기 선택 context, selector JSON contract, outerHTML replace, append-only general CSS, helper command catalog, shortcut normalization, writing veto, pre-selector gate, command normalization, selector JSON sanitizer까지 하나의 AI editing layer로 정리되었습니다. 반복 검수에서도 미리보기 편집, UI command routing, HTML/CSS patch 적용, unknown command 제거, JSON retry, document state 반영이 모두 안정적으로 통과했습니다. 옵클 에디터는 이제 사용자가 보는 EPUB 미리보기와 실제로 저장되는 EPUB 코드를 같은 편집 흐름 안에서 맞춰낼 수 있게 되었습니다.
이전글목록으로다음글
저작권 고시Copyright Notice본 웹사이트의 모든 디자인 결과물 및 영상에 대한 저작권은 Abstract Cloud에 있으며, 저작권법 및 관련 법령에 의해 보호받습니다. 웹, 영상, 본문, 표지, 내지 디자인을 포함한 모든 콘텐츠는 저작권자의 자산으로, 사전 동의 없이 무단 복제, 배포, 2차 저작물 제작, 온라인 공유 등을 금지합니다. 이를 위반할 시, 저작권법에 따라 민형사상 책임을 질 수 있습니다. 정당한 구매와 저작권 보호는 창작자의 권리를 지키며, 더 나은 작품으로 보답할 힘이 됩니다.저작권자: Abstract Cloud | 대표자: 배창규(uragen)
© Abstract Cloud. All Rights Reserved.
HOMEFAQ이용 약관개인정보 이용방침help@opkle.app
010-2747-3403
상호 :추상적 형상 디자인(Abstract cloud)  |  대표자 :배창규사업자등록번호 :249-74-00533통신판매업 신고번호 :2025-의정부송산-0634주소 :경기도 의정부시 부용로 49, 108동 402호웹의 모든 콘텐츠, 디자인, 소스 코드에 대한
저작권은 Opkle에게 있습니다.
img default description