dialoghelper 0.0.31__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.
Files changed (25) hide show
  1. {dialoghelper-0.0.31/dialoghelper.egg-info → dialoghelper-0.0.41}/PKG-INFO +7 -2
  2. dialoghelper-0.0.41/dialoghelper/__init__.py +2 -0
  3. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper/_modidx.py +20 -13
  4. dialoghelper-0.0.41/dialoghelper/capture.py +42 -0
  5. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper/core.py +207 -56
  6. dialoghelper-0.0.41/dialoghelper/screenshot.js +26 -0
  7. {dialoghelper-0.0.31 → dialoghelper-0.0.41/dialoghelper.egg-info}/PKG-INFO +7 -2
  8. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper.egg-info/SOURCES.txt +1 -1
  9. dialoghelper-0.0.41/dialoghelper.egg-info/requires.txt +13 -0
  10. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/settings.ini +3 -3
  11. dialoghelper-0.0.31/dialoghelper/__init__.py +0 -2
  12. dialoghelper-0.0.31/dialoghelper/experimental.py +0 -96
  13. dialoghelper-0.0.31/dialoghelper/screenshot.js +0 -129
  14. dialoghelper-0.0.31/dialoghelper.egg-info/requires.txt +0 -8
  15. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/LICENSE +0 -0
  16. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/MANIFEST.in +0 -0
  17. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/README.md +0 -0
  18. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper/db_dc.py +0 -0
  19. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper.egg-info/dependency_links.txt +0 -0
  20. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper.egg-info/entry_points.txt +0 -0
  21. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper.egg-info/not-zip-safe +0 -0
  22. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/dialoghelper.egg-info/top_level.txt +0 -0
  23. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/pyproject.toml +0 -0
  24. {dialoghelper-0.0.31 → dialoghelper-0.0.41}/setup.cfg +0 -0
  25. {dialoghelper-0.0.31 → 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.31
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.5
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
@@ -0,0 +1,2 @@
1
+ __version__ = "0.0.41"
2
+ from .core import *
@@ -5,38 +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.core': { 'dialoghelper.core._add_msg_unsafe': ('core.html#_add_msg_unsafe', 'dialoghelper/core.py'),
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
47
  'dialoghelper.core.update_msg': ('core.html#update_msg', 'dialoghelper/core.py'),
30
48
  'dialoghelper.core.url2note': ('core.html#url2note', 'dialoghelper/core.py')},
31
- 'dialoghelper.db_dc': {},
32
- 'dialoghelper.experimental': { 'dialoghelper.experimental._pop_data': ( 'experimental.html#_pop_data',
33
- 'dialoghelper/experimental.py'),
34
- 'dialoghelper.experimental.capture_screen': ( 'experimental.html#capture_screen',
35
- 'dialoghelper/experimental.py'),
36
- 'dialoghelper.experimental.iife': ('experimental.html#iife', 'dialoghelper/experimental.py'),
37
- 'dialoghelper.experimental.load_screenshot_js': ( 'experimental.html#load_screenshot_js',
38
- 'dialoghelper/experimental.py'),
39
- 'dialoghelper.experimental.start_screen_share': ( 'experimental.html#start_screen_share',
40
- 'dialoghelper/experimental.py'),
41
- 'dialoghelper.experimental.stop_screen_share': ( 'experimental.html#stop_screen_share',
42
- 'dialoghelper/experimental.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__ = ['Placements', 'empty', 'find_var', 'call_endp', 'find_dname', 'find_msg_id', 'curr_dialog', 'find_msgs', 'msg_idx',
5
- 'read_msg', 'add_html', 'del_msg', 'run_msg', 'add_msg', 'update_msg', 'url2note', 'load_gist', 'gist_file',
6
- 'import_string', 'is_usable_tool', 'mk_toollist', 'import_gist', 'tool_info', 'asdict']
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, importlib, linecache
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
- _all_ = ["asdict"]
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 find_var(var:str):
31
- "Search for var in all frames of the call stack"
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
- dv = frame.f_globals.get(var, frame.f_locals.get(var, None))
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:5001/{path}', data=data)
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 __dialog_id."
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
- def del_msg(
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 run_msg(
119
- msgid:str=None, # id of message to execute
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
- "Adds a message to the run queue. Use read_msg to see the output once it runs."
123
- return call_endp('add_runq_', dname, msgid=msgid, api=True)
124
-
125
- # %% ../nbs/00_core.ipynb
126
- Placements = str_enum('Placements', 'add_after', 'add_before', 'at_start', 'at_end')
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='', # For prompts/code, initial output
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
- return call_endp(
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
- dname = kwargs.pop('dname')
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, # For prompts/code, the output
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,20 @@ 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
- return call_endp('add_relative_', dname, placement='update', msgid=msgid, **kwargs)
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)
193
261
 
194
262
  # %% ../nbs/00_core.ipynb
195
263
  def url2note(
@@ -201,6 +269,66 @@ def url2note(
201
269
  res = read_url(url, as_md=True, extract_section=extract_section, selector=selector)
202
270
  return add_msg(res)
203
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)
331
+
204
332
  # %% ../nbs/00_core.ipynb
205
333
  def load_gist(gist_id:str):
206
334
  "Retrieve a gist"
@@ -278,8 +406,31 @@ def tool_info():
278
406
  - &`find_msgs`: Find messages in current specific dialog that contain the given information.
279
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.)
280
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.
281
412
  - &`del_msg`: Delete a message from the dialog.
282
413
  - &`add_msg`: Add/update a message to the queue to show after code execution completes.
283
414
  - &`update_msg`: Update an existing message.
284
- - &`url2note`: Read URL as markdown, and add a note below current message with the result'''
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'''
285
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.31
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.5
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
@@ -0,0 +1,13 @@
1
+ fastcore>=1.8.10
2
+ fastlite
3
+ ghapi
4
+ ipykernel-helper
5
+ claudette
6
+ ast-grep-cli
7
+ ast-grep-py
8
+ MonsterUI
9
+ lisette>=0.0.13
10
+
11
+ [dev]
12
+ python-fasthtml
13
+ pillow
@@ -1,12 +1,12 @@
1
1
  [DEFAULT]
2
2
  repo = dialoghelper
3
3
  lib_name = dialoghelper
4
- version = 0.0.31
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.5 fastlite ghapi ipykernel-helper claudette
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,2 +0,0 @@
1
- __version__ = "0.0.31"
2
- from .core import *
@@ -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;
@@ -1,8 +0,0 @@
1
- fastcore>=1.8.5
2
- fastlite
3
- ghapi
4
- ipykernel-helper
5
- claudette
6
-
7
- [dev]
8
- python-fasthtml
File without changes
File without changes
File without changes
File without changes
File without changes