Upload src/app.py with huggingface_hub
Browse files- src/app.py +63 -265
src/app.py
CHANGED
|
@@ -22,9 +22,7 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
| 22 |
img_path = os.path.join(current_dir, "kt.png")
|
| 23 |
client = OpenAI()
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
# 2. ์ธ์
์ํ ๋ฐ ์์ ์ค์
|
| 27 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 28 |
if "analysis_result" not in st.session_state:
|
| 29 |
st.session_state.analysis_result = None
|
| 30 |
if "transcript" not in st.session_state:
|
|
@@ -34,10 +32,9 @@ if "transcript" not in st.session_state:
|
|
| 34 |
PRIORITY_LABEL = {"high": "๐ด ๋์", "medium": "๐ก ๋ณดํต", "low": "๐ข ๋ฎ์"}
|
| 35 |
|
| 36 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 37 |
-
#
|
| 38 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 39 |
|
| 40 |
-
# ๋ฏผ๊ฐ์ ๋ณด ๊ฐ์ง ํจ์
|
| 41 |
def detect_sensitive(text):
|
| 42 |
SENSITIVE_PATTERNS = [
|
| 43 |
(r"๊ธฐ๋ฐ|๋น๋ฐ|๋์ธ๋น|๋ด๋ถ.*๋ณด์|๋ณด์.*์ ์ง|confidential", "๊ธฐ๋ฐ/๋์ธ๋น ํํ"),
|
|
@@ -48,7 +45,6 @@ def detect_sensitive(text):
|
|
| 48 |
]
|
| 49 |
return list({label for pat, label in SENSITIVE_PATTERNS if re.search(pat, text, re.IGNORECASE)})
|
| 50 |
|
| 51 |
-
# AI ๋น์ ์ญํ ์ ์
|
| 52 |
sys_role = """
|
| 53 |
## ์ญํ
|
| 54 |
๋น์ ์ KT Enterprise IT๊ธฐ์ ํ์ ํ DX Consulting์ AI ํ์ ๋น์์
๋๋ค.
|
|
@@ -59,313 +55,117 @@ sys_role = """
|
|
| 59 |
2. ๊ฒฐ์ ๋ ์ฌํญ โ ํต์ฌ ๊ฒฐ์ ๋ง ๊ฐ๊ฒฐํ๊ฒ
|
| 60 |
3. ๋ด๋น ์
๋ฌด(Action Items) โ [๋ด๋น์]:[์
๋ฌด]/๊ธฐํ/์ฐ์ ์์
|
| 61 |
4. ์ผ์ ์์ฝ โ ๋ง๊ฐ ๊ธฐํ ์๊ฐ์ ์ ๋ฆฌ
|
| 62 |
-
|
| 63 |
-
## ์ฃผ์์ฌํญ
|
| 64 |
-
- ํ์๋ก์ ์๋ ๋ด์ฉ์ ์ถ๊ฐํ์ง ์์ต๋๋ค.
|
| 65 |
-
- ๋ถ๋ช
ํํ ๋ด์ฉ์ 'ํ์ธ ํ์'๋ก ํ์ํฉ๋๋ค.
|
| 66 |
"""
|
| 67 |
|
| 68 |
-
# ํ์ ๋ถ์ ํจ์
|
| 69 |
def analyze_meeting(text):
|
| 70 |
today = datetime.now().strftime("%Y๋
%m์ %d์ผ")
|
| 71 |
prompt = f"""
|
| 72 |
-
๋ค์์ ์ค๋({today}) ์งํ๋ ํ์ ๋ด์ฉ์
๋๋ค.
|
| 73 |
-
์๋ JSON ํ์์ผ๋ก ๋ถ์ํ์ธ์. ๋ฐ๋์ JSON๋ง ์ถ๋ ฅํ์ธ์.
|
| 74 |
|
| 75 |
ํ์ ๋ด์ฉ:
|
| 76 |
{text}
|
| 77 |
|
| 78 |
{{
|
| 79 |
"meeting_title": "ํ์ ์ ๋ชฉ",
|
| 80 |
-
"purpose": "ํ์ ๋ชฉ์
|
| 81 |
-
"decisions": ["๊ฒฐ์ ์ฌํญ
|
| 82 |
"tasks": [
|
| 83 |
-
{{"person":"๋ด๋น์","task":"์
๋ฌด ๋ด์ฉ","deadline":"๊ธฐํ
|
| 84 |
],
|
| 85 |
-
"agenda_items": ["์๊ฑด
|
| 86 |
-
"
|
| 87 |
-
"
|
| 88 |
-
"keywords": ["ํค์๋1", "ํค์๋2", "ํค์๋3"]
|
| 89 |
}}
|
| 90 |
"""
|
| 91 |
resp = client.chat.completions.create(
|
| 92 |
model="gpt-4o",
|
| 93 |
-
messages=[{"role": "system", "content": sys_role},
|
| 94 |
-
{"role": "user", "content": prompt}],
|
| 95 |
temperature=0.2,
|
| 96 |
)
|
| 97 |
raw = re.sub(r"^```json\s*|```\s*$", "", resp.choices[0].message.content.strip())
|
| 98 |
return json.loads(raw)
|
| 99 |
|
| 100 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 101 |
-
#
|
| 102 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 103 |
-
|
|
|
|
| 104 |
st.markdown("""
|
| 105 |
<div style="display:flex;align-items:center;gap:20px;padding:25px 30px;
|
| 106 |
background:linear-gradient(135deg,#16213E 0%,#0F3460 100%);
|
| 107 |
border-bottom:4px solid #E3000B;border-radius:15px;margin-bottom:25px;">
|
| 108 |
-
<!-- KT ๋ก๊ณ ํฌ๊ธฐ ํ๋ -->
|
| 109 |
<div style="font-family:'Rajdhani',sans-serif;font-size:3.5rem;font-weight:800;
|
| 110 |
color:#E3000B;letter-spacing:1px;line-height:1;">KT</div>
|
| 111 |
<div class="title-container">
|
| 112 |
-
<
|
| 113 |
-
<div class="
|
| 114 |
-
Meeting AI ํ์ ๋ถ์ ์ด์์คํดํธ
|
| 115 |
-
</div>
|
| 116 |
-
<!-- ์๋ธ ํ์ดํ ํฌ๊ธฐ ํ๋ -->
|
| 117 |
-
<div class="sub-title" style="font-size:1.0rem; color:#E0E0E0; margin-top:5px;">
|
| 118 |
-
IT๊ธฐ์ ํ์ ํ DX Consulting ยท Intelligent Meeting Solution
|
| 119 |
-
</div>
|
| 120 |
</div>
|
| 121 |
</div>
|
| 122 |
""", unsafe_allow_html=True)
|
| 123 |
|
| 124 |
col1, col2 = st.columns([1, 2], gap="large")
|
| 125 |
|
| 126 |
-
# ์ข์ธก: ์ด๋ฏธ์ง ๋ฐ ์
๋ ฅ ์๋ด
|
| 127 |
with col1:
|
| 128 |
try:
|
| 129 |
st.image(Image.open(img_path), caption="KT DX Assistant", use_container_width=True)
|
| 130 |
except:
|
| 131 |
st.info("์ด๋ฏธ์ง(kt.png)๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.")
|
| 132 |
|
| 133 |
-
# ์ฐ์ธก: ์
๋ ฅ ํผ
|
| 134 |
with col2:
|
| 135 |
st.subheader("ํ์๋ก์ ์
๋ก๋ํ์๋ฉด ์์ฝํด๋๋ฆฌ๊ฒ ์ต๋๋ค.")
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
uploaded_file = st.file_uploader("๐ ํ์ ๋ด์ฉ ํ
์คํธ ํ์ผ ์
๋ก๋", type=["txt"])
|
| 139 |
-
|
| 140 |
-
# โโ ์์ฒญ ๋ฐฉ์ ์ ํ (form ๋ฐ โ ์ ํ ์ฆ์ ์นธ์ด ๋ฐ๋) โโ
|
| 141 |
-
st.write("**์์ฒญ ๋ฐฉ์ ์ ํ**")
|
| 142 |
-
input_mode = st.radio(
|
| 143 |
-
"์์ฒญ ๋ฐฉ์",
|
| 144 |
-
["๐๏ธ ์ค์๊ฐ ์์ฑ ๋
น์", "๐ ์์ฑ ํ์ผ ์
๋ก๋", "๐ ํ
์คํธ ์ง์ ์
๋ ฅ"],
|
| 145 |
-
horizontal=True,
|
| 146 |
-
label_visibility="collapsed",
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
# โโ ์ ํ๋ ๋ฐฉ์์ ๋ฐ๋ผ ์
๋ ฅ ์นธ ํ์ โโ
|
| 150 |
-
audio_value = None
|
| 151 |
-
uploaded_audio_file = None
|
| 152 |
-
text_input = ""
|
| 153 |
-
|
| 154 |
-
if input_mode == "๐๏ธ ์ค์๊ฐ ์์ฑ ๋
น์":
|
| 155 |
-
audio_value = st.audio_input("๋ฌด์์ ๋์๋๋ฆด๊น์?")
|
| 156 |
-
|
| 157 |
-
elif input_mode == "๐ ์์ฑ ํ์ผ ์
๋ก๋":
|
| 158 |
-
uploaded_audio_file = st.file_uploader(
|
| 159 |
-
"์์ฒญ ์์ฑ ํ์ผ์ ์
๋ก๋ํ์ธ์",
|
| 160 |
-
type=["mp3", "wav", "m4a"],
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
else: # ํ
์คํธ ์ง์ ์
๋ ฅ
|
| 164 |
-
text_input = st.text_area("์์ฒญ ์ฌํญ์ ์
๋ ฅํ์ธ์", height=120,
|
| 165 |
-
placeholder="์) ํ์ ๋ด์ฉ์ ์์ฝํด์ค / ๊น๋๋ฆฌ ํ ์ผ๋ง ์ ๋ฆฌํด์ค")
|
| 166 |
-
|
| 167 |
-
# โโ ์คํ ๋ฒํผ โโ
|
| 168 |
-
submit = st.button("์คํ", use_container_width=True)
|
| 169 |
-
|
| 170 |
-
# โโ ์คํ ๋ฒํผ์ ๋๋ ์ ๋ โโ
|
| 171 |
-
if submit:
|
| 172 |
-
# ํ์๋ก ํ
์คํธ ํ์ผ ์ฝ๊ธฐ
|
| 173 |
-
text = ""
|
| 174 |
-
if uploaded_file is not None:
|
| 175 |
-
text = uploaded_file.read().decode("utf-8")
|
| 176 |
-
|
| 177 |
-
# ์
๋ ฅ๊ฐ ๊ฒ์ฆ
|
| 178 |
-
if audio_value is None and uploaded_audio_file is None and not text_input.strip():
|
| 179 |
-
st.warning("์์ฒญ ๋ฐฉ์์ ์ ํํ๊ณ ๋ด์ฉ์ ์
๋ ฅํด์ฃผ์ธ์.")
|
| 180 |
-
st.stop()
|
| 181 |
-
|
| 182 |
-
# ์์คํ
๋ฉ์์ง ๊ตฌ์ฑ
|
| 183 |
-
system_content = sys_role
|
| 184 |
-
if text:
|
| 185 |
-
system_content += f"\n\n๋ค์ ํ์ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ธ์:\n{text}"
|
| 186 |
-
input_messages = [{"role": "system", "content": system_content}]
|
| 187 |
-
|
| 188 |
-
# STT ๋๋ ํ
์คํธ ์ฒ๋ฆฌ
|
| 189 |
-
if audio_value is not None:
|
| 190 |
-
transcript = client.audio.transcriptions.create(
|
| 191 |
-
model="whisper-1",
|
| 192 |
-
file=("audio.wav", audio_value, "audio/wav"),
|
| 193 |
-
)
|
| 194 |
-
user_text = transcript.text
|
| 195 |
-
|
| 196 |
-
elif uploaded_audio_file is not None:
|
| 197 |
-
transcript = client.audio.transcriptions.create(
|
| 198 |
-
model="whisper-1",
|
| 199 |
-
file=(uploaded_audio_file.name, uploaded_audio_file, uploaded_audio_file.type),
|
| 200 |
-
)
|
| 201 |
-
user_text = transcript.text
|
| 202 |
-
|
| 203 |
-
else:
|
| 204 |
-
user_text = text_input.strip()
|
| 205 |
-
|
| 206 |
-
input_messages.append({"role": "user", "content": user_text})
|
| 207 |
-
# with col2:
|
| 208 |
-
# st.markdown('<div class="input-title">๐ ํ์ ๋ด์ฉ ์
๋ ฅ</div>', unsafe_allow_html=True)
|
| 209 |
-
# with st.form("task_form"):
|
| 210 |
-
# uploaded_file = st.file_uploader("ํ
์คํธ ํ์ผ ์
๋ก๋ (.txt)", type=["txt"])
|
| 211 |
-
# audio_value = st.audio_input("์์ฑ์ผ๋ก ํ์ ๋ด์ฉ์ ์
๋ ฅํ์ธ์")
|
| 212 |
-
# submit = st.form_submit_button("๐ ํ์ ๋ถ์ ์์")
|
| 213 |
-
|
| 214 |
-
# if submit:
|
| 215 |
-
# final_text = ""
|
| 216 |
-
# # 1. ํ
์คํธ ํ์ผ ์ฒ๋ฆฌ
|
| 217 |
-
# if uploaded_file:
|
| 218 |
-
# final_text = uploaded_file.read().decode("utf-8")
|
| 219 |
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
# with st.spinner("๐๏ธ ์์ฑ์ ํ
์คํธ๋ก ๋ณํํ๋ ์ค..."):
|
| 223 |
-
# transcript = client.audio.transcriptions.create(
|
| 224 |
-
# model="whisper-1",
|
| 225 |
-
# file=("audio.wav", audio_value, "audio/wav"),
|
| 226 |
-
# language="ko"
|
| 227 |
-
# ).text
|
| 228 |
-
# final_text += "\n" + transcript
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
# with st.spinner("๐ค AI๊ฐ ํ์๋ก์ ๋ถ์ํ๊ณ ์์ต๋๋ค..."):
|
| 234 |
-
# try:
|
| 235 |
-
# st.session_state.transcript = final_text
|
| 236 |
-
# st.session_state.analysis_result = analyze_meeting(final_text)
|
| 237 |
-
# except Exception as e:
|
| 238 |
-
# st.error(f"๋ถ์ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
| 239 |
-
|
| 240 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 241 |
-
# 5. ๊ฒฐ๊ณผ ์ถ๋ ฅ ์์ญ (Tabs ํ์ฉ)
|
| 242 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 243 |
-
if st.session_state.analysis_result:
|
| 244 |
-
res = st.session_state.analysis_result
|
| 245 |
-
|
| 246 |
-
# ๋ณด์ ๊ฒฝ๊ณ ์ถ๋ ฅ
|
| 247 |
-
hits = detect_sensitive(st.session_state.transcript)
|
| 248 |
-
if hits:
|
| 249 |
-
st.error(f"๐ ๋ฏผ๊ฐ์ ๋ณด ๊ฐ์ง: {', '.join(hits)} ํญ๋ชฉ์ด ํฌํจ๋์ด ์์ต๋๋ค. ์ธ๋ถ ์ ์ถ์ ์ฃผ์ํ์ธ์.")
|
| 250 |
-
|
| 251 |
-
tab1, tab2, tab3 = st.tabs(["๐ ํ์ ์์ฝ", "โ
ํ ์ผ & ์ฐ์ ์์", "๐ ์๋ฌธ ๋ณด๊ธฐ"])
|
| 252 |
-
|
| 253 |
-
with tab1:
|
| 254 |
-
st.markdown(f"### ๐ {res.get('meeting_title', 'ํ์ ๊ฒฐ๊ณผ')}")
|
| 255 |
-
st.info(f"**ํ์ ๋ชฉ์ :** {res.get('purpose', '๋ด์ฉ ์์')}")
|
| 256 |
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
st.write(f"- {d}")
|
| 262 |
-
with c2:
|
| 263 |
-
st.subheader("๐ ์ฃผ์ ์๊ฑด")
|
| 264 |
-
for a in res.get('agenda_items', []):
|
| 265 |
-
st.write(f"- {a}")
|
| 266 |
-
|
| 267 |
-
st.divider()
|
| 268 |
-
st.subheader("๐ฌ ์ ์ฒด ์์ฝ")
|
| 269 |
-
st.write(res.get('summary', '์์ฝ ๋ด์ฉ์ด ์์ต๋๋ค.'))
|
| 270 |
-
|
| 271 |
-
with tab2:
|
| 272 |
-
tasks = res.get('tasks', [])
|
| 273 |
-
if not tasks:
|
| 274 |
-
st.write("๋ฐฐ์ ๋ ํ ์ผ์ด ์์ต๋๋ค.")
|
| 275 |
else:
|
| 276 |
-
|
| 277 |
-
p = t.get('priority', 'low')
|
| 278 |
-
label = PRIORITY_LABEL.get(p, "โช ์ ๋ณด์์")
|
| 279 |
-
deadline = f" (๊ธฐํ: {t['deadline']})" if t.get('deadline') else ""
|
| 280 |
-
st.markdown(f"**{label} [{t.get('person')}]** : {t.get('task')}{deadline}")
|
| 281 |
|
| 282 |
-
|
| 283 |
-
st.markdown("#### ๐ ํต์ฌ ํค์๋")
|
| 284 |
-
st.write(", ".join(res.get('keywords', [])))
|
| 285 |
-
st.divider()
|
| 286 |
-
st.text_area("์ ์ฌ ์๋ฌธ", st.session_state.transcript, height=300)
|
| 287 |
-
submit = st.button("์คํ", use_container_width=True)
|
| 288 |
-
|
| 289 |
-
# โโ ์คํ ๋ฒํผ์ ๋๋ ์ ๋ โโ
|
| 290 |
-
if submit:
|
| 291 |
-
# ํ์๋ก ํ
์คํธ ํ์ผ ์ฝ๊ธฐ
|
| 292 |
-
text = ""
|
| 293 |
-
if uploaded_file is not None:
|
| 294 |
-
text = uploaded_file.read().decode("utf-8")
|
| 295 |
-
|
| 296 |
-
# ์
๋ ฅ๊ฐ ๊ฒ์ฆ
|
| 297 |
-
if audio_value is None and uploaded_audio_file is None and not text_input.strip():
|
| 298 |
-
st.warning("์์ฒญ ๋ฐฉ์์ ์ ํํ๊ณ ๋ด์ฉ์ ์
๋ ฅํด์ฃผ์ธ์.")
|
| 299 |
-
st.stop()
|
| 300 |
-
|
| 301 |
-
# ์์คํ
๋ฉ์์ง ๊ตฌ์ฑ
|
| 302 |
-
system_content = sys_role
|
| 303 |
-
if text:
|
| 304 |
-
system_content += f"\n\n๋ค์ ํ์ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ธ์:\n{text}"
|
| 305 |
-
input_messages = [{"role": "system", "content": system_content}]
|
| 306 |
-
|
| 307 |
-
# STT ๋๋ ํ
์คํธ ์ฒ๋ฆฌ
|
| 308 |
-
if audio_value is not None:
|
| 309 |
-
transcript = client.audio.transcriptions.create(
|
| 310 |
-
model="whisper-1",
|
| 311 |
-
file=("audio.wav", audio_value, "audio/wav"),
|
| 312 |
-
)
|
| 313 |
-
user_text = transcript.text
|
| 314 |
-
|
| 315 |
-
elif uploaded_audio_file is not None:
|
| 316 |
-
transcript = client.audio.transcriptions.create(
|
| 317 |
-
model="whisper-1",
|
| 318 |
-
file=(uploaded_audio_file.name, uploaded_audio_file, uploaded_audio_file.type),
|
| 319 |
-
)
|
| 320 |
-
user_text = transcript.text
|
| 321 |
-
|
| 322 |
-
else:
|
| 323 |
-
user_text = text_input.strip()
|
| 324 |
-
|
| 325 |
-
input_messages.append({"role": "user", "content": user_text})
|
| 326 |
-
# with col2:
|
| 327 |
-
# st.markdown('<div class="input-title">๐ ํ์ ๋ด์ฉ ์
๋ ฅ</div>', unsafe_allow_html=True)
|
| 328 |
-
# with st.form("task_form"):
|
| 329 |
-
# uploaded_file = st.file_uploader("ํ
์คํธ ํ์ผ ์
๋ก๋ (.txt)", type=["txt"])
|
| 330 |
-
# audio_value = st.audio_input("์์ฑ์ผ๋ก ํ์ ๋ด์ฉ์ ์
๋ ฅํ์ธ์")
|
| 331 |
-
# submit = st.form_submit_button("๐ ํ์ ๋ถ์ ์์")
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
|
| 359 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 360 |
-
#
|
| 361 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 362 |
if st.session_state.analysis_result:
|
| 363 |
res = st.session_state.analysis_result
|
| 364 |
-
|
| 365 |
-
# ๋ณด์ ๊ฒฝ๊ณ ์ถ๋ ฅ
|
| 366 |
hits = detect_sensitive(st.session_state.transcript)
|
| 367 |
if hits:
|
| 368 |
-
st.error(f"๐ ๋ฏผ๊ฐ์ ๋ณด ๊ฐ์ง: {', '.join(hits)} ํญ๋ชฉ
|
| 369 |
|
| 370 |
tab1, tab2, tab3 = st.tabs(["๐ ํ์ ์์ฝ", "โ
ํ ์ผ & ์ฐ์ ์์", "๐ ์๋ฌธ ๋ณด๊ธฐ"])
|
| 371 |
|
|
@@ -376,16 +176,14 @@ if st.session_state.analysis_result:
|
|
| 376 |
c1, c2 = st.columns(2)
|
| 377 |
with c1:
|
| 378 |
st.subheader("โ
๊ฒฐ์ ์ฌํญ")
|
| 379 |
-
for d in res.get('decisions', []):
|
| 380 |
-
st.write(f"- {d}")
|
| 381 |
with c2:
|
| 382 |
st.subheader("๐ ์ฃผ์ ์๊ฑด")
|
| 383 |
-
for a in res.get('agenda_items', []):
|
| 384 |
-
st.write(f"- {a}")
|
| 385 |
|
| 386 |
st.divider()
|
| 387 |
st.subheader("๐ฌ ์ ์ฒด ์์ฝ")
|
| 388 |
-
st.write(res.get('summary', '์์ฝ ๋ด์ฉ
|
| 389 |
|
| 390 |
with tab2:
|
| 391 |
tasks = res.get('tasks', [])
|
|
@@ -393,13 +191,13 @@ if st.session_state.analysis_result:
|
|
| 393 |
st.write("๋ฐฐ์ ๋ ํ ์ผ์ด ์์ต๋๋ค.")
|
| 394 |
else:
|
| 395 |
for t in tasks:
|
| 396 |
-
p = t.get('priority', 'low')
|
| 397 |
-
label = PRIORITY_LABEL.get(p, "
|
| 398 |
-
deadline = f" (๊ธฐํ: {t['deadline']})" if t.get('deadline') else ""
|
| 399 |
-
st.markdown(f"**{label} [{t.get('person')}]** : {t.get('task')}{deadline}")
|
| 400 |
|
| 401 |
with tab3:
|
| 402 |
st.markdown("#### ๐ ํต์ฌ ํค์๋")
|
| 403 |
-
st.write(", ".join(res.get('keywords', [])))
|
| 404 |
st.divider()
|
| 405 |
st.text_area("์ ์ฌ ์๋ฌธ", st.session_state.transcript, height=300)
|
|
|
|
| 22 |
img_path = os.path.join(current_dir, "kt.png")
|
| 23 |
client = OpenAI()
|
| 24 |
|
| 25 |
+
# ์ธ์
์ํ ์ด๊ธฐํ
|
|
|
|
|
|
|
| 26 |
if "analysis_result" not in st.session_state:
|
| 27 |
st.session_state.analysis_result = None
|
| 28 |
if "transcript" not in st.session_state:
|
|
|
|
| 32 |
PRIORITY_LABEL = {"high": "๐ด ๋์", "medium": "๐ก ๋ณดํต", "low": "๐ข ๋ฎ์"}
|
| 33 |
|
| 34 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 35 |
+
# 2. ๋ฐฑ์๋ ํต์ฌ ํจ์ (๋ฏผ๊ฐ์ ๋ณด ๊ฐ์ง, ๋ถ์)
|
| 36 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 37 |
|
|
|
|
| 38 |
def detect_sensitive(text):
|
| 39 |
SENSITIVE_PATTERNS = [
|
| 40 |
(r"๊ธฐ๋ฐ|๋น๋ฐ|๋์ธ๋น|๋ด๋ถ.*๋ณด์|๋ณด์.*์ ์ง|confidential", "๊ธฐ๋ฐ/๋์ธ๋น ํํ"),
|
|
|
|
| 45 |
]
|
| 46 |
return list({label for pat, label in SENSITIVE_PATTERNS if re.search(pat, text, re.IGNORECASE)})
|
| 47 |
|
|
|
|
| 48 |
sys_role = """
|
| 49 |
## ์ญํ
|
| 50 |
๋น์ ์ KT Enterprise IT๊ธฐ์ ํ์ ํ DX Consulting์ AI ํ์ ๋น์์
๋๋ค.
|
|
|
|
| 55 |
2. ๊ฒฐ์ ๋ ์ฌํญ โ ํต์ฌ ๊ฒฐ์ ๋ง ๊ฐ๊ฒฐํ๊ฒ
|
| 56 |
3. ๋ด๋น ์
๋ฌด(Action Items) โ [๋ด๋น์]:[์
๋ฌด]/๊ธฐํ/์ฐ์ ์์
|
| 57 |
4. ์ผ์ ์์ฝ โ ๋ง๊ฐ ๊ธฐํ ์๊ฐ์ ์ ๋ฆฌ
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
"""
|
| 59 |
|
|
|
|
| 60 |
def analyze_meeting(text):
|
| 61 |
today = datetime.now().strftime("%Y๋
%m์ %d์ผ")
|
| 62 |
prompt = f"""
|
| 63 |
+
๋ค์์ ์ค๋({today}) ์งํ๋ ํ์ ๋ด์ฉ์
๋๋ค. ์๋ JSON ํ์์ผ๋ก ๋ถ์ํ์ธ์. ๋ฐ๋์ JSON๋ง ์ถ๋ ฅํ์ธ์.
|
|
|
|
| 64 |
|
| 65 |
ํ์ ๋ด์ฉ:
|
| 66 |
{text}
|
| 67 |
|
| 68 |
{{
|
| 69 |
"meeting_title": "ํ์ ์ ๋ชฉ",
|
| 70 |
+
"purpose": "ํ์ ๋ชฉ์ ",
|
| 71 |
+
"decisions": ["๊ฒฐ์ ์ฌํญ ๋ฆฌ์คํธ"],
|
| 72 |
"tasks": [
|
| 73 |
+
{{"person":"๋ด๋น์","task":"์
๋ฌด ๋ด์ฉ","deadline":"๊ธฐํ","priority":"high|medium|low"}}
|
| 74 |
],
|
| 75 |
+
"agenda_items": ["์๊ฑด ๋ฆฌ์คํธ"],
|
| 76 |
+
"summary": "์ ์ฒด ์์ฝ",
|
| 77 |
+
"keywords": ["ํค์๋1", "ํค์๋2"]
|
|
|
|
| 78 |
}}
|
| 79 |
"""
|
| 80 |
resp = client.chat.completions.create(
|
| 81 |
model="gpt-4o",
|
| 82 |
+
messages=[{"role": "system", "content": sys_role}, {"role": "user", "content": prompt}],
|
|
|
|
| 83 |
temperature=0.2,
|
| 84 |
)
|
| 85 |
raw = re.sub(r"^```json\s*|```\s*$", "", resp.choices[0].message.content.strip())
|
| 86 |
return json.loads(raw)
|
| 87 |
|
| 88 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 89 |
+
# 3. UI ๋ ์ด์์ ๊ตฌ์ฑ (ํค๋ ๋ฐ ์
๋ ฅ๋ถ)
|
| 90 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 91 |
+
|
| 92 |
+
# ์๋จ ํค๋ ์์ญ (ํฌ๊ธฐ ํ๋ ๋ฒ์ )
|
| 93 |
st.markdown("""
|
| 94 |
<div style="display:flex;align-items:center;gap:20px;padding:25px 30px;
|
| 95 |
background:linear-gradient(135deg,#16213E 0%,#0F3460 100%);
|
| 96 |
border-bottom:4px solid #E3000B;border-radius:15px;margin-bottom:25px;">
|
|
|
|
| 97 |
<div style="font-family:'Rajdhani',sans-serif;font-size:3.5rem;font-weight:800;
|
| 98 |
color:#E3000B;letter-spacing:1px;line-height:1;">KT</div>
|
| 99 |
<div class="title-container">
|
| 100 |
+
<div class="main-title" style="font-size:2.2rem; font-weight:700; color:#FFFFFF;">Meeting AI ํ์ ๋ถ์ ์ด์์คํดํธ</div>
|
| 101 |
+
<div class="sub-title" style="font-size:1.0rem; color:#E0E0E0; margin-top:5px;">IT๊ธฐ์ ํ์ ํ DX Consulting ยท Intelligent Meeting Solution</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
</div>
|
| 103 |
</div>
|
| 104 |
""", unsafe_allow_html=True)
|
| 105 |
|
| 106 |
col1, col2 = st.columns([1, 2], gap="large")
|
| 107 |
|
|
|
|
| 108 |
with col1:
|
| 109 |
try:
|
| 110 |
st.image(Image.open(img_path), caption="KT DX Assistant", use_container_width=True)
|
| 111 |
except:
|
| 112 |
st.info("์ด๋ฏธ์ง(kt.png)๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.")
|
| 113 |
|
|
|
|
| 114 |
with col2:
|
| 115 |
st.subheader("ํ์๋ก์ ์
๋ก๋ํ์๋ฉด ์์ฝํด๋๋ฆฌ๊ฒ ์ต๋๋ค.")
|
| 116 |
+
with st.container(border=True):
|
| 117 |
+
uploaded_file = st.file_uploader("๐ ํ์ ๋ด์ฉ ํ
์คํธ ํ์ผ ์
๋ก๋", type=["txt"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
st.write("**์์ฒญ ๋ฐฉ์ ์ ํ**")
|
| 120 |
+
input_mode = st.radio("์์ฒญ ๋ฐฉ์", ["๐๏ธ ์ค์๊ฐ ๋
น์", "๐ ์์ฑ ํ์ผ ์
๋ก๋", "๐ ํ
์คํธ ์
๋ ฅ"], horizontal=True, label_visibility="collapsed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
+
audio_value = None
|
| 123 |
+
uploaded_audio_file = None
|
| 124 |
+
text_input = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
if input_mode == "๐๏ธ ์ค์๊ฐ ๋
น์":
|
| 127 |
+
audio_value = st.audio_input("์์ฑ์ผ๋ก ๋ด์ฉ์ ๋งํด์ฃผ์ธ์.")
|
| 128 |
+
elif input_mode == "๐ ์์ฑ ํ์ผ ์
๋ก๋":
|
| 129 |
+
uploaded_audio_file = st.file_uploader("์์ฑ ํ์ผ ์
๋ก๋", type=["mp3", "wav", "m4a"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
else:
|
| 131 |
+
text_input = st.text_area("๋ด์ฉ์ ์ง์ ์
๋ ฅํ์ธ์.", height=150)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
submit = st.button("๐ ํ์ ๋ถ์ ์์", use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
if submit:
|
| 136 |
+
meeting_text = ""
|
| 137 |
+
if uploaded_file:
|
| 138 |
+
meeting_text = uploaded_file.read().decode("utf-8")
|
| 139 |
+
|
| 140 |
+
with st.spinner("AI๊ฐ ๋ด์ฉ์ ๋ถ์ ์ค์
๋๋ค..."):
|
| 141 |
+
try:
|
| 142 |
+
user_req = ""
|
| 143 |
+
if audio_value:
|
| 144 |
+
user_req = client.audio.transcriptions.create(model="whisper-1", file=("audio.wav", audio_value, "audio/wav")).text
|
| 145 |
+
elif uploaded_audio_file:
|
| 146 |
+
user_req = client.audio.transcriptions.create(model="whisper-1", file=(uploaded_audio_file.name, uploaded_audio_file, uploaded_audio_file.type)).text
|
| 147 |
+
else:
|
| 148 |
+
user_req = text_input
|
| 149 |
+
|
| 150 |
+
final_content = (meeting_text + "\n" + user_req).strip()
|
| 151 |
+
|
| 152 |
+
if not final_content:
|
| 153 |
+
st.warning("๋ถ์ํ ๋ด์ฉ์ด ์์ต๋๋ค.")
|
| 154 |
+
else:
|
| 155 |
+
st.session_state.transcript = final_content
|
| 156 |
+
st.session_state.analysis_result = analyze_meeting(final_content)
|
| 157 |
+
st.rerun()
|
| 158 |
+
except Exception as e:
|
| 159 |
+
st.error(f"์ค๋ฅ ๋ฐ์: {e}")
|
| 160 |
|
| 161 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 162 |
+
# 4. ๊ฒฐ๊ณผ ์ถ๋ ฅ ์์ญ (Tabs ํ์ฉ)
|
| 163 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 164 |
if st.session_state.analysis_result:
|
| 165 |
res = st.session_state.analysis_result
|
|
|
|
|
|
|
| 166 |
hits = detect_sensitive(st.session_state.transcript)
|
| 167 |
if hits:
|
| 168 |
+
st.error(f"๐ ๋ฏผ๊ฐ์ ๋ณด ๊ฐ์ง: {', '.join(hits)} ํญ๋ชฉ ์ฃผ์")
|
| 169 |
|
| 170 |
tab1, tab2, tab3 = st.tabs(["๐ ํ์ ์์ฝ", "โ
ํ ์ผ & ์ฐ์ ์์", "๐ ์๋ฌธ ๋ณด๊ธฐ"])
|
| 171 |
|
|
|
|
| 176 |
c1, c2 = st.columns(2)
|
| 177 |
with c1:
|
| 178 |
st.subheader("โ
๊ฒฐ์ ์ฌํญ")
|
| 179 |
+
for d in res.get('decisions', []): st.write(f"- {d}")
|
|
|
|
| 180 |
with c2:
|
| 181 |
st.subheader("๐ ์ฃผ์ ์๊ฑด")
|
| 182 |
+
for a in res.get('agenda_items', []): st.write(f"- {a}")
|
|
|
|
| 183 |
|
| 184 |
st.divider()
|
| 185 |
st.subheader("๐ฌ ์ ์ฒด ์์ฝ")
|
| 186 |
+
st.write(res.get('summary', '์์ฝ ๋ด์ฉ ์์'))
|
| 187 |
|
| 188 |
with tab2:
|
| 189 |
tasks = res.get('tasks', [])
|
|
|
|
| 191 |
st.write("๋ฐฐ์ ๋ ํ ์ผ์ด ์์ต๋๋ค.")
|
| 192 |
else:
|
| 193 |
for t in tasks:
|
| 194 |
+
p = t.get('priority', 'low').lower()
|
| 195 |
+
label = PRIORITY_LABEL.get(p, "๐ข ๋ฎ์")
|
| 196 |
+
deadline = f" (๊ธฐํ: {t['deadline']})" if t.get('deadline') and t['deadline'] != "null" else ""
|
| 197 |
+
st.markdown(f"**{label} [{t.get('person', '๋ฏธ์ง์ ')}]** : {t.get('task')}{deadline}")
|
| 198 |
|
| 199 |
with tab3:
|
| 200 |
st.markdown("#### ๐ ํต์ฌ ํค์๋")
|
| 201 |
+
st.write(", ".join([f"#{k}" for k in res.get('keywords', [])]))
|
| 202 |
st.divider()
|
| 203 |
st.text_area("์ ์ฌ ์๋ฌธ", st.session_state.transcript, height=300)
|