Download src/app.py from hyerong/streamlit: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/spaces/hyerong/streamlit/resolve/main/src/app.py
- Command line
-
hf download hf://spaces/hyerong/streamlit/src/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hyerong/streamlit/resolve/main/src/app.py
16 kB
| import os, json, re, io, requests | |
| 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"<style>{f.read()}</style>", 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 = "" | |
| if "messages" not in st.session_state: # ์ค์ : AttributeError ํด๊ฒฐ์ ์ํ ํต์ฌ ์ด๊ธฐํ | |
| st.session_state.messages = [] | |
| # ์ธ์ ์ํ ์ด๊ธฐํ ์ถ๊ฐ!!!!!!!!!!! | |
| if "mail_body" not in st.session_state: # ์ด๋ฉ์ผ ๋ด์ฉ ์ถ๊ฐ | |
| st.session_state.mail_body = "" | |
| # ์ฐ์ ์์ ๋ผ๋ฒจ ์ค์ | |
| 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. ์ผ์ ์์ฝ โ ๋ง๊ฐ ๊ธฐํ ์๊ฐ์ ์ ๋ฆฌ | |
| """ | |
| sys_role_chat = "๋น์ ์ ํ์ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ๋ต๋ณํ๋ ์น์ ํ AI ๋น์ '์์ด๋ธ'์ ๋๋ค. ์ด์ ๋ํ๋ฅผ ๊ธฐ์ตํ๋ฉฐ ์๋ตํฉ๋๋ค." | |
| 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) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Email ๋ฐ์ก ํจ์ ์ถ๊ฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ์ด๋ฉ์ผ ์ด์ ์์ฑ ํจ์ ์ถ๊ฐ!!!!!!!!!!!! | |
| def generate_email_body(res, recipient_name): | |
| decisions = "\n".join(f" โข {d}" for d in res.get("decisions", [])) | |
| tasks = "\n".join( | |
| f" - [{t['person']}] {t['task']}" + (f" (๊ธฐํ: {t['deadline']})" if t.get("deadline") and t["deadline"] != "null" else "") | |
| for t in res.get("tasks", []) | |
| ) | |
| prompt = f""" | |
| ๋ค์ ํ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ์ผ๋ก {recipient_name}์๊ฒ ๋ณด๋ด๋ ๊ณต์ ๋ฆฌ๋ง์ธ๋ ์ด๋ฉ์ผ์ ์์ฑํ์ธ์. | |
| ์ ์คํ๊ณ ์ ๋ฌธ์ ์ธ ์ด์กฐ๋ก ์์ฑํ์ธ์. | |
| ๊ท์น: | |
| - ์ด๋ฉ์ผ ์ฒซ ์ค ์ธ์ฌ๋ ๋ฐ๋์ "KT Enterprise IT๊ธฐ์ ํ์ ํ DX Consulting AI ๋น์ AIBLE์ ๋๋ค." ๋ก ์์ํ์ธ์. | |
| - ์ด๋ฉ์ผ ๋ง์ง๋ง ์๋ช ์ ๋ฐ๋์ "AIBLE ๋๋ฆผ" ์ผ๋ก ๋๋ด์ธ์. | |
| - ๊ทธ ์ธ ์ง์ ์ด๋ฆ์ด๋ ๊ฐ์ธ ์๋ช ์ ์ ๋ ํฌํจํ์ง ๋ง์ธ์. | |
| ํ์ ์ ๋ชฉ: {res.get('meeting_title', 'ํ์')} | |
| ๊ฒฐ์ ์ฌํญ: | |
| {decisions} | |
| ๋ด๋น ์ ๋ฌด: | |
| {tasks} | |
| ์ด๋ฉ์ผ ๋ณธ๋ฌธ๋ง ์ถ๋ ฅํ์ธ์ (์ ๋ชฉ ์ ์ธ). | |
| """ | |
| resp = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.4, | |
| ) | |
| return resp.choices[0].message.content.strip() | |
| # ์ด๋ฉ์ผ ๋ฐ์ก ํจ์ (SendGrid REST API) | |
| def send_email(to, subject, body): | |
| api_key = os.getenv("SENDGRID_API_KEY", "") | |
| sender = os.getenv("EMAIL_ADDRESS", "") | |
| resp = requests.post( | |
| "https://api.sendgrid.com/v3/mail/send", | |
| headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, | |
| json={ | |
| "personalizations": [{"to": [{"email": to}], "subject": subject}], | |
| "from": {"email": sender}, | |
| "content": [{"type": "text/plain", "value": body}], | |
| }, | |
| ) | |
| return resp.status_code == 202 | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 3. UI ๋ ์ด์์ ๊ตฌ์ฑ (ํค๋ ๋ฐ ์ ๋ ฅ๋ถ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ์๋จ ํค๋ ์์ญ (ํฌ๊ธฐ ํ๋ ๋ฒ์ ) | |
| st.markdown(""" | |
| <div style="display:flex;align-items:center;gap:20px;padding:25px 30px; | |
| background:linear-gradient(135deg,#16213E 0%,#0F3460 100%); | |
| border-bottom:4px solid #E3000B;border-radius:15px;margin-bottom:25px;"> | |
| <div style="font-family:'Rajdhani',sans-serif;font-size:3.5rem;font-weight:800; | |
| color:#E3000B;letter-spacing:1px;line-height:1;">KT</div> | |
| <div class="title-container"> | |
| <div class="main-title" style="font-size:2.2rem; font-weight:700; color:#FFFFFF;">Meeting AI ํ์ ๋ถ์ ์ด์์คํดํธ</div> | |
| <div class="sub-title" style="font-size:1.0rem; color:#E0E0E0; margin-top:5px;">IT๊ธฐ์ ํ์ ํ DX Consulting ยท Intelligent Meeting Solution</div> | |
| </div> | |
| </div> | |
| """, 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.session_state.messages = [] # ์ ๋ถ์ ์ ๋ํ ์ด๊ธฐํ, ์ธ์ ๋ฆฌ์คํธ ํ๋ฒ ๋น์์ค์ผํจ | |
| 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, tab4, tab5 = 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) | |
| ###################################################### ์ด๋ฉ์ผ ๋ฐ์ก ์ถ๊ฐ | |
| with tab4: | |
| st.subheader("โ๏ธ ํ์ ๊ฒฐ๊ณผ ๋ฆฌ๋ง์ธ๋ ๋ฉ์ผ") | |
| # ์์ ์ ์ ๋ณด๋ฅผ ๋จผ์ ์ ๋ ฅ | |
| recipient_name = st.text_input("์์ ์ ์ด๋ฆ", placeholder="์) ๊น๋๋ฆฌ") | |
| recipient_email = st.text_input("์์ ์ ์ด๋ฉ์ผ ์ฃผ์", placeholder="์) kim@kt.com") | |
| if st.button("๐ ๋ฉ์ผ ์ด์ ์์ฑ"): | |
| if not recipient_name.strip(): | |
| st.warning("์์ ์ ์ด๋ฆ์ ์ ๋ ฅํด์ฃผ์ธ์.") | |
| else: | |
| with st.spinner("๋ฉ์ผ ์ด์์ ์์ฑํ๋ ์ค..."): | |
| st.session_state.mail_body = generate_email_body(res, recipient_name) | |
| if st.session_state.mail_body: | |
| mail_subject = st.text_input( | |
| "๋ฉ์ผ ์ ๋ชฉ", | |
| value=f"[ํ์ ๊ฒฐ๊ณผ ๊ณต์ ] {res.get('meeting_title', 'ํ์')}", | |
| ) | |
| edited_body = st.text_area( | |
| "๋ฉ์ผ ๋ณธ๋ฌธ (์์ ๊ฐ๋ฅ)", | |
| value=st.session_state.mail_body, | |
| height=300, | |
| ) | |
| if st.button("๐ค ๋ฉ์ผ ๋ฐ์ก"): | |
| if not recipient_email.strip(): | |
| st.warning("์์ ์ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์.") | |
| else: | |
| with st.spinner("๋ฉ์ผ์ ๋ฐ์กํ๋ ์ค..."): | |
| ok = send_email(recipient_email, mail_subject, edited_body) | |
| if ok: | |
| st.success(f"โ {recipient_email} ์ผ๋ก ๋ฉ์ผ์ ๋ฐ์กํ์ต๋๋ค.") | |
| else: | |
| st.error("๋ฉ์ผ ๋ฐ์ก์ ์คํจํ์ต๋๋ค. SENDGRID_API_KEY์ EMAIL_ADDRESS๋ฅผ ํ์ธํด์ฃผ์ธ์.") | |
| ###################################################### ๋ฉํฐํด ๋ํ ์์ฑ, ํ์คํ ๋ฆฌ ์ต๋ 10๊ฐ ์ ๋๋ก | |
| with tab5: | |
| st.markdown("#### ๐ฌ GPT 4o mini์ ๋ํํ๊ธฐ ") | |
| st.info("AI์๊ฒ ๊ถ๊ธํ ์ ์ ๋ฌผ์ด๋ณด์ธ์. ์ฃผ์: ์ต๊ทผ 10๊ฐ์ ๋ํ ๊ธฐ์ต") | |
| # ๋ํ ๊ธฐ๋ก ํ์ | |
| chat_container = st.container(height=500) | |
| with chat_container: | |
| for message in st.session_state.messages: | |
| with st.chat_message(message["role"]): | |
| st.markdown(message["content"]) | |
| # ์ ๋ ฅ ์์ญ (์์ฑ ๋ฐ ํ ์คํธ) | |
| user_msg = st.chat_input("์ง๋ฌธ์ ์ ๋ ฅํ์ธ์...") | |
| if user_msg: | |
| # 1. ์ฌ์ฉ์ ๋ฉ์์ง ์ ์ฅ ๋ฐ ํ์ | |
| st.session_state.messages.append({"role": "user", "content": user_msg}) | |
| with chat_container.chat_message("user"): | |
| st.markdown(user_msg) | |
| # 2. AI ์๋ต ์์ฑ | |
| with chat_container.chat_message("assistant"): | |
| with st.spinner("๋ต๋ณ ์์ฑ ์ค..."): | |
| # ์์คํ ํ๋กฌํํธ์ ํ์ ์๋ฌธ ์ฃผ์ | |
| full_messages = [ | |
| {"role": "system", "content": f"{sys_role_chat}\n\n[ํ์ ์๋ฌธ]\n{st.session_state.transcript}"} | |
| ] | |
| full_messages.extend(st.session_state.messages) | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=full_messages | |
| ) | |
| answer = response.choices[0].message.content | |
| st.markdown(answer) | |
| st.session_state.messages.append({"role": "assistant", "content": answer}) | |
| # 3. ํ์คํ ๋ฆฌ ๊ด๋ฆฌ (์ต๊ทผ 10๊ฐ ๋ฉ์์ง ์ ์ง) | |
| if len(st.session_state.messages) > 10: | |
| st.session_state.messages = st.session_state.messages[-10:] | |
| st.rerun() | |
| if st.button("๐๏ธ ๋ํ ๊ธฐ๋ก ์ด๊ธฐํ"): | |
| st.session_state.messages = [] | |
| st.rerun() | |