wuhp commited on
Commit
a2c6146
·
verified ·
1 Parent(s): 5d33ebb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -166
app.py CHANGED
@@ -1,9 +1,8 @@
1
  import asyncio
2
  import json
3
  import os
4
- import re
5
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
6
- from fastapi.responses import HTMLResponse, FileResponse
7
  from playwright.async_api import async_playwright
8
 
9
  app = FastAPI()
@@ -11,211 +10,177 @@ app = FastAPI()
11
  PORT = int(os.environ.get("PORT", 7860))
12
 
13
  BASE = os.getcwd()
14
- DOWNLOADS = os.path.join(BASE, "downloads")
15
  SCRIPTS = os.path.join(BASE, "scripts")
 
 
16
 
17
- os.makedirs(DOWNLOADS, exist_ok=True)
18
  os.makedirs(SCRIPTS, exist_ok=True)
 
 
19
 
 
 
 
 
20
  browser = None
21
  context = None
22
- page = None
23
- pw = None
24
 
25
  # =========================
26
  # SMART NAVIGATION FIX
27
  # =========================
28
- def normalize_input(text: str) -> str:
29
- text = text.strip()
30
 
31
- if not text:
32
  return "https://example.com"
33
 
34
- # already URL
35
- if text.startswith("http://") or text.startswith("https://"):
36
- return text
 
 
 
 
 
 
 
 
 
 
37
 
38
- # looks like domain
39
- if "." in text and " " not in text:
40
- return "https://" + text
 
 
 
 
 
 
 
 
41
 
42
- # fallback search
43
- q = text.replace(" ", "+")
44
- return f"https://www.google.com/search?q={q}"
45
 
 
 
 
46
 
47
  # =========================
48
- # FRONTEND
49
  # =========================
50
  HTML = """
51
  <!DOCTYPE html>
52
  <html>
53
  <head>
54
  <meta charset="utf-8">
55
- <title>HF Browser</title>
56
  <style>
57
- body { margin:0; background:#0e0e0e; color:white; font-family:sans-serif; overflow:hidden; }
58
- #bar { display:flex; padding:8px; background:#1a1a1a; }
59
- input { flex:1; background:#222; color:white; border:none; padding:6px; }
60
- button { margin-left:5px; background:#333; color:white; border:none; padding:6px; }
61
-
62
- #canvas { width:100vw; height:calc(100vh - 40px); display:block; }
63
-
64
- #menu, #editor {
65
- position:absolute;
66
- background:#111;
67
- border:1px solid #444;
68
- display:none;
69
- }
70
 
71
- #menu { top:45px; right:10px; padding:10px; }
72
- #editor { top:60px; left:60px; width:600px; height:400px; }
73
 
74
- textarea {
75
- width:100%; height:80%;
76
- background:black; color:#0f0;
77
- border:none;
78
- font-family:monospace;
 
 
 
79
  }
 
 
80
  </style>
81
  </head>
82
  <body>
83
 
84
- <div id="bar">
85
- <button onclick="nav('back')">←</button>
86
- <button onclick="nav('forward')">→</button>
87
- <button onclick="nav('reload')">⟳</button>
88
- <input id="url" placeholder="Search or URL"/>
89
  <button onclick="go()">Go</button>
90
- <button onclick="menu()">⋮</button>
91
  </div>
92
 
93
- <canvas id="canvas"></canvas>
94
 
95
- <div id="menu">
96
- <button onclick="openEditor()">Editor</button><br>
97
- <button onclick="runScript()">Run</button><br>
 
98
  </div>
99
 
100
- <div id="editor">
101
- <textarea id="code">// JS scraping script
102
- let links = await queryAll("a");
103
- await saveJson("links.json", links.map(l=>l.href));
104
- </textarea>
105
- <button onclick="save()">Save</button>
106
- <button onclick="closeEditor()">Close</button>
107
- </div>
108
 
109
  <script>
110
- let ws = new WebSocket(`ws://${location.host}/ws`);
111
- let c = document.getElementById("canvas");
112
- let x = c.getContext("2d");
113
 
114
- function resize(){
115
- c.width = innerWidth;
116
- c.height = innerHeight - 40;
117
  }
118
- onresize = resize;
119
- resize();
120
-
121
- ws.onmessage = (e)=>{
122
- let b = new Blob([e.data], {type:'image/jpeg'});
123
- let i = new Image();
124
- i.onload = ()=>x.drawImage(i,0,0,c.width,c.height);
125
- i.src = URL.createObjectURL(b);
126
- };
127
 
128
- function send(d){ ws.send(JSON.stringify(d)); }
129
-
130
- function go(){
131
- send({type:"goto", url:document.getElementById("url").value});
 
132
  }
133
 
134
- function nav(t){ send({type:t}); }
 
 
 
 
 
135
 
136
- function menu(){
137
- let m=document.getElementById("menu");
138
- m.style.display = m.style.display==="block"?"none":"block";
139
  }
140
 
141
- function openEditor(){ document.getElementById("editor").style.display="block"; }
142
- function closeEditor(){ document.getElementById("editor").style.display="none"; }
 
143
 
144
- function runScript(){
145
- send({type:"run", code:document.getElementById("code").value});
146
  }
147
 
148
- function save(){
149
- send({
150
- type:"save",
151
- name:prompt("name"),
152
- code:document.getElementById("code").value
153
- });
154
  }
155
-
156
- canvas.onclick = e=>{
157
- send({type:"click", x:e.offsetX, y:e.offsetY});
158
- };
159
-
160
- document.onkeydown = e=>{
161
- send({type:"key", key:e.key});
162
- };
163
  </script>
164
 
165
  </body>
166
  </html>
167
  """
