11import os
22import sys
33import json
4+ import base64
45import signal
56import threading
67import contextlib
1819from middleware .laas .heuristic import outside_slack_jira_user_map
1920from middleware .laas .jira_operator import JiraOperator
2021from middleware .laas .jira_fields_schema import Issue , get_format_instructions
22+ from middleware .laas .mimetype import get_mime_type_from_url , is_supported_mime_type
2123
2224if os .getenv ('DEBUG' , False ):
2325 from dotenv import load_dotenv
@@ -39,7 +41,7 @@ class PICollection:
3941 workspace = 'wantedlab.atlassian.net'
4042 project = 'PI'
4143 trigger_emoji = 'pi_jira_gen'
42- laas_jira_hash = '7d7e1e4c2652e5c82b29e9dd88a7630a1e0f004b4cd971314b8126e4f16aab1c '
44+ laas_jira_hash = '8008b106b08d86b0af7a55d0ad18ca058aab88fc7e7a5945eedee7f16827d21e '
4345
4446
4547class SlackOperator :
@@ -53,22 +55,23 @@ def __init__(self, event, say, trigger_emoji):
5355 self .reaction_user = event ['user' ]
5456 self .emoji = trigger_emoji
5557
56- # after get_conversation_data
58+ # after set_conversation_data
5759 self .thread_ts = None
58- self .context = None
59- self .screenshots = None
60+ self .messages = None
61+ self .file_data = None
6062
6163 self .user_map = {
6264 x ['id' ]: {'real_name' : x ['real_name' ], 'email' : x ['profile' ].get ('email' )}
6365 for x in get_all_slack_user_list () if not x ['deleted' ]
6466 }
6567
66- def get_conversation_data (self ):
68+ def set_conversation_data (self ):
6769 """
6870 스레드의 모든 메시지를 가져와 정제합니다
71+ https://laas.wanted.co.kr/docs/guide/api/api-preset#%EC%B6%94%EA%B0%80-%EB%A9%94%EC%8B%9C%EC%A7%80%EC%97%90-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-%ED%8F%AC%ED%95%A8%ED%95%9C-%ED%98%B8%EC%B6%9C
6972 """
70- context = ''
71- screenshots = []
73+ messages = []
74+ file_data = []
7275 conversations = app .client .conversations_replies (
7376 channel = self .item_channel ,
7477 ts = self .item_ts ,
@@ -80,36 +83,73 @@ def get_conversation_data(self):
8083 # Process each message in the thread
8184 message_dt = datetime .fromtimestamp (float (message ['ts' ])).isoformat ()
8285 user_name = self .user_map .get (message ['user' ], {'real_name' : 'Unknown' })['real_name' ]
83- text = message .get ("text" , "" )
84- context += f' { message_dt } { user_name } : """ { text } """' + ' \n \n '
86+ text = f' { message_dt } { user_name } : """ { message .get ("text" , "" )} """'
87+ images = []
8588
8689 # conversation 에 대한 모든 첨부파일을 복제합니다.
8790 for file in message .get ('files' , []):
8891 private_file_url = file ['url_private' ]
92+
93+ # 슬랙 파일 다운로드
8994 headers = {'Authorization' : f'Bearer { os .environ ["SLACK_BOT_TOKEN" ]} ' }
9095 req = Request (private_file_url , headers = headers )
9196 try :
9297 response = urlopen (req )
9398 except HTTPError :
9499 continue
100+
95101 content = response .read ()
96- screenshots .append (content )
102+ file_data .append (content )
103+
104+ # MIME 타입 확인
105+ mime_type = response .getheader ('Content-Type' ) or get_mime_type_from_url (private_file_url )
106+
107+ # 지원되는 형식인지 확인
108+ if not is_supported_mime_type (mime_type ):
109+ # raise ValueError(f"Unsupported MIME type: {mime_type}")
110+ continue
97111
98- self .context = context
99- self .screenshots = screenshots
112+ # Non-animated GIF인지 확인 (GIF에만 적용)
113+ if mime_type == "image/gif" and b"NETSCAPE2.0" in content :
114+ # raise ValueError("Animated GIFs are not supported.")
115+ continue
116+
117+ # Base64 인코딩 수행
118+ base64_image = base64 .b64encode (content ).decode ('utf-8' )
119+
120+ # 웹에서 사용할 수 있는 형식으로 반환
121+ if base64_image :
122+ images .append ({
123+ "type" : "image_url" ,
124+ "image_url" : {"url" : f"data:{ mime_type } ;base64,{ base64_image } " },
125+ })
126+
127+ if images :
128+ messages .append ({
129+ "role" : "user" ,
130+ "content" : [{"type" : "text" , "text" : text }, * images ]
131+ })
132+ else :
133+ messages .append ({
134+ "role" : "user" ,
135+ "content" : text ,
136+ })
137+
138+ self .messages = messages
139+ self .file_data = file_data
100140 return True
101141
102142 @property
103143 def link (self ):
104144 return f'https://{ SlackCollection .workspace } /archives/{ self .item_channel } /p{ self .item_ts .replace ("." , "" )} { f"?thread_ts={ self .thread_ts } " if self .thread_ts else "" } '
105145
106- def check_gpt_response (self , hash , params ):
146+ def check_gpt_response (self , hash , params , messages ):
107147 """
108148 GPT 응답이 올바른지 확인합니다.
109149 이 단계는 LaaS 서버의 응답을 잘 받았는지 확인하는 단계입니다
110150 """
111151 try :
112- gpt_response = jira_summary_generator (hash , params )
152+ gpt_response = jira_summary_generator (hash , params , messages )
113153 except Exception as e :
114154 self .say (
115155 channel = self .reaction_user ,
@@ -309,13 +349,13 @@ def laas_jira(event, say, collection: PICollection):
309349
310350 slack = SlackOperator (event , say , collection .trigger_emoji )
311351
312- if not slack .get_conversation_data ():
352+ if not slack .set_conversation_data ():
313353 return
314354
315-
316355 gpt_response = slack .check_gpt_response (
317356 collection .laas_jira_hash ,
318- {'conversations' : slack .context , 'schema' : get_format_instructions (Issue )},
357+ {'schema' : get_format_instructions (Issue )},
358+ slack .messages ,
319359 )
320360 gpt_metadata = slack .validate_gpt_response_json (gpt_response , say )
321361
@@ -372,7 +412,7 @@ def laas_jira(event, say, collection: PICollection):
372412 )
373413
374414 try :
375- jira_response = jira .safe_create_issues (refined_fields , slack .screenshots )
415+ jira_response = jira .safe_create_issues (refined_fields , slack .file_data )
376416 except Exception as e :
377417 slack .say (
378418 channel = slack .reaction_user ,
0 commit comments