Nattapong Tapachoom commited on
Commit
1da8c51
·
1 Parent(s): 7908154

Enhance README with supported tasks and schemas; update app.py for task templates and localization; modify requirements.txt for additional dependencies

Browse files
Files changed (4) hide show
  1. README.md +71 -16
  2. __pycache__/app.cpython-313.pyc +0 -0
  3. app.py +447 -57
  4. requirements.txt +8 -0
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: AutoGDataset
3
- emoji: 🏆
4
- colorFrom: green
5
- colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.44.1
8
  app_file: app.py
@@ -10,22 +10,77 @@ pinned: false
10
  hf_oauth: true
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
 
15
- ## LangChain + HF Inference
16
 
17
- This app uses LangChain with the Hugging Face Inference API to generate QA datasets from PDFs.
18
 
19
- - Preset models: `HuggingFaceH4/zephyr-7b-beta`, `mistralai/Mistral-7B-Instruct-v0.2`, `google/flan-t5-large`.
20
- - Provide an `HF_TOKEN` (environment or UI) if your chosen model requires authentication.
 
 
21
 
22
- ## Usage
23
 
24
- - Run locally: `pip install -r requirements.txt` then `python app.py` and open the link. Upload one or more PDFs, choose the inference method, and click Generate.
25
- - On Spaces: add a secret `HF_TOKEN` if your chosen model requires it; or paste it in the UI when running.
 
 
26
 
27
- ### Notes
 
 
 
28
 
29
- - Uses HF Inference API via LangChain; no local `transformers` needed.
30
- - Output files are saved to `outputs/` as JSON and JSONL.
31
- - Login: the app requires Hugging Face login by default on Spaces. Set env `REQUIRE_LOGIN=0` to disable (useful for local runs). A Login button is shown at the top; Generate is enabled after login.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: AutoGDataset Thai
3
+ emoji: �🇭
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.44.1
8
  app_file: app.py
 
10
  hf_oauth: true
11
  ---
12
 
13
+ # AutoGDataset Thai 🇹🇭
14
 
15
+ เครื่องมือสร้างชุดข้อมูล (Dataset) ภาษาไทยจากไฟล์ PDF โดยใช้ LangChain กับ Hugging Face Inference API
16
 
17
+ ## คุณสมบัติเด่น
18
 
19
+ - **รองรับงานหลากหลาย**: QA, RLHF, DPO, Constitutional AI, Chain of Thought, Dialogue และอื่นๆ
20
+ - **เน้นภาษาไทย**: รองรับโมเดลภาษาไทยและ prompt ที่เหมาะสมกับบริบททางวัฒนธรรม
21
+ - **โมเดลที่รองรับ**: OpenThaiGPT, Typhoon, WangchanBERTa และ multilingual models
22
+ - **ปรับแต่งได้**: สามารถกำหนด prompt และพารามิเตอร์ต่างๆ ได้
23
 
24
+ ## โมเดลที่แนะนำ 🤖
25
 
26
+ ### โมเดลภาษาไทย
27
+ - `openthaigpt/openthaigpt-1.0.0-alpha-7b-chat`
28
+ - `scb10x/llama-3-typhoon-v1.5-8b-instruct`
29
+ - `airesearch/wangchanberta-base-att-spm-uncased`
30
 
31
+ ### โมเดล Multilingual
32
+ - `google/mt5-large`
33
+ - `microsoft/mdeberta-v3-base`
34
+ - `facebook/xglm-7.5B`
35
 
36
+ ## การใช้งาน 🚀
37
+
38
+ ### รันในเครื่อง
39
+ ```bash
40
+ pip install -r requirements.txt
41
+ python app.py
42
+ ```
43
+
44
+ ### บน Hugging Face Spaces
45
+ 1. เพิ่ม secret `HF_TOKEN` หากจำเป็น
46
+ 2. อัปโหลดไฟล์ PDF
47
+ 3. เลือกประเภทงานและโมเดล
48
+ 4. คลิกสร้างชุดข้อมูล
49
+
50
+ ## ประเภทงานที่รองรับ 📋
51
+
52
+ ### งานพื้นฐาน
53
+ - **QA**: คำถาม-คำตอบ `{question: str, answer: str}`
54
+ - **Summarization**: การสรุป `{summary: str}`
55
+ - **Keywords**: คำสำคัญ `{keyword: str}`
56
+ - **NER**: การจดจำเอนทิตี `{text: str, label: str, start: int, end: int}`
57
+ - **Classification**: การจำแนกประเภท `{labels: [str], rationale: str}`
58
+ - **MCQ**: คำถามแบบเลือกตอบ `{question: str, options: [str], answer_index: int}`
59
+ - **True/False**: จริง/เท็จ `{statement: str, answer: bool, explanation: str}`
60
+ - **Translation**: การแปล `{source: str, target: str}`
61
+
62
+ ### งานขั้นสูงสำหรับ AI Training
63
+ - **RLHF**: `{prompt: str, responses: [str], scores: [float], preferred_response: str}`
64
+ - **DPO**: `{prompt: str, chosen: str, rejected: str, reason: str}`
65
+ - **Instruction_Following**: `{instruction: str, input: str, output: str, difficulty: str}`
66
+ - **Constitutional_AI**: `{problematic_prompt: str, constitutional_response: str, principle: str}`
67
+ - **Chain_of_Thought**: `{problem: str, thinking_steps: [str], final_answer: str}`
68
+ - **Dialogue**: `{dialogue: [{role: str, content: str}], context: str}`
69
+ - **Thai_Culture**: `{question_th: str, answer_th: str, cultural_context: str}`
70
+
71
+ ## หมายเหตุสำคัญ 📝
72
+
73
+ - ใช้ HF Inference API ผ่าน LangChain ไม่ต้องติดตั้ง `transformers` ในเครื่อง
74
+ - ไฟล์ผลลัพธ์จะถูกบันทึกใน `outputs/` ทั้งแบบ JSON และ JSONL
75
+ - ต้องเข้าสู่ระบบ Hugging Face สำหรับ Spaces (ตั้งค่า `REQUIRE_LOGIN=0` เพื่อปิดการใช้งาน)
76
+ - รองรับการปรับแต่ง prompt สำหรับผลลัพธ์ที่ดีขึ้น
77
+
78
+ ## การติดตั้ง Dependencies 📦
79
+
80
+ ```bash
81
+ pip install gradio pypdf huggingface_hub langchain langchain-community pythainlp transformers torch
82
+ ```
83
+
84
+ สำหรับการประมวลผลภาษาไทยที่ดีขึ้น แนะนำให้ติดตั้ง:
85
+ - `pythainlp`: สำหรับการประมวลผลภาษาไทย
86
+ - `thai-word-segmentation`: สำหรับการตัดคำภาษาไทย
__pycache__/app.cpython-313.pyc CHANGED
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
 
