/* 회의록 브레인 — 공용 컴포넌트 (전역 노출) */
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 (
);
}
/* ---------- 회의록 상세 모달 ---------- */
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,
});