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)