app.py CHANGED
@@ -89,11 +89,89 @@ def chunk_text(text: str, chunk_size: int = 1500, overlap: int = 200, max_chunks
89
 
90
 
91
  DEFAULT_QA_PROMPT_TMPL = (
92
- 'You are a helpful dataset creator. Read the provided content and generate between {min_pairs} and {max_pairs} high-quality, factual question-answer pairs. '
93
- 'Return ONLY a JSON array with objects of the form {"question": str, "answer": str}. Do not include any extra text, comments, or code fences.\n\n'
94
- 'Content:\n{content}\n'
95
  )
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  def extract_json_array(text: str) -> List[Dict[str, Any]]:
99
  if not text:
@@ -126,11 +204,10 @@ def extract_json_array(text: str) -> List[Dict[str, Any]]:
126
  return []
127
 
128
 
129
- def build_langchain(model_id: str, hf_token: str | None, max_new_tokens: int, temperature: float, custom_instruction: str | None, min_pairs: int, max_pairs: int):
130
  if any(x is None for x in [PromptTemplate, JsonOutputParser, HuggingFaceHub]):
131
  raise RuntimeError("langchain and langchain-community are required. Please add to requirements.txt.")
132
  # Prompt
133
- template = custom_instruction.strip() + "\n\nContent:\n{content}\n" if (custom_instruction and custom_instruction.strip()) else DEFAULT_QA_PROMPT_TMPL
134
  prompt = PromptTemplate.from_template(template)
135
  # Model wrapper (Hugging Face Inference API)
136
  llm = HuggingFaceHub(
@@ -144,14 +221,195 @@ def build_langchain(model_id: str, hf_token: str | None, max_new_tokens: int, te
144
  )
145
  parser = JsonOutputParser()
146
  chain = prompt | llm | parser
147
- # Provide default formatting variables via partials
148
- chain = chain.bind(min_pairs=min_pairs, max_pairs=max_pairs)
149
  return chain
150
 
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  def generate_dataset(
153
  user_profile: Any | None,
154
  files: List[gr.File],
 
155
  preset_model: str,
156
  custom_model_id: str,
157
  hf_token: str,
@@ -163,83 +421,159 @@ def generate_dataset(
163
  custom_instruction: str,
164
  min_pairs: int,
165
  max_pairs: int,
 
 
 
 
 
166
  ):
167
  # Enforce login if required
168
  if REQUIRE_LOGIN and not user_profile:
169
- return "Please login first to generate a dataset.", None, None
170
 
171
  # Read and chunk
172
  full_text, _docs = read_pdfs(files)
173
  chunks = chunk_text(full_text, chunk_size=chunk_size, overlap=overlap, max_chunks=max_chunks)
174
  if not chunks:
175
- return "No text extracted from PDF(s).", None, None
176
 
177
  model_id = (custom_model_id or "").strip() or preset_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  try:
179
- chain = build_langchain(model_id, hf_token or None, max_new_tokens, temperature, custom_instruction, min_pairs, max_pairs)
180
  except Exception as e:
181
- return f"Error preparing LangChain: {e}", None, None
182
 
183
  results: List[Dict[str, Any]] = []
184
  for ch in chunks:
185
  try:
186
- data = chain.invoke({"content": ch["text"]})
187
- if isinstance(data, list):
188
- items = data
189
- else:
190
- items = extract_json_array(str(data))
191
  except Exception:
192
  # If parser fails, try best-effort extraction on raw string
193
  try:
194
- from langchain_core.runnables import Runnable
195
- raw = (PromptTemplate.from_template(DEFAULT_QA_PROMPT_TMPL) | HuggingFaceHub(repo_id=model_id, huggingfacehub_api_token=hf_token)).invoke({"content": ch["text"], "min_pairs": min_pairs, "max_pairs": max_pairs}) # type: ignore
196
- items = extract_json_array(str(raw))
197
  except Exception:
198
  items = []
199
 
200
  for it in items:
201
- if isinstance(it, dict) and it.get("question") and it.get("answer"):
202
- it["context"] = (ch["text"][:500] + ("..." if len(ch["text"]) > 500 else ""))
203
- results.append(it)
 
204
 
205
  if not results:
206
- return "Model did not return any valid QA pairs. Try adjusting prompt or model.", None, None
207
 
208
- # Deduplicate by question
 
209
  seen = set()
210
- unique = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  for r in results:
212
- q = r.get("question", "").strip()
213
- if q and q.lower() not in seen:
214
  unique.append(r)
215
- seen.add(q.lower())
216
 
217
  # Save to outputs
218
  outdir = ensure_output_dir()
219
  ts = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
220
- json_path = os.path.join(outdir, f"dataset_qa_{ts}.json")
221
- jsonl_path = os.path.join(outdir, f"dataset_qa_{ts}.jsonl")
 
222
  with io.open(json_path, "w", encoding="utf-8") as f:
223
  json.dump(unique, f, ensure_ascii=False, indent=2)
224
  with io.open(jsonl_path, "w", encoding="utf-8") as f:
225
  for item in unique:
226
  f.write(json.dumps(item, ensure_ascii=False) + "\n")
227
 
228
- return f"Generated {len(unique)} QA pairs.", json_path, jsonl_path
229
 
230
 
231
  PRESET_MODELS = [
 
 
 
 
 
 
 
 
 
 
 
 
232
  "HuggingFaceH4/zephyr-7b-beta",
233
  "mistralai/Mistral-7B-Instruct-v0.2",
234
  "google/flan-t5-large",
 
 
235
  ]
236
 
237
 
238
- with gr.Blocks(title="AutoGDataset - PDF to QA Dataset (LangChain)") as demo:
239
  gr.Markdown("""
240
- # AutoGDataset
241
- Generate QA datasets from PDFs using LangChain with Hugging Face models (Inference API).
242
- Choose one of the preset models or provide a custom repo id. Provide a valid `HF_TOKEN` if required by the model.
 
 
 
 
 
 
 
243
  """)
244
 
245
  # Login requirement (Hugging Face OAuth via Gradio LoginButton when available)
@@ -248,54 +582,105 @@ with gr.Blocks(title="AutoGDataset - PDF to QA Dataset (LangChain)") as demo:
248
  with gr.Row():
249
  login_info = gr.Markdown(
250
  value=(
251
- "Please login with your Hugging Face account to use the app."
252
  if effective_require_login
253
  else (
254
- "Login is optional." if OAUTH_AVAILABLE else "OAuth login not configured on this deployment."
255
  )
256
  ),
257
  elem_id="login-info",
258
  )
259
  if OAUTH_AVAILABLE:
260
  with gr.Row():
261
- login_btn = gr.LoginButton(value="Login with Hugging Face")
262
 
263
  with gr.Row():
264
- pdf_files = gr.File(label="Upload PDF(s)", file_count="multiple", file_types=[".pdf"])
265
 
266
  with gr.Group():
267
  with gr.Row():
268
- preset_model = gr.Dropdown(label="Preset Model", choices=PRESET_MODELS, value=PRESET_MODELS[0])
269
- custom_model_id = gr.Textbox(label="Custom Model ID (optional)", placeholder="org/model-name")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  with gr.Row():
271
- hf_token = gr.Textbox(label="HF Token", type="password", value=os.environ.get("HF_TOKEN", ""), placeholder="hf_xxx (required for many models)")
 
272
  with gr.Row():
273
- max_new_tokens = gr.Slider(64, 1024, value=512, step=16, label="Max New Tokens")
274
- temperature = gr.Slider(0.0, 1.5, value=0.2, step=0.05, label="Temperature")
 
 
275
 
276
- with gr.Accordion("Advanced", open=False):
277
  with gr.Row():
278
- chunk_size = gr.Slider(500, 4000, value=1500, step=50, label="Chunk Size (chars)")
279
- overlap = gr.Slider(0, 1000, value=200, step=50, label="Overlap (chars)")
280
- max_chunks = gr.Slider(1, 40, value=5, step=1, label="Max Chunks")
281
  with gr.Row():
282
- min_pairs = gr.Slider(1, 10, value=3, step=1, label="Min Pairs/Chunk")
283
- max_pairs = gr.Slider(1, 12, value=6, step=1, label="Max Pairs/Chunk")
284
- custom_instruction = gr.Textbox(label="Custom Instruction (optional)", lines=3, placeholder="Override default instruction. Must ask for a pure JSON array of {question, answer}.")
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
- generate_btn = gr.Button("Generate Dataset", variant="primary", interactive=(not effective_require_login))
287
 
288
  with gr.Row():
289
  status = gr.Markdown()
290
  with gr.Row():
291
- out_json = gr.File(label="Download JSON")
292
- out_jsonl = gr.File(label="Download JSONL")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
  generate_btn.click(
295
  fn=generate_dataset,
296
  inputs=[
297
  user_state,
298
  pdf_files,
 
299
  preset_model,
300
  custom_model_id,
301
  hf_token,
@@ -307,6 +692,11 @@ with gr.Blocks(title="AutoGDataset - PDF to QA Dataset (LangChain)") as demo:
307
  custom_instruction,
308
  min_pairs,
309
  max_pairs,
 
 
 
 
 
310
  ],
311
  outputs=[status, out_json, out_jsonl],
312
  show_progress=True,
@@ -321,9 +711,9 @@ with gr.Blocks(title="AutoGDataset - PDF to QA Dataset (LangChain)") as demo:
321
  username = user.get("username") or user.get("name")
322
  if not username and hasattr(user, "username"):
323
  username = getattr(user, "username")
324
- msg = f"Logged in as @{username}" if username else "Logged in"
325
  except Exception:
326
- msg = "Logged in"
327
  return user, gr.update(value=msg), gr.update(interactive=True)
328
 
329
  # Enable Generate button after login and store user profile
@@ -331,7 +721,7 @@ with gr.Blocks(title="AutoGDataset - PDF to QA Dataset (LangChain)") as demo:
331
  login_btn.login(_on_login, inputs=None, outputs=[user_state, login_info, generate_btn])
332
  else:
333
  # In local/dev without OAuth routing, clicking will mock-login
334
- login_btn.click(lambda: ("local_user", gr.update(value="Logged in (local)"), gr.update(interactive=True)), inputs=None, outputs=[user_state, login_info, generate_btn])
335
 
336
  if __name__ == "__main__":
337
  # For local runs
 
89
 
90
 
91
  DEFAULT_QA_PROMPT_TMPL = (
92
+ 'คุณเป็นผู้สร้างชุดข้อมูลที่เป็นประโยชน์ อ่านเนื้อหาที่ให้มาและสร้างคู่คำถาม-คำตอบที่มีคุณภาพสูงและตรงตามข้อเท็จจริง จำนวน {min_pairs} ถึง {max_pairs} คู่ '
93
+ 'ส่งคืนเฉพาะ JSON array ที่มี objects ในรูปแบบ {{"question": str, "answer": str}} เท่านั้น ไม่ต้องใส่ข้อความเพิ่มเติม คำอธิบาย หรือ code fences\n\n'
94
+ 'เนื้อหา:\n{content}\n'
95
  )
96
 
97
+ TASK_TEMPLATES: Dict[str, str] = {
98
+ "QA": DEFAULT_QA_PROMPT_TMPL,
99
+ "Summarization": (
100
+ 'สรุปเนื้อหาต่อไปนี้เป็นบทสรุปที่กระชับ จำนวน {min_pairs} ถึง {max_pairs} บทสรุป โดยครอบคลุมข้อมูลสำคัญ '
101
+ 'ส่งคืนเฉพาะ JSON array ที่มี objects ในรูปแบบ {{"summary": str}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
102
+ 'เนื้อหา:\n{content}\n'
103
+ ),
104
+ "Keywords": (
105
+ 'แยกคำสำคัญหรือวลีสำคัญจากเนื้อหา จำนวน {min_pairs} ถึง {max_pairs} คำ '
106
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"keyword": str}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
107
+ 'เนื้อหา:\n{content}\n'
108
+ ),
109
+ "NER": (
110
+ 'แยกเอนทิตีที่มีชื่อเฉพาะจากเนื้อหา ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"text": str, "label": str, "start": int, "end": int}} '
111
+ 'ป้ายกำกับควรเป็นประเภทมาตรฐาน เช่น PER (บุคคล), ORG (องค์กร), LOC (สถานที่), MISC (อื่นๆ){ner_labels_clause}\n\n'
112
+ 'เนื้อหา:\n{content}\n'
113
+ ),
114
+ "Classification": (
115
+ 'จำแนกเนื้อหาตามป้ายกำกับต่อไปนี้: {labels} {multi_label_clause} '
116
+ 'ส่งคืนเฉพาะ JSON array ที่มี objects ในรูปแบบ {{"labels": [str], "rationale": str}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
117
+ 'เนื้อหา:\n{content}\n'
118
+ ),
119
+ "MCQ": (
120
+ 'สร้างคำถามแบบเลือกตอบจากเนื้อหา จำนวน {min_pairs} ถึง {max_pairs} ข้อ แต่ละข้อมี {num_options} ตัวเลือก '
121
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"question": str, "options": [str], "answer_index": int}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
122
+ 'เนื้อหา:\n{content}\n'
123
+ ),
124
+ "True/False": (
125
+ 'สร้างข้อความจริง/เท็จที่อิงจากเนื้อหาเท่านั้น จำนวน {min_pairs} ถึง {max_pairs} ข้อความ '
126
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"statement": str, "answer": bool, "explanation": str}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
127
+ 'เนื้อหา:\n{content}\n'
128
+ ),
129
+ "Translation": (
130
+ 'แปลเนื้อหาเป็น{target_language} สร้างคู่ประโยคแบบคู่ขนาน จำนวน {min_pairs} ถึง {max_pairs} คู่ '
131
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"source": str, "target": str}} เท่านั้น ไม่ต้องมีข้อความเพิ่มเติม\n\n'
132
+ 'เนื้อหา:\n{content}\n'
133
+ ),
134
+ "RLHF": (
135
+ 'สร้างข้อมูลสำหรับ Reinforcement Learning from Human Feedback (RLHF) จากเนื้อหานี้ '
136
+ 'สร้างคำถามและการตอบสนองหลายแบบ พร้อมคะแนนความต้องการของมนุษย์ จำนวน {min_pairs} ถึง {max_pairs} ชุด '
137
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"prompt": str, "responses": [str], "scores": [float], "preferred_response": str}} เท่านั้น\n\n'
138
+ 'เนื้อหา:\n{content}\n'
139
+ ),
140
+ "DPO": (
141
+ 'สร้างข้อมูลสำหรับ Direct Preference Optimization (DPO) จากเนื้อหานี้ '
142
+ 'สร้างคำถามพร้อมการตอบสนองที่ดีและไม่ดี จำนวน {min_pairs} ถึง {max_pairs} คู่ '
143
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"prompt": str, "chosen": str, "rejected": str, "reason": str}} เท่านั้น\n\n'
144
+ 'เนื้อหา:\n{content}\n'
145
+ ),
146
+ "Instruction_Following": (
147
+ 'สร้างคำสั่งและการตอบสนองสำหรับการฝึกการทำตามคำสั่ง จำนวน {min_pairs} ถึง {max_pairs} คู่ '
148
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"instruction": str, "input": str, "output": str, "difficulty": str}} เท่านั้น\n\n'
149
+ 'เนื้อหา:\n{content}\n'
150
+ ),
151
+ "Constitutional_AI": (
152
+ 'สร้างข้อมูลสำหรับ Constitutional AI โดยสร้างคำถามที่อาจมีปัญหาทางจริยธรรมและคำตอบที่เหมาะสม '
153
+ 'จำนวน {min_pairs} ถึง {max_pairs} คู่ '
154
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"problematic_prompt": str, "constitutional_response": str, "principle": str}} เท่านั้น\n\n'
155
+ 'เนื้อหา:\n{content}\n'
156
+ ),
157
+ "Chain_of_Thought": (
158
+ 'สร้างตัวอย่างการคิดแบบขั้นตอน (Chain of Thought) จากเนื้อหา จำนวน {min_pairs} ถึง {max_pairs} ตัวอย่าง '
159
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"problem": str, "thinking_steps": [str], "final_answer": str}} เท่านั้น\n\n'
160
+ 'เนื้อหา:\n{content}\n'
161
+ ),
162
+ "Dialogue": (
163
+ 'สร้างบทสนทนาระหว่างผู้ใช้และผู้ช่วย AI จากเนื้อหา จำนวน {min_pairs} ถึง {max_pairs} บทสนทนา '
164
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"dialogue": [{{"role": str, "content": str}}], "context": str}} เท่านั้น\n\n'
165
+ 'เนื้อหา:\n{content}\n'
166
+ ),
167
+ "Thai_Culture": (
168
+ 'สร้างคำถาม-คำตอบเกี่ยวกับวัฒนธรรมไทยจากเนื้อหา เน้นความเข้าใจภาษาไทยและบริบททางวัฒนธรรม '
169
+ 'จำนวน {min_pairs} ถึง {max_pairs} คู่ '
170
+ 'ส่งคืนเฉพาะ JSON array ของ objects ที่มี {{"question_th": str, "answer_th": str, "cultural_context": str}} เท่านั้น\n\n'
171
+ 'เนื้อหา:\n{content}\n'
172
+ ),
173
+ }
174
+
175
 
