dialoghelper 0.0.30__tar.gz → 0.0.41__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dialoghelper-0.0.30/dialoghelper.egg-info → dialoghelper-0.0.41}/PKG-INFO +7 -2
- dialoghelper-0.0.41/dialoghelper/__init__.py +2 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper/_modidx.py +22 -14
- dialoghelper-0.0.41/dialoghelper/capture.py +42 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper/core.py +218 -56
- dialoghelper-0.0.41/dialoghelper/screenshot.js +26 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41/dialoghelper.egg-info}/PKG-INFO +7 -2
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper.egg-info/SOURCES.txt +1 -1
- dialoghelper-0.0.41/dialoghelper.egg-info/requires.txt +13 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/settings.ini +3 -3
- dialoghelper-0.0.30/dialoghelper/__init__.py +0 -2
- dialoghelper-0.0.30/dialoghelper/experimental.py +0 -96
- dialoghelper-0.0.30/dialoghelper/screenshot.js +0 -129
- dialoghelper-0.0.30/dialoghelper.egg-info/requires.txt +0 -8
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/LICENSE +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/MANIFEST.in +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/README.md +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper/db_dc.py +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper.egg-info/dependency_links.txt +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper.egg-info/entry_points.txt +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper.egg-info/not-zip-safe +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/dialoghelper.egg-info/top_level.txt +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/pyproject.toml +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/setup.cfg +0 -0
- {dialoghelper-0.0.30 → dialoghelper-0.0.41}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dialoghelper
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.41
|
|
4
4
|
Summary: Helper functions for solveit dialogs
|
|
5
5
|
Home-page: https://github.com/AnswerDotAI/dialoghelper
|
|
6
6
|
Author: Jeremy Howard
|
|
@@ -18,13 +18,18 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: fastcore>=1.8.
|
|
21
|
+
Requires-Dist: fastcore>=1.8.10
|
|
22
22
|
Requires-Dist: fastlite
|
|
23
23
|
Requires-Dist: ghapi
|
|
24
24
|
Requires-Dist: ipykernel-helper
|
|
25
25
|
Requires-Dist: claudette
|
|
26
|
+
Requires-Dist: ast-grep-cli
|
|
27
|
+
Requires-Dist: ast-grep-py
|
|
28
|
+
Requires-Dist: MonsterUI
|
|
29
|
+
Requires-Dist: lisette>=0.0.13
|
|
26
30
|
Provides-Extra: dev
|
|
27
31
|
Requires-Dist: python-fasthtml; extra == "dev"
|
|
32
|
+
Requires-Dist: pillow; extra == "dev"
|
|
28
33
|
Dynamic: author
|
|
29
34
|
Dynamic: author-email
|
|
30
35
|
Dynamic: classifier
|
|
@@ -5,37 +5,45 @@ d = { 'settings': { 'branch': 'main',
|
|
|
5
5
|
'doc_host': 'https://AnswerDotAI.github.io',
|
|
6
6
|
'git_url': 'https://github.com/AnswerDotAI/dialoghelper',
|
|
7
7
|
'lib_path': 'dialoghelper'},
|
|
8
|
-
'syms': { 'dialoghelper.
|
|
8
|
+
'syms': { 'dialoghelper.capture': { 'dialoghelper.capture.capture_screen': ('capture.html#capture_screen', 'dialoghelper/capture.py'),
|
|
9
|
+
'dialoghelper.capture.capture_tool': ('capture.html#capture_tool', 'dialoghelper/capture.py'),
|
|
10
|
+
'dialoghelper.capture.setup_share': ('capture.html#setup_share', 'dialoghelper/capture.py'),
|
|
11
|
+
'dialoghelper.capture.start_share': ('capture.html#start_share', 'dialoghelper/capture.py')},
|
|
12
|
+
'dialoghelper.core': { 'dialoghelper.core._add_msg_unsafe': ('core.html#_add_msg_unsafe', 'dialoghelper/core.py'),
|
|
13
|
+
'dialoghelper.core._find_frame_dict': ('core.html#_find_frame_dict', 'dialoghelper/core.py'),
|
|
9
14
|
'dialoghelper.core._umsg': ('core.html#_umsg', 'dialoghelper/core.py'),
|
|
10
15
|
'dialoghelper.core.add_html': ('core.html#add_html', 'dialoghelper/core.py'),
|
|
11
16
|
'dialoghelper.core.add_msg': ('core.html#add_msg', 'dialoghelper/core.py'),
|
|
17
|
+
'dialoghelper.core.add_scr': ('core.html#add_scr', 'dialoghelper/core.py'),
|
|
18
|
+
'dialoghelper.core.add_styles': ('core.html#add_styles', 'dialoghelper/core.py'),
|
|
19
|
+
'dialoghelper.core.ast_grep': ('core.html#ast_grep', 'dialoghelper/core.py'),
|
|
20
|
+
'dialoghelper.core.ast_py': ('core.html#ast_py', 'dialoghelper/core.py'),
|
|
12
21
|
'dialoghelper.core.call_endp': ('core.html#call_endp', 'dialoghelper/core.py'),
|
|
13
22
|
'dialoghelper.core.curr_dialog': ('core.html#curr_dialog', 'dialoghelper/core.py'),
|
|
14
23
|
'dialoghelper.core.del_msg': ('core.html#del_msg', 'dialoghelper/core.py'),
|
|
24
|
+
'dialoghelper.core.fc_tool_info': ('core.html#fc_tool_info', 'dialoghelper/core.py'),
|
|
15
25
|
'dialoghelper.core.find_dname': ('core.html#find_dname', 'dialoghelper/core.py'),
|
|
16
26
|
'dialoghelper.core.find_msg_id': ('core.html#find_msg_id', 'dialoghelper/core.py'),
|
|
17
27
|
'dialoghelper.core.find_msgs': ('core.html#find_msgs', 'dialoghelper/core.py'),
|
|
18
28
|
'dialoghelper.core.find_var': ('core.html#find_var', 'dialoghelper/core.py'),
|
|
29
|
+
'dialoghelper.core.fire_event': ('core.html#fire_event', 'dialoghelper/core.py'),
|
|
19
30
|
'dialoghelper.core.gist_file': ('core.html#gist_file', 'dialoghelper/core.py'),
|
|
31
|
+
'dialoghelper.core.iife': ('core.html#iife', 'dialoghelper/core.py'),
|
|
20
32
|
'dialoghelper.core.import_gist': ('core.html#import_gist', 'dialoghelper/core.py'),
|
|
21
33
|
'dialoghelper.core.import_string': ('core.html#import_string', 'dialoghelper/core.py'),
|
|
22
34
|
'dialoghelper.core.is_usable_tool': ('core.html#is_usable_tool', 'dialoghelper/core.py'),
|
|
23
35
|
'dialoghelper.core.load_gist': ('core.html#load_gist', 'dialoghelper/core.py'),
|
|
24
36
|
'dialoghelper.core.mk_toollist': ('core.html#mk_toollist', 'dialoghelper/core.py'),
|
|
25
37
|
'dialoghelper.core.msg_idx': ('core.html#msg_idx', 'dialoghelper/core.py'),
|
|
38
|
+
'dialoghelper.core.msg_insert_line': ('core.html#msg_insert_line', 'dialoghelper/core.py'),
|
|
39
|
+
'dialoghelper.core.msg_replace_lines': ('core.html#msg_replace_lines', 'dialoghelper/core.py'),
|
|
40
|
+
'dialoghelper.core.msg_str_replace': ('core.html#msg_str_replace', 'dialoghelper/core.py'),
|
|
41
|
+
'dialoghelper.core.msg_strs_replace': ('core.html#msg_strs_replace', 'dialoghelper/core.py'),
|
|
42
|
+
'dialoghelper.core.pop_data': ('core.html#pop_data', 'dialoghelper/core.py'),
|
|
26
43
|
'dialoghelper.core.read_msg': ('core.html#read_msg', 'dialoghelper/core.py'),
|
|
27
44
|
'dialoghelper.core.run_msg': ('core.html#run_msg', 'dialoghelper/core.py'),
|
|
45
|
+
'dialoghelper.core.set_var': ('core.html#set_var', 'dialoghelper/core.py'),
|
|
28
46
|
'dialoghelper.core.tool_info': ('core.html#tool_info', 'dialoghelper/core.py'),
|
|
29
|
-
'dialoghelper.core.update_msg': ('core.html#update_msg', 'dialoghelper/core.py')
|
|
30
|
-
|
|
31
|
-
'dialoghelper.
|
|
32
|
-
'dialoghelper/experimental.py'),
|
|
33
|
-
'dialoghelper.experimental.capture_screen': ( 'experimental.html#capture_screen',
|
|
34
|
-
'dialoghelper/experimental.py'),
|
|
35
|
-
'dialoghelper.experimental.iife': ('experimental.html#iife', 'dialoghelper/experimental.py'),
|
|
36
|
-
'dialoghelper.experimental.load_screenshot_js': ( 'experimental.html#load_screenshot_js',
|
|
37
|
-
'dialoghelper/experimental.py'),
|
|
38
|
-
'dialoghelper.experimental.start_screen_share': ( 'experimental.html#start_screen_share',
|
|
39
|
-
'dialoghelper/experimental.py'),
|
|
40
|
-
'dialoghelper.experimental.stop_screen_share': ( 'experimental.html#stop_screen_share',
|
|
41
|
-
'dialoghelper/experimental.py')}}}
|
|
47
|
+
'dialoghelper.core.update_msg': ('core.html#update_msg', 'dialoghelper/core.py'),
|
|
48
|
+
'dialoghelper.core.url2note': ('core.html#url2note', 'dialoghelper/core.py')},
|
|
49
|
+
'dialoghelper.db_dc': {}}}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Screen Capture."""
|
|
2
|
+
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_capture.ipynb.
|
|
4
|
+
|
|
5
|
+
# %% auto 0
|
|
6
|
+
__all__ = ['setup_share', 'start_share', 'capture_screen', 'capture_tool']
|
|
7
|
+
|
|
8
|
+
# %% ../nbs/01_capture.ipynb
|
|
9
|
+
from .core import *
|
|
10
|
+
from fastcore.all import *
|
|
11
|
+
from fasthtml.common import Div,Script
|
|
12
|
+
from httpx import post as xpost
|
|
13
|
+
from importlib import resources
|
|
14
|
+
from lisette.core import *
|
|
15
|
+
|
|
16
|
+
import base64,json,time,uuid
|
|
17
|
+
|
|
18
|
+
# %% ../nbs/01_capture.ipynb
|
|
19
|
+
def setup_share():
|
|
20
|
+
"Setup screen sharing"
|
|
21
|
+
iife((resources.files('dialoghelper')/'screenshot.js').read_text())
|
|
22
|
+
add_scr(Div(id='share-screen', style='display:none;'))
|
|
23
|
+
add_scr(Div(id='capture-screen', style='display:none;'))
|
|
24
|
+
|
|
25
|
+
# %% ../nbs/01_capture.ipynb
|
|
26
|
+
def start_share(): fire_event('shareScreen', id='share-screen')
|
|
27
|
+
|
|
28
|
+
# %% ../nbs/01_capture.ipynb
|
|
29
|
+
def capture_screen(timeout=15):
|
|
30
|
+
"Capture the screen. Re-call this function to get the most recent screenshot, as needed. Use default timeout where possible"
|
|
31
|
+
idx = uuid.uuid4()
|
|
32
|
+
fire_event('captureScreen', 'share-screen', data={'idx': str(idx)})
|
|
33
|
+
d = pop_data(idx, timeout)
|
|
34
|
+
if 'img_data' in d: return d.img_data
|
|
35
|
+
else: raise Exception(f'Capture failed: {d.error}')
|
|
36
|
+
|
|
37
|
+
# %% ../nbs/01_capture.ipynb
|
|
38
|
+
def capture_tool(timeout:int=15):
|
|
39
|
+
"Capture the screen. Re-call this function to get the most recent screenshot, as needed. Use default timeout where possible"
|
|
40
|
+
try: d = capture_screen(timeout)
|
|
41
|
+
except Exception as e: return f'Capture failed: {e}'
|
|
42
|
+
return ToolResponse([{'type': 'image_url', 'image_url': d}])
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
2
2
|
|
|
3
3
|
# %% auto 0
|
|
4
|
-
__all__ = ['
|
|
5
|
-
'
|
|
6
|
-
'
|
|
4
|
+
__all__ = ['md_cls_d', 'dh_settings', 'Placements', 'empty', 'add_styles', 'find_var', 'set_var', 'call_endp', 'find_dname',
|
|
5
|
+
'find_msg_id', 'curr_dialog', 'msg_idx', 'add_scr', 'iife', 'pop_data', 'fire_event', 'find_msgs',
|
|
6
|
+
'add_html', 'read_msg', 'add_msg', 'del_msg', 'update_msg', 'run_msg', 'url2note', 'ast_py', 'ast_grep',
|
|
7
|
+
'msg_insert_line', 'msg_str_replace', 'msg_strs_replace', 'msg_replace_lines', 'load_gist', 'gist_file',
|
|
8
|
+
'import_string', 'is_usable_tool', 'mk_toollist', 'import_gist', 'tool_info', 'fc_tool_info']
|
|
7
9
|
|
|
8
10
|
# %% ../nbs/00_core.ipynb
|
|
9
|
-
import json,
|
|
11
|
+
import json,importlib,linecache,re,inspect
|
|
10
12
|
from typing import Dict
|
|
11
13
|
from tempfile import TemporaryDirectory
|
|
12
14
|
from ipykernel_helper import *
|
|
@@ -20,27 +22,53 @@ from fastlite import *
|
|
|
20
22
|
from fastcore.xtras import asdict
|
|
21
23
|
from inspect import currentframe,Parameter,signature
|
|
22
24
|
from httpx import get as xget, post as xpost
|
|
23
|
-
from .core import __all__ as _all
|
|
24
25
|
from IPython.display import display,Markdown
|
|
26
|
+
from monsterui.all import franken_class_map,apply_classes
|
|
27
|
+
from fasthtml.common import Safe,Script,Div
|
|
25
28
|
|
|
26
29
|
# %% ../nbs/00_core.ipynb
|
|
27
|
-
|
|
30
|
+
md_cls_d = {
|
|
31
|
+
**{f'h{i}': f'uk-h{i}' for i in range(1,5)},
|
|
32
|
+
'a': "uk-link",
|
|
33
|
+
'blockquote': "uk-blockquote",
|
|
34
|
+
'hr':'uk-divider-icon',
|
|
35
|
+
'table':'uk-table uk-table-sm uk-table-middle uk-table-divider border [&_tr]:divide-x w-[80%] mx-auto',
|
|
36
|
+
'ol': 'uk-list uk-list-decimal space-y-0',
|
|
37
|
+
'ul': 'uk-list uk-list-bullet space-y-0',
|
|
38
|
+
'p': 'leading-tight',
|
|
39
|
+
'li': 'leading-tight',
|
|
40
|
+
'pre': '', 'pre code': '',
|
|
41
|
+
'code': 'tracking-tight'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def add_styles(s:str, cls_map:dict=None):
|
|
45
|
+
"Add solveit styles to `s`"
|
|
46
|
+
return Safe(apply_classes(s, class_map=cls_map or md_cls_d))
|
|
28
47
|
|
|
29
48
|
# %% ../nbs/00_core.ipynb
|
|
30
|
-
def
|
|
31
|
-
"
|
|
32
|
-
frame = currentframe()
|
|
49
|
+
def _find_frame_dict(var:str):
|
|
50
|
+
"Find the dict (globals or locals) containing var"
|
|
51
|
+
frame = currentframe().f_back.f_back
|
|
33
52
|
while frame:
|
|
34
|
-
|
|
35
|
-
if dv: return dv
|
|
53
|
+
if var in frame.f_globals: return frame.f_globals
|
|
36
54
|
frame = frame.f_back
|
|
37
55
|
raise ValueError(f"Could not find {var} in any scope")
|
|
38
56
|
|
|
57
|
+
def find_var(var:str):
|
|
58
|
+
"Search for var in all frames of the call stack"
|
|
59
|
+
return _find_frame_dict(var)[var]
|
|
60
|
+
|
|
61
|
+
def set_var(var:str, val):
|
|
62
|
+
"Set var to val after finding it in all frames of the call stack"
|
|
63
|
+
_find_frame_dict(var)[var] = val
|
|
64
|
+
|
|
39
65
|
# %% ../nbs/00_core.ipynb
|
|
66
|
+
dh_settings = {'port':5001}
|
|
67
|
+
|
|
40
68
|
def call_endp(path, dname='', json=False, raiseex=False, **data):
|
|
41
69
|
if not dname: dname = find_dname()
|
|
42
70
|
data['dlg_name'] = dname
|
|
43
|
-
res = xpost(f'http://localhost:
|
|
71
|
+
res = xpost(f'http://localhost:{dh_settings["port"]}/{path}', data=data)
|
|
44
72
|
if raiseex: res.raise_for_status()
|
|
45
73
|
try: return res.json() if json else res.text
|
|
46
74
|
except Exception as e: return str(e)
|
|
@@ -52,7 +80,7 @@ def find_dname():
|
|
|
52
80
|
|
|
53
81
|
# %% ../nbs/00_core.ipynb
|
|
54
82
|
def find_msg_id():
|
|
55
|
-
"Get the message id by searching the call stack for
|
|
83
|
+
"Get the message id by searching the call stack for __msg_id."
|
|
56
84
|
return find_var('__msg_id')
|
|
57
85
|
|
|
58
86
|
# %% ../nbs/00_core.ipynb
|
|
@@ -64,6 +92,41 @@ def curr_dialog(
|
|
|
64
92
|
res = call_endp('curr_dialog_', dname, json=True, with_messages=with_messages)
|
|
65
93
|
if res: return {'name': res['name'], 'mode': res['mode']}
|
|
66
94
|
|
|
95
|
+
# %% ../nbs/00_core.ipynb
|
|
96
|
+
def msg_idx(
|
|
97
|
+
msgid=None, # Message id to find (defaults to current message)
|
|
98
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
99
|
+
):
|
|
100
|
+
"Get absolute index of message in dialog."
|
|
101
|
+
if not msgid: msgid = find_msg_id()
|
|
102
|
+
return call_endp('msg_idx_', dname, json=True, msgid=msgid)['msgid']
|
|
103
|
+
|
|
104
|
+
# %% ../nbs/00_core.ipynb
|
|
105
|
+
def add_scr(scr, oob='beforeend:#js-script'):
|
|
106
|
+
"Swap a script element to the end of the js-script element"
|
|
107
|
+
if isinstance(scr,str): scr = Script(scr)
|
|
108
|
+
add_html(Div(scr, hx_swap_oob=oob))
|
|
109
|
+
|
|
110
|
+
# %% ../nbs/00_core.ipynb
|
|
111
|
+
def iife(code: str) -> str:
|
|
112
|
+
"Wrap javascript code string in an IIFE and execute it via `add_html`"
|
|
113
|
+
add_scr(f'''
|
|
114
|
+
(async () => {{
|
|
115
|
+
{code}
|
|
116
|
+
}})();
|
|
117
|
+
''')
|
|
118
|
+
|
|
119
|
+
# %% ../nbs/00_core.ipynb
|
|
120
|
+
def pop_data(idx, timeout=15):
|
|
121
|
+
url = 'http://localhost:5001/pop_data_blocking_'
|
|
122
|
+
return dict2obj(xpost(url, data={'data_id': idx, 'timeout': timeout}).json())
|
|
123
|
+
|
|
124
|
+
# %% ../nbs/00_core.ipynb
|
|
125
|
+
def fire_event(evt:str, id, data=None):
|
|
126
|
+
params = f"'{evt}'"
|
|
127
|
+
if data is not None: params += f", {json.dumps(data)}"
|
|
128
|
+
add_html(Script(f"htmx.trigger(document.body, {params});", id=id, hx_swap_oob='true'))
|
|
129
|
+
|
|
67
130
|
# %% ../nbs/00_core.ipynb
|
|
68
131
|
def find_msgs(
|
|
69
132
|
re_pattern:str='', # Optional regex to search for (re.DOTALL+re.MULTILINE is used)
|
|
@@ -78,26 +141,6 @@ def find_msgs(
|
|
|
78
141
|
for o in res: o.pop('output', None)
|
|
79
142
|
return res
|
|
80
143
|
|
|
81
|
-
# %% ../nbs/00_core.ipynb
|
|
82
|
-
def msg_idx(
|
|
83
|
-
msgid=None, # Message id to find (defaults to current message)
|
|
84
|
-
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
85
|
-
):
|
|
86
|
-
"Get absolute index of message in dialog."
|
|
87
|
-
if not msgid: msgid = find_msg_id()
|
|
88
|
-
return call_endp('msg_idx_', dname, json=True, msgid=msgid)['msgid']
|
|
89
|
-
|
|
90
|
-
# %% ../nbs/00_core.ipynb
|
|
91
|
-
def read_msg(
|
|
92
|
-
n:int=-1, # Message index (if relative, +ve is downwards)
|
|
93
|
-
msgid=None, # Message id to find (defaults to current message)
|
|
94
|
-
relative:bool=True, # Is `n` relative to current message (True) or absolute (False)?
|
|
95
|
-
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
96
|
-
):
|
|
97
|
-
"Get the `Message` object indexed in the current dialog."
|
|
98
|
-
if not msgid: msgid = find_msg_id()
|
|
99
|
-
return call_endp('read_msg_', dname, json=True, msgid=msgid, n=n, relative=relative)['msg']
|
|
100
|
-
|
|
101
144
|
# %% ../nbs/00_core.ipynb
|
|
102
145
|
def add_html(
|
|
103
146
|
content:str, # The HTML to send to the client (generally should include hx-swap-oob)
|
|
@@ -107,23 +150,26 @@ def add_html(
|
|
|
107
150
|
call_endp('add_html_', dname, content=to_xml(content))
|
|
108
151
|
|
|
109
152
|
# %% ../nbs/00_core.ipynb
|
|
110
|
-
|
|
111
|
-
msgid:str=None, # id of message to delete
|
|
112
|
-
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
113
|
-
):
|
|
114
|
-
"Delete a message from the dialog."
|
|
115
|
-
call_endp('rm_msg_', dname, raiseex=True, msid=msgid)
|
|
153
|
+
Placements = str_enum('Placements', 'add_after', 'add_before', 'at_start', 'at_end')
|
|
116
154
|
|
|
117
155
|
# %% ../nbs/00_core.ipynb
|
|
118
|
-
def
|
|
119
|
-
|
|
156
|
+
def read_msg(
|
|
157
|
+
n:int=-1, # Message index (if relative, +ve is downwards)
|
|
158
|
+
relative:bool=True, # Is `n` relative to current message (True) or absolute (False)?
|
|
159
|
+
msgid:str=None, # Message id to find (defaults to current message)
|
|
160
|
+
view_range:list[int,int]=None, # Optional 1-indexed (start, end) line range for files, end=-1 for EOF
|
|
161
|
+
nums:bool=False, # Whether to show line numbers
|
|
120
162
|
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
121
|
-
):
|
|
122
|
-
"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
163
|
+
):
|
|
164
|
+
"""Get the message indexed in the current dialog.
|
|
165
|
+
- To get the exact message use `n=0` and `relative=True` together with `msgid`.
|
|
166
|
+
- To get a relative message use `n` (relative position index).
|
|
167
|
+
- To get the nth message use `n` with `relative=False`, e.g `n=0` first message, `n=-1` last message."""
|
|
168
|
+
if relative and not msgid: msgid = find_msg_id()
|
|
169
|
+
data = dict(n=n, relative=relative, msgid=msgid)
|
|
170
|
+
if view_range: data['view_range'] = view_range # None gets converted to '' so we avoid passing it to use the p.default
|
|
171
|
+
if nums: data['nums'] = nums
|
|
172
|
+
return call_endp('read_msg_', dname, json=True, **data)
|
|
127
173
|
|
|
128
174
|
# %% ../nbs/00_core.ipynb
|
|
129
175
|
def add_msg(
|
|
@@ -131,7 +177,7 @@ def add_msg(
|
|
|
131
177
|
placement:str='add_after', # Can be 'add_after', 'add_before', 'at_start', 'at_end'
|
|
132
178
|
msgid:str=None, # id of message that placement is relative to (if None, uses current message)
|
|
133
179
|
msg_type: str='note', # Message type, can be 'code', 'note', or 'prompt'
|
|
134
|
-
output:str='', #
|
|
180
|
+
output:str='', # Prompt/code output; Code outputs must be .ipynb-compatible JSON array
|
|
135
181
|
time_run: str | None = '', # When was message executed
|
|
136
182
|
is_exported: int | None = 0, # Export message to a module?
|
|
137
183
|
skipped: int | None = 0, # Hide message from prompt?
|
|
@@ -143,10 +189,20 @@ def add_msg(
|
|
|
143
189
|
):
|
|
144
190
|
"Add/update a message to the queue to show after code execution completes."
|
|
145
191
|
if placement not in ('at_start','at_end') and not msgid: msgid = find_msg_id()
|
|
146
|
-
|
|
192
|
+
res = call_endp(
|
|
147
193
|
'add_relative_', dname, content=content, placement=placement, msgid=msgid, msg_type=msg_type, output=output,
|
|
148
194
|
time_run=time_run, is_exported=is_exported, skipped=skipped, pinned=pinned,
|
|
149
195
|
i_collapsed=i_collapsed, o_collapsed=o_collapsed, heading_collapsed=heading_collapsed)
|
|
196
|
+
set_var('__msg_id', res)
|
|
197
|
+
return res
|
|
198
|
+
|
|
199
|
+
# %% ../nbs/00_core.ipynb
|
|
200
|
+
def del_msg(
|
|
201
|
+
msgid:str=None, # id of message to delete
|
|
202
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
203
|
+
):
|
|
204
|
+
"Delete a message from the dialog."
|
|
205
|
+
call_endp('rm_msg_', dname, raiseex=True, msid=msgid)
|
|
150
206
|
|
|
151
207
|
# %% ../nbs/00_core.ipynb
|
|
152
208
|
@delegates(add_msg)
|
|
@@ -155,20 +211,22 @@ def _add_msg_unsafe(
|
|
|
155
211
|
placement:str='add_after', # Can be 'add_after', 'add_before', 'at_start', 'at_end'
|
|
156
212
|
msgid:str=None, # id of message that placement is relative to (if None, uses current message)
|
|
157
213
|
run:bool=False, # For prompts, send it to the AI; for code, execute it (*DANGEROUS -- be careful of what you run!)
|
|
214
|
+
dname:str='', # Running dialog to get info for; defaults to current dialog
|
|
158
215
|
**kwargs
|
|
159
216
|
):
|
|
160
217
|
"""Add/update a message to the queue to show after code execution completes, and optionally run it. Be sure to pass a `sid` (stable id) not a `mid` (which is used only for sorting, and can change).
|
|
161
218
|
*WARNING*--This can execute arbitrary code, so check carefully what you run!--*WARNING"""
|
|
162
219
|
if placement not in ('at_start','at_end') and not msgid: msgid = find_msg_id()
|
|
163
|
-
|
|
164
|
-
return call_endp(
|
|
220
|
+
res = call_endp(
|
|
165
221
|
'add_relative_', dname, content=content, placement=placement, msgid=msgid, run=run, **kwargs)
|
|
222
|
+
set_var('__msg_id', res)
|
|
223
|
+
return res
|
|
166
224
|
|
|
167
225
|
# %% ../nbs/00_core.ipynb
|
|
168
226
|
def _umsg(
|
|
169
227
|
content:str|None=None, # Content of the message (i.e the message prompt, code, or note text)
|
|
170
228
|
msg_type: str|None = None, # Message type, can be 'code', 'note', or 'prompt'
|
|
171
|
-
output:str|None = None, #
|
|
229
|
+
output:str|None = None, # Prompt/code output; Code outputs must be .ipynb-compatible JSON array
|
|
172
230
|
time_run: str | None = None, # When was message executed
|
|
173
231
|
is_exported: int | None = None, # Export message to a module?
|
|
174
232
|
skipped: int | None = None, # Hide message from prompt?
|
|
@@ -186,10 +244,90 @@ def update_msg(
|
|
|
186
244
|
dname:str='', # Running dialog to get info for; defaults to current dialog
|
|
187
245
|
**kwargs):
|
|
188
246
|
"""Update an existing message. Provide either `msg` OR field key/values to update.
|
|
189
|
-
Use `content` param to update contents.
|
|
190
|
-
Only include parameters to update--missing ones will be left unchanged."""
|
|
247
|
+
- Use `content` param to update contents.
|
|
248
|
+
- Only include parameters to update--missing ones will be left unchanged."""
|
|
191
249
|
if not msgid and not msg: raise TypeError("update_msg needs either a dict message or `msgid=`")
|
|
192
|
-
|
|
250
|
+
res = call_endp('update_msg_', dname, msgid=msgid, **kwargs)
|
|
251
|
+
set_var('__msg_id', res)
|
|
252
|
+
return res
|
|
253
|
+
|
|
254
|
+
# %% ../nbs/00_core.ipynb
|
|
255
|
+
def run_msg(
|
|
256
|
+
msgid:str=None, # id of message to execute
|
|
257
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
258
|
+
):
|
|
259
|
+
"Adds a message to the run queue. Use read_msg to see the output once it runs."
|
|
260
|
+
return call_endp('add_runq_', dname, msgid=msgid, api=True)
|
|
261
|
+
|
|
262
|
+
# %% ../nbs/00_core.ipynb
|
|
263
|
+
def url2note(
|
|
264
|
+
url:str, # URL to read
|
|
265
|
+
extract_section:bool=True, # If url has an anchor, return only that section
|
|
266
|
+
selector:str=None # Select section(s) using BeautifulSoup.select (overrides extract_section)
|
|
267
|
+
):
|
|
268
|
+
"Read URL as markdown, and add a note below current message with the result"
|
|
269
|
+
res = read_url(url, as_md=True, extract_section=extract_section, selector=selector)
|
|
270
|
+
return add_msg(res)
|
|
271
|
+
|
|
272
|
+
# %% ../nbs/00_core.ipynb
|
|
273
|
+
def ast_py(code:str):
|
|
274
|
+
"Get an SgRoot root node for python `code`"
|
|
275
|
+
from ast_grep_py import SgRoot
|
|
276
|
+
return SgRoot(code, "python").root()
|
|
277
|
+
|
|
278
|
+
# %% ../nbs/00_core.ipynb
|
|
279
|
+
def ast_grep(
|
|
280
|
+
pattern:str, # ast-grep pattern to search
|
|
281
|
+
path=".", # path to recursively search for files
|
|
282
|
+
lang="python" # language to search/scan
|
|
283
|
+
):
|
|
284
|
+
"Use the `ast-grep` command to find `pattern` in `path`"
|
|
285
|
+
import json, subprocess
|
|
286
|
+
cmd = f"ast-grep --pattern '{pattern}' --lang {lang} --json=compact"
|
|
287
|
+
if path != ".": cmd = f"cd {path} && {cmd}"
|
|
288
|
+
res = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
289
|
+
return json.loads(res.stdout) if res.stdout else res.stderr
|
|
290
|
+
|
|
291
|
+
# %% ../nbs/00_core.ipynb
|
|
292
|
+
def msg_insert_line(
|
|
293
|
+
msgid:str, # Message id to edit
|
|
294
|
+
insert_line: int, # The line number after which to insert the text (0 for beginning of file)
|
|
295
|
+
new_str: str, # The text to insert
|
|
296
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
297
|
+
):
|
|
298
|
+
"Insert text at a specific line number in a message"
|
|
299
|
+
return call_endp('msg_insert_line_', dname, json=True, msgid=msgid, insert_line=insert_line, new_str=new_str)
|
|
300
|
+
|
|
301
|
+
# %% ../nbs/00_core.ipynb
|
|
302
|
+
def msg_str_replace(
|
|
303
|
+
msgid:str, # Message id to edit
|
|
304
|
+
old_str: str, # Text to find and replace
|
|
305
|
+
new_str: str, # Text to replace with
|
|
306
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
307
|
+
):
|
|
308
|
+
"Replace first occurrence of old_str with new_str in a message"
|
|
309
|
+
return call_endp('msg_str_replace_', dname, json=True, msgid=msgid, old_str=old_str, new_str=new_str)
|
|
310
|
+
|
|
311
|
+
# %% ../nbs/00_core.ipynb
|
|
312
|
+
def msg_strs_replace(
|
|
313
|
+
msgid:str, # Message id to edit
|
|
314
|
+
old_strs:list[str], # List of strings to find and replace
|
|
315
|
+
new_strs:list[str], # List of replacement strings (must match length of old_strs)
|
|
316
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
317
|
+
):
|
|
318
|
+
"Replace multiple strings simultaneously in a message"
|
|
319
|
+
return call_endp('msg_strs_replace_', dname, json=True, msgid=msgid, old_strs=old_strs, new_strs=new_strs)
|
|
320
|
+
|
|
321
|
+
# %% ../nbs/00_core.ipynb
|
|
322
|
+
def msg_replace_lines(
|
|
323
|
+
msgid:str, # Message id to edit
|
|
324
|
+
start_line:int, # Starting line number to replace (1-based indexing)
|
|
325
|
+
end_line:int, # Ending line number to replace (1-based indexing, inclusive)
|
|
326
|
+
new_content:str, # New content to replace the specified lines
|
|
327
|
+
dname:str='' # Running dialog to get info for; defaults to current dialog
|
|
328
|
+
):
|
|
329
|
+
"Replace a range of lines with new content in a message"
|
|
330
|
+
return call_endp('msg_replace_lines_', dname, json=True, msgid=msgid, start_line=start_line, end_line=end_line, new_content=new_content)
|
|
193
331
|
|
|
194
332
|
# %% ../nbs/00_core.ipynb
|
|
195
333
|
def load_gist(gist_id:str):
|
|
@@ -268,7 +406,31 @@ def tool_info():
|
|
|
268
406
|
- &`find_msgs`: Find messages in current specific dialog that contain the given information.
|
|
269
407
|
- (solveit can often get this id directly from its context, and will not need to use this if the required information is already available to it.)
|
|
270
408
|
- &`read_msg`: Get the message indexed in the current dialog.
|
|
409
|
+
- To get the exact message use `n=0` and `relative=True` together with `msgid`.
|
|
410
|
+
- To get a relative message use `n` (relative position index).
|
|
411
|
+
- To get the nth message use `n` with `relative=False`, e.g `n=0` first message, `n=-1` last message.
|
|
271
412
|
- &`del_msg`: Delete a message from the dialog.
|
|
272
413
|
- &`add_msg`: Add/update a message to the queue to show after code execution completes.
|
|
273
|
-
- &`update_msg`: Update an existing message.
|
|
414
|
+
- &`update_msg`: Update an existing message.
|
|
415
|
+
- &`url2note`: Read URL as markdown, and add a note below current message with the result
|
|
416
|
+
- &`msg_insert_line`: Insert text at a specific location in a message.
|
|
417
|
+
- &`msg_str_replace`: Find and replace text in a message.
|
|
418
|
+
- &`msg_strs_replace`: Find and replace multiple strings in a message.
|
|
419
|
+
- &`msg_replace_lines`: Replace a range of lines in a message with new content.
|
|
420
|
+
- Always first use `read_msg( msgid=msgid, n=0, relative=True, nums=True)` to view the content with line numbers.'''
|
|
421
|
+
add_msg(cts)
|
|
422
|
+
|
|
423
|
+
# %% ../nbs/00_core.ipynb
|
|
424
|
+
def fc_tool_info():
|
|
425
|
+
cts='''Tools available from `fastcore.tools`:
|
|
426
|
+
|
|
427
|
+
- &`rg`: Run the `rg` command with the args in `argstr` (no need to backslash escape)
|
|
428
|
+
- &`sed`: Run the `sed` command with the args in `argstr` (e.g for reading a section of a file)
|
|
429
|
+
- &`view`: View directory or file contents with optional line range and numbers
|
|
430
|
+
- &`create`: Creates a new file with the given content at the specified path
|
|
431
|
+
- &`insert`: Insert new_str at specified line number
|
|
432
|
+
- &`str_replace`: Replace first occurrence of old_str with new_str in file
|
|
433
|
+
- &`strs_replace`: Replace for each str pair in old_strs,new_strs
|
|
434
|
+
- &`replace_lines`: Replace lines in file using start and end line-numbers'''
|
|
274
435
|
add_msg(cts)
|
|
436
|
+
add_msg('from fastcore.tools import *', msg_type='code')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
window.getScreenshot = async (maxWidth = 1280, maxHeight = 1024) => {
|
|
2
|
+
if (window?.vtrack) {
|
|
3
|
+
const cap = new ImageCapture(window.vtrack);
|
|
4
|
+
const img = await cap.grabFrame();
|
|
5
|
+
// rescale and convert to b64
|
|
6
|
+
const scaleX = maxWidth / img.width;
|
|
7
|
+
const scaleY = maxHeight / img.height;
|
|
8
|
+
const scale = Math.min(scaleX, scaleY, 1); // don't upscale
|
|
9
|
+
const newWidth = Math.floor(img.width * scale);
|
|
10
|
+
const newHeight = Math.floor(img.height * scale);
|
|
11
|
+
const c = document.createElement('canvas');
|
|
12
|
+
c.width = newWidth; c.height = newHeight;
|
|
13
|
+
c.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight);
|
|
14
|
+
return c.toDataURL();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
document.body.addEventListener('shareScreen', async (e) => {
|
|
19
|
+
vstream = await navigator.mediaDevices.getDisplayMedia();
|
|
20
|
+
window.vtrack = vstream.getVideoTracks()[0];
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
document.body.addEventListener('captureScreen', async (e) => {
|
|
24
|
+
pushData(e.detail.idx, {img_data: await getScreenshot()});
|
|
25
|
+
});
|
|
26
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dialoghelper
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.41
|
|
4
4
|
Summary: Helper functions for solveit dialogs
|
|
5
5
|
Home-page: https://github.com/AnswerDotAI/dialoghelper
|
|
6
6
|
Author: Jeremy Howard
|
|
@@ -18,13 +18,18 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: fastcore>=1.8.
|
|
21
|
+
Requires-Dist: fastcore>=1.8.10
|
|
22
22
|
Requires-Dist: fastlite
|
|
23
23
|
Requires-Dist: ghapi
|
|
24
24
|
Requires-Dist: ipykernel-helper
|
|
25
25
|
Requires-Dist: claudette
|
|
26
|
+
Requires-Dist: ast-grep-cli
|
|
27
|
+
Requires-Dist: ast-grep-py
|
|
28
|
+
Requires-Dist: MonsterUI
|
|
29
|
+
Requires-Dist: lisette>=0.0.13
|
|
26
30
|
Provides-Extra: dev
|
|
27
31
|
Requires-Dist: python-fasthtml; extra == "dev"
|
|
32
|
+
Requires-Dist: pillow; extra == "dev"
|
|
28
33
|
Dynamic: author
|
|
29
34
|
Dynamic: author-email
|
|
30
35
|
Dynamic: classifier
|
|
@@ -6,9 +6,9 @@ settings.ini
|
|
|
6
6
|
setup.py
|
|
7
7
|
dialoghelper/__init__.py
|
|
8
8
|
dialoghelper/_modidx.py
|
|
9
|
+
dialoghelper/capture.py
|
|
9
10
|
dialoghelper/core.py
|
|
10
11
|
dialoghelper/db_dc.py
|
|
11
|
-
dialoghelper/experimental.py
|
|
12
12
|
dialoghelper/screenshot.js
|
|
13
13
|
dialoghelper.egg-info/PKG-INFO
|
|
14
14
|
dialoghelper.egg-info/SOURCES.txt
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = dialoghelper
|
|
3
3
|
lib_name = dialoghelper
|
|
4
|
-
version = 0.0.
|
|
4
|
+
version = 0.0.41
|
|
5
5
|
min_python = 3.9
|
|
6
6
|
license = apache2
|
|
7
7
|
black_formatting = False
|
|
8
|
-
requirements = fastcore>=1.8.
|
|
9
|
-
dev_requirements = python-fasthtml
|
|
8
|
+
requirements = fastcore>=1.8.10 fastlite ghapi ipykernel-helper claudette ast-grep-cli ast-grep-py MonsterUI lisette>=0.0.13
|
|
9
|
+
dev_requirements = python-fasthtml pillow
|
|
10
10
|
doc_path = _docs
|
|
11
11
|
lib_path = dialoghelper
|
|
12
12
|
nbs_path = nbs
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
"""Experimental `dialoghelper` capabilities."""
|
|
2
|
-
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_experimental.ipynb.
|
|
4
|
-
|
|
5
|
-
# %% auto 0
|
|
6
|
-
__all__ = ['iife', 'load_screenshot_js', 'start_screen_share', 'stop_screen_share', 'capture_screen']
|
|
7
|
-
|
|
8
|
-
# %% ../nbs/01_experimental.ipynb
|
|
9
|
-
import uuid,json
|
|
10
|
-
from importlib import resources
|
|
11
|
-
from fasthtml.common import Div,Script
|
|
12
|
-
from claudette import ToolResult
|
|
13
|
-
from .core import *
|
|
14
|
-
from httpx import post as xpost
|
|
15
|
-
|
|
16
|
-
# %% ../nbs/01_experimental.ipynb
|
|
17
|
-
def _pop_data(data_id, timeout=20):
|
|
18
|
-
params = {'data_id': data_id, 'timeout':timeout}
|
|
19
|
-
result = xpost('http://localhost:5001/pop_data_blocking_', data=params, timeout=timeout+1)
|
|
20
|
-
if result.status_code==200 and result.text.strip():
|
|
21
|
-
if (data := result.json()): return data
|
|
22
|
-
# except json.JSONDecodeError as e: print(f"JSON decode error: {e}")
|
|
23
|
-
raise RuntimeError("No data received")
|
|
24
|
-
raise RuntimeError(result.status_code)
|
|
25
|
-
|
|
26
|
-
# %% ../nbs/01_experimental.ipynb
|
|
27
|
-
def iife(code: str) -> str:
|
|
28
|
-
"Wrap javascript code string in an IIFE and execute it via `add_html`"
|
|
29
|
-
trigger_script = f'''
|
|
30
|
-
(async () => {{
|
|
31
|
-
{code}
|
|
32
|
-
}})();
|
|
33
|
-
'''
|
|
34
|
-
add_html(Div(Script(trigger_script), hx_swap_oob=f'beforeend:#js-script'))
|
|
35
|
-
|
|
36
|
-
# %% ../nbs/01_experimental.ipynb
|
|
37
|
-
_js_loaded = False
|
|
38
|
-
|
|
39
|
-
def load_screenshot_js(force=False, timeout=5):
|
|
40
|
-
"Load screenshot capability and wait for confirmation it's ready."
|
|
41
|
-
global _js_loaded
|
|
42
|
-
if _js_loaded and not force: return
|
|
43
|
-
# print("Loading screenshot.js ...")
|
|
44
|
-
status_id = str(uuid.uuid4())
|
|
45
|
-
js_content = (resources.files('dialoghelper')/'screenshot.js').read_text()
|
|
46
|
-
iife(js_content + f'sendDataToServer("{status_id}", {{"js_status": "ready"}});')
|
|
47
|
-
data = _pop_data(status_id, timeout)
|
|
48
|
-
if (stat:=data.get('js_status'))!='ready': raise RuntimeError(f"Failed to load screenshot.js: {stat}")
|
|
49
|
-
_js_loaded = True
|
|
50
|
-
# print("Screenshot.js loaded and ready")
|
|
51
|
-
|
|
52
|
-
# %% ../nbs/01_experimental.ipynb
|
|
53
|
-
_screen_share_active = False
|
|
54
|
-
|
|
55
|
-
def start_screen_share(timeout=45):
|
|
56
|
-
"Start persistent screen sharing session, waiting for confirmation."
|
|
57
|
-
global _screen_share_active
|
|
58
|
-
load_screenshot_js()
|
|
59
|
-
status_id = str(uuid.uuid4())
|
|
60
|
-
iife(f'startPersistentScreenShare("{status_id}");')
|
|
61
|
-
# print("Requesting screen share permission ...")
|
|
62
|
-
data = _pop_data(status_id, timeout)
|
|
63
|
-
js_status = data.get('js_status')
|
|
64
|
-
if js_status=='ready':
|
|
65
|
-
_screen_share_active = True
|
|
66
|
-
# print("Screen share started successfully.")
|
|
67
|
-
elif js_status=='error': raise RuntimeError(f"Screen share failed: {data.get('error', 'Unknown error')}")
|
|
68
|
-
elif js_status=='connecting': raise RuntimeError("Screen share timed out after {timeout} seconds.")
|
|
69
|
-
|
|
70
|
-
# %% ../nbs/01_experimental.ipynb
|
|
71
|
-
def stop_screen_share():
|
|
72
|
-
"Stop persistent screen sharing session."
|
|
73
|
-
global _screen_share_active
|
|
74
|
-
load_screenshot_js()
|
|
75
|
-
iife('stopPersistentScreenShare();')
|
|
76
|
-
_screen_share_active = False
|
|
77
|
-
# print("Screen share stopped.")
|
|
78
|
-
|
|
79
|
-
# %% ../nbs/01_experimental.ipynb
|
|
80
|
-
def capture_screen():
|
|
81
|
-
"Capture screenshot, automatically starting screen share if needed."
|
|
82
|
-
global _screen_share_active
|
|
83
|
-
load_screenshot_js()
|
|
84
|
-
if not _screen_share_active:
|
|
85
|
-
# print("🔄 No active screen share, starting one...")
|
|
86
|
-
result = start_screen_share()
|
|
87
|
-
if not _screen_share_active: raise RuntimeError(f"Failed to start screen share: {result}")
|
|
88
|
-
data_id = str(uuid.uuid4())
|
|
89
|
-
screenshot_code = f'captureScreenFromStream("{data_id}");'
|
|
90
|
-
# print("📸 Capturing from persistent stream...")
|
|
91
|
-
iife(screenshot_code)
|
|
92
|
-
data = _pop_data(data_id, timeout=45)
|
|
93
|
-
if 'error' in data: raise RuntimeError(f"Screenshot failed: {data['error']}")
|
|
94
|
-
if not ('img_data' in data and 'img_type' in data):
|
|
95
|
-
raise RuntimeError("Screenshot capture failed, failed to retrieve data.")
|
|
96
|
-
return ToolResult(data=data['img_data'], result_type=data['img_type'])
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot capture functionality for dialoghelper
|
|
3
|
-
* Provides persistent screen sharing with HTTP polling for screenshots
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let persistentStream = null;
|
|
7
|
-
let streamStatus = "disconnected"; // 'disconnected', 'connecting', 'connected', 'error'
|
|
8
|
-
|
|
9
|
-
function sendDataToServer(dataId, data) {
|
|
10
|
-
return fetch('/push_data_blocking_', {
|
|
11
|
-
method: 'POST',
|
|
12
|
-
headers: {'Content-Type': 'application/json'},
|
|
13
|
-
body: JSON.stringify({data_id: dataId, ...data})
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function streamToBlob(stream, maxWidth = 1280, maxHeight = 1024) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const video = document.createElement("video");
|
|
20
|
-
video.srcObject = stream;
|
|
21
|
-
video.muted = true;
|
|
22
|
-
video.playsInline = true;
|
|
23
|
-
video.addEventListener("loadedmetadata", () => {
|
|
24
|
-
// Downscale to maxWidth x maxHeight using canvas
|
|
25
|
-
const videoWidth = video.videoWidth;
|
|
26
|
-
const videoHeight = video.videoHeight;
|
|
27
|
-
const scaleX = maxWidth / videoWidth;
|
|
28
|
-
const scaleY = maxHeight / videoHeight;
|
|
29
|
-
const scale = Math.min(scaleX, scaleY, 1); // don't upscale
|
|
30
|
-
const newWidth = Math.floor(videoWidth * scale);
|
|
31
|
-
const newHeight = Math.floor(videoHeight * scale);
|
|
32
|
-
const canvas = document.createElement("canvas");
|
|
33
|
-
canvas.width = newWidth;
|
|
34
|
-
canvas.height = newHeight;
|
|
35
|
-
const ctx = canvas.getContext("2d");
|
|
36
|
-
ctx.drawImage(video, 0, 0, newWidth, newHeight);
|
|
37
|
-
canvas.toBlob(resolve, "image/png");
|
|
38
|
-
});
|
|
39
|
-
video.addEventListener("error", reject);
|
|
40
|
-
video.play().catch(reject);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function waitForGetDisplayMedia(timeout = 30000) {
|
|
45
|
-
const start = Date.now();
|
|
46
|
-
while (Date.now() - start < timeout) {
|
|
47
|
-
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { return true; }
|
|
48
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
49
|
-
}
|
|
50
|
-
throw new Error("getDisplayMedia not available after timeout");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function startPersistentScreenShare(statusId = null) {
|
|
54
|
-
try {
|
|
55
|
-
await waitForGetDisplayMedia();
|
|
56
|
-
persistentStream = await navigator.mediaDevices.getDisplayMedia({
|
|
57
|
-
video: { mediaSource: "screen", displaySurface: "monitor" }, audio: false,
|
|
58
|
-
});
|
|
59
|
-
persistentStream.getVideoTracks()[0].addEventListener("ended", () => {
|
|
60
|
-
console.log("Screen share ended by user");
|
|
61
|
-
stopPersistentScreenShare();
|
|
62
|
-
});
|
|
63
|
-
console.log("✅ Persistent screen share started");
|
|
64
|
-
if (statusId) { sendDataToServer(statusId, { js_status: "ready" }); }
|
|
65
|
-
streamStatus = "connected";
|
|
66
|
-
return { status: "success", message: "Screen share started" };
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error("Failed to start persistent screen share:", error);
|
|
69
|
-
if (statusId) { sendDataToServer(statusId, { js_status: "error", error: error.message }); }
|
|
70
|
-
return { status: "error", message: error.message };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function stopPersistentScreenShare() {
|
|
75
|
-
if (persistentStream) {
|
|
76
|
-
persistentStream.getTracks().forEach((track) => track.stop());
|
|
77
|
-
persistentStream = null;
|
|
78
|
-
}
|
|
79
|
-
streamStatus = "disconnected";
|
|
80
|
-
console.log("🛑 Persistent screen share stopped");
|
|
81
|
-
return { status: "success", message: "Screen share stopped" };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function blobToBase64(blob) {
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
const reader = new FileReader();
|
|
87
|
-
reader.onload = () => resolve(reader.result);
|
|
88
|
-
reader.onerror = reject;
|
|
89
|
-
reader.readAsDataURL(blob);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function processScreenshotBlob(blob, dataId) {
|
|
94
|
-
const base64String = await blobToBase64(blob);
|
|
95
|
-
const blob_type = blob.type || "image/png";
|
|
96
|
-
const b64data = base64String.split(",")[1]; // Remove the data URL prefix
|
|
97
|
-
return {
|
|
98
|
-
data_id: dataId,
|
|
99
|
-
size: blob.size,
|
|
100
|
-
img_type: blob_type,
|
|
101
|
-
img_data: b64data,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function captureScreenFromStream(dataId) {
|
|
106
|
-
console.log("Executing screenshot from persistent stream");
|
|
107
|
-
try {
|
|
108
|
-
if (!persistentStream || streamStatus !== "connected") {
|
|
109
|
-
console.log("Stream status:", streamStatus);
|
|
110
|
-
console.log("Persistent stream:", persistentStream);
|
|
111
|
-
throw new Error("No active screen share. Call startPersistentScreenShare() first.");
|
|
112
|
-
}
|
|
113
|
-
const blob = await streamToBlob(persistentStream);
|
|
114
|
-
const result = await processScreenshotBlob(blob, dataId);
|
|
115
|
-
console.log("Screenshot result:", result);
|
|
116
|
-
const pushResponse = await sendDataToServer(dataId, result)
|
|
117
|
-
if (pushResponse.ok) { console.log("✅ Screenshot data pushed to server"); }
|
|
118
|
-
else { console.log("❌ Failed to push screenshot data"); }
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error("Screenshot error:", error);
|
|
121
|
-
sendDataToServer(dataId, { error: error.message });
|
|
122
|
-
}
|
|
123
|
-
console.log("Finished executing screenshot");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
window.startPersistentScreenShare = startPersistentScreenShare;
|
|
127
|
-
window.stopPersistentScreenShare = stopPersistentScreenShare;
|
|
128
|
-
window.captureScreenFromStream = captureScreenFromStream;
|
|
129
|
-
window.getStreamStatus = () => streamStatus;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|