168
 
169
-
170
- # =========================
171
- # PLAYWRIGHT START
172
- # =========================
173
- async def start():
174
- global browser, context, page, pw
175
-
176
- pw = await async_playwright().start()
177
-
178
- browser = await pw.chromium.launch(
179
- headless=True,
180
- args=["--no-sandbox", "--disable-dev-shm-usage"]
181
- )
182
-
183
- context = await browser.new_context(viewport={"width":1280,"height":720})
184
- page = await context.new_page()
185
- await page.goto("https://example.com")
186
-
187
-
188
- @app.on_event("startup")
189
- async def startup():
190
- await start()
191
-
192
-
193
- # =========================
194
- # ROUTES
195
- # =========================
196
- @app.get("/")
197
- async def home():
198
- return HTMLResponse(HTML)
199
-
200
-
201
- @app.get("/download/{file}")
202
- async def dl(file: str):
203
- return FileResponse(os.path.join(DOWNLOADS, file))
204
-
205
-
206
  # =========================
207
- # WEBSOCKET
208
  # =========================
209
  @app.websocket("/ws")
210
  async def ws(ws: WebSocket):
211
  await ws.accept()
212
 
 
 
213
  async def stream():
214
  while True:
215
  try:
216
- img = await page.screenshot(type="jpeg", quality=60)
217
  await ws.send_bytes(img)
218
- await asyncio.sleep(0.04)
219
  except:
220
  break
221
 
@@ -223,50 +188,53 @@ async def ws(ws: WebSocket):
223
 
224
  try:
225
  while True:
226
- msg = json.loads(await ws.receive_text())
227
- t = msg["type"]
228
 
229
- if t == "goto":
230
- url = normalize_input(msg["url"])
 
 
231
  await page.goto(url)
232
 
233
- elif t == "back":
234
  await page.go_back()
235
 
236
- elif t == "forward":
237
  await page.go_forward()
238
 
239
- elif t == "reload":
240
  await page.reload()
241
 
242
- elif t == "click":
243
- await page.mouse.click(msg["x"], msg["y"])
244
-
245
- elif t == "key":
246
- await page.keyboard.press(msg["key"])
247
 
248
- elif t == "save":
249
- path = os.path.join(SCRIPTS, msg["name"] + ".js")
250
- open(path, "w").write(msg["code"])
251
 
252
- elif t == "run":
253
- code = msg["code"]
 
 
254
 
255
- await page.evaluate(f"""
256
- async () => {{
257
- const queryAll = (s) => [...document.querySelectorAll(s)];
258
- const saveJson = async (n,o)=>fetch('/save',{{method:'POST'}});
259
- {code}
260
- }}
261
  """)