176
  def extract_json_array(text: str) -> List[Dict[str, Any]]:
177
  if not text:
 
204
  return []
205
 
206
 
207
+ def build_langchain(model_id: str, hf_token: str | None, max_new_tokens: int, temperature: float, template: str):
208
  if any(x is None for x in [PromptTemplate, JsonOutputParser, HuggingFaceHub]):
209
  raise RuntimeError("langchain and langchain-community are required. Please add to requirements.txt.")
210
  # Prompt
 
211
  prompt = PromptTemplate.from_template(template)
212
  # Model wrapper (Hugging Face Inference API)
213
  llm = HuggingFaceHub(
 
221
  )
222
  parser = JsonOutputParser()
223
  chain = prompt | llm | parser
 
 
224
  return chain
225
 
226
 
227
+ def get_task_template(task: str, custom_instruction: str | None) -> str:
228
+ base = TASK_TEMPLATES.get(task, DEFAULT_QA_PROMPT_TMPL)
229
+ if custom_instruction and custom_instruction.strip():
230
+ # Allow user to override fully, but ensure {content} is present
231
+ if "{content}" not in custom_instruction:
232
+ custom_instruction = custom_instruction.strip() + "\n\nContent:\n{content}\n"
233
+ return custom_instruction
234
+ return base
235
+
236
+
237
+ def normalize_items(task: str, data: Any) -> List[Dict[str, Any]]:
238
+ # Convert model output to list[dict] per task
239
+ items: List[Dict[str, Any]] = []
240
+ if data is None:
241
+ return items
242
+ if isinstance(data, str):
243
+ data = extract_json_array(data)
244
+ if isinstance(data, dict):
245
+ # handle wrappers like {"items": [...]}
246
+ if "items" in data and isinstance(data["items"], list):
247
+ data = data["items"]
248
+ else:
249
+ data = [data]
250
+ if isinstance(data, list):
251
+ # keywords may be list[str]
252
+ if task == "Keywords" and data and all(isinstance(x, str) for x in data):
253
+ return [{"keyword": x} for x in data if x]
254
+ for el in data:
255
+ if isinstance(el, dict):
256
+ items.append(el)
257
+ # Validate per-task required fields and normalize variants
258
+ norm: List[Dict[str, Any]] = []
259
+ for it in items:
260
+ if task == "QA":
261
+ q = str(it.get("question", "")).strip()
262
+ a = str(it.get("answer", "")).strip()
263
+ if q and a:
264
+ norm.append({"question": q, "answer": a})
265
+ elif task == "Summarization":
266
+ s = str(it.get("summary", "")).strip()
267
+ if s:
268
+ norm.append({"summary": s})
269
+ elif task == "Keywords":
270
+ k = it.get("keyword")
271
+ if isinstance(k, str) and k.strip():
272
+ norm.append({"keyword": k.strip()})
273
+ elif isinstance(it.get("keywords"), list):
274
+ for kw in it["keywords"]:
275
+ if isinstance(kw, str) and kw.strip():
276
+ norm.append({"keyword": kw.strip()})
277
+ elif task == "NER":
278
+ txt = it.get("text")
279
+ label = it.get("label")
280
+ start = it.get("start")
281
+ end = it.get("end")
282
+ if isinstance(txt, str) and isinstance(label, str) and isinstance(start, int) and isinstance(end, int):
283
+ norm.append({"text": txt, "label": label, "start": start, "end": end})
284
+ elif isinstance(it.get("entities"), list):
285
+ for ent in it["entities"]:
286
+ if all(k in ent for k in ("text", "label", "start", "end")):
287
+ norm.append({
288
+ "text": str(ent.get("text", "")),
289
+ "label": str(ent.get("label", "")),
290
+ "start": int(ent.get("start", 0)),
291
+ "end": int(ent.get("end", 0)),
292
+ })
293
+ elif task == "Classification":
294
+ labels = it.get("labels")
295
+ if isinstance(labels, str):
296
+ labels = [labels]
297
+ if isinstance(labels, list):
298
+ labels = [str(x).strip() for x in labels if str(x).strip()]
299
+ rationale = str(it.get("rationale", "")).strip()
300
+ if labels:
301
+ norm.append({"labels": labels, "rationale": rationale})
302
+ elif task == "MCQ":
303
+ q = it.get("question")
304
+ options = it.get("options")
305
+ answer_index = it.get("answer_index")
306
+ answer = it.get("answer")
307
+ if isinstance(options, list) and all(isinstance(o, str) for o in options) and isinstance(q, str):
308
+ if isinstance(answer_index, int):
309
+ idx = answer_index
310
+ elif isinstance(answer, str) and answer in options:
311
+ idx = options.index(answer)
312
+ else:
313
+ continue
314
+ norm.append({"question": q, "options": options, "answer_index": idx})
315
+ elif task == "True/False":
316
+ st = it.get("statement")
317
+ ans = it.get("answer")
318
+ expl = it.get("explanation", "")
319
+ if isinstance(st, str):
320
+ if isinstance(ans, bool):
321
+ val = ans
322
+ elif isinstance(ans, str):
323
+ val = ans.strip().lower() in ("true", "t", "yes", "1")
324
+ else:
325
+ continue
326
+ norm.append({"statement": st, "answer": val, "explanation": str(expl)})
327
+ elif task == "Translation":
328
+ src = it.get("source")
329
+ tgt = it.get("target")
330
+ if isinstance(src, str) and isinstance(tgt, str) and src.strip() and tgt.strip():
331
+ norm.append({"source": src, "target": tgt})
332
+ elif task == "RLHF":
333
+ prompt = it.get("prompt")
334
+ responses = it.get("responses")
335
+ scores = it.get("scores")
336
+ preferred = it.get("preferred_response")
337
+ if isinstance(prompt, str) and isinstance(responses, list) and isinstance(scores, list):
338
+ norm.append({
339
+ "prompt": prompt,
340
+ "responses": responses,
341
+ "scores": scores,
342
+ "preferred_response": str(preferred) if preferred else ""
343
+ })
344
+ elif task == "DPO":
345
+ prompt = it.get("prompt")
346
+ chosen = it.get("chosen")
347
+ rejected = it.get("rejected")
348
+ reason = it.get("reason", "")
349
+ if isinstance(prompt, str) and isinstance(chosen, str) and isinstance(rejected, str):
350
+ norm.append({
351
+ "prompt": prompt,
352
+ "chosen": chosen,
353
+ "rejected": rejected,
354
+ "reason": str(reason)
355
+ })
356
+ elif task == "Instruction_Following":
357
+ instruction = it.get("instruction")
358
+ input_text = it.get("input", "")
359
+ output = it.get("output")
360
+ difficulty = it.get("difficulty", "medium")
361
+ if isinstance(instruction, str) and isinstance(output, str):
362
+ norm.append({
363
+ "instruction": instruction,
364
+ "input": str(input_text),
365
+ "output": output,
366
+ "difficulty": str(difficulty)
367
+ })
368
+ elif task == "Constitutional_AI":
369
+ problematic = it.get("problematic_prompt")
370
+ constitutional = it.get("constitutional_response")
371
+ principle = it.get("principle", "")
372
+ if isinstance(problematic, str) and isinstance(constitutional, str):
373
+ norm.append({
374
+ "problematic_prompt": problematic,
375
+ "constitutional_response": constitutional,
376
+ "principle": str(principle)
377
+ })
378
+ elif task == "Chain_of_Thought":
379
+ problem = it.get("problem")
380
+ steps = it.get("thinking_steps")
381
+ answer = it.get("final_answer")
382
+ if isinstance(problem, str) and isinstance(steps, list) and isinstance(answer, str):
383
+ norm.append({
384
+ "problem": problem,
385
+ "thinking_steps": steps,
386
+ "final_answer": answer
387
+ })
388
+ elif task == "Dialogue":
389
+ dialogue = it.get("dialogue")
390
+ context = it.get("context", "")
391
+ if isinstance(dialogue, list):
392
+ norm.append({
393
+ "dialogue": dialogue,
394
+ "context": str(context)
395
+ })
396
+ elif task == "Thai_Culture":
397
+ question_th = it.get("question_th")
398
+ answer_th = it.get("answer_th")
399
+ cultural_context = it.get("cultural_context", "")
400
+ if isinstance(question_th, str) and isinstance(answer_th, str):
401
+ norm.append({
402
+ "question_th": question_th,
403
+ "answer_th": answer_th,
404
+ "cultural_context": str(cultural_context)
405
+ })
406
+ return norm
407
+
408
+
409
  def generate_dataset(
410
  user_profile: Any | None,
411
  files: List[gr.File],
412
+ task: str,
413
  preset_model: str,
414
  custom_model_id: str,
415
  hf_token: str,
 
421
  custom_instruction: str,
422
  min_pairs: int,
423
  max_pairs: int,
424
+ class_labels_text: str,
425
+ multi_label: bool,
426
+ target_language: str,
427
+ num_options: int,
428
+ ner_labels_text: str,
429
  ):
