File size: 13,109 Bytes
613de59
 
29f738a
 
 
613de59
29f738a
 
 
 
 
 
 
 
1f8af00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613de59
b2a11e9
7fc8e3a
29f738a
7fc8e3a
 
29f738a
7fc8e3a
 
 
 
 
29f738a
613de59
29f738a
 
 
 
 
 
 
 
7fc8e3a
 
29f738a
 
 
 
 
7fc8e3a
29f738a
 
7fc8e3a
 
 
29f738a
7fc8e3a
29f738a
 
 
613de59
0d6bf83
29f738a
 
b1ee607
 
 
 
613de59
29f738a
 
 
1f8af00
29f738a
 
1f8af00
 
 
29f738a
1f8af00
 
 
 
 
29f738a
 
 
1f8af00
 
 
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
 
29f738a
 
1f8af00
29f738a
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
1f8af00
29f738a
1f8af00
 
 
 
 
 
29f738a
 
 
1f8af00
29f738a
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
 
29f738a
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
 
1f8af00
29f738a
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
1f8af00
29f738a
1f8af00
 
 
 
 
29f738a
 
 
1f8af00
29f738a
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
1f8af00
 
 
 
29f738a
 
1f8af00
29f738a
1f8af00
 
 
 
 
 
 
29f738a
 
 
0d6bf83
 
7fc8e3a
6266aa3
 
613de59
29f738a
7646174
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import gradio as gr
import os
import json
from datetime import datetime
from config import STATIONS, STATION_NAMES
from supabase_utils import get_supabase_client
from api_utils import (
    api_get_tide_level,
    api_get_tide_series,
    api_get_extremes_info,
    api_check_tide_alert,
    api_compare_stations,
    api_health_check
)
# --- API 문서 생성을 위한 헬퍼 함수 ---
def generate_api_docs(endpoint: str, params: dict, description: str):
    """엔드포인트별 API 문서 Markdown 및 코드 블록을 생성합니다."""
    base_url = "https://alwaysgood-my-tide-env.hf.space"
    query_string = "&".join([f"{key}={value}" for key, value in params.items()])
    full_url = f'"{base_url}{endpoint}?{query_string}"'
    
    python_code = f"""
import requests

BASE_URL = "{base_url}"
params = {params}

response = requests.get(f"{{BASE_URL}}{endpoint}", params=params)

if response.status_code == 200:
    print(response.json())
else:
    print(f"Error: {{response.status_code}}")
"""
    with gr.Blocks():
        gr.Markdown("---")
        gr.Markdown(f"### 📚 API 사용법: {description}")
        gr.Markdown("#### cURL")
        gr.Code(language="bash", value=f"curl -X GET {full_url}", interactive=False)
        gr.Markdown("#### Python (`requests`)")
        gr.Code(language="python", value=python_code.strip(), interactive=False)


