Spaces:
Runtime error
Runtime error
Commit
·
bdc9d3f
1
Parent(s):
fda2213
공감 비서 프롬프트 및 설문조사 링크 위치 개선
Browse files
app.py
CHANGED
@@ -278,7 +278,7 @@ A: 단기적으로는 맞습니다. 성과 없이 퇴사하면 이력서에 남
|
|
278 |
logger.error(f"Error during OpenAI API call: {e}", exc_info=True)
|
279 |
return "답변을 생성하는 중에 문제가 발생했습니다. OpenAI API 키 또는 서비스 상태를 확인해주세요."
|
280 |
|
281 |
-
# --- Streamlit 앱 UI (
|
282 |
st.set_page_config(page_title="Khan 멘토 (PM 영상 기반)", layout="wide")
|
283 |
|
284 |
# --- 사이드바 메뉴 ---
|
@@ -286,16 +286,18 @@ menu = st.sidebar.radio(
|
|
286 |
"기능 선택",
|
287 |
("Khan 멘토에게 상담하기", "상사에게 잘보이기")
|
288 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
|
290 |
openai_client = init_openai_client()
|
291 |
|
292 |
if menu == "Khan 멘토에게 상담하기":
|
293 |
st.title("✨ Khan 멘토에게 질문하기")
|
294 |
-
|
295 |
-
'<a href="https://forms.gle/SUqrGBT3dktSB7v26" target="_blank" style="display:inline-block; background:#f9e79f; color:#1a237e; font-weight:bold; padding:0.5em 1.2em; border-radius:8px; text-decoration:none; font-size:1.1em; margin-bottom:16px;">📝 서비스 사용성 설문조사 참여하기</a>',
|
296 |
-
unsafe_allow_html=True
|
297 |
-
)
|
298 |
-
st.markdown("PM 관련 영상 내용을 기반으로 Khan 멘토가 답변해 드립니다.")
|
299 |
|
300 |
# --- API 키 확인 및 리소스 초기화 ---
|
301 |
pc = init_pinecone()
|
@@ -303,99 +305,76 @@ if menu == "Khan 멘토에게 상담하기":
|
|
303 |
index = get_pinecone_index(pc, INDEX_NAME)
|
304 |
|
305 |
# --- 상태 관리 ---
|
306 |
-
if '
|
307 |
-
st.session_state['
|
308 |
if 'empathy_message' not in st.session_state:
|
309 |
st.session_state['empathy_message'] = ''
|
310 |
-
if 'example_questions' not in st.session_state:
|
311 |
-
st.session_state['example_questions'] = []
|
312 |
-
if 'selected_question' not in st.session_state:
|
313 |
-
st.session_state['selected_question'] = ''
|
314 |
if 'khan_answer' not in st.session_state:
|
315 |
st.session_state['khan_answer'] = ''
|
316 |
-
if '
|
317 |
-
st.session_state['
|
318 |
-
|
319 |
-
|
320 |
-
if
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
with st.spinner("
|
331 |
-
# 1. 공감 메시지 생성
|
332 |
empathy_prompt = f"""
|
333 |
-
너는 따뜻하고
|
334 |
-
아래 사용자의 상황을 듣고, 충분히 감정적으로 공감해주고, 용기를 북돋아주는 말을 해줘.
|
335 |
-
부를 때는 '당신은', '당신이'처럼 친근한 말투로 부르고, 물음표는 사용하지 않아.
|
336 |
-
상황: "{st.session_state['user_state']}"
|
337 |
"""
|
338 |
try:
|
339 |
empathy_response = openai_client.chat.completions.create(
|
340 |
-
model="gpt-4o",
|
341 |
messages=[{"role": "system", "content": empathy_prompt}],
|
342 |
temperature=0.7,
|
343 |
)
|
344 |
st.session_state['empathy_message'] = empathy_response.choices[0].message.content.strip()
|
345 |
except Exception as e:
|
346 |
st.session_state['empathy_message'] = f"공감 메시지 생성 중 오류: {e}"
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
try:
|
352 |
-
|
353 |
-
model="gpt-4o",
|
354 |
-
messages=[{"role": "system", "content":
|
355 |
-
temperature=0.5
|
356 |
)
|
357 |
-
# 응답에서 질문만 추출 (숫자/기호/줄바꿈 등 정제)
|
358 |
import re
|
359 |
-
raw =
|
360 |
questions = re.findall(r'\d+\.\s*(.+)', raw)
|
361 |
if not questions:
|
362 |
-
# 숫자 없이 줄바꿈만 있을 경우
|
363 |
questions = [q.strip('-• ').strip() for q in raw.split('\n') if q.strip()]
|
364 |
-
st.session_state['
|
365 |
-
except Exception as e:
|
366 |
-
st.session_state['example_questions'] = [f"예시 질문 생성 중 오류: {e}"]
|
367 |
-
# 3단계로 이동
|
368 |
-
st.session_state['step'] = 3
|
369 |
-
st.rerun()
|
370 |
-
|
371 |
-
# --- 3단계: 공감 메시지 + 예시 질문 버튼/직접입력 + Khan 답변 ---
|
372 |
-
if st.session_state['step'] == 3:
|
373 |
-
st.success(st.session_state['empathy_message'])
|
374 |
-
st.markdown("#### 이런 질문을 해볼 수 있어요!")
|
375 |
-
cols = st.columns(len(st.session_state['example_questions']))
|
376 |
-
for i, q in enumerate(st.session_state['example_questions']):
|
377 |
-
if cols[i].button(q):
|
378 |
-
st.session_state['selected_question'] = q
|
379 |
-
st.session_state['step'] = 4
|
380 |
st.rerun()
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
st.session_state['selected_question'] = user_q
|
385 |
-
st.session_state['step'] = 4
|
386 |
-
st.rerun()
|
387 |
|
388 |
-
# ---
|
389 |
-
if st.session_state['
|
390 |
-
|
391 |
-
pinecone_results = search(st.session_state['selected_question'], top_k=5, _index=index, _model=model)
|
392 |
-
khan_answer = generate_khan_answer(st.session_state['selected_question'], pinecone_results, openai_client)
|
393 |
-
st.session_state['khan_answer'] = khan_answer
|
394 |
st.subheader("💡 Khan 멘토의 답변")
|
395 |
st.markdown(st.session_state['khan_answer'])
|
396 |
# 참고 영상 정보 표시
|
|
|
397 |
if pinecone_results:
|
398 |
-
with st.expander("답변에 참고한 영상 정보 보기"):
|
399 |
displayed_urls = set()
|
400 |
for i, r in enumerate(pinecone_results):
|
401 |
url = r.get('URL', 'N/A')
|
@@ -437,25 +416,49 @@ if menu == "Khan 멘토에게 상담하기":
|
|
437 |
except Exception as e:
|
438 |
logger.warning(f"st.video failed for non-YouTube URL {url}: {e}")
|
439 |
st.markdown("---")
|
440 |
-
# ---
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
if cols[i].button(q, key=f"followup_{i}"):
|
445 |
-
st.session_state['selected_question'] = q
|
446 |
-
st.session_state['step'] = 4
|
447 |
-
st.rerun()
|
448 |
-
user_q = st.text_input("직접 궁금한 점을 입력해도 좋아요! (후속 질문)", value="", key="followup_input")
|
449 |
-
if st.button("Khan 멘토에게 추가 질문하기", key="followup_btn"):
|
450 |
-
st.session_state['selected_question'] = user_q
|
451 |
-
st.session_state['step'] = 4
|
452 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
453 |
st.markdown("---")
|
454 |
st.caption("Powered by Pinecone, Sentence Transformers, and OpenAI")
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
st.session_state[k] = '' if k != 'step' else 1
|
459 |
st.rerun()
|
460 |
else:
|
461 |
st.title("👔 상사에게 잘보이기: 맞춤 보고문 만들기")
|
|
|
278 |
logger.error(f"Error during OpenAI API call: {e}", exc_info=True)
|
279 |
return "답변을 생성하는 중에 문제가 발생했습니다. OpenAI API 키 또는 서비스 상태를 확인해주세요."
|
280 |
|
281 |
+
# --- Streamlit 앱 UI (Khan 멘토 단일 루프 구조) ---
|
282 |
st.set_page_config(page_title="Khan 멘토 (PM 영상 기반)", layout="wide")
|
283 |
|
284 |
# --- 사이드바 메뉴 ---
|
|
|
286 |
"기능 선택",
|
287 |
("Khan 멘토에게 상담하기", "상사에게 잘보이기")
|
288 |
)
|
289 |
+
# 사이드바 맨 아래에 설문조사 링크
|
290 |
+
st.sidebar.markdown('<hr style="margin:1em 0;">', unsafe_allow_html=True)
|
291 |
+
st.sidebar.markdown(
|
292 |
+
'<a href="https://forms.gle/SUqrGBT3dktSB7v26" target="_blank" style="display:inline-block; background:#f9e79f; color:#1a237e; font-weight:bold; padding:0.5em 1.2em; border-radius:8px; text-decoration:none; font-size:1.1em; margin-bottom:16px;">📝 서비스 사용성 설문조사 참여하기</a>',
|
293 |
+
unsafe_allow_html=True
|
294 |
+
)
|
295 |
|
296 |
openai_client = init_openai_client()
|
297 |
|
298 |
if menu == "Khan 멘토에게 상담하기":
|
299 |
st.title("✨ Khan 멘토에게 질문하기")
|
300 |
+
|
|
|
|
|
|
|
|
|
301 |
|
302 |
# --- API 키 확인 및 리소스 초기화 ---
|
303 |
pc = init_pinecone()
|
|
|
305 |
index = get_pinecone_index(pc, INDEX_NAME)
|
306 |
|
307 |
# --- 상태 관리 ---
|
308 |
+
if 'user_question' not in st.session_state:
|
309 |
+
st.session_state['user_question'] = ''
|
310 |
if 'empathy_message' not in st.session_state:
|
311 |
st.session_state['empathy_message'] = ''
|
|
|
|
|
|
|
|
|
312 |
if 'khan_answer' not in st.session_state:
|
313 |
st.session_state['khan_answer'] = ''
|
314 |
+
if 'pinecone_results' not in st.session_state:
|
315 |
+
st.session_state['pinecone_results'] = []
|
316 |
+
if 'extra_questions' not in st.session_state:
|
317 |
+
st.session_state['extra_questions'] = []
|
318 |
+
if 'current_input' not in st.session_state:
|
319 |
+
st.session_state['current_input'] = ''
|
320 |
+
|
321 |
+
# --- 질문 입력 및 답변 생성 ---
|
322 |
+
st.markdown("#### 당신의 고민을 알려 주세요!")
|
323 |
+
user_q = st.text_input("나의 고민은...", value=st.session_state['current_input'], key="main_input")
|
324 |
+
if st.button("질문하기", key="main_ask") or (user_q and st.session_state['user_question'] != user_q):
|
325 |
+
st.session_state['user_question'] = user_q
|
326 |
+
st.session_state['current_input'] = user_q
|
327 |
+
# 1. 공감 비서 메시지
|
328 |
+
with st.spinner("생각중..."):
|
|
|
329 |
empathy_prompt = f"""
|
330 |
+
너는 따뜻하고 친절한 비서야. 아래 사용자의 질문을 듣고, 감정적으로 충분하게 1~2문장으로 공감해주되 질문에 대한 답변은 하지마, 마지막에 '칸 멘토의 생각을 들어볼까요?'라고 안내해줘. \n질문: "{user_q}"
|
|
|
|
|
|
|
331 |
"""
|
332 |
try:
|
333 |
empathy_response = openai_client.chat.completions.create(
|
334 |
+
model="gpt-4o-mini",
|
335 |
messages=[{"role": "system", "content": empathy_prompt}],
|
336 |
temperature=0.7,
|
337 |
)
|
338 |
st.session_state['empathy_message'] = empathy_response.choices[0].message.content.strip()
|
339 |
except Exception as e:
|
340 |
st.session_state['empathy_message'] = f"공감 메시지 생성 중 오류: {e}"
|
341 |
+
# 2. Pinecone 검색 및 Khan 멘토 답변
|
342 |
+
with st.spinner("Khan 멘토가 뜸을 들이며..."):
|
343 |
+
pinecone_results = search(user_q, top_k=5, _index=index, _model=model)
|
344 |
+
st.session_state['pinecone_results'] = pinecone_results
|
345 |
+
khan_answer = generate_khan_answer(user_q, pinecone_results, openai_client)
|
346 |
+
st.session_state['khan_answer'] = khan_answer
|
347 |
+
# 3. 추가 질문 생성
|
348 |
+
with st.spinner("추가 질문을 생성하는 중..."):
|
349 |
+
extra_prompt = (
|
350 |
+
f"아래 질문에서 유사하게 궁금할 수 있는 추가 질문 3~4개를 한국어로 만들어줘. 지나치게 세부적인 툴에 대한 얘기보다는 프로덕트, 프로젝트, 리더십에 대한 일반적인 질문으로 만들어. 질문: \"{st.session_state['user_question']}"
|
351 |
+
)
|
352 |
try:
|
353 |
+
extra_response = openai_client.chat.completions.create(
|
354 |
+
model="gpt-4o-mini",
|
355 |
+
messages=[{"role": "system", "content": extra_prompt}],
|
356 |
+
temperature=0.5
|
357 |
)
|
|
|
358 |
import re
|
359 |
+
raw = extra_response.choices[0].message.content.strip()
|
360 |
questions = re.findall(r'\d+\.\s*(.+)', raw)
|
361 |
if not questions:
|
|
|
362 |
questions = [q.strip('-• ').strip() for q in raw.split('\n') if q.strip()]
|
363 |
+
st.session_state['extra_questions'] = questions[:4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
364 |
st.rerun()
|
365 |
+
except Exception as e:
|
366 |
+
st.session_state['extra_questions'] = [f"추가 질문 생성 중 오류: {e}"]
|
367 |
+
st.rerun()
|
|
|
|
|
|
|
368 |
|
369 |
+
# --- 답변 및 추가질문 UI ---
|
370 |
+
if st.session_state['user_question']:
|
371 |
+
st.info(st.session_state['empathy_message'])
|
|
|
|
|
|
|
372 |
st.subheader("💡 Khan 멘토의 답변")
|
373 |
st.markdown(st.session_state['khan_answer'])
|
374 |
# 참고 영상 정보 표시
|
375 |
+
pinecone_results = st.session_state['pinecone_results']
|
376 |
if pinecone_results:
|
377 |
+
with st.expander("답변에 참고한 영상 정보 보기", expanded=True):
|
378 |
displayed_urls = set()
|
379 |
for i, r in enumerate(pinecone_results):
|
380 |
url = r.get('URL', 'N/A')
|
|
|
416 |
except Exception as e:
|
417 |
logger.warning(f"st.video failed for non-YouTube URL {url}: {e}")
|
418 |
st.markdown("---")
|
419 |
+
# --- 추가 질문 생성 타이밍 제어 ---
|
420 |
+
if 'extra_questions_ready' not in st.session_state or not st.session_state['extra_questions_ready']:
|
421 |
+
# 답변이 렌더링된 후에만 spinner 돌리기
|
422 |
+
st.session_state['extra_questions_ready'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
st.rerun()
|
424 |
+
elif not st.session_state['extra_questions']:
|
425 |
+
# 백그라운드에서 추가 질문 생성 (스피너 없이)
|
426 |
+
extra_prompt = (
|
427 |
+
f"아래 질문에서 유사하게 궁금할 수 있는 추가 질문 3~4개를 한국어로 만들어줘. 지나치게 세부적인 툴에 대한 얘기보다는 프로덕트, 프로젝트, 리더십에 대한 일반적인 질문으로 만들어. 질문: \"{st.session_state['user_question']}"
|
428 |
+
)
|
429 |
+
try:
|
430 |
+
extra_response = openai_client.chat.completions.create(
|
431 |
+
model="gpt-4o-mini",
|
432 |
+
messages=[{"role": "system", "content": extra_prompt}],
|
433 |
+
temperature=0.5
|
434 |
+
)
|
435 |
+
import re
|
436 |
+
raw = extra_response.choices[0].message.content.strip()
|
437 |
+
questions = re.findall(r'\d+\.\s*(.+)', raw)
|
438 |
+
if not questions:
|
439 |
+
questions = [q.strip('-• ').strip() for q in raw.split('\n') if q.strip()]
|
440 |
+
st.session_state['extra_questions'] = questions[:4]
|
441 |
+
st.rerun()
|
442 |
+
except Exception as e:
|
443 |
+
st.session_state['extra_questions'] = [f"추가 질문 생성 중 오류: {e}"]
|
444 |
+
else:
|
445 |
+
st.markdown("#### 추가로 궁금한 점이 있으신가요? 아래 예시 질문을 클릭하거나 직접 입력해보세요!")
|
446 |
+
cols = st.columns(len(st.session_state['extra_questions']))
|
447 |
+
for i, q in enumerate(st.session_state['extra_questions']):
|
448 |
+
if cols[i].button(q, key=f"extra_{i}"):
|
449 |
+
st.session_state['current_input'] = q
|
450 |
+
st.session_state['user_question'] = ''
|
451 |
+
st.rerun()
|
452 |
+
user_extra = st.text_input("직접 추가 질문 입력", value="", key="extra_input")
|
453 |
+
if st.button("추가 질문하기", key="extra_btn"):
|
454 |
+
st.session_state['current_input'] = user_extra
|
455 |
+
st.session_state['user_question'] = ''
|
456 |
+
st.rerun()
|
457 |
st.markdown("---")
|
458 |
st.caption("Powered by Pinecone, Sentence Transformers, and OpenAI")
|
459 |
+
if st.button("다른 고민 상담하기"):
|
460 |
+
for k in ['user_question','empathy_message','khan_answer','pinecone_results','extra_questions','current_input','extra_questions_ready']:
|
461 |
+
st.session_state[k] = ''
|
|
|
462 |
st.rerun()
|
463 |
else:
|
464 |
st.title("👔 상사에게 잘보이기: 맞춤 보고문 만들기")
|