import os import json import re import streamlit as st from openai import OpenAI from googleapiclient.discovery import build from google.oauth2 import service_account st.set_page_config( page_title="회의록 요약 + 캘린더 등록", page_icon="📅", layout="wide" ) SCOPES = ["https://www.googleapis.com/auth/calendar"] def get_openai_client(): api_key = os.environ.get("OPENAI_API_KEY") if not api_key: st.error("OPENAI_API_KEY Secret이 설정되어 있지 않습니다.") st.stop() return OpenAI(api_key=api_key) def get_calendar_service(): service_account_json = os.environ.get("GOOGLE_SERVICE_ACCOUNT_JSON") if not service_account_json: st.error("GOOGLE_SERVICE_ACCOUNT_JSON Secret이 설정되어 있지 않습니다.") st.stop() try: service_account_info = json.loads(service_account_json) except json.JSONDecodeError: st.error("GOOGLE_SERVICE_ACCOUNT_JSON 값이 올바른 JSON 형식이 아닙니다.") st.stop() creds = service_account.Credentials.from_service_account_info( service_account_info, scopes=SCOPES ) return build("calendar", "v3", credentials=creds) def clean_json_text(text): text = text.strip() text = re.sub(r"^```json", "", text) text = re.sub(r"^```", "", text) text = re.sub(r"```$", "", text) return text.strip() def summarize_meeting(client, meeting_text): prompt = f""" 다음 회의록을 간략하게 요약해줘. 너무 길게 쓰지 말고, 아래 형식으로만 정리해. 형식: 1. 회의 주제: 2. 일시: 3. 장소: 4. 참석자: 5. 주요 논의 내용: 6. 주요 일정: 7. 담당 업무: 회의록: {meeting_text} """ response = client.responses.create( model="gpt-4o-mini", input=prompt ) return response.output_text def extract_calendar_events(client, meeting_text): prompt = f""" 다음 회의록에서 Google Calendar에 등록할 일정만 JSON 배열로 추출해줘. 반드시 JSON 배열만 출력해. 설명 문장 금지. 마크다운 코드블록 금지. 조건: - 날짜가 명확한 일정만 추출 - 시간이 없으면 시작시간은 09:00, 종료시간은 10:00 - 날짜 형식은 YYYY-MM-DD - 시간 형식은 HH:MM - 한국 시간 기준 - 장소가 없으면 빈 문자열 - 담당자가 없으면 빈 문자열 - 회의 자체 일정도 날짜와 시간이 있으면 포함 출력 형식: [ {{ "제목": "", "날짜": "YYYY-MM-DD", "시작시간": "HH:MM", "종료시간": "HH:MM", "장소": "", "설명": "", "담당자": "" }} ] 회의록: {meeting_text} """ response = client.responses.create( model="gpt-4o-mini", input=prompt ) raw = clean_json_text(response.output_text) try: return json.loads(raw) except json.JSONDecodeError: st.error("일정 JSON 변환 실패") st.text(raw) return [] def add_events_to_calendar(events, calendar_id): service = get_calendar_service() created_links = [] for item in events: date = item.get("날짜") start_time = item.get("시작시간", "09:00") end_time = item.get("종료시간", "10:00") if not date: continue event = { "summary": item.get("제목", "회의 일정"), "location": item.get("장소", ""), "description": f"담당자: {item.get('담당자', '')}\n\n{item.get('설명', '')}", "start": { "dateTime": f"{date}T{start_time}:00", "timeZone": "Asia/Seoul", }, "end": { "dateTime": f"{date}T{end_time}:00", "timeZone": "Asia/Seoul", }, } created_event = service.events().insert( calendarId=calendar_id, body=event ).execute() created_links.append(created_event.get("htmlLink")) return created_links st.title("회의록 요약 + Google Calendar 일정 등록") st.info("txt 회의록을 업로드하면 내용을 요약하고 날짜가 있는 일정을 Google Calendar에 등록합니다.") calendar_id = st.text_input( "Google Calendar ID", value="primary", help="기본 캘린더는 primary를 그대로 두세요." ) uploaded_file = st.file_uploader("회의록 txt 파일 업로드", type=["txt"]) if uploaded_file is not None: meeting_text = uploaded_file.read().decode("utf-8") st.subheader("업로드한 회의록") st.text_area("회의록 내용", meeting_text, height=250) client = get_openai_client() if st.button("1. 회의록 요약 및 일정 추출"): with st.spinner("회의록을 분석하는 중입니다..."): summary = summarize_meeting(client, meeting_text) events = extract_calendar_events(client, meeting_text) st.session_state["summary"] = summary st.session_state["events"] = events if "summary" in st.session_state: st.subheader("회의록 요약") st.write(st.session_state["summary"]) if "events" in st.session_state: st.subheader("추출된 일정") events = st.session_state["events"] if len(events) == 0: st.warning("등록할 일정이 없습니다.") else: st.dataframe(events, use_container_width=True) if st.button("2. Google Calendar에 일정 등록"): with st.spinner("Google Calendar에 등록하는 중입니다..."): links = add_events_to_calendar(events, calendar_id) st.success(f"총 {len(links)}개 일정 등록 완료") for link in links: st.write(link) else: st.warning("회의록 txt 파일을 업로드하세요.")