430
  # Enforce login if required
431
  if REQUIRE_LOGIN and not user_profile:
432
+ return "กรุณาเข้าสู่ระบบก่อนเพื่อสร้างชุดข้อมูล", None, None
433
 
434
  # Read and chunk
435
  full_text, _docs = read_pdfs(files)
436
  chunks = chunk_text(full_text, chunk_size=chunk_size, overlap=overlap, max_chunks=max_chunks)
437
  if not chunks:
438
+ return "ไม่สามารถแยกข้อความจากไฟล์ PDF ได้", None, None
439
 
440
  model_id = (custom_model_id or "").strip() or preset_model
441
+ # Prepare template per task
442
+ base_template = get_task_template(task, custom_instruction)
443
+ # enrich template with conditional clauses
444
+ ner_clause = ""
445
+ if ner_labels_text.strip():
446
+ ner_clause = f" (limit to: {ner_labels_text.strip()})"
447
+ base_template = base_template.replace("{ner_labels_clause}", ner_clause)
448
+ if "{labels}" in base_template:
449
+ labels_text = class_labels_text.strip() or "[]"
450
+ base_template = base_template.replace("{labels}", labels_text)
451
+ if "{multi_label_clause}" in base_template:
452
+ base_template = base_template.replace("{multi_label_clause}", " Allow multiple labels." if multi_label else " Choose a single best label.")
453
+ if "{num_options}" in base_template:
454
+ base_template = base_template.replace("{num_options}", str(int(num_options)))
455
  try:
