Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -102,7 +102,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 102 |
async def _chat(self, model_label: str, prompt: str) -> str:
|
| 103 |
page = await self.persistent_context.new_page()
|
| 104 |
try:
|
| 105 |
-
page.set_default_timeout(
|
| 106 |
|
| 107 |
# โโ 1. ูุชุญ ุตูุญุฉ ุฌุฏูุฏุฉ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 108 |
await page.goto(
|
|
@@ -115,6 +115,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 115 |
agree = page.locator('button:has-text("Agree and Continue")')
|
| 116 |
if await agree.count() > 0 and await agree.first.is_visible():
|
| 117 |
await agree.first.click()
|
|
|
|
| 118 |
await asyncio.sleep(2)
|
| 119 |
except Exception:
|
| 120 |
pass
|
|
@@ -125,7 +126,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 125 |
)
|
| 126 |
print("[DUCK] Input ready โ")
|
| 127 |
|
| 128 |
-
# โโ 4. ุชุบููุฑ ุงููู
ูุฐุฌ โโโโโโโโโโโโโโโโโโโโโโ
|
| 129 |
try:
|
| 130 |
model_btn = page.locator('button[data-testid="model-select-button"]')
|
| 131 |
current_text = await model_btn.inner_text()
|
|
@@ -137,7 +138,6 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 137 |
|
| 138 |
option = page.locator(
|
| 139 |
f"li:has-text('{model_label}'), "
|
| 140 |
-
f"button:has-text('{model_label}'), "
|
| 141 |
f"[role='option']:has-text('{model_label}')"
|
| 142 |
)
|
| 143 |
if await option.count() > 0:
|
|
@@ -147,80 +147,106 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 147 |
await page.keyboard.press("Escape")
|
| 148 |
print(f"[DUCK] Model not found, using default")
|
| 149 |
|
| 150 |
-
# ุงูุชุธุฑ ุฅุนุงุฏุฉ
|
| 151 |
-
await asyncio.sleep(
|
| 152 |
await page.wait_for_selector(
|
| 153 |
-
'textarea[name="user-prompt"]',
|
|
|
|
|
|
|
| 154 |
)
|
|
|
|
|
|
|
| 155 |
except Exception as e:
|
| 156 |
print(f"[DUCK] Model select (non-fatal): {e}")
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
#
|
| 165 |
-
await page.evaluate(
|
|
|
|
| 166 |
(text) => {
|
| 167 |
const ta = document.querySelector('textarea[name="user-prompt"]');
|
| 168 |
if (!ta) return;
|
| 169 |
-
// React/Vue ูุญุชุงุฌ nativeInputValueSetter
|
| 170 |
const nativeSetter = Object.getOwnPropertyDescriptor(
|
| 171 |
window.HTMLTextAreaElement.prototype, 'value'
|
| 172 |
).set;
|
| 173 |
nativeSetter.call(ta, text);
|
| 174 |
-
ta.dispatchEvent(new
|
| 175 |
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
| 176 |
}
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
#
|
|
|
|
|
|
|
|
|
|
| 181 |
send_enabled = False
|
| 182 |
for _ in range(10):
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
).get_attribute("disabled")
|
| 186 |
-
if disabled is None:
|
| 187 |
send_enabled = True
|
| 188 |
break
|
| 189 |
await asyncio.sleep(0.5)
|
| 190 |
|
| 191 |
if send_enabled:
|
| 192 |
-
await
|
| 193 |
print(f"[DUCK] Sent via button โ ({len(prompt)} chars)")
|
| 194 |
else:
|
| 195 |
-
# fallback: Enter
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
| 197 |
print(f"[DUCK] Sent via Enter โ ({len(prompt)} chars)")
|
| 198 |
|
| 199 |
-
# โโ
|
| 200 |
-
await asyncio.sleep(
|
|
|
|
| 201 |
try:
|
| 202 |
stop_btn = page.locator('button[aria-label="Stop generating"]')
|
| 203 |
-
await stop_btn.wait_for(state="visible", timeout=
|
| 204 |
print("[DUCK] Response started โ")
|
|
|
|
| 205 |
except Exception:
|
| 206 |
-
print("[DUCK] Stop btn not
|
| 207 |
|
| 208 |
-
# โโ
|
|
|
|
| 209 |
max_wait = 120
|
| 210 |
elapsed = 0
|
| 211 |
-
while elapsed < max_wait:
|
| 212 |
-
await asyncio.sleep(2)
|
| 213 |
-
elapsed += 2
|
| 214 |
-
still_running = await page.locator(
|
| 215 |
-
'button[aria-label="Stop generating"]:not([disabled])'
|
| 216 |
-
).count()
|
| 217 |
-
if still_running == 0:
|
| 218 |
-
print(f"[DUCK] Done after ~{elapsed}s โ")
|
| 219 |
-
break
|
| 220 |
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
-
# โโ
|
| 224 |
response_text = await page.evaluate("""
|
| 225 |
() => {
|
| 226 |
// ุทุฑููุฉ 1: article
|
|
@@ -243,7 +269,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 243 |
return valid[valid.length - 1].innerText.trim();
|
| 244 |
}
|
| 245 |
}
|
| 246 |
-
// ุทุฑููุฉ 3: ุฃุทูู div
|
| 247 |
const divs = [...document.querySelectorAll('div')].filter(el =>
|
| 248 |
el.children.length < 10 &&
|
| 249 |
el.innerText &&
|
|
@@ -260,7 +286,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 260 |
}
|
| 261 |
""")
|
| 262 |
|
| 263 |
-
# fallback
|
| 264 |
if not response_text or len(response_text.strip()) < 10:
|
| 265 |
await asyncio.sleep(5)
|
| 266 |
response_text = await page.evaluate("""
|
|
@@ -286,7 +312,7 @@ class AsyncBrowserThread(threading.Thread):
|
|
| 286 |
future = asyncio.run_coroutine_threadsafe(
|
| 287 |
self._chat(model_label, prompt), self.loop
|
| 288 |
)
|
| 289 |
-
return future.result(timeout=
|
| 290 |
|
| 291 |
|
| 292 |
browser_engine = AsyncBrowserThread()
|
|
@@ -360,7 +386,10 @@ def _parse_tool_calls(text: str):
|
|
| 360 |
|
| 361 |
|
| 362 |
def _auth(request: Request) -> bool:
|
| 363 |
-
return
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
|
| 366 |
def _get_model_label(model: str) -> str:
|
|
@@ -497,7 +526,11 @@ async def list_models(request: Request):
|
|
| 497 |
@app.get("/health")
|
| 498 |
@app.get("/")
|
| 499 |
async def health():
|
| 500 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
|
| 502 |
|
| 503 |
if __name__ == "__main__":
|
|
|
|
| 102 |
async def _chat(self, model_label: str, prompt: str) -> str:
|
| 103 |
page = await self.persistent_context.new_page()
|
| 104 |
try:
|
| 105 |
+
page.set_default_timeout(30000)
|
| 106 |
|
| 107 |
# โโ 1. ูุชุญ ุตูุญุฉ ุฌุฏูุฏุฉ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 108 |
await page.goto(
|
|
|
|
| 115 |
agree = page.locator('button:has-text("Agree and Continue")')
|
| 116 |
if await agree.count() > 0 and await agree.first.is_visible():
|
| 117 |
await agree.first.click()
|
| 118 |
+
print("[DUCK] Terms re-accepted โ")
|
| 119 |
await asyncio.sleep(2)
|
| 120 |
except Exception:
|
| 121 |
pass
|
|
|
|
| 126 |
)
|
| 127 |
print("[DUCK] Input ready โ")
|
| 128 |
|
| 129 |
+
# โโ 4. ุชุบููุฑ ุงููู
ูุฐุฌ ุจุทุฑููุฉ ุขู
ูุฉ โโโโโโโโโโโโโโโโโโโโโโ
|
| 130 |
try:
|
| 131 |
model_btn = page.locator('button[data-testid="model-select-button"]')
|
| 132 |
current_text = await model_btn.inner_text()
|
|
|
|
| 138 |
|
| 139 |
option = page.locator(
|
| 140 |
f"li:has-text('{model_label}'), "
|
|
|
|
| 141 |
f"[role='option']:has-text('{model_label}')"
|
| 142 |
)
|
| 143 |
if await option.count() > 0:
|
|
|
|
| 147 |
await page.keyboard.press("Escape")
|
| 148 |
print(f"[DUCK] Model not found, using default")
|
| 149 |
|
| 150 |
+
# ุงูุชุธุฑ ุฅุนุงุฏุฉ render ุจุนุฏ ุชุบููุฑ ุงููู
ูุฐุฌ
|
| 151 |
+
await asyncio.sleep(3)
|
| 152 |
await page.wait_for_selector(
|
| 153 |
+
'textarea[name="user-prompt"]',
|
| 154 |
+
state="visible",
|
| 155 |
+
timeout=15000
|
| 156 |
)
|
| 157 |
+
print("[DUCK] Textarea re-confirmed โ")
|
| 158 |
+
|
| 159 |
except Exception as e:
|
| 160 |
print(f"[DUCK] Model select (non-fatal): {e}")
|
| 161 |
+
await page.wait_for_selector(
|
| 162 |
+
'textarea[name="user-prompt"]',
|
| 163 |
+
state="visible",
|
| 164 |
+
timeout=10000
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# โโ 5. ูุชุงุจุฉ ุงูุฑุณุงูุฉ ุจู JavaScript โโโโโโโโโโโโโโโโโโโโ
|
| 168 |
+
# ูุชุฌุงูุฒ React's synthetic events ููุถู
ู ุชูุนูู ุฒุฑ Send
|
| 169 |
+
await page.evaluate(
|
| 170 |
+
"""
|
| 171 |
(text) => {
|
| 172 |
const ta = document.querySelector('textarea[name="user-prompt"]');
|
| 173 |
if (!ta) return;
|
|
|
|
| 174 |
const nativeSetter = Object.getOwnPropertyDescriptor(
|
| 175 |
window.HTMLTextAreaElement.prototype, 'value'
|
| 176 |
).set;
|
| 177 |
nativeSetter.call(ta, text);
|
| 178 |
+
ta.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
| 179 |
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
| 180 |
+
ta.focus();
|
| 181 |
}
|
| 182 |
+
""",
|
| 183 |
+
prompt
|
| 184 |
+
)
|
| 185 |
+
await asyncio.sleep(1.5)
|
| 186 |
|
| 187 |
+
# โโ 6. ุฅุฑุณุงู ุงูุฑุณุงูุฉ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 188 |
+
send_btn = page.locator('button[type="submit"][aria-label="Send"]')
|
| 189 |
+
|
| 190 |
+
# ุงูุชุธุฑ ุญุชู 5 ุซูุงู ูุฒุฑ Send
|
| 191 |
send_enabled = False
|
| 192 |
for _ in range(10):
|
| 193 |
+
is_disabled = await send_btn.get_attribute("disabled")
|
| 194 |
+
if is_disabled is None:
|
|
|
|
|
|
|
| 195 |
send_enabled = True
|
| 196 |
break
|
| 197 |
await asyncio.sleep(0.5)
|
| 198 |
|
| 199 |
if send_enabled:
|
| 200 |
+
await send_btn.click()
|
| 201 |
print(f"[DUCK] Sent via button โ ({len(prompt)} chars)")
|
| 202 |
else:
|
| 203 |
+
# fallback: Enter ุนูู ุงูู textarea
|
| 204 |
+
ta = page.locator('textarea[name="user-prompt"]')
|
| 205 |
+
await ta.click()
|
| 206 |
+
await asyncio.sleep(0.3)
|
| 207 |
+
await page.keyboard.press("Enter")
|
| 208 |
print(f"[DUCK] Sent via Enter โ ({len(prompt)} chars)")
|
| 209 |
|
| 210 |
+
# โโ 7. ุงูุชุธุงุฑ ุจุฏุก ุงูุฑุฏ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 211 |
+
await asyncio.sleep(2)
|
| 212 |
+
response_started = False
|
| 213 |
try:
|
| 214 |
stop_btn = page.locator('button[aria-label="Stop generating"]')
|
| 215 |
+
await stop_btn.wait_for(state="visible", timeout=30000)
|
| 216 |
print("[DUCK] Response started โ")
|
| 217 |
+
response_started = True
|
| 218 |
except Exception:
|
| 219 |
+
print("[DUCK] Stop btn not detected, will watch for content...")
|
| 220 |
|
| 221 |
+
# โโ 8. ุงูุชุธุงุฑ ุงูุชู
ุงู ุงูุฑุฏ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 222 |
+
page.set_default_timeout(120000)
|
| 223 |
max_wait = 120
|
| 224 |
elapsed = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
+
if response_started:
|
| 227 |
+
while elapsed < max_wait:
|
| 228 |
+
await asyncio.sleep(2)
|
| 229 |
+
elapsed += 2
|
| 230 |
+
still_running = await page.locator(
|
| 231 |
+
'button[aria-label="Stop generating"]:not([disabled])'
|
| 232 |
+
).count()
|
| 233 |
+
if still_running == 0:
|
| 234 |
+
print(f"[DUCK] Response complete (~{elapsed}s) โ")
|
| 235 |
+
break
|
| 236 |
+
else:
|
| 237 |
+
# ุงูุชุธุฑ ุธููุฑ article (ู
ุญุชูู ุงูุฑุฏ)
|
| 238 |
+
while elapsed < max_wait:
|
| 239 |
+
await asyncio.sleep(2)
|
| 240 |
+
elapsed += 2
|
| 241 |
+
arts = await page.locator('article').count()
|
| 242 |
+
if arts > 0:
|
| 243 |
+
await asyncio.sleep(5)
|
| 244 |
+
print(f"[DUCK] Content appeared (~{elapsed}s) โ")
|
| 245 |
+
break
|
| 246 |
+
|
| 247 |
+
await asyncio.sleep(2)
|
| 248 |
|
| 249 |
+
# โโ 9. ุงุณุชุฎุฑุงุฌ ุงูุฑุฏ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 250 |
response_text = await page.evaluate("""
|
| 251 |
() => {
|
| 252 |
// ุทุฑููุฉ 1: article
|
|
|
|
| 269 |
return valid[valid.length - 1].innerText.trim();
|
| 270 |
}
|
| 271 |
}
|
| 272 |
+
// ุทุฑููุฉ 3: ุฃุทูู div ูุธูู
|
| 273 |
const divs = [...document.querySelectorAll('div')].filter(el =>
|
| 274 |
el.children.length < 10 &&
|
| 275 |
el.innerText &&
|
|
|
|
| 286 |
}
|
| 287 |
""")
|
| 288 |
|
| 289 |
+
# fallback ููุงุฆู
|
| 290 |
if not response_text or len(response_text.strip()) < 10:
|
| 291 |
await asyncio.sleep(5)
|
| 292 |
response_text = await page.evaluate("""
|
|
|
|
| 312 |
future = asyncio.run_coroutine_threadsafe(
|
| 313 |
self._chat(model_label, prompt), self.loop
|
| 314 |
)
|
| 315 |
+
return future.result(timeout=200)
|
| 316 |
|
| 317 |
|
| 318 |
browser_engine = AsyncBrowserThread()
|
|
|
|
| 386 |
|
| 387 |
|
| 388 |
def _auth(request: Request) -> bool:
|
| 389 |
+
return (
|
| 390 |
+
request.headers.get("authorization", "")
|
| 391 |
+
.replace("Bearer ", "").strip() == API_SECRET_KEY
|
| 392 |
+
)
|
| 393 |
|
| 394 |
|
| 395 |
def _get_model_label(model: str) -> str:
|
|
|
|
| 526 |
@app.get("/health")
|
| 527 |
@app.get("/")
|
| 528 |
async def health():
|
| 529 |
+
return {
|
| 530 |
+
"status": "running",
|
| 531 |
+
"message": "Duck.ai API Server is active!",
|
| 532 |
+
"models": ALL_MODELS,
|
| 533 |
+
}
|
| 534 |
|
| 535 |
|
| 536 |
if __name__ == "__main__":
|