262
 
 
 
 
263
  except WebSocketDisconnect:
264
  pass
265
 
266
-
267
  # =========================
268
- # ENTRY (HF SAFE)
269
  # =========================
270
- if __name__ == "__main__":
 
 
 
 
271
  import uvicorn
272
- uvicorn.run(app, host="0.0.0.0", port=PORT)
 
1
  import asyncio
2
  import json
3
  import os
 
4
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
5
+ from fastapi.responses import HTMLResponse
6
  from playwright.async_api import async_playwright
7
 
8
  app = FastAPI()
 
10
  PORT = int(os.environ.get("PORT", 7860))
11
 
12
  BASE = os.getcwd()
 
13
  SCRIPTS = os.path.join(BASE, "scripts")
14
+ FILES = os.path.join(BASE, "files")
15
+ HAR_DIR = os.path.join(BASE, "har")
16
 
 
17
  os.makedirs(SCRIPTS, exist_ok=True)
18
+ os.makedirs(FILES, exist_ok=True)
19
+ os.makedirs(HAR_DIR, exist_ok=True)
20
 
21
+ # =========================
22
+ # BROWSER STATE (MULTI TAB)
23
+ # =========================
24
+ pw = None
25
  browser = None
26
  context = None
27
+ pages = []
28
+ active_tab = 0
29
 
30
  # =========================
31
  # SMART NAVIGATION FIX
32
  # =========================
33
+ def resolve_input(q: str):
34
+ q = q.strip()
35
 
36
+ if not q:
37
  return "https://example.com"
38
 
39
+ if q.startswith("http://") or q.startswith("https://"):
40
+ return q
41
+
42
+ if "." in q and " " not in q:
43
+ return "https://" + q
44
+
45
+ return "https://www.google.com/search?q=" + q.replace(" ", "+")
46
+
47
+ # =========================
48
+ # INIT BROWSER
49
+ # =========================
50
+ async def start_browser():
51
+ global pw, browser, context, pages
52
 
53
+ pw = await async_playwright().start()
54
+
55
+ browser = await pw.chromium.launch(
56
+ headless=True,
57
+ args=["--no-sandbox","--disable-dev-shm-usage"]
58
+ )
59
+
60
+ context = await browser.new_context()
61
+
62
+ page = await context.new_page()
63
+ await page.goto("https://example.com")
64
 
65
+ pages.append(page)
 
 
66
 
67
+ @app.on_event("startup")
68
+ async def startup():
69
+ await start_browser()
70
 
71
  # =========================
72
+ # UI
73
  # =========================