456
+ chain = build_langchain(model_id, hf_token or None, max_new_tokens, temperature, base_template)
457
  except Exception as e:
458
+ return f"ข้อผิดพลาดในการเตรียม LangChain: {e}", None, None
459
 
460
  results: List[Dict[str, Any]] = []
461
  for ch in chunks:
462
  try:
463
+ variables = {"content": ch["text"], "min_pairs": min_pairs, "max_pairs": max_pairs}
464
+ if "{target_language}" in base_template:
465
+ variables["target_language"] = target_language or "English"
466
+ data = chain.invoke(variables)
467
+ items = normalize_items(task, data)
468
  except Exception:
469
  # If parser fails, try best-effort extraction on raw string
470
  try:
471
+ raw = (PromptTemplate.from_template(base_template) | HuggingFaceHub(repo_id=model_id, huggingfacehub_api_token=hf_token)).invoke(variables) # type: ignore
472
+ items = normalize_items(task, raw)
 
473
  except Exception:
474
  items = []
475
 
476
  for it in items:
477
+ # Enrich with context and task
478
+ it["context"] = (ch["text"][:500] + ("..." if len(ch["text"]) > 500 else ""))
479
+ it["task"] = task
480
+ results.append(it)
481
 
482
  if not results:
483
+ return f"โมเดลไม่ได้ส่งคืนข้อมูลที่ถูกต้องสำหรับงาน {task} ลองปรับ prompt หรือโมเดล", None, None
484
 
