muyeong commited on
Commit
19cf7d1
·
verified ·
1 Parent(s): f6e083c

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +301 -0
app.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 소방 복무관리 RAG 챗봇
3
+ 깔끔하고 심플한 Gradio UI + 관리자 탭
4
+ """
5
+
6
+ import gradio as gr
7
+ from rag import chat
8
+ from pdf_processor import process_pdf
9
+ from database import get_all_documents
10
+ import uuid
11
+ import os
12
+
13
+ # 세션별 대화 기록 저장
14
+ conversation_history = {}
15
+
16
+ # 관리자 비밀번호 (환경변수로 설정 권장)
17
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin1234")
18
+
19
+
20
+ def respond(message: str, history: list, session_id: str) -> tuple:
21
+ """채팅 응답 처리"""
22
+ if not message.strip():
23
+ return "", history
24
+
25
+ if session_id not in conversation_history:
26
+ conversation_history[session_id] = []
27
+
28
+ response = chat(message, conversation_history[session_id])
29
+
30
+ conversation_history[session_id].append({
31
+ "user": message,
32
+ "assistant": response
33
+ })
34
+
35
+ history.append((message, response))
36
+ return "", history
37
+
38
+
39
+ def clear_chat(session_id: str) -> tuple:
40
+ """대화 초기화"""
41
+ if session_id in conversation_history:
42
+ conversation_history[session_id] = []
43
+ return [], ""
44
+
45
+
46
+ def verify_admin(password: str) -> tuple:
47
+ """관리자 인증"""
48
+ if password == ADMIN_PASSWORD:
49
+ return gr.update(visible=False), gr.update(visible=True), "✅ 인증 성공"
50
+ return gr.update(visible=True), gr.update(visible=False), "❌ 비밀번호가 틀렸습니다"
51
+
52
+
53
+ def upload_pdf(file, title: str, category: str) -> str:
54
+ """PDF 업로드 및 처리"""
55
+ if file is None:
56
+ return "❌ PDF 파일을 선택해주세요."
57
+
58
+ if not title.strip():
59
+ return "❌ 문서 제목을 입력해주세요."
60
+
61
+ result = process_pdf(
62
+ pdf_path=file.name,
63
+ title=title.strip(),
64
+ category=category
65
+ )
66
+
67
+ return result["message"]
68
+
69
+
70
+ def get_documents_list() -> list:
71
+ """저장된 문서 목록 조회"""
72
+ docs = get_all_documents()
73
+ if not docs:
74
+ return [["데이터 없음", "-", "-"]]
75
+
76
+ return [[d.get("title", ""), d.get("category", ""), str(d.get("id", ""))] for d in docs]
77
+
78
+
79
+ # 커스텀 CSS
80
+ custom_css = """
81
+ .gradio-container {
82
+ max-width: 900px !important;
83
+ margin: auto !important;
84
+ font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, sans-serif !important;
85
+ }
86
+
87
+ .header {
88
+ text-align: center;
89
+ padding: 20px 0;
90
+ border-bottom: 1px solid #e5e7eb;
91
+ margin-bottom: 20px;
92
+ }
93
+
94
+ .header h1 {
95
+ color: #dc2626;
96
+ font-size: 1.8rem;
97
+ font-weight: 700;
98
+ margin: 0;
99
+ }
100
+
101
+ .header p {
102
+ color: #6b7280;
103
+ font-size: 0.9rem;
104
+ margin-top: 8px;
105
+ }
106
+
107
+ .chatbot {
108
+ border-radius: 12px !important;
109
+ border: 1px solid #e5e7eb !important;
110
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
111
+ }
112
+
113
+ .input-area {
114
+ border-radius: 12px !important;
115
+ border: 1px solid #e5e7eb !important;
116
+ }
117
+
118
+ .primary-btn {
119
+ background-color: #dc2626 !important;
120
+ border: none !important;
121
+ border-radius: 8px !important;
122
+ color: white !important;
123
+ font-weight: 600 !important;
124
+ }
125
+
126
+ .primary-btn:hover {
127
+ background-color: #b91c1c !important;
128
+ }
129
+
130
+ .secondary-btn {
131
+ background-color: #f3f4f6 !important;
132
+ border: 1px solid #e5e7eb !important;
133
+ border-radius: 8px !important;
134
+ color: #374151 !important;
135
+ }
136
+
137
+ .admin-section {
138
+ background: #fef2f2;
139
+ border: 1px solid #fecaca;
140
+ border-radius: 12px;
141
+ padding: 20px;
142
+ margin: 10px 0;
143
+ }
144
+
145
+ .upload-section {
146
+ background: #f9fafb;
147
+ border: 1px solid #e5e7eb;
148
+ border-radius: 12px;
149
+ padding: 20px;
150
+ margin: 10px 0;
151
+ }
152
+
153
+ .footer {
154
+ text-align: center;
155
+ padding: 16px 0;
156
+ color: #9ca3af;
157
+ font-size: 0.8rem;
158
+ }
159
+ """
160
+
161
+ # Gradio 앱 구성
162
+ with gr.Blocks(css=custom_css, title="소방 복무관리 챗봇", theme=gr.themes.Soft()) as app:
163
+ session_id = gr.State(value=str(uuid.uuid4()))
164
+
165
+ # 헤더
166
+ gr.HTML("""
167
+ <div class="header">
168
+ <h1>🚒 소방 복무관리 챗봇</h1>
169
+ <p>복무, 근무, 휴가 등 소방 업무에 관해 질문해주세요</p>
170
+ </div>
171
+ """)
172
+
173
+ # 탭 구성
174
+ with gr.Tabs():
175
+ # ========== 채팅 탭 ==========
176
+ with gr.TabItem("💬 채팅"):
177
+ chatbot = gr.Chatbot(
178
+ label="",
179
+ height=450,
180
+ show_label=False,
181
+ container=True,
182
+ elem_classes=["chatbot"]
183
+ )
184
+
185
+ with gr.Row():
186
+ msg = gr.Textbox(
187
+ placeholder="메시지를 입력하세요...",
188
+ show_label=False,
189
+ container=False,
190
+ scale=9,
191
+ elem_classes=["input-area"]
192
+ )
193
+ submit_btn = gr.Button("전송", scale=1, elem_classes=["primary-btn"])
194
+
195
+ with gr.Row():
196
+ clear_btn = gr.Button("대화 초기화", elem_classes=["secondary-btn"])
197
+
198
+ gr.Examples(
199
+ examples=[
200
+ "연차 휴가 ���수는 어떻게 계산하나요?",
201
+ "당직 근무 시간은 어떻게 되나요?",
202
+ "병가 신청 절차가 어떻게 되나요?",
203
+ "초과 근무 수당 기준이 어떻게 되나요?"
204
+ ],
205
+ inputs=msg,
206
+ label="예시 질문"
207
+ )
208
+
209
+ # ========== 관리자 탭 ==========
210
+ with gr.TabItem("⚙️ 관리자"):
211
+ # 로그인 섹션
212
+ with gr.Column(visible=True) as login_section:
213
+ gr.Markdown("### 🔐 관리자 인증")
214
+ gr.Markdown("문서를 관리하려면 관리자 비밀번호를 입력하세요.")
215
+
216
+ with gr.Row():
217
+ password_input = gr.Textbox(
218
+ label="비밀번호",
219
+ type="password",
220
+ placeholder="비밀번호 입력...",
221
+ scale=3
222
+ )
223
+ login_btn = gr.Button("로그인", elem_classes=["primary-btn"], scale=1)
224
+
225
+ login_status = gr.Markdown("")
226
+
227
+ # 관리자 패널 (인증 후 표시)
228
+ with gr.Column(visible=False) as admin_panel:
229
+ gr.Markdown("### 📄 PDF 문서 업로드")
230
+ gr.Markdown("새로운 복무 관련 문서를 업로드하여 챗봇 지식을 확장하세요.")
231
+
232
+ with gr.Group(elem_classes=["upload-section"]):
233
+ pdf_file = gr.File(
234
+ label="PDF 파일 선택",
235
+ file_types=[".pdf"],
236
+ file_count="single"
237
+ )
238
+
239
+ with gr.Row():
240
+ doc_title = gr.Textbox(
241
+ label="문서 제목",
242
+ placeholder="예: 소방공무원 복무규정 2024",
243
+ scale=2
244
+ )
245
+ doc_category = gr.Dropdown(
246
+ label="카테고리",
247
+ choices=["일반", "휴가", "근무", "수당", "규정", "교육"],
248
+ value="일반",
249
+ scale=1
250
+ )
251
+
252
+ upload_btn = gr.Button("📤 업로드 및 처리", elem_classes=["primary-btn"])
253
+ upload_result = gr.Markdown("")
254
+
255
+ gr.Markdown("---")
256
+ gr.Markdown("### 📚 저장된 문서 목록")
257
+
258
+ refresh_btn = gr.Button("🔄 새로고침", elem_classes=["secondary-btn"])
259
+ documents_table = gr.Dataframe(
260
+ headers=["제목", "카테고리", "ID"],
261
+ label="",
262
+ interactive=False
263
+ )
264
+
265
+ # 푸터
266
+ gr.HTML("""
267
+ <div class="footer">
268
+ 소방 복무관리 AI 어시스턴트 | Powered by HuggingFace & Supabase
269
+ </div>
270
+ """)
271
+
272
+ # ========== 이벤트 핸들러 ==========
273
+ # 채팅
274
+ msg.submit(respond, [msg, chatbot, session_id], [msg, chatbot])
275
+ submit_btn.click(respond, [msg, chatbot, session_id], [msg, chatbot])
276
+ clear_btn.click(clear_chat, [session_id], [chatbot, msg])
277
+
278
+ # 관리자
279
+ login_btn.click(
280
+ verify_admin,
281
+ [password_input],
282
+ [login_section, admin_panel, login_status]
283
+ )
284
+ upload_btn.click(
285
+ upload_pdf,
286
+ [pdf_file, doc_title, doc_category],
287
+ [upload_result]
288
+ )
289
+ refresh_btn.click(
290
+ get_documents_list,
291
+ [],
292
+ [documents_table]
293
+ )
294
+
295
+
296
+ if __name__ == "__main__":
297
+ app.launch(
298
+ server_name="0.0.0.0",
299
+ server_port=7860,
300
+ share=False
301
+ )