/* 회의록 브레인 — 공용 컴포넌트 (전역 노출) */ const DS = window.CalComDesignSystem_436c9d; const { Avatar } = DS; const D = window.MB_DATA; /* ---------- 인라인 아이콘 (Lucide 스타일 스트로크) ---------- */ const ICON_PATHS = { "file-plus": '', search: '', gantt: '', dashboard: '', "bar-chart": '', "arrow-right": '', send: '', check: '', "check-circle": '', clock: '', alert: '', calendar: '', users: '', x: '', menu: '', database: '', layers: '', plus: '', "file-text": '', sparkle: '', link: '', "chevron-right": '', refresh: '', inbox: '', target: '', "wifi-off": '', }; function Icon({ name, size = 18, style, strokeWidth = 2 }) { return ( ); } /* ---------- **bold** → 하이라이트 변환 ---------- */ function renderRich(text) { const parts = String(text).split(/(\*\*[^*]+\*\*)/g); return parts.map((p, i) => p.startsWith("**") && p.endsWith("**") ? ( {p.slice(2, -2)} ) : ( {p} ) ); } /* ---------- 상태 배지 ---------- */ function StatusBadge({ status }) { const s = D.STATUS[status]; return ( {s.label} ); } /* ---------- 진행률 바 ---------- */ function ProgressBar({ value, color = "var(--mb-blue)", height = 7 }) { return (
); } /* ---------- 출처 칩 ---------- */ function SourceChip({ meetingId, onClick }) { // 시드 출처: 회의 id 문자열 / 백엔드 출처: {title, date} 객체 둘 다 지원 let m; if (meetingId && typeof meetingId === "object") { m = { title: meetingId.title, date: meetingId.date || meetingId.meeting_date || "", summary: [], decisions: [], attendees: [], tags: [], }; } else { m = D.meetingById(meetingId); } if (!m || !m.title) return null; return ( ); } /* ---------- 교차점검 콜아웃 ---------- */ function Crosscheck({ note, a, b, onOpenMeeting }) { // a/b: 시드 출처는 회의 id 문자열, 백엔드 출처는 {title, date} 객체 둘 다 지원 const resolve = (x) => (x && typeof x === "object") ? x : D.meetingById(x); const ma = resolve(a); const mb = resolve(b); return (

⚠️ 과거 결정 교차점검

{note}

{(ma || mb) && (
{ma && } {mb && }
)}
); } /* ---------- 빈 상태 ---------- */ function EmptyState({ icon = "inbox", title, sub }) { return (

{title}

{sub &&

{sub}

}
); } /* ---------- 회의록 상세 모달 ---------- */ function MeetingModal({ meeting, onClose }) { if (!meeting) return null; return (
e.stopPropagation()} className="mb-panel mb-rise" style={{ width: "min(560px, 100%)", maxHeight: "84vh", overflowY: "auto", background: "var(--mb-elev)" }} >
{meeting.tags && meeting.tags.map((t) => ( {t} ))}

{meeting.title}

{meeting.date} · 참석 {meeting.attendees.join(", ")}

); } function ModalBlock({ label, items, accent }) { return (

{label}

    {items.map((it, i) => (
  • {it}
  • ))}
); } /* ---------- 토스트 ---------- */ function Toast({ show, message }) { return (
{message}
); } /* ---------- 검색 결과 카드 (갤러리·외부 재사용용) ---------- */ function SearchResult({ res, onOpenMeeting }) { return (
답변 {res._source === "ai" ? "실시간 AI" : "데모 검색"}

{renderRich(res.answer)}

{res.sources && res.sources.length > 0 && (

출처 · {res.sources.length}건

{res.sources.map((src, i) => ( ))}
)}
{res.crossCheck && ( )}
); } /* ---------- 오류 알림 배너 ---------- */ const _ERR_META = { "no-claude": { icon: "alert", title: "CLOVA API 키가 설정되지 않았습니다", sub: "서버의 .env 파일에 CLOVA_API_KEY를 설정해 주세요.", retry: false }, timeout: { icon: "clock", title: "응답 시간이 초과됐습니다", sub: "다시 시도해 주세요.", retry: true }, network: { icon: "wifi-off", title: "네트워크에 연결할 수 없습니다", sub: "인터넷 연결을 확인하고 다시 시도해 주세요.", retry: true }, }; function ErrorNotice({ code, onRetry }) { const { Button } = window.CalComDesignSystem_436c9d; const meta = _ERR_META[code] || _ERR_META.network; return (

{meta.title}

{meta.sub}

{meta.retry && onRetry && (
)}
); } Object.assign(window, { Icon, renderRich, StatusBadge, ProgressBar, SourceChip, Crosscheck, EmptyState, MeetingModal, Toast, SearchResult, ErrorNotice, });