openfree commited on
Commit
ee0cb34
ยท
verified ยท
1 Parent(s): b5d5080

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +466 -41
app.py CHANGED
@@ -1,6 +1,19 @@
1
  import gradio as gr
 
 
 
 
 
2
 
3
- # Custom CSS for gradient background and styling
 
 
 
 
 
 
 
 
4
  custom_css = """
5
  .gradio-container {
6
  background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
@@ -8,20 +21,16 @@ custom_css = """
8
  animation: gradient-animation 15s ease infinite;
9
  min-height: 100vh;
10
  }
11
-
12
  @keyframes gradient-animation {
13
  0% { background-position: 0% 50%; }
14
  50% { background-position: 100% 50%; }
15
  100% { background-position: 0% 50%; }
16
  }
17
-
18
  .dark .gradio-container {
19
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 25%, #0f3460 50%, #533483 75%, #e94560 100%);
20
  background-size: 400% 400%;
21
  animation: gradient-animation 15s ease infinite;
22
  }
23
-
24
- /* Style for content areas */
25
  .main-container {
26
  background-color: rgba(255, 255, 255, 0.95);
27
  backdrop-filter: blur(10px);
@@ -31,51 +40,380 @@ custom_css = """
31
  border: 1px solid rgba(255, 255, 255, 0.18);
32
  margin: 10px;
33
  }
34
-
35
  .dark .main-container {
36
  background-color: rgba(30, 30, 30, 0.95);
37
  border: 1px solid rgba(255, 255, 255, 0.1);
38
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  """
40
 
41
- # State variable to track current model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  current_model = gr.State("openai/gpt-oss-120b")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  def switch_model(model_choice):
45
- """Function to switch between models"""
46
  return gr.update(visible=False), gr.update(visible=True), model_choice
47
 
 
48
  with gr.Blocks(fill_height=True, theme="Nymbo/Nymbo_Theme", css=custom_css) as demo:
49
  with gr.Row():
50
- # Sidebar
51
  with gr.Column(scale=1):
52
  with gr.Group(elem_classes="main-container"):
53
- gr.Markdown("# ๐Ÿš€ Inference Provider")
54
  gr.Markdown(
55
- "This Space showcases OpenAI GPT-OSS models, served by the Cerebras API. "
56
- "Sign in with your Hugging Face account to use this API."
57
  )
58
 
59
- # Model selection
60
  model_dropdown = gr.Dropdown(
61
- choices=[
62
- "openai/gpt-oss-120b",
63
- "openai/gpt-oss-20b"
64
- ],
65
  value="openai/gpt-oss-120b",
66
- label="๐Ÿ“Š Select Model",
67
- info="Choose between different model sizes"
68
  )
69
 
70
- # Login button
71
  login_button = gr.LoginButton("Sign in with Hugging Face", size="lg")
 
72
 
73
- # Reload button to apply model change
74
- reload_btn = gr.Button("๐Ÿ”„ Apply Model Change", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- # Additional options
77
- with gr.Accordion("โš™๏ธ Advanced Options", open=False):
78
- gr.Markdown("*These options will be available after model implementation*")
79
  temperature = gr.Slider(
80
  minimum=0,
81
  maximum=2,
@@ -91,42 +429,129 @@ with gr.Blocks(fill_height=True, theme="Nymbo/Nymbo_Theme", css=custom_css) as d
91
  label="Max Tokens"
92
  )
93
 
94
- # Main chat area
95
  with gr.Column(scale=3):
96
  with gr.Group(elem_classes="main-container"):
97
  gr.Markdown("## ๐Ÿ’ฌ Chat Interface")
98
 
99
- # Container for model interfaces
 
 
 
 
 
 
100
  with gr.Column(visible=True) as model_120b_container:
101
  gr.Markdown("### Model: openai/gpt-oss-120b")
102
- gr.load("models/openai/gpt-oss-120b", accept_token=login_button, provider="fireworks-ai")
 
 
 
 
 
 
 
 
 
103
 
104
  with gr.Column(visible=False) as model_20b_container:
105
  gr.Markdown("### Model: openai/gpt-oss-20b")
106
- gr.load("models/openai/gpt-oss-20b", accept_token=login_button, provider="fireworks-ai")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- # Handle model switching
109
  reload_btn.click(
110
  fn=switch_model,
111
  inputs=[model_dropdown],
112
  outputs=[model_120b_container, model_20b_container, current_model]
113
  ).then(
114
- fn=lambda: gr.Info("Model switched successfully!"),
115
  inputs=[],
116
  outputs=[]
117
  )
118
 
119
- # Update visibility based on dropdown selection
120
- def update_visibility(model_choice):
121
- if model_choice == "openai/gpt-oss-120b":
122
- return gr.update(visible=True), gr.update(visible=False)
 
 
 
 
 
 
123
  else:
124
- return gr.update(visible=False), gr.update(visible=True)
 
 
 
125
 
126
- model_dropdown.change(
127
- fn=update_visibility,
128
- inputs=[model_dropdown],
129
- outputs=[model_120b_container, model_20b_container]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  )
131
 
132
- demo.launch()
 
 
1
  import gradio as gr
2
+ import os
3
+ from typing import List, Dict, Any, Optional
4
+ import hashlib
5
+ import json
6
+ from datetime import datetime
7
 
8
+ # PDF ์ฒ˜๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
9
+ import pymupdf # PyMuPDF
10
+ import chromadb
11
+ from chromadb.utils import embedding_functions
12
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
13
+ from sentence_transformers import SentenceTransformer
14
+ import numpy as np
15
+
16
+ # Custom CSS (๊ธฐ์กด CSS + ์ถ”๊ฐ€ ์Šคํƒ€์ผ)
17
  custom_css = """
18
  .gradio-container {
19
  background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
 
21
  animation: gradient-animation 15s ease infinite;
22
  min-height: 100vh;
23
  }
 
24
  @keyframes gradient-animation {
25
  0% { background-position: 0% 50%; }
26
  50% { background-position: 100% 50%; }
27
  100% { background-position: 0% 50%; }
28
  }
 
29
  .dark .gradio-container {
30
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 25%, #0f3460 50%, #533483 75%, #e94560 100%);
31
  background-size: 400% 400%;
32
  animation: gradient-animation 15s ease infinite;
33
  }
 
 
34
  .main-container {
35
  background-color: rgba(255, 255, 255, 0.95);
36
  backdrop-filter: blur(10px);
 
40
  border: 1px solid rgba(255, 255, 255, 0.18);
41
  margin: 10px;
42
  }
 
43
  .dark .main-container {
44
  background-color: rgba(30, 30, 30, 0.95);
45
  border: 1px solid rgba(255, 255, 255, 0.1);
46
  }
47
+ .pdf-status {
48
+ padding: 10px;
49
+ border-radius: 10px;
50
+ margin: 10px 0;
51
+ font-size: 0.9em;
52
+ }
53
+ .pdf-success {
54
+ background-color: rgba(52, 211, 153, 0.2);
55
+ border: 1px solid rgba(52, 211, 153, 0.5);
56
+ color: #10b981;
57
+ }
58
+ .pdf-error {
59
+ background-color: rgba(248, 113, 113, 0.2);
60
+ border: 1px solid rgba(248, 113, 113, 0.5);
61
+ color: #ef4444;
62
+ }
63
+ .pdf-processing {
64
+ background-color: rgba(251, 191, 36, 0.2);
65
+ border: 1px solid rgba(251, 191, 36, 0.5);
66
+ color: #f59e0b;
67
+ }
68
+ .document-card {
69
+ padding: 12px;
70
+ margin: 8px 0;
71
+ border-radius: 8px;
72
+ background: rgba(255, 255, 255, 0.1);
73
+ border: 1px solid rgba(255, 255, 255, 0.2);
74
+ cursor: pointer;
75
+ transition: all 0.3s ease;
76
+ }
77
+ .document-card:hover {
78
+ background: rgba(255, 255, 255, 0.2);
79
+ transform: translateX(5px);
80
+ }
81
  """
82
 
83
+ class PDFRAGSystem:
84
+ """PDF ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ ํด๋ž˜์Šค"""
85
+
86
+ def __init__(self):
87
+ self.documents = {}
88
+ self.embedder = None
89
+ self.vector_store = None
90
+ self.text_splitter = RecursiveCharacterTextSplitter(
91
+ chunk_size=1000,
92
+ chunk_overlap=200,
93
+ length_function=len,
94
+ separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""]
95
+ )
96
+ self.initialize_vector_store()
97
+
98
+ def initialize_vector_store(self):
99
+ """๋ฒกํ„ฐ ์ €์žฅ์†Œ ์ดˆ๊ธฐํ™”"""
100
+ try:
101
+ # Sentence Transformer ๋ชจ๋ธ ๋กœ๋“œ
102
+ self.embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
103
+
104
+ # ChromaDB ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
105
+ self.chroma_client = chromadb.Client()
106
+ self.collection = self.chroma_client.create_collection(
107
+ name="pdf_documents",
108
+ metadata={"hnsw:space": "cosine"}
109
+ )
110
+ except Exception as e:
111
+ print(f"Vector store initialization error: {e}")
112
+
113
+ def extract_text_from_pdf(self, pdf_path: str) -> Dict[str, Any]:
114
+ """PDF์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
115
+ try:
116
+ doc = pymupdf.open(pdf_path)
117
+ text_content = []
118
+ metadata = {
119
+ "title": doc.metadata.get("title", "Untitled"),
120
+ "author": doc.metadata.get("author", "Unknown"),
121
+ "pages": len(doc),
122
+ "creation_date": doc.metadata.get("creationDate", ""),
123
+ "file_name": os.path.basename(pdf_path)
124
+ }
125
+
126
+ for page_num, page in enumerate(doc):
127
+ text = page.get_text()
128
+ if text.strip():
129
+ text_content.append({
130
+ "page": page_num + 1,
131
+ "content": text
132
+ })
133
+
134
+ doc.close()
135
+
136
+ return {
137
+ "metadata": metadata,
138
+ "pages": text_content,
139
+ "full_text": "\n\n".join([p["content"] for p in text_content])
140
+ }
141
+ except Exception as e:
142
+ raise Exception(f"PDF ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
143
+
144
+ def process_and_index_pdf(self, pdf_path: str, doc_id: str) -> Dict[str, Any]:
145
+ """PDF ์ฒ˜๋ฆฌ ๋ฐ ๋ฒกํ„ฐ ์ธ๋ฑ์‹ฑ"""
146
+ try:
147
+ # PDF ํ…์ŠคํŠธ ์ถ”์ถœ
148
+ pdf_data = self.extract_text_from_pdf(pdf_path)
149
+
150
+ # ํ…์ŠคํŠธ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• 
151
+ chunks = self.text_splitter.split_text(pdf_data["full_text"])
152
+
153
+ # ๊ฐ ์ฒญํฌ์— ๋Œ€ํ•œ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
154
+ embeddings = self.embedder.encode(chunks)
155
+
156
+ # ChromaDB์— ์ €์žฅ
157
+ ids = [f"{doc_id}_{i}" for i in range(len(chunks))]
158
+ metadatas = [
159
+ {
160
+ "doc_id": doc_id,
161
+ "chunk_index": i,
162
+ "source": pdf_data["metadata"]["file_name"],
163
+ "page_count": pdf_data["metadata"]["pages"]
164
+ }
165
+ for i in range(len(chunks))
166
+ ]
167
+
168
+ self.collection.add(
169
+ ids=ids,
170
+ embeddings=embeddings.tolist(),
171
+ documents=chunks,
172
+ metadatas=metadatas
173
+ )
174
+
175
+ # ๋ฌธ์„œ ์ •๋ณด ์ €์žฅ
176
+ self.documents[doc_id] = {
177
+ "metadata": pdf_data["metadata"],
178
+ "chunk_count": len(chunks),
179
+ "upload_time": datetime.now().isoformat()
180
+ }
181
+
182
+ return {
183
+ "success": True,
184
+ "doc_id": doc_id,
185
+ "chunks": len(chunks),
186
+ "pages": pdf_data["metadata"]["pages"],
187
+ "title": pdf_data["metadata"]["title"]
188
+ }
189
+
190
+ except Exception as e:
191
+ return {
192
+ "success": False,
193
+ "error": str(e)
194
+ }
195
+
196
+ def search_relevant_chunks(self, query: str, top_k: int = 5) -> List[Dict]:
197
+ """์ฟผ๋ฆฌ์™€ ๊ด€๋ จ๋œ ์ฒญํฌ ๊ฒ€์ƒ‰"""
198
+ try:
199
+ # ์ฟผ๋ฆฌ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
200
+ query_embedding = self.embedder.encode([query])
201
+
202
+ # ์œ ์‚ฌํ•œ ๋ฌธ์„œ ๊ฒ€์ƒ‰
203
+ results = self.collection.query(
204
+ query_embeddings=query_embedding.tolist(),
205
+ n_results=top_k
206
+ )
207
+
208
+ if results and results['documents']:
209
+ chunks = []
210
+ for i in range(len(results['documents'][0])):
211
+ chunks.append({
212
+ "content": results['documents'][0][i],
213
+ "metadata": results['metadatas'][0][i],
214
+ "distance": results['distances'][0][i] if 'distances' in results else None
215
+ })
216
+ return chunks
217
+ return []
218
+
219
+ except Exception as e:
220
+ print(f"Search error: {e}")
221
+ return []
222
+
223
+ def generate_rag_prompt(self, query: str, context_chunks: List[Dict]) -> str:
224
+ """RAG ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
225
+ context = "\n\n---\n\n".join([
226
+ f"[์ถœ์ฒ˜: {chunk['metadata']['source']}, ์ฒญํฌ {chunk['metadata']['chunk_index']+1}]\n{chunk['content']}"
227
+ for chunk in context_chunks
228
+ ])
229
+
230
+ prompt = f"""๋‹ค์Œ ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
231
+ ๋‹ต๋ณ€์€ ์ œ๊ณต๋œ ๋ฌธ์„œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑํ•˜๋˜, ํ•„์š”์‹œ ์ถ”๊ฐ€ ์„ค๋ช…์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
232
+ ๋ฌธ์„œ์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๊ทธ ์‚ฌ์‹ค์„ ๋ช…์‹œํ•ด์ฃผ์„ธ์š”.
233
+
234
+ ๐Ÿ“š ์ฐธ๊ณ  ๋ฌธ์„œ:
235
+ {context}
236
+
237
+ โ“ ์งˆ๋ฌธ: {query}
238
+
239
+ ๐Ÿ’ก ๋‹ต๋ณ€:"""
240
+
241
+ return prompt
242
+
243
+ # RAG ์‹œ์Šคํ…œ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
244
+ rag_system = PDFRAGSystem()
245
+
246
+ # State variables
247
  current_model = gr.State("openai/gpt-oss-120b")
248
+ uploaded_documents = gr.State({})
249
+ rag_enabled = gr.State(False)
250
+
251
+ def upload_pdf(file):
252
+ """PDF ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ"""
253
+ if file is None:
254
+ return gr.update(value="ํŒŒ์ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”"), gr.update(choices=[]), gr.update(value=False)
255
+
256
+ try:
257
+ # ํŒŒ์ผ ํ•ด์‹œ๋ฅผ ID๋กœ ์‚ฌ์šฉ
258
+ with open(file.name, 'rb') as f:
259
+ file_hash = hashlib.md5(f.read()).hexdigest()[:8]
260
+
261
+ doc_id = f"doc_{file_hash}"
262
+
263
+ # PDF ์ฒ˜๋ฆฌ ๋ฐ ์ธ๋ฑ์‹ฑ
264
+ result = rag_system.process_and_index_pdf(file.name, doc_id)
265
+
266
+ if result["success"]:
267
+ status_html = f"""
268
+ <div class="pdf-status pdf-success">
269
+ โœ… PDF ์—…๋กœ๋“œ ์„ฑ๊ณต!<br>
270
+ ๐Ÿ“„ ์ œ๋ชฉ: {result.get('title', 'Unknown')}<br>
271
+ ๐Ÿ“‘ ํŽ˜์ด์ง€: {result['pages']}ํŽ˜์ด์ง€<br>
272
+ ๐Ÿ” ์ƒ์„ฑ๋œ ์ฒญํฌ: {result['chunks']}๊ฐœ<br>
273
+ ๐Ÿ†” ๋ฌธ์„œ ID: {doc_id}
274
+ </div>
275
+ """
276
+
277
+ # ๋ฌธ์„œ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
278
+ doc_list = list(rag_system.documents.keys())
279
+ doc_choices = [f"{doc_id}: {rag_system.documents[doc_id]['metadata']['file_name']}"
280
+ for doc_id in doc_list]
281
+
282
+ return status_html, gr.update(choices=doc_choices, value=doc_choices), gr.update(value=True)
283
+ else:
284
+ status_html = f"""
285
+ <div class="pdf-status pdf-error">
286
+ โŒ PDF ์—…๋กœ๋“œ ์‹คํŒจ<br>
287
+ ์˜ค๋ฅ˜: {result['error']}
288
+ </div>
289
+ """
290
+ return status_html, gr.update(choices=[]), gr.update(value=False)
291
+
292
+ except Exception as e:
293
+ status_html = f"""
294
+ <div class="pdf-status pdf-error">
295
+ โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}
296
+ </div>
297
+ """
298
+ return status_html, gr.update(choices=[]), gr.update(value=False)
299
+
300
+ def clear_documents():
301
+ """์—…๋กœ๋“œ๋œ ๋ฌธ์„œ ์ดˆ๊ธฐํ™”"""
302
+ try:
303
+ # ChromaDB ์ปฌ๋ ‰์…˜ ์žฌ์ƒ์„ฑ
304
+ rag_system.chroma_client.delete_collection("pdf_documents")
305
+ rag_system.collection = rag_system.chroma_client.create_collection(
306
+ name="pdf_documents",
307
+ metadata={"hnsw:space": "cosine"}
308
+ )
309
+ rag_system.documents = {}
310
+
311
+ return gr.update(value="<div class='pdf-status pdf-success'>โœ… ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค</div>"), gr.update(choices=[], value=[]), gr.update(value=False)
312
+ except Exception as e:
313
+ return gr.update(value=f"<div class='pdf-status pdf-error'>โŒ ์‚ญ์ œ ์‹คํŒจ: {str(e)}</div>"), gr.update(), gr.update()
314
+
315
+ def process_with_rag(message: str, enable_rag: bool, selected_docs: List[str], top_k: int = 5):
316
+ """RAG๋ฅผ ํ™œ์šฉํ•œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ"""
317
+ if not enable_rag or not selected_docs:
318
+ return message # RAG ๋น„ํ™œ์„ฑํ™”์‹œ ์›๋ณธ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
319
+
320
+ try:
321
+ # ๊ด€๋ จ ์ฒญํฌ ๊ฒ€์ƒ‰
322
+ relevant_chunks = rag_system.search_relevant_chunks(message, top_k=top_k)
323
+
324
+ if relevant_chunks:
325
+ # ์„ ํƒ๋œ ๋ฌธ์„œ์˜ ์ฒญํฌ๋งŒ ํ•„ํ„ฐ๋ง
326
+ selected_doc_ids = [doc.split(":")[0] for doc in selected_docs]
327
+ filtered_chunks = [
328
+ chunk for chunk in relevant_chunks
329
+ if chunk['metadata']['doc_id'] in selected_doc_ids
330
+ ]
331
+
332
+ if filtered_chunks:
333
+ # RAG ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
334
+ rag_prompt = rag_system.generate_rag_prompt(message, filtered_chunks[:top_k])
335
+ return rag_prompt
336
+
337
+ return message
338
+
339
+ except Exception as e:
340
+ print(f"RAG processing error: {e}")
341
+ return message
342
 
343
  def switch_model(model_choice):
344
+ """๋ชจ๋ธ ์ „ํ™˜ ํ•จ์ˆ˜"""
345
  return gr.update(visible=False), gr.update(visible=True), model_choice
346
 
347
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค
348
  with gr.Blocks(fill_height=True, theme="Nymbo/Nymbo_Theme", css=custom_css) as demo:
349
  with gr.Row():
350
+ # ์‚ฌ์ด๋“œ๋ฐ”
351
  with gr.Column(scale=1):
352
  with gr.Group(elem_classes="main-container"):
353
+ gr.Markdown("# ๐Ÿš€ AI Chat with RAG")
354
  gr.Markdown(
355
+ "PDF ๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ AI๊ฐ€ ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•ด ๋‹ต๋ณ€ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
 
356
  )
357
 
358
+ # ๋ชจ๋ธ ์„ ํƒ
359
  model_dropdown = gr.Dropdown(
360
+ choices=["openai/gpt-oss-120b", "openai/gpt-oss-20b"],
 
 
 
361
  value="openai/gpt-oss-120b",
362
+ label="๐Ÿ“Š ๋ชจ๋ธ ์„ ํƒ"
 
363
  )
364
 
 
365
  login_button = gr.LoginButton("Sign in with Hugging Face", size="lg")
366
+ reload_btn = gr.Button("๐Ÿ”„ ๋ชจ๋ธ ๋ณ€๊ฒฝ ์ ์šฉ", variant="primary", size="lg")
367
 
368
+ # RAG ์„ค์ •
369
+ with gr.Accordion("๐Ÿ“š PDF RAG ์„ค์ •", open=True):
370
+ pdf_upload = gr.File(
371
+ label="PDF ์—…๋กœ๋“œ",
372
+ file_types=[".pdf"],
373
+ type="filepath"
374
+ )
375
+
376
+ upload_status = gr.HTML(
377
+ value="<div class='pdf-status'>PDF๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ RAG๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”</div>"
378
+ )
379
+
380
+ document_list = gr.CheckboxGroup(
381
+ choices=[],
382
+ label="๐Ÿ“„ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ",
383
+ info="์งˆ๋ฌธ์— ์ฐธ๊ณ ํ•  ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
384
+ )
385
+
386
+ with gr.Row():
387
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ ๋ชจ๋“  ๋ฌธ์„œ ์‚ญ์ œ", size="sm")
388
+ refresh_btn = gr.Button("๐Ÿ”„ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ", size="sm")
389
+
390
+ enable_rag = gr.Checkbox(
391
+ label="RAG ํ™œ์„ฑํ™”",
392
+ value=False,
393
+ info="๋ฌธ์„œ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€ ์ƒ์„ฑ ํ™œ์„ฑํ™”"
394
+ )
395
+
396
+ with gr.Accordion("โš™๏ธ RAG ๊ณ ๊ธ‰ ์„ค์ •", open=False):
397
+ top_k_chunks = gr.Slider(
398
+ minimum=1,
399
+ maximum=10,
400
+ value=5,
401
+ step=1,
402
+ label="์ฐธ์กฐํ•  ์ฒญํฌ ์ˆ˜",
403
+ info="๋‹ต๋ณ€ ์ƒ์„ฑ์‹œ ์ฐธ๊ณ ํ•  ๋ฌธ์„œ ์ฒญํฌ์˜ ๊ฐœ์ˆ˜"
404
+ )
405
+
406
+ chunk_size = gr.Slider(
407
+ minimum=500,
408
+ maximum=2000,
409
+ value=1000,
410
+ step=100,
411
+ label="์ฒญํฌ ํฌ๊ธฐ",
412
+ info="๋ฌธ์„œ๋ฅผ ๋ถ„ํ• ํ•˜๋Š” ์ฒญํฌ์˜ ํฌ๊ธฐ (๋ฌธ์ž ์ˆ˜)"
413
+ )
414
 
415
+ # ๊ณ ๊ธ‰ ์˜ต์…˜
416
+ with gr.Accordion("โš™๏ธ ๋ชจ๋ธ ์„ค์ •", open=False):
 
417
  temperature = gr.Slider(
418
  minimum=0,
419
  maximum=2,
 
429
  label="Max Tokens"
430
  )
431
 
432
+ # ๋ฉ”์ธ ์ฑ„ํŒ… ์˜์—ญ
433
  with gr.Column(scale=3):
434
  with gr.Group(elem_classes="main-container"):
435
  gr.Markdown("## ๐Ÿ’ฌ Chat Interface")
436
 
437
+ # RAG ์ƒํƒœ ํ‘œ์‹œ
438
+ with gr.Row():
439
+ rag_status = gr.HTML(
440
+ value="<div style='padding: 10px; background: rgba(59, 130, 246, 0.1); border-radius: 8px; margin-bottom: 10px;'>๐Ÿ” RAG: <strong>๋น„ํ™œ์„ฑํ™”</strong></div>"
441
+ )
442
+
443
+ # ๋ชจ๋ธ ์ธํ„ฐํŽ˜์ด์Šค ์ปจํ…Œ์ด๋„ˆ
444
  with gr.Column(visible=True) as model_120b_container:
445
  gr.Markdown("### Model: openai/gpt-oss-120b")
446
+ # ์‹ค์ œ ๋ชจ๋ธ ๋กœ๋“œ๋Š” gr.load()๋กœ ์ฒ˜๋ฆฌ
447
+ chatbot_120b = gr.Chatbot(height=400)
448
+ msg_box_120b = gr.Textbox(
449
+ label="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ",
450
+ placeholder="PDF ๋‚ด์šฉ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”...",
451
+ lines=2
452
+ )
453
+ with gr.Row():
454
+ send_btn_120b = gr.Button("๐Ÿ“ค ์ „์†ก", variant="primary")
455
+ clear_btn_120b = gr.Button("๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”")
456
 
457
  with gr.Column(visible=False) as model_20b_container:
458
  gr.Markdown("### Model: openai/gpt-oss-20b")
459
+ chatbot_20b = gr.Chatbot(height=400)
460
+ msg_box_20b = gr.Textbox(
461
+ label="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ",
462
+ placeholder="PDF ๋‚ด์šฉ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”...",
463
+ lines=2
464
+ )
465
+ with gr.Row():
466
+ send_btn_20b = gr.Button("๐Ÿ“ค ์ „์†ก", variant="primary")
467
+ clear_btn_20b = gr.Button("๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”")
468
+
469
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
470
+
471
+ # PDF ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ
472
+ pdf_upload.upload(
473
+ fn=upload_pdf,
474
+ inputs=[pdf_upload],
475
+ outputs=[upload_status, document_list, enable_rag]
476
+ )
477
+
478
+ # ๋ฌธ์„œ ์ดˆ๊ธฐํ™”
479
+ clear_btn.click(
480
+ fn=clear_documents,
481
+ outputs=[upload_status, document_list, enable_rag]
482
+ )
483
+
484
+ # RAG ์ƒํƒœ ์—…๋ฐ์ดํŠธ
485
+ enable_rag.change(
486
+ fn=lambda x: gr.update(
487
+ value=f"<div style='padding: 10px; background: rgba(59, 130, 246, 0.1); border-radius: 8px; margin-bottom: 10px;'>๐Ÿ” RAG: <strong>{'ํ™œ์„ฑํ™”' if x else '๋น„ํ™œ์„ฑํ™”'}</strong></div>"
488
+ ),
489
+ inputs=[enable_rag],
490
+ outputs=[rag_status]
491
+ )
492
 
493
+ # ๋ชจ๋ธ ์ „ํ™˜
494
  reload_btn.click(
495
  fn=switch_model,
496
  inputs=[model_dropdown],
497
  outputs=[model_120b_container, model_20b_container, current_model]
498
  ).then(
499
+ fn=lambda: gr.Info("๋ชจ๋ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"),
500
  inputs=[],
501
  outputs=[]
502
  )
503
 
504
+ # ์ฑ„ํŒ… ๊ธฐ๋Šฅ (RAG ํ†ตํ•ฉ)
505
+ def chat_with_rag(message, history, enable_rag, selected_docs, top_k):
506
+ """RAG๋ฅผ ํ™œ์šฉํ•œ ์ฑ„ํŒ…"""
507
+ # RAG ์ฒ˜๋ฆฌ
508
+ processed_message = process_with_rag(message, enable_rag, selected_docs, top_k)
509
+
510
+ # ์—ฌ๊ธฐ์— ์‹ค์ œ ๋ชจ๋ธ API ํ˜ธ์ถœ ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€์•ผ ํ•จ
511
+ # ํ˜„์žฌ๋Š” ์˜ˆ์‹œ ์‘๋‹ต
512
+ if enable_rag and selected_docs:
513
+ response = f"[RAG ํ™œ์„ฑํ™”] ์„ ํƒ๋œ {len(selected_docs)}๊ฐœ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค:\n\n{processed_message[:200]}..."
514
  else:
515
+ response = f"[์ผ๋ฐ˜ ๋ชจ๋“œ] {message}์— ๋Œ€ํ•œ ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค."
516
+
517
+ history.append((message, response))
518
+ return "", history
519
 
520
+ # 120b ๋ชจ๋ธ ์ฑ„ํŒ…
521
+ msg_box_120b.submit(
522
+ fn=chat_with_rag,
523
+ inputs=[msg_box_120b, chatbot_120b, enable_rag, document_list, top_k_chunks],
524
+ outputs=[msg_box_120b, chatbot_120b]
525
+ )
526
+
527
+ send_btn_120b.click(
528
+ fn=chat_with_rag,
529
+ inputs=[msg_box_120b, chatbot_120b, enable_rag, document_list, top_k_chunks],
530
+ outputs=[msg_box_120b, chatbot_120b]
531
+ )
532
+
533
+ clear_btn_120b.click(
534
+ lambda: ([], ""),
535
+ outputs=[chatbot_120b, msg_box_120b]
536
+ )
537
+
538
+ # 20b ๋ชจ๋ธ ์ฑ„ํŒ…
539
+ msg_box_20b.submit(
540
+ fn=chat_with_rag,
541
+ inputs=[msg_box_20b, chatbot_20b, enable_rag, document_list, top_k_chunks],
542
+ outputs=[msg_box_20b, chatbot_20b]
543
+ )
544
+
545
+ send_btn_20b.click(
546
+ fn=chat_with_rag,
547
+ inputs=[msg_box_20b, chatbot_20b, enable_rag, document_list, top_k_chunks],
548
+ outputs=[msg_box_20b, chatbot_20b]
549
+ )
550
+
551
+ clear_btn_20b.click(
552
+ lambda: ([], ""),
553
+ outputs=[chatbot_20b, msg_box_20b]
554
  )
555
 
556
+ if __name__ == "__main__":
557
+ demo.launch()