485
+ # Deduplicate per task key
486
+ unique: List[Dict[str, Any]] = []
487
  seen = set()
488
+ def key_of(item: Dict[str, Any]) -> str:
489
+ if task == "QA":
490
+ return (item.get("question") or "").strip().lower()
491
+ if task == "Summarization":
492
+ return (item.get("summary") or "").strip().lower()
493
+ if task == "Keywords":
494
+ return (item.get("keyword") or "").strip().lower()
495
+ if task == "NER":
496
+ return f"{item.get('text')}|{item.get('label')}|{item.get('start')}|{item.get('end')}"
497
+ if task == "Classification":
498
+ return ",".join(sorted([str(x).lower() for x in item.get("labels", [])]))
499
+ if task == "MCQ":
500
+ return (item.get("question") or "").strip().lower()
501
+ if task == "True/False":
502
+ return (item.get("statement") or "").strip().lower()
503
+ if task == "Translation":
504
+ return f"{item.get('source')}|{item.get('target')}"
505
+ if task == "RLHF":
506
+ return (item.get("prompt") or "").strip().lower()
507
+ if task == "DPO":
508
+ return (item.get("prompt") or "").strip().lower()
509
+ if task == "Instruction_Following":
510
+ return (item.get("instruction") or "").strip().lower()
511
+ if task == "Constitutional_AI":
512
+ return (item.get("problematic_prompt") or "").strip().lower()
513
+ if task == "Chain_of_Thought":
514
+ return (item.get("problem") or "").strip().lower()
515
+ if task == "Dialogue":
516
+ dialogue = item.get("dialogue", [])
517
+ if dialogue and isinstance(dialogue, list):
518
+ return str(dialogue[0].get("content", "")).strip().lower()
519
+ return ""
520
+ if task == "Thai_Culture":
521
+ return (item.get("question_th") or "").strip().lower()
522
+ return json.dumps(item, ensure_ascii=False)
523
  for r in results:
