diff --git a/web-ui/html/workshop-app.html b/web-ui/html/workshop-app.html index 5d9bf00..da93742 100644 --- a/web-ui/html/workshop-app.html +++ b/web-ui/html/workshop-app.html @@ -201,7 +201,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis - +
@@ -305,9 +305,13 @@ async function checkAuth(){ } // โ”€โ”€ Theme โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}} +function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}const tb=document.getElementById('theme-btn');if(tb)tb.textContent=n==='light'?'โ˜€๏ธ':'๐ŸŒ™';} function escAttr(s){return String(s||'').replace(/'/g,''').replace(/"/g,'"');} +// onclick="fn('...')" ์•ˆ์˜ JS ๋ฌธ์ž์—ด ์ปจํ…์ŠคํŠธ์šฉ โ€” escAttr์˜ HTML ์—”ํ‹ฐํ‹ฐ(')๋Š” +// HTML ํŒŒ์„œ๊ฐ€ ์†์„ฑ๊ฐ’์„ JS๋กœ ๋„˜๊ธฐ๊ธฐ ์ „์— ๋‹ค์‹œ ' ๋กœ ๋””์ฝ”๋”ฉํ•ด๋ฒ„๋ ค ๋”ฐ์˜ดํ‘œ ์ฃผ์ž…์„ ๋ชป ๋ง‰์œผ๋ฏ€๋กœ +// JS ์ˆ˜์ค€์—์„œ ์ด์Šค์ผ€์ดํ”„ํ•œ๋‹ค. ํด๋”/ํŒŒ์ผ ์ด๋ฆ„(์‚ฌ์šฉ์ž ์ž…๋ ฅ)์„ ์‹ฃ๋Š” ๋ฐ๋งŒ ์“ด๋‹ค. +function escJs(s){return String(s??'').replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/<\//g,'<\\/').replace(/\r?\n/g,'\\n');} function genId(prefix){return prefix+'_'+Math.random().toString(36).slice(2,10);} // โ”€โ”€ ์ƒํƒœ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -413,6 +417,34 @@ function scheduleSave(){ saveDebounce=setTimeout(saveProject,600); } +// ์ฑ„ํŒ… ์‘๋‹ต ํ›„ ํ˜ธ์ถœ: ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋‹น๊ฒจ์˜ค๋˜, ๋‚ด์šฉ์ด ์•ˆ ๋ฐ”๋€Œ์—ˆ์œผ๋ฉด ํ™”๋ฉด์„ ๊ฑด๋“œ๋ฆฌ์ง€ +// ์•Š๋Š”๋‹ค(ํŽธ์ง‘ ์ค‘ ํฌ์ปค์Šค/ํŽผ์นจ ์นด๋“œ ๋ณด์กด). ๋ฐ”๋€Œ์—ˆ์œผ๋ฉด ์‚ด์•„์žˆ๋Š” ๋ถ€ํ’ˆ์— ํ•œํ•ด ํŽผ์นจ ์ƒํƒœ๋ฅผ +// ์œ ์ง€ํ•œ ์ฑ„ ๋‹ค์‹œ ๊ทธ๋ฆฐ๋‹ค. loadWorkshopData(์ดˆ๊ธฐ ๋กœ๋”ฉ์šฉ, ๋ฌด์กฐ๊ฑด ์ „์ฒด ์ƒˆ๋กœ๊ณ ์นจ)์™€ ๋‹ค๋ฅธ ์šฉ๋„. +async function softRefreshWorkshop(){ + // ๋Œ€๊ธฐ ์ค‘์ธ ์ €์žฅ(debounce)์ด ์žˆ์œผ๋ฉด ๋จผ์ € flush โ€” ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๋ฐฉ๊ธˆ ํŽธ์ง‘ํ•œ ๋‚ด์šฉ์ด ์•„์ง + // ์„œ๋ฒ„์— ์•ˆ ๊ฐ„ ์ƒํƒœ์—์„œ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹น๊ฒจ์˜ค๋ฉด ๊ทธ ํŽธ์ง‘์ด ์˜› ๋ฐ์ดํ„ฐ์— ๋ฎ์–ด์”Œ์›Œ ์‚ฌ๋ผ์ง„๋‹ค. + if(saveDebounce){clearTimeout(saveDebounce);saveDebounce=null;await saveProject();} + try{ + const r=await fetch('/api/workshop/data',{headers:authH()}); + if(!r.ok)return; + const d=await r.json(); + const newProjects=Array.isArray(d.projects)?d.projects:[]; + const newActive=d.activeProjectId||(newProjects[0]?.id||''); + if(JSON.stringify(newProjects)===JSON.stringify(workshopData.projects)&&newActive===workshopData.activeProjectId)return; + workshopData.projects=newProjects; + workshopData.activeProjectId=newActive; + loadLocalFromProject(); + const liveIds=new Set(parts.map(p=>p.id)); + for(const id of [...expandedParts])if(!liveIds.has(id))expandedParts.delete(id); + renderProjectSelector(); + renderParts(); + renderPhases(); + const ta=document.getElementById('notes-textarea'); + if(ta&&ta.value!==notes)ta.value=notes; + updateBudget(); + }catch{} +} + async function saveProject(){ syncLocalToProject(); try{ @@ -834,7 +866,7 @@ function renderFiles(){ container.innerHTML=keys.map(key=>{ const files=groups.get(key)||[]; const label=key||'๋ฏธ๋ถ„๋ฅ˜'; - const delBtn=key?``:''; + const delBtn=key?``:''; const grid=files.length?`
${files.map(renderFileCard).join('')}
` :'
(๋นˆ ํด๋”)
'; return `
@@ -860,13 +892,13 @@ function renderFileCard(file){ ? `${escAttr(file.name)}` : `${icon}`; // STL์€ ์ƒˆํƒญ ๋‹ค์šด๋กœ๋“œ ๋Œ€์‹  Three.js CAD ํŽธ์ง‘๊ธฐ ํŒ์—…์œผ๋กœ ์—ฐ๋‹ค(์ž‘์—…์‹คโ†”K2 CAD ํ”Œ๋Ÿฌ๊ทธ์ธ ์—ฐ๋™). - const openAttrs=isStl?`href="javascript:void(0)" onclick="openCadEditorForFile('${escAttr(file.relPath)}')"`:`href="${escAttr(file.url)}" target="_blank" rel="noopener"`; + const openAttrs=isStl?`href="javascript:void(0)" onclick="openCadEditorForFile('${escJs(file.relPath)}')"`:`href="${escAttr(file.url)}" target="_blank" rel="noopener"`; // STL๋งŒ ์Šฌ๋ผ์ด์„œ๋กœ ๋ณด๋‚ผ ๋‹ค์ค‘์„ ํƒ ์ฒดํฌ๋ฐ•์Šค(09-22) โ€” ์—ฌ๋Ÿฌ ๋ถ€ํ’ˆ STL์„ ํ•œ ํŒ์— ์–น์–ด ๊ฐ™์ด ์Šฌ๋ผ์ด์‹ฑ. const checked=stlSelection.has(file.relPath); - const selBox=isStl?``:''; + const selBox=isStl?``:''; return `
${selBox} - +
${thumb}
@@ -1040,16 +1072,34 @@ function addMsg(role,html){ } function renderMd(text){ - return text - .replace(/&/g,'&').replace(//g,'>') - .replace(/```[\w]*\n?([\s\S]*?)```/g,'
$1
') - .replace(/`([^`]+)`/g,'$1') - .replace(/\*\*(.+?)\*\*/g,'$1') - .replace(/^#{1,3}\s+(.+)$/gm,'

$1

') - .replace(/^[-*]\s+(.+)$/gm,'
  • $1
  • ') - .replace(/(
  • [\s\S]+?<\/li>)/g,m=>`
      ${m}
    `) - .replace(/\n\n/g,'

    ').replace(/^/,'

    ').replace(/$/,'

    ') - .replace(/

    <\/p>/g,'').replace(/

    (<[hup])/g,'$1').replace(/(<\/[hup][^>]*>)<\/p>/g,'$1'); + // ์ฝ”๋“œํŽœ์Šค๋ฅผ ๋จผ์ € ์ž๋ฆฌํ‘œ์‹œ์ž๋กœ ๋นผ๋‘๊ณ  ๋ชจ๋“  ์ธ๋ผ์ธ/๋‹จ๋ฝ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚œ ๋’ค ๋ณต์›ํ•œ๋‹ค โ€” + // ๊ทธ๋ž˜์•ผ ์˜ˆ์ „์ฒ˜๋Ÿผ \n\nโ†’

    ์น˜ํ™˜์ด

     ์•ˆ๊นŒ์ง€ ํŒŒ๊ณ ๋“ค์–ด ์ฝ”๋“œ ๋ธ”๋ก์˜ ๋นˆ ์ค„์„
    +  // 

    ๋กœ ๋Š์–ด๋จน๋Š” ์‚ฌ๊ณ ๊ฐ€ ์•ˆ ๋‚œ๋‹ค. ์ธ๋ผ์ธ `์ฝ”๋“œ`/๊ตต๊ฒŒ ์ฒ˜๋ฆฌ๋„ pre ์•ˆ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ๋ชปํ•œ๋‹ค. + const blocks=[]; + let s=String(text||'').replace(/&/g,'&').replace(//g,'>'); + s=s.replace(/```[\w]*\n?([\s\S]*?)```/g,(m,code)=>{ + blocks.push('

    '+code.replace(/^\n/,'')+'
    '); + return '\n\n\u0000B'+(blocks.length-1)+'\u0000\n\n'; + }); + s=s.replace(/`([^`]+)`/g,'$1') + .replace(/\*\*(.+?)\*\*/g,'$1') + .replace(/^#{1,3}\s+(.+)$/gm,'

    $1

    '); + // ๋ฆฌ์ŠคํŠธ โ€” ์—ฐ์†ํ•˜๋Š” [-*] ์ค„์„ ํ•˜๋‚˜์˜
      ๋กœ ๋ฌถ๋Š”๋‹ค(ํ•ญ๋ชฉ๋ณ„
        ์ด ๋”ฐ๋กœ ์ƒ๊ธฐ๋˜ ๋ฒ„๊ทธ ์ˆ˜์ •). + s=s.replace(/(^|\n)([-*]\s+.+(?:\n[-*]\s+.+)*)/g,(m,lead,grp)=>{ + const items=grp.split('\n').map(line=>'
      • '+line.replace(/^[-*]\s+/,'')+'
      • ').join(''); + return lead+'
          '+items+'
        '; + }); + // ๋‹จ๋ฝ โ€” ๋นˆ ์ค„ ๋‹จ์œ„๋กœ split. ์ด๋ฏธ ๋ธ”๋ก ํƒœ๊ทธ(

        /
          /์ฝ”๋“œ ์ž๋ฆฌํ‘œ์‹œ์ž)๋กœ ์‹œ์ž‘ํ•˜๋ฉด + //

          ๋กœ ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค. + s=s.split(/\n{2,}/).map(chunk=>{ + const t=chunk.trim(); + if(!t)return ''; + if(/^\u0000B\d+\u0000$/.test(t))return t; + if(/^<(h3|ul|pre)\b/.test(t))return t; + return '

          '+t+'

          '; + }).join(''); + s=s.replace(/\u0000B(\d+)\u0000/g,(m,i)=>blocks[+i]||''); + return s; } async function sendMessage(){ @@ -1099,7 +1149,9 @@ async function callChat(message){ if(e.name!=='AbortError'){if(!bubble)bubble=addMsg('assistant','');bubble.innerHTML='์˜ค๋ฅ˜: '+escHtml(e.message)+'';} }finally{btn.disabled=false;btn.textContent='์ „์†ก';} // ์ฑ„ํŒ…์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ(workshop_project ๋„๊ตฌ ํ˜ธ์ถœ) ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•œ๋‹ค. - await loadWorkshopData(); + // ๋‹จ, ๋ฐ์ดํ„ฐ๊ฐ€ ์•ˆ ๋ฐ”๋€Œ์—ˆ์œผ๋ฉด(๋„๊ตฌ๊ฐ€ get ์ฒ˜๋Ÿผ ์ฝ๊ธฐ๋งŒ ํ•œ ๊ฒฝ์šฐ) ํ™”๋ฉด์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” + // ํŽผ์ณ๋‘” ๋ถ€ํ’ˆ ์นด๋“œ๊ฐ€ ์ ‘ํžˆ๊ฑฐ๋‚˜ ํŽธ์ง‘ ์ค‘์ด๋˜ input์˜ ํฌ์ปค์Šค๊ฐ€ ๋‚ ์•„๊ฐ€๋Š” ์‚ฌ๊ณ ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•จ. + await softRefreshWorkshop(); // 09-22: ์ฑ„ํŒ…์œผ๋กœ scad_to_stl ๋“ฑ ํŒŒ์ผ๋„๊ตฌ๋ฅผ ๋ถˆ๋Ÿฌ ์ƒˆ ํŒŒ์ผ์ด ์ƒ๊ฒจ๋„ ํŒŒ์ผํƒญ์ด ์ด๋ฏธ ์—ด๋ ค์žˆ์œผ๋ฉด // ์•ˆ ์ƒˆ๋กœ๊ณ ์นจ๋˜๋˜ ๋ฌธ์ œ(์‚ฌ์šฉ์ž ์‹ ๊ณ : "์ €์žฅ๋˜์—ˆ๋Š”๋ฐ ๋ณด์ด์ง€ ์•Š๋„ค") โ€” ํƒญ ์ „ํ™˜ ์—†์ด๋„ ๊ฐฑ์‹ . if(document.getElementById('pane-files').classList.contains('active'))loadFiles(); @@ -1185,6 +1237,7 @@ document.addEventListener('click',(e)=>{ // โ”€โ”€ Init โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ (async function init(){ if(!(await checkAuth()))return; + {const tb=document.getElementById('theme-btn');if(tb)tb.textContent=document.documentElement.getAttribute('data-theme')==='light'?'โ˜€๏ธ':'๐ŸŒ™';} await loadWorkshopData(); await initSessionId(); await loadHistory();