Download src/app.py from kbg05204/streamlit: direct link, hf CLI and curl.
- Browser
- Download file 9.19 kB
-
https://huggingface.co/spaces/kbg05204/streamlit/resolve/main/src/app.py
- Command line
-
hf download hf://spaces/kbg05204/streamlit/src/app.py
-
curl -L -o app.py https://huggingface.co/spaces/kbg05204/streamlit/resolve/main/src/app.py
9.19 kB
| 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"<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 = "" | |
| # ์ฐ์ ์์ ๋ผ๋ฒจ ์ค์ | |
| 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(""" | |
| <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.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) | |