524
+ k = key_of(r)
525
+ if k and k not in seen:
526
  unique.append(r)
527
+ seen.add(k)
528
 
529
  # Save to outputs
530
  outdir = ensure_output_dir()
531
  ts = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
532
+ safe_task = task.lower().replace("/", "-").replace(" ", "_")
533
+ json_path = os.path.join(outdir, f"dataset_{safe_task}_{ts}.json")
534
+ jsonl_path = os.path.join(outdir, f"dataset_{safe_task}_{ts}.jsonl")
535
  with io.open(json_path, "w", encoding="utf-8") as f:
536
  json.dump(unique, f, ensure_ascii=False, indent=2)
537
  with io.open(jsonl_path, "w", encoding="utf-8") as f:
538
  for item in unique:
539
  f.write(json.dumps(item, ensure_ascii=False) + "\n")
540
 
541
+ return f"สร้างข้อมูลสำเร็จ {len(unique)} รายการสำหรับงาน: {task} 🎉", json_path, jsonl_path
542
 
543
 
544
  PRESET_MODELS = [
545
+ # Thai-capable models
546
+ "openthaigpt/openthaigpt-1.0.0-alpha-7b-chat",
547
+ "scb10x/llama-3-typhoon-v1.5-8b-instruct",
548
+ "airesearch/wangchanberta-base-att-spm-uncased",
549
+
550
+ # Multilingual models good for Thai
551
+ "google/mt5-large",
552
+ "microsoft/mdeberta-v3-base",
553
+ "facebook/xglm-7.5B",
554
+ "microsoft/DialoGPT-medium",
555
+
556
+ # General powerful models
557
  "HuggingFaceH4/zephyr-7b-beta",
558
  "mistralai/Mistral-7B-Instruct-v0.2",
559
  "google/flan-t5-large",
560
+ "meta-llama/Llama-2-7b-chat-hf",
561
+ "microsoft/DialoGPT-large",
562
  ]
563
 
564
 
565
+ with gr.Blocks(title="AutoGDataset Thai - PDF to Dataset Generator") as demo:
566
  gr.Markdown("""
567
+ # AutoGDataset Thai 🇹🇭
568
+ สร้างชุดข้อมูล (Dataset) ภาษาไทยจากไฟล์ PDF โดยใช้ LangChain กับโมเดล Hugging Face
569
+
570
+ **คุณสมบัติ:**
571
+ - รองรับงานหลากหลายประเภท: QA, RLHF, DPO, Constitutional AI และอื่นๆ
572
+ - เน้นการสร้างข้อมูลภาษาไทยคุณภาพสูง
573
+ - รองรับโมเดลภาษาไทยและ multilingual models
574
+ - สามารถปรับแต่ง prompt เพื่อเพิ่มประสิทธิภาพ
575
+
576
+ เลือกโมเดลที่มีอยู่หรือระบุ repo id ที่กำหนดเอง ระบุ `HF_TOKEN` หากจำเป็นสำหรับโมเดล
577
  """)
578
 
579
  # Login requirement (Hugging Face OAuth via Gradio LoginButton when available)
 
582
  with gr.Row():
583
  login_info = gr.Markdown(
584
  value=(
585
+ "กรุณาเข้าสู่ระบบด้วยบัญชี Hugging Face เพื่อใช้งานแอป"
586
  if effective_require_login
587
  else (
588
+ "การเข้าสู่ระบบเป็นทางเลือก" if OAUTH_AVAILABLE else "ไม่ได้ตั้งค่าการเข้าสู่ระบบ OAuth ในการติดตั้งนี้"
589
  )
590
  ),
591
  elem_id="login-info",
592
  )
593
  if OAUTH_AVAILABLE:
594
  with gr.Row():
595
+ login_btn = gr.LoginButton(value="เข้าสู่ระบบด้วย Hugging Face")
596
 
597
  with gr.Row():
598
+ pdf_files = gr.File(label="อัปโหลดไฟล์ PDF", file_count="multiple", file_types=[".pdf"])
599
 
600
  with gr.Group():
601
  with gr.Row():
602
+ task = gr.Dropdown(
603
+ label="งานที่ต้องการ (Task Type)",
604
+ choices=[
605
+ "QA",
606
+ "Summarization",
607
+ "Keywords",
608
+ "NER",
609
+ "Classification",
610
+ "MCQ",
611
+ "True/False",
612
+ "Translation",
613
+ "RLHF",
614
+ "DPO",
615
+ "Instruction_Following",
616
+ "Constitutional_AI",
617
+ "Chain_of_Thought",
618
+ "Dialogue",
619
+ "Thai_Culture",
620
+ ],
621
+ value="Thai_Culture",
622
+ )
623
  with gr.Row():
624
+ preset_model = gr.Dropdown(label="โมเดลที่กำหนดไว้ (Preset Model)", choices=PRESET_MODELS, value=PRESET_MODELS[0])
625
+ custom_model_id = gr.Textbox(label="รหัสโมเดลกำหนดเอง (ไม่บังคับ)", placeholder="org/model-name")
626
  with gr.Row():