74
  HTML = """
75
  <!DOCTYPE html>
76
  <html>
77
  <head>
78
  <meta charset="utf-8">
79
+ <title>HF Dev Browser</title>
80
  <style>
81
+ body{margin:0;background:#0b0b0b;color:white;font-family:sans-serif;overflow:hidden}
82
+
83
+ #top{display:flex;background:#1a1a1a;padding:6px}
84
+ input{flex:1;background:#222;color:white;border:none;padding:6px}
 
 
 
 
 
 
 
 
 
85
 
86
+ button{background:#333;color:white;border:none;margin-left:5px;padding:6px}
 
87
 
88
+ #canvas{width:100vw;height:calc(100vh - 40px)}
89
+
90
+ #panel{
91
+ position:absolute;right:10px;top:50px;background:#111;padding:10px;display:none
92
+ }
93
+
94
+ #tabs{
95
+ position:absolute;bottom:0;left:0;background:#111;width:100%;display:flex
96
  }
97
+
98
+ .tab{padding:5px;background:#222;margin:2px;cursor:pointer}
99
  </style>
100
  </head>
101
  <body>
102
 
103
+ <div id="top">
104
+ <button onclick="back()">←</button>
105
+ <button onclick="fwd()">→</button>
106
+ <button onclick="reload()">⟳</button>
107
+ <input id="q">
108
  <button onclick="go()">Go</button>
109
+ <button onclick="toggle()">⋮</button>
110
  </div>
111
 
112
+ <canvas id="c"></canvas>
113
 
114
+ <div id="panel">
115
+ <button onclick="pick()">Inspect</button><br>
116
+ <button onclick="logs()">Network</button><br>
117
+ <button onclick="newTab()">New Tab</button><br>
118
  </div>
119
 
120
+ <div id="tabs"></div>
 
 
 
 
 
 
 
121
 
122
  <script>
123
+ let ws=new WebSocket(`ws://${location.host}/ws`)
124
+ let c=document.getElementById("c")
125
+ let x=c.getContext("2d")
126
 
127
+ function r(){
128
+ c.width=innerWidth
129
+ c.height=innerHeight-40
130
  }
131
+ onresize=r;r()
 
 
 
 
 
 
 
 
132
 
133
+ ws.onmessage=e=>{
134
+ let b=new Blob([e.data],{type:'image/jpeg'})
135
+ let i=new Image()
136
+ i.onload=()=>x.drawImage(i,0,0,c.width,c.height)
137
+ i.src=URL.createObjectURL(b)
138
  }
139
 
140
+ function send(d){ws.send(JSON.stringify(d))}
141
+
142
+ function go(){send({t:"goto",q:document.getElementById("q").value})}
143
+ function back(){send({t:"back"})}
144
+ function fwd(){send({t:"forward"})}
145
+ function reload(){send({t:"reload"})}
146
 
147
+ function toggle(){
148
+ let p=document.getElementById("panel")
149
+ p.style.display=p.style.display=="block"?"none":"block"
150
  }
151
 
152
+ function newTab(){send({t:"tab"})}
153
+ function pick(){send({t:"inspect"})}
154
+ function logs(){send({t:"logs"})}
155
 
156
+ c.onclick=e=>{
157
+ send({t:"click",x:e.offsetX,y:e.offsetY})
158
  }
159
 
160
+ document.onkeydown=e=>{
161
+ send({t:"key",k:e.key})
 
 
 
 
162
  }
 
 
 
 
 
 
 
 
163
  </script>
164
 
165
  </body>
166
  </html>
167
  """
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  # =========================
170
+ # WEBSOCKET ENGINE
171
  # =========================
172
  @app.websocket("/ws")
173
  async def ws(ws: WebSocket):
174
  await ws.accept()
175
 
176
+ global active_tab
177
+
178
  async def stream():
179
  while True:
180
  try:
181
+ img = await pages[active_tab].screenshot(type="jpeg", quality=55)
182
  await ws.send_bytes(img)
183
+ await asyncio.sleep(0.03)
184
  except:
185
  break
186
 
 
188
 
189
  try:
190
  while True:
191
+ msg=json.loads(await ws.receive_text())
192
+ t=msg["t"]
193
 
194
+ page=pages[active_tab]
195
+
196
+ if t=="goto":
197
+ url=resolve_input(msg["q"])
198
  await page.goto(url)
199
 
200
+ elif t=="back":
201
  await page.go_back()
202
 
203
+ elif t=="forward":
204
  await page.go_forward()
205
 
206
+ elif t=="reload":
207
  await page.reload()
208
 
209
+ elif t=="click":
210
+ await page.mouse.click(msg["x"],msg["y"])
 
 
 
211
 
212
+ elif t=="key":
213
+ await page.keyboard.press(msg["k"])
 
214
 
215
+ elif t=="tab":
216
+ p=await context.new_page()
217
+ pages.append(p)
218
+ active_tab=len(pages)-1
219
 
220
+ elif t=="inspect":
221
+ await page.evaluate("""
222
+ document.body.style.outline='2px solid red'
 
 
 
223
  """)
224
 
225
+ elif t=="logs":
226
+ await page.route("**/*", lambda route: route.continue_())
227
+
228
  except WebSocketDisconnect:
229
  pass
230
 
 
231
  # =========================
232
+ # RUN
233
  # =========================
234
+ @app.get("/")
235
+ async def home():
236
+ return HTMLResponse(HTML)
237
+
238
+ if __name__=="__main__":
239
  import uvicorn
240
+ uvicorn.run(app,host="0.0.0.0",port=PORT)