def create_ui(prediction_handler, chatbot_handler, api_handlers: dict):
    """Gradio UI를 생성하고 반환합니다."""
    
    with gr.Blocks(title="통합 조위 예측 시스템", theme=gr.themes.Soft()) as demo:
        gr.Markdown("# 🌊 통합 조위 예측 시스템 with Gemini")
        
        # 연결 상태 표시
        client = get_supabase_client()
        supabase_status = "🟢 연결됨" if client else "🔴 연결 안됨 (환경변수 확인 필요)"
        gemini_status = "🟢 연결됨" if os.getenv("GEMINI_API_KEY") else "🔴 연결 안됨 (환경변수 확인 필요)"
        gr.Markdown(f"**Supabase 상태**: {supabase_status} | **Gemini 상태**: {gemini_status}")
        
        with gr.Tabs():
            # 1. 예측 탭
            with gr.TabItem("🔮 통합 조위 예측"):
                gr.Markdown("""
                ### TimeXer 모델을 활용한 조위 예측
                - 과거 기상 데이터를 업로드하여 미래 72시간 조위 예측
                - 잔차 예측 + 조화 예측 = 최종 조위
                """)
                
                with gr.Row():
                    with gr.Column(scale=1):
                        station_id_input = gr.Dropdown(
                            choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS],
                            label="관측소 선택",
                            value="DT_0001"
                        )
                        input_csv = gr.File(label="과거 데이터 업로드 (.csv)")
                        predict_btn = gr.Button("🚀 예측 실행", variant="primary")
                    
                    with gr.Column(scale=3):
                        output_plot = gr.Plot(label="예측 결과 시각화")
                        output_df = gr.DataFrame(label="예측 결과 데이터")
                
                output_log = gr.Textbox(label="실행 로그", lines=5, interactive=False)
            
            # 2. 챗봇 탭
            with gr.TabItem("💬 AI 조위 챗봇"):
                gr.ChatInterface(
                    fn=chatbot_handler,
                    title="",
                    description="조위에 대해 궁금한 점을 물어보세요.",
                    # examples=[
                    #     "인천 현재 조위 알려줘",
                    #     "오늘 만조 시간은?"
                    # ]
                )
            
            # 3. API 탭 (새로 추가)
            with gr.TabItem("🔌 API"):
                gr.Markdown("## RESTful API 엔드포인트\n실무에서 바로 사용 가능한 API 기능을 제공합니다.")
                
                with gr.Tabs():
                    # 3-1. 특정 시간 조위
                    with gr.TabItem("조위 조회"):
                        gr.Markdown("#### 특정 관측소, 특정 시간의 조위 정보를 조회합니다.")
                        with gr.Row():
                            with gr.Column(scale=1):
                                api1_station = gr.Dropdown(choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS], label="관측소", value="DT_0001")
                                api1_time = gr.Textbox(label="조회 시간 (비워두면 현재)", placeholder="2025-08-10T15:00:00")
                                api1_btn = gr.Button("UI에서 테스트", variant="primary")
                            with gr.Column(scale=2):
                                api1_output = gr.JSON(label="API 응답")
                        
                        api1_btn.click(
                            fn=lambda s, t: api_handlers["tide_level"](s, t if t else None),
                            inputs=[api1_station, api1_time],
                            outputs=api1_output
                        )
                        
                        generate_api_docs(
                            endpoint="/api/tide_level",
                            params={"station_id": "DT_0001", "target_time": "2025-08-10T09:00:00"},
                            description="특정 시간 조위 조회"
                        )

                    # 3-2. 시계열 데이터
                    with gr.TabItem("시계열"):
                        gr.Markdown("#### 지정된 기간의 시계열 조위 데이터를 조회합니다.")
                        with gr.Row():
                            with gr.Column(scale=1):
                                 api2_station = gr.Dropdown(choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS], label="관측소", value="DT_0001")
                                 api2_start = gr.Textbox(label="시작 시간", placeholder="2025-08-10T00:00:00")
                                 api2_end = gr.Textbox(label="종료 시간", placeholder="2025-08-11T00:00:00")
                                 api2_interval = gr.Number(label="간격(분)", value=60, minimum=5)
                                 api2_btn = gr.Button("UI에서 테스트", variant="primary")
                            with gr.Column(scale=2):
                                 api2_output = gr.JSON(label="API 응답 (공공 API 형식)")
                        
                        api2_btn.click(
                            fn=lambda s, st, et, i: api_handlers["tide_series"](s, st if st else None, et if et else None, int(i)),
                            inputs=[api2_station, api2_start, api2_end, api2_interval],
                            outputs=api2_output
                        )
                        
                        generate_api_docs(
                            endpoint="/api/tide_series",
                            params={"station_id": "DT_0001", "start_time": "2025-08-10T00:00:00", "end_time": "2025-08-11T00:00:00"},
                            description="기간별 시계열 데이터 조회"
                        )

                    # 3-3. 만조/간조
                    with gr.TabItem("만조/간조"):
                        gr.Markdown("#### 특정 날짜의 만조/간조 정보를 조회합니다.")
                        with gr.Row():
                            with gr.Column(scale=1):
                                api3_station = gr.Dropdown(choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS], label="관측소", value="DT_0001")
                                api3_date = gr.Textbox(label="날짜 (YYYY-MM-DD)", placeholder=datetime.now().strftime("%Y-%m-%d"))
                                api3_secondary = gr.Checkbox(label="부차 만조/간조 포함", value=False)
                                api3_btn = gr.Button("UI에서 테스트", variant="primary")
                            with gr.Column(scale=2):
                                api3_output = gr.JSON(label="만조/간조 정보")
                        
                        api3_btn.click(
                            fn=lambda s, d, sec: api_handlers["extremes"](s, d if d else None, sec),
                            inputs=[api3_station, api3_date, api3_secondary],
                            outputs=api3_output
                        )
                        
                        generate_api_docs(
                            endpoint="/api/extremes",
                            params={"station_id": "DT_0001", "date": "2025-08-10"},
                            description="만조/간조 정보 조회"
                        )

                    # 3-4. 위험 알림
                    with gr.TabItem("위험 알림"):
                        gr.Markdown("#### 향후 설정된 시간 동안 위험 수위 도달 여부를 체크합니다.")
                        with gr.Row():
                            with gr.Column(scale=1):
                                api4_station = gr.Dropdown(choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS], label="관측소", value="DT_0001")
                                api4_hours = gr.Number(label="확인 시간(시간)", value=24, minimum=1, maximum=72)
                                api4_warning = gr.Number(label="주의 수위(cm)", value=700)
                                api4_danger = gr.Number(label="경고 수위(cm)", value=750)
                                api4_btn = gr.Button("UI에서 테스트", variant="primary")
                            with gr.Column(scale=2):
                                api4_output = gr.JSON(label="위험 수위 정보")
                        
                        api4_btn.click(
                            fn=lambda s, h, w, d: api_handlers["alert"](s, int(h), w, d),
                            inputs=[api4_station, api4_hours, api4_warning, api4_danger],
                            outputs=api4_output
                        )
                        
                        generate_api_docs(
                            endpoint="/api/alert",
                            params={"station_id": "DT_0001", "warning_level": 700, "danger_level": 750},
                            description="위험 수위 체크"
                        )

                    # 3-5. 관측소 비교
                    with gr.TabItem("비교"):
                        gr.Markdown("#### 여러 관측소의 조위를 동시에 비교합니다.")
                        with gr.Row():
                            with gr.Column(scale=1):
                                api5_stations = gr.CheckboxGroup(choices=[(f"{STATION_NAMES[s]}", s) for s in STATIONS], label="비교할 관측소 선택", value=["DT_0001", "DT_0002", "DT_0003"])
                                api5_time = gr.Textbox(label="비교 시간 (비워두면 현재)", placeholder="2025-08-10T15:00:00")
                                api5_btn = gr.Button("UI에서 테스트", variant="primary")
                            with gr.Column(scale=2):
                                api5_output = gr.JSON(label="비교 결과")
                        
                        api5_btn.click(
                            fn=lambda s, t: api_handlers["compare"](s, t if t else None),
                            inputs=[api5_stations, api5_time],
                            outputs=api5_output
                        )
    
                        generate_api_docs(
                            endpoint="/api/compare",
                            params={"station_ids": ["DT_0001", "DT_0002"]}, # requests 라이브러리는 리스트를 올바르게 처리합니다.
                            description="다중 관측소 비교"
                        )

                    # 3-6. 상태 체크
                    with gr.TabItem("상태"):
                        gr.Markdown("#### API 및 시스템의 현재 상태를 확인합니다.")
                        with gr.Row():
                            api6_btn = gr.Button("🔍 상태 확인", variant="secondary", scale=1)
                            api6_output = gr.JSON(label="시스템 상태", scale=2)
                        
                        api6_btn.click(
                            fn=api_handlers["health"],
                            inputs=[],
                            outputs=api6_output
                        )
                        
                        generate_api_docs(
                            endpoint="/api/health",
                            params={},
                            description="시스템 상태 확인"
                        )
        
        # 이벤트 핸들러 연결
        predict_btn.click(
            fn=prediction_handler,
            inputs=[station_id_input, input_csv],
            outputs=[output_plot, output_df, output_log],
            api_name="predict"
        )
    
    return demo