627
+ hf_token = gr.Textbox(label="HF Token", type="password", value=os.environ.get("HF_TOKEN", ""), placeholder="hf_xxx (จำเป็นสำหรับหลายโมเดล)")
628
+ with gr.Row():
629
+ max_new_tokens = gr.Slider(64, 1024, value=512, step=16, label="จำนวน Token สูงสุด")
630
+ temperature = gr.Slider(0.0, 1.5, value=0.2, step=0.05, label="อุณหภูมิ (ความสร้างสรรค์)")
631
 
632
+ with gr.Accordion("การตั้งค่าขั้นสูง (Advanced Settings)", open=False):
633
  with gr.Row():
634
+ chunk_size = gr.Slider(500, 4000, value=1500, step=50, label="ขนาดส่วนข้อความ (ตัวอักษร)")
635
+ overlap = gr.Slider(0, 1000, value=200, step=50, label="การทับซ้อน (ตัวอักษร)")
636
+ max_chunks = gr.Slider(1, 40, value=5, step=1, label="จำนวนส่วนสูงสุด")
637
  with gr.Row():
638
+ min_pairs = gr.Slider(1, 10, value=3, step=1, label="คู่ข้อมูลต่ำสุด/ส่วน")
639
+ max_pairs = gr.Slider(1, 12, value=6, step=1, label="คู่ข้อมูลสูงสุด/ส่วน")
640
+ custom_instruction = gr.Textbox(
641
+ label="คำสั่งกำหนดเอง (ไม่บังคับ)",
642
+ lines=3,
643
+ placeholder="แทนที่คำสั่งเริ่มต้น ต้องส่งคืน JSON array บริสุทธิ์ตามโครงสร้างงาน",
644
+ value="สร้างข้อมูลภาษาไทยคุณภาพสูงที่เข้าใจบริบททางวัฒนธรรมไทย ใช้ภาษาไทยที่เป็นธรรมชาติและเหมาะสมกับเนื้อหา"
645
+ )
646
+
647
+ # Task-specific controls
648
+ classification_labels = gr.Textbox(label="ป้ายกำกับการจำแนก (คั่นด้วยคอมมา)", visible=False)
649
+ multi_label = gr.Checkbox(label="อนุญาตหลายป้ายกำกับ", value=False, visible=False)
650
+ target_language = gr.Textbox(label="ภาษาเป้าหมาย (การแปล)", value="ไทย", visible=False)
651
+ num_options = gr.Slider(3, 6, value=4, step=1, label="ตัวเลือก MCQ", visible=False)
652
+ ner_labels = gr.Textbox(label="ป้ายกำกับ NER (คั่นด้วยคอมมา, ไม่บังคับ)", visible=False)
653
 
654
+ generate_btn = gr.Button("สร้างชุดข้อมูล (Generate Dataset)", variant="primary", interactive=(not effective_require_login))
655
 
656
  with gr.Row():
657
  status = gr.Markdown()
658
  with gr.Row():
659
+ out_json = gr.File(label="ดาวน์โหลด JSON")
660
+ out_jsonl = gr.File(label="ดาวน์โหลด JSONL")
661
+
662
+ # Toggle visibility for task-specific controls
663
+ def _switch_task(t: str):
664
+ is_cls = t == "Classification"
665
+ is_tr = t == "Translation"
666
+ is_mcq = t == "MCQ"
667
+ is_ner = t == "NER"
668
+ return (
669
+ gr.update(visible=is_cls), # classification_labels
670
+ gr.update(visible=is_cls), # multi_label
671
+ gr.update(visible=is_tr), # target_language
672
+ gr.update(visible=is_mcq), # num_options
673
+ gr.update(visible=is_ner), # ner_labels
674
+ )
675
+
676
+ task.change(_switch_task, inputs=task, outputs=[classification_labels, multi_label, target_language, num_options, ner_labels])
677
 
678
  generate_btn.click(
679
  fn=generate_dataset,
680
  inputs=[
681
  user_state,
682
  pdf_files,
683
+ task,
684
  preset_model,
685
  custom_model_id,
686
  hf_token,
 
692
  custom_instruction,
693
  min_pairs,
694
  max_pairs,
695
+ classification_labels,
696
+ multi_label,
697
+ target_language,
698
+ num_options,
699
+ ner_labels,
700
  ],
701
  outputs=[status, out_json, out_jsonl],
702
  show_progress=True,
 
711
  username = user.get("username") or user.get("name")
712
  if not username and hasattr(user, "username"):
713
  username = getattr(user, "username")
714
+ msg = f"เข้าสู่ระบบแล้วในนาม @{username}" if username else "เข้าสู่ระบบแล้ว"
715
  except Exception:
716
+ msg = "เข้าสู่ระบบแล้ว"
717
  return user, gr.update(value=msg), gr.update(interactive=True)
718
 
719
  # Enable Generate button after login and store user profile
 
721
  login_btn.login(_on_login, inputs=None, outputs=[user_state, login_info, generate_btn])
722
  else:
723
  # In local/dev without OAuth routing, clicking will mock-login
724
+ login_btn.click(lambda: ("local_user", gr.update(value="เข้าสู่ระบบแล้ว (ภายในเครื่อง)"), gr.update(interactive=True)), inputs=None, outputs=[user_state, login_info, generate_btn])
725
 
726
  if __name__ == "__main__":
727
  # For local runs
requirements.txt CHANGED
@@ -3,3 +3,11 @@ pypdf>=4.2.0
3
  huggingface_hub>=0.23.0
4
  langchain>=0.2.0
5
  langchain-community>=0.2.0
 
 
 
 
 
 
 
 
 
3
  huggingface_hub>=0.23.0
4
  langchain>=0.2.0
5
  langchain-community>=0.2.0
6
+ # Thai language processing
7
+ pythainlp>=5.0.0
8
+ thai-word-segmentation>=0.1.0
9
+ # Additional ML libraries for better text processing
10
+ transformers>=4.30.0
11
+ torch>=2.0.0
12
+ # For better JSON parsing
13
+ ujson>=5.8.0