import os, json, re, io
import streamlit as st
from openai import OpenAI
from PIL import Image
from datetime import datetime
# ──────────────────────────────────────────────
# 1. 초기 설정 및 CSS 로드
# ──────────────────────────────────────────────
def local_css(file_name):
current_dir = os.path.dirname(os.path.abspath(__file__))
css_path = os.path.join(current_dir, file_name)
if os.path.exists(css_path):
with open(css_path, encoding="utf-8") as f:
st.markdown(f"", unsafe_allow_html=True)
st.set_page_config(page_title="MeetingAI | KT Enterprise", page_icon="🎙️", layout="wide")
local_css("style.css")
# 경로 및 클라이언트 설정
current_dir = os.path.dirname(os.path.abspath(__file__))
img_path = os.path.join(current_dir, "kt.png")
client = OpenAI()
# 세션 상태 초기화
if "analysis_result" not in st.session_state:
st.session_state.analysis_result = None
if "transcript" not in st.session_state:
st.session_state.transcript = ""
# 우선순위 라벨 설정
PRIORITY_LABEL = {"high": "🔴 높음", "medium": "🟡 보통", "low": "🟢 낮음"}
# ──────────────────────────────────────────────
# 2. 백엔드 핵심 함수 (민감정보 감지, 분석)
# ──────────────────────────────────────────────
def detect_sensitive(text):
SENSITIVE_PATTERNS = [
(r"기밀|비밀|대외비|내부.*보안|보안.*유지|confidential", "기밀/대외비 표현"),
(r"급여|연봉|임금|salary", "급여·인사 정보"),
(r"개인정보|주민|생년월일", "개인정보"),
(r"\d{3}-\d{4}-\d{4}", "전화번호 패턴"),
(r"\d{6}-\d{7}", "주민번호 패턴"),
]
return list({label for pat, label in SENSITIVE_PATTERNS if re.search(pat, text, re.IGNORECASE)})
sys_role = """
## 역할
당신은 KT Enterprise IT기술혁신팀 DX Consulting의 AI 회의 비서입니다.
회의 내용을 정확하고 신속하게 분석·정리합니다. 모든 응답은 한국어로 작성합니다.
## 회의록 정리 규칙
1. 회의 개요 — 제목 / 목적 / 주요 안건
2. 결정된 사항 — 핵심 결정만 간결하게
3. 담당 업무(Action Items) — [담당자]:[업무]/기한/우선순위
4. 일정 요약 — 마감 기한 시간순 정리
"""
def analyze_meeting(text):
today = datetime.now().strftime("%Y년 %m월 %d일")
prompt = f"""
다음은 오늘({today}) 진행된 회의 내용입니다. 아래 JSON 형식으로 분석하세요. 반드시 JSON만 출력하세요.
회의 내용:
{text}
{{
"meeting_title": "회의 제목",
"purpose": "회의 목적",
"decisions": ["결정 사항 리스트"],
"tasks": [
{{"person":"담당자","task":"업무 내용","deadline":"기한","priority":"high|medium|low"}}
],
"agenda_items": ["안건 리스트"],
"summary": "전체 요약",
"keywords": ["키워드1", "키워드2"]
}}
"""
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "system", "content": sys_role}, {"role": "user", "content": prompt}],
temperature=0.2,
)
raw = re.sub(r"^```json\s*|```\s*$", "", resp.choices[0].message.content.strip())
return json.loads(raw)
# ──────────────────────────────────────────────
# 3. UI 레이아웃 구성 (헤더 및 입력부)
# ──────────────────────────────────────────────
# 상단 헤더 영역 (크기 확대 버전)
st.markdown("""
KT
Meeting AI 회의 분석 어시스턴트
IT기술혁신팀 DX Consulting · Intelligent Meeting Solution
""", unsafe_allow_html=True)
col1, col2 = st.columns([1, 2], gap="large")
with col1:
try:
st.image(Image.open(img_path), caption="KT DX Assistant", use_container_width=True)
except:
st.info("이미지(kt.png)를 불러올 수 없습니다.")
with col2:
st.subheader("회의록을 업로드하시면 요약해드리겠습니다.")
with st.container(border=True):
uploaded_file = st.file_uploader("📄 회의 내용 텍스트 파일 업로드", type=["txt"])
st.write("**요청 방식 선택**")
input_mode = st.radio("요청 방식", ["🎙️ 실시간 녹음", "📁 음성 파일 업로드", "📝 텍스트 입력"], horizontal=True, label_visibility="collapsed")
audio_value = None
uploaded_audio_file = None
text_input = ""
if input_mode == "🎙️ 실시간 녹음":
audio_value = st.audio_input("음성으로 내용을 말해주세요.")
elif input_mode == "📁 음성 파일 업로드":
uploaded_audio_file = st.file_uploader("음성 파일 업로드", type=["mp3", "wav", "m4a"])
else:
text_input = st.text_area("내용을 직접 입력하세요.", height=150)
submit = st.button("🚀 회의 분석 시작", use_container_width=True)
if submit:
meeting_text = ""
if uploaded_file:
meeting_text = uploaded_file.read().decode("utf-8")
with st.spinner("AI가 내용을 분석 중입니다..."):
try:
user_req = ""
if audio_value:
user_req = client.audio.transcriptions.create(model="whisper-1", file=("audio.wav", audio_value, "audio/wav")).text
elif uploaded_audio_file:
user_req = client.audio.transcriptions.create(model="whisper-1", file=(uploaded_audio_file.name, uploaded_audio_file, uploaded_audio_file.type)).text
else:
user_req = text_input
final_content = (meeting_text + "\n" + user_req).strip()
if not final_content:
st.warning("분석할 내용이 없습니다.")
else:
st.session_state.transcript = final_content
st.session_state.analysis_result = analyze_meeting(final_content)
st.rerun()
except Exception as e:
st.error(f"오류 발생: {e}")
# ──────────────────────────────────────────────
# 4. 결과 출력 영역 (Tabs 활용)
# ──────────────────────────────────────────────
if st.session_state.analysis_result:
res = st.session_state.analysis_result
hits = detect_sensitive(st.session_state.transcript)
if hits:
st.error(f"🔒 민감정보 감지: {', '.join(hits)} 항목 주의")
tab1, tab2, tab3 = st.tabs(["📋 회의 요약", "✅ 할 일 & 우선순위", "📄 원문 보기"])
with tab1:
st.markdown(f"### 📌 {res.get('meeting_title', '회의 결과')}")
st.info(f"**회의 목적:** {res.get('purpose', '내용 없음')}")
c1, c2 = st.columns(2)
with c1:
st.subheader("✅ 결정 사항")
for d in res.get('decisions', []): st.write(f"- {d}")
with c2:
st.subheader("📌 주요 안건")
for a in res.get('agenda_items', []): st.write(f"- {a}")
st.divider()
st.subheader("💬 전체 요약")
st.write(res.get('summary', '요약 내용 없음'))
with tab2:
tasks = res.get('tasks', [])
if not tasks:
st.write("배정된 할 일이 없습니다.")
else:
for t in tasks:
p = t.get('priority', 'low').lower()
label = PRIORITY_LABEL.get(p, "🟢 낮음")
deadline = f" (기한: {t['deadline']})" if t.get('deadline') and t['deadline'] != "null" else ""
st.markdown(f"**{label} [{t.get('person', '미지정')}]** : {t.get('task')}{deadline}")
with tab3:
st.markdown("#### 🔑 핵심 키워드")
st.write(", ".join([f"#{k}" for k in res.get('keywords', [])]))
st.divider()
st.text_area("전사 원문", st.session_state.transcript, height=300)