Mesa 3.1.5__py3-none-any.whl → 3.2.0.dev0__py3-none-any.whl
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.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -1
- mesa/agent.py +20 -5
- mesa/discrete_space/__init__.py +50 -0
- mesa/{experimental/cell_space → discrete_space}/cell.py +29 -10
- mesa/{experimental/cell_space → discrete_space}/cell_agent.py +1 -1
- mesa/{experimental/cell_space → discrete_space}/cell_collection.py +3 -3
- mesa/{experimental/cell_space → discrete_space}/discrete_space.py +65 -3
- mesa/{experimental/cell_space → discrete_space}/grid.py +2 -2
- mesa/{experimental/cell_space → discrete_space}/network.py +22 -2
- mesa/{experimental/cell_space → discrete_space}/property_layer.py +1 -10
- mesa/{experimental/cell_space → discrete_space}/voronoi.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/agents.py +1 -1
- mesa/examples/advanced/epstein_civil_violence/model.py +1 -1
- mesa/examples/advanced/pd_grid/agents.py +1 -1
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/agents.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/model.py +2 -2
- mesa/examples/advanced/wolf_sheep/agents.py +1 -1
- mesa/examples/advanced/wolf_sheep/app.py +2 -1
- mesa/examples/advanced/wolf_sheep/model.py +1 -1
- mesa/examples/basic/boid_flockers/agents.py +1 -0
- mesa/examples/basic/boid_flockers/app.py +17 -2
- mesa/examples/basic/boid_flockers/model.py +12 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +6 -11
- mesa/examples/basic/boltzmann_wealth_model/app.py +2 -2
- mesa/examples/basic/boltzmann_wealth_model/model.py +7 -11
- mesa/examples/basic/conways_game_of_life/agents.py +13 -5
- mesa/examples/basic/conways_game_of_life/model.py +10 -7
- mesa/examples/basic/schelling/agents.py +13 -8
- mesa/examples/basic/schelling/model.py +6 -9
- mesa/examples/basic/virus_on_network/agents.py +13 -17
- mesa/examples/basic/virus_on_network/model.py +20 -24
- mesa/experimental/__init__.py +2 -2
- mesa/experimental/cell_space/__init__.py +18 -8
- mesa/space.py +1 -12
- mesa/visualization/__init__.py +2 -0
- mesa/visualization/command_console.py +482 -0
- mesa/visualization/components/altair_components.py +276 -16
- mesa/visualization/mpl_space_drawing.py +17 -9
- mesa/visualization/solara_viz.py +150 -21
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/METADATA +12 -8
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/RECORD +45 -43
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/WHEEL +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"""A command console interface for interactive Python code execution in the browser.
|
|
2
|
+
|
|
3
|
+
This module provides a set of classes and functions to create an interactive Python console
|
|
4
|
+
that can be embedded in a web browser. It supports command history, multi-line code blocks,
|
|
5
|
+
and special commands for console management.
|
|
6
|
+
|
|
7
|
+
Notes:
|
|
8
|
+
- The console supports multi-line code blocks with proper indentation
|
|
9
|
+
- Output is captured and displayed with appropriate formatting
|
|
10
|
+
- Error messages are displayed in red with distinct styling
|
|
11
|
+
- The console maintains a history of commands and their outputs
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import code
|
|
15
|
+
import io
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
import solara
|
|
21
|
+
from solara.components.input import use_change
|
|
22
|
+
|
|
23
|
+
from mesa.visualization.utils import force_update
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ConsoleEntry:
|
|
28
|
+
"""A class to store command console entries.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
command (str): The command entered
|
|
32
|
+
output (str): The output of the command
|
|
33
|
+
is_error (bool): Whether the entry represents an error
|
|
34
|
+
is_continuation (bool): Whether the entry is a continuation of previous command
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
command: str
|
|
38
|
+
output: str = ""
|
|
39
|
+
is_error: bool = False
|
|
40
|
+
is_continuation: bool = False
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
"""Return a string representation of the ConsoleEntry."""
|
|
44
|
+
return f"ConsoleEntry({self.command}, {self.output}, {self.is_error}, {self.is_continuation})"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CaptureOutput:
|
|
48
|
+
"""A context manager for capturing stdout and stderr output.
|
|
49
|
+
|
|
50
|
+
This class provides a way to capture output that would normally be printed
|
|
51
|
+
to stdout and stderr during the execution of code within its context.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
"""Initialize the CaptureOutput context manager with empty string buffers."""
|
|
56
|
+
self.stdout = io.StringIO()
|
|
57
|
+
self.stderr = io.StringIO()
|
|
58
|
+
self._old_stdout = None
|
|
59
|
+
self._old_stderr = None
|
|
60
|
+
|
|
61
|
+
def __enter__(self):
|
|
62
|
+
"""Set up the context manager by redirecting stdout and stderr.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
self: The context manager instance
|
|
66
|
+
"""
|
|
67
|
+
self._old_stdout = sys.stdout
|
|
68
|
+
self._old_stderr = sys.stderr
|
|
69
|
+
sys.stdout = self.stdout
|
|
70
|
+
sys.stderr = self.stderr
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
74
|
+
"""Restore the original stdout and stderr when exiting the context."""
|
|
75
|
+
sys.stdout = self._old_stdout
|
|
76
|
+
sys.stderr = self._old_stderr
|
|
77
|
+
|
|
78
|
+
def get_output(self):
|
|
79
|
+
"""Retrieve and clear the captured output.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
tuple: A pair of strings (stdout_output, stderr_output)
|
|
83
|
+
"""
|
|
84
|
+
output = self.stdout.getvalue()
|
|
85
|
+
error = self.stderr.getvalue()
|
|
86
|
+
self.stdout.seek(0)
|
|
87
|
+
self.stdout.truncate(0)
|
|
88
|
+
self.stderr.seek(0)
|
|
89
|
+
self.stderr.truncate(0)
|
|
90
|
+
return output, error
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class InteractiveConsole(code.InteractiveConsole):
|
|
94
|
+
"""A custom interactive Python console with output capturing capabilities.
|
|
95
|
+
|
|
96
|
+
This class extends code.InteractiveConsole to provide output capturing functionality
|
|
97
|
+
when executing Python code interactively.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
locals_dict (dict, optional): Dictionary of local variables. Defaults to None.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, locals_dict=None):
|
|
104
|
+
"""Initialize the InteractiveConsole with the provided locals dictionary."""
|
|
105
|
+
super().__init__(locals=locals_dict or {})
|
|
106
|
+
self.capturer = CaptureOutput()
|
|
107
|
+
|
|
108
|
+
def push(self, line):
|
|
109
|
+
"""Push a line to the command interpreter and execute it.
|
|
110
|
+
|
|
111
|
+
This method captures the output of the executed command and returns both
|
|
112
|
+
the 'more' flag and the captured output.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
line (str): The line of code to be executed.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
tuple: A tuple containing:
|
|
119
|
+
- more (bool): Flag indicating if more input is needed
|
|
120
|
+
- str: The captured output from executing the command
|
|
121
|
+
"""
|
|
122
|
+
with self.capturer:
|
|
123
|
+
more = super().push(line)
|
|
124
|
+
return more, self.capturer.get_output()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ConsoleManager:
|
|
128
|
+
"""A console manager for executing Python code interactively.
|
|
129
|
+
|
|
130
|
+
This class provides functionality to execute Python code in an interactive console environment,
|
|
131
|
+
maintain command history, and handle multi-line code blocks.
|
|
132
|
+
|
|
133
|
+
Attributes:
|
|
134
|
+
locals_dict (dict): Dictionary containing local variables available to the console
|
|
135
|
+
console (InteractiveConsole): Python's interactive console instance
|
|
136
|
+
buffer (list): Buffer for storing multi-line code blocks
|
|
137
|
+
history (list[ConsoleEntry]): List of console entries containing commands and their outputs
|
|
138
|
+
Special Commands:
|
|
139
|
+
1. `history` : Shows the command history
|
|
140
|
+
2. `cls` : Clears the console screen
|
|
141
|
+
3. `tips` : Shows available console commands and usage tips
|
|
142
|
+
Example:
|
|
143
|
+
>>> console = ConsoleManager(model=my_model)
|
|
144
|
+
>>> console.execute_code("print('hello world')", set_input_callback)
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(self, model=None, additional_imports=None):
|
|
148
|
+
"""Initialize the console manager with the provided model and imports."""
|
|
149
|
+
# Create locals dictionary with model and imports
|
|
150
|
+
locals_dict = {}
|
|
151
|
+
if model is not None:
|
|
152
|
+
locals_dict["model"] = model
|
|
153
|
+
if additional_imports:
|
|
154
|
+
locals_dict.update(additional_imports)
|
|
155
|
+
|
|
156
|
+
self.locals_dict = locals_dict
|
|
157
|
+
self.console = InteractiveConsole(locals_dict)
|
|
158
|
+
self.buffer = []
|
|
159
|
+
self.history: list[ConsoleEntry] = []
|
|
160
|
+
self.history_index = -1
|
|
161
|
+
self.current_input = ""
|
|
162
|
+
|
|
163
|
+
def execute_code(
|
|
164
|
+
self, code_line: str, set_input_text: Callable[[str], None]
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Execute the provided code line and update the console history."""
|
|
167
|
+
# Custom Commands
|
|
168
|
+
# A. History
|
|
169
|
+
if code_line == "history":
|
|
170
|
+
# Get the current history except the custom commands
|
|
171
|
+
cur_his = [
|
|
172
|
+
(
|
|
173
|
+
f"Command: {entry.command}, \nOutput: {entry.output if entry.output else None}\n"
|
|
174
|
+
)
|
|
175
|
+
for entry in self.history
|
|
176
|
+
if entry.command != "[history]"
|
|
177
|
+
and entry.command != "[tips]"
|
|
178
|
+
and entry.command != ""
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
self.history.append(
|
|
182
|
+
ConsoleEntry(
|
|
183
|
+
command="[history]",
|
|
184
|
+
output="\n".join(cur_his) if cur_his else "No history",
|
|
185
|
+
is_error=False,
|
|
186
|
+
is_continuation=False,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
set_input_text("")
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# B. Clear
|
|
194
|
+
if code_line == "cls":
|
|
195
|
+
self.clear_console()
|
|
196
|
+
set_input_text("")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# C. Tips
|
|
200
|
+
if code_line == "tips":
|
|
201
|
+
self.history.append(
|
|
202
|
+
ConsoleEntry(
|
|
203
|
+
command="[tips]",
|
|
204
|
+
output="Available Console Commands:\n1. Press Enter to execute a command\n2. Type 'cls' to clear the console screen (it doesn't delete past variables and functions)\n3. Type 'history' to view previous commands\n4. Press Enter on an empty line to complete a multiline block\n5. Use proper indentation for multiline blocks\n6. The console will show '..: ' for continuation lines",
|
|
205
|
+
is_error=False,
|
|
206
|
+
is_continuation=False,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
set_input_text("")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# Handle empty lines
|
|
214
|
+
if not code_line.strip():
|
|
215
|
+
# If we have a buffer, complete the block
|
|
216
|
+
if self.buffer:
|
|
217
|
+
full_code = "\n".join(self.buffer)
|
|
218
|
+
more, (output, error) = self.console.push("")
|
|
219
|
+
|
|
220
|
+
# Remove the redundant commands from the history
|
|
221
|
+
for _ in range(len(self.buffer) - 1):
|
|
222
|
+
self.history.pop()
|
|
223
|
+
|
|
224
|
+
# Completing a multi-line block
|
|
225
|
+
if self.history:
|
|
226
|
+
self.history[-1].command = full_code
|
|
227
|
+
self.history[-1].output = error if error else output
|
|
228
|
+
self.history[-1].is_error = bool(error)
|
|
229
|
+
self.history[-1].is_continuation = False
|
|
230
|
+
self.buffer = []
|
|
231
|
+
else:
|
|
232
|
+
# Empty line with no buffer - just add a blank entry
|
|
233
|
+
self.history.append(ConsoleEntry(command=""))
|
|
234
|
+
set_input_text("")
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
# Execute the line
|
|
238
|
+
more, (output, error) = self.console.push(code_line)
|
|
239
|
+
|
|
240
|
+
# Force update to display any changes to the model
|
|
241
|
+
force_update()
|
|
242
|
+
|
|
243
|
+
# If this is the start of a multi-line block
|
|
244
|
+
if more:
|
|
245
|
+
self.buffer.append(code_line)
|
|
246
|
+
self.history.append(
|
|
247
|
+
ConsoleEntry(
|
|
248
|
+
command=code_line,
|
|
249
|
+
output="", # Don't show partial output for incomplete blocks
|
|
250
|
+
is_error=False,
|
|
251
|
+
is_continuation=True,
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
# Single complete command
|
|
256
|
+
if not self.buffer:
|
|
257
|
+
# Normal single-line command
|
|
258
|
+
self.history.append(
|
|
259
|
+
ConsoleEntry(
|
|
260
|
+
command=code_line,
|
|
261
|
+
output=error if error else output,
|
|
262
|
+
is_error=bool(error),
|
|
263
|
+
is_continuation=False,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
# Remove the redundant commands from the history
|
|
268
|
+
for _ in range(len(self.buffer) - 1):
|
|
269
|
+
self.history.pop()
|
|
270
|
+
|
|
271
|
+
# Completing a multi-line block
|
|
272
|
+
self.buffer.append(code_line)
|
|
273
|
+
full_code = "\n".join(self.buffer)
|
|
274
|
+
if self.history:
|
|
275
|
+
self.history[-1].command = full_code
|
|
276
|
+
self.history[-1].output = error if error else output
|
|
277
|
+
self.history[-1].is_error = bool(error)
|
|
278
|
+
self.history[-1].is_continuation = False
|
|
279
|
+
self.buffer = []
|
|
280
|
+
|
|
281
|
+
set_input_text("")
|
|
282
|
+
|
|
283
|
+
def clear_console(self) -> None:
|
|
284
|
+
"""Clear the console history and reset the console state."""
|
|
285
|
+
self.history.clear()
|
|
286
|
+
self.buffer.clear()
|
|
287
|
+
self.history_index = -1
|
|
288
|
+
self.current_input = ""
|
|
289
|
+
# Reset the console while maintaining the locals dictionary
|
|
290
|
+
self.console = InteractiveConsole(self.locals_dict)
|
|
291
|
+
|
|
292
|
+
def get_entries(self) -> list[ConsoleEntry]:
|
|
293
|
+
"""Get the list of console entries."""
|
|
294
|
+
return self.history
|
|
295
|
+
|
|
296
|
+
def prev_command(
|
|
297
|
+
self, current_text: str, set_input_text: Callable[[str], None]
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Navigate to previous command in history."""
|
|
300
|
+
if not self.history:
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
# Save the current input
|
|
304
|
+
if self.history_index == -1:
|
|
305
|
+
self.current_input = current_text
|
|
306
|
+
|
|
307
|
+
# Move up in history
|
|
308
|
+
if self.history_index == -1:
|
|
309
|
+
self.history_index = len(self.history) - 1
|
|
310
|
+
elif self.history_index > 0:
|
|
311
|
+
self.history_index -= 1
|
|
312
|
+
|
|
313
|
+
# Set text to the historical command
|
|
314
|
+
if 0 <= self.history_index < len(self.history):
|
|
315
|
+
set_input_text(self.history[self.history_index].command)
|
|
316
|
+
|
|
317
|
+
def next_command(self, set_input_text: Callable[[str], None]) -> None:
|
|
318
|
+
"""Navigate to next command in history."""
|
|
319
|
+
if self.history_index == -1:
|
|
320
|
+
return # Not in history navigation mode
|
|
321
|
+
|
|
322
|
+
# Move down in history
|
|
323
|
+
self.history_index += 1
|
|
324
|
+
|
|
325
|
+
# If we've moved past the end of history, restore the saved input
|
|
326
|
+
if self.history_index >= len(self.history):
|
|
327
|
+
self.history_index = -1
|
|
328
|
+
set_input_text(self.current_input)
|
|
329
|
+
else:
|
|
330
|
+
set_input_text(self.history[self.history_index].command)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def format_command_html(entry):
|
|
334
|
+
"""Format the command part of a console entry as HTML."""
|
|
335
|
+
prompt = "..: " if entry.is_continuation else ">>> "
|
|
336
|
+
prompt_color = "#6c757d" if entry.is_continuation else "#2196F3"
|
|
337
|
+
|
|
338
|
+
# Handle multi-line commands with proper formatting
|
|
339
|
+
command_lines = entry.command.split("\n")
|
|
340
|
+
formatted_lines = []
|
|
341
|
+
|
|
342
|
+
for i, line in enumerate(command_lines):
|
|
343
|
+
line_prompt = prompt if i == 0 else "..: "
|
|
344
|
+
line_prompt_color = prompt_color if i == 0 else "#6c757d"
|
|
345
|
+
formatted_lines.append(
|
|
346
|
+
f'<span style="color: {line_prompt_color};">{line_prompt}</span><span>{line.removeprefix(">>> ").removeprefix("..: ")}</span>'
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
command_html = "<br>".join(formatted_lines)
|
|
350
|
+
|
|
351
|
+
return f"""
|
|
352
|
+
<div style="margin: 0px 0 0 0;">
|
|
353
|
+
<div style="background-color: #f5f5f5; padding: 6px 8px; border-radius: 4px; font-family: 'Consolas', monospace; font-size: 0.9em;">
|
|
354
|
+
{command_html}
|
|
355
|
+
</div>
|
|
356
|
+
"""
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def format_output_html(entry):
|
|
360
|
+
"""Format the output part of a console entry as HTML."""
|
|
361
|
+
if not entry.output:
|
|
362
|
+
return "</div>"
|
|
363
|
+
|
|
364
|
+
escaped_output = (
|
|
365
|
+
entry.output.replace("&", "&")
|
|
366
|
+
.replace("<", "<")
|
|
367
|
+
.replace(">", ">")
|
|
368
|
+
.replace(" ", " ")
|
|
369
|
+
.replace("\n", "<br>")
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return f"""
|
|
373
|
+
<div style="background-color: #ffffff; padding: 6px 8px; border-left: 3px solid {"#ff3860" if entry.is_error else "#2196F3"}; margin-top: 2px; font-family: 'Consolas', monospace; font-size: 0.9em; {"color: #ff3860;" if entry.is_error else ""}">
|
|
374
|
+
{escaped_output}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@solara.component
|
|
381
|
+
def ConsoleInput(on_submit, on_up, on_down):
|
|
382
|
+
"""A solara component for handling console input."""
|
|
383
|
+
input_text, set_input_text = solara.use_state("")
|
|
384
|
+
|
|
385
|
+
def handle_submit(*ignore_args):
|
|
386
|
+
on_submit(input_text, set_input_text)
|
|
387
|
+
|
|
388
|
+
def handle_up(*ignore_args):
|
|
389
|
+
on_up(input_text, set_input_text)
|
|
390
|
+
|
|
391
|
+
def handle_down(*ignore_args):
|
|
392
|
+
on_down(set_input_text)
|
|
393
|
+
|
|
394
|
+
input_elem = solara.v.TextField(
|
|
395
|
+
v_model=input_text,
|
|
396
|
+
on_v_model=set_input_text,
|
|
397
|
+
flat=True,
|
|
398
|
+
hide_details=True,
|
|
399
|
+
dense=True,
|
|
400
|
+
height="auto",
|
|
401
|
+
background_color="transparent",
|
|
402
|
+
style_="font-family: monospace; border: none; box-shadow: none; padding: 0; margin: 0; background-color: transparent; color: #000; flex-grow: 1;",
|
|
403
|
+
placeholder="",
|
|
404
|
+
solo=False,
|
|
405
|
+
filled=False,
|
|
406
|
+
outlined=False,
|
|
407
|
+
id="console-input",
|
|
408
|
+
attributes={
|
|
409
|
+
"spellcheck": "false",
|
|
410
|
+
"autocomplete": "off",
|
|
411
|
+
},
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Bind key events with the input element
|
|
415
|
+
use_change(input_elem, handle_submit, update_events=["keypress.enter"])
|
|
416
|
+
use_change(input_elem, handle_up, update_events=["keyup.38"]) # 38 -> Up arrow
|
|
417
|
+
use_change(
|
|
418
|
+
input_elem, handle_down, update_events=["keydown.40"]
|
|
419
|
+
) # 40 -> Down arrow
|
|
420
|
+
|
|
421
|
+
return input_elem
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@solara.component
|
|
425
|
+
def CommandConsole(model=None, additional_imports=None):
|
|
426
|
+
"""A solara component for executing Python code interactively in the browser."""
|
|
427
|
+
# Initialize state for the console manager
|
|
428
|
+
console_ref = solara.use_ref(None)
|
|
429
|
+
if console_ref.current is None:
|
|
430
|
+
console_ref.current = ConsoleManager(
|
|
431
|
+
model=model, additional_imports=additional_imports
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# State to trigger re-renders
|
|
435
|
+
refresh, set_refresh = solara.use_state(0)
|
|
436
|
+
|
|
437
|
+
def handle_code_execution(code, set_input_text):
|
|
438
|
+
console_ref.current.execute_code(code, set_input_text)
|
|
439
|
+
set_refresh(refresh + 1)
|
|
440
|
+
|
|
441
|
+
def handle_up(current_text, set_input_text):
|
|
442
|
+
console_ref.current.prev_command(current_text, set_input_text)
|
|
443
|
+
set_refresh(refresh + 1)
|
|
444
|
+
|
|
445
|
+
def handle_down(set_input_text):
|
|
446
|
+
console_ref.current.next_command(set_input_text)
|
|
447
|
+
set_refresh(refresh + 1)
|
|
448
|
+
|
|
449
|
+
with solara.Column(
|
|
450
|
+
style={
|
|
451
|
+
"height": "300px",
|
|
452
|
+
"overflow-y": "auto",
|
|
453
|
+
"gap": "0px",
|
|
454
|
+
"box-shadow": "inset 0 0 10px rgba(0,0,0,0.1)",
|
|
455
|
+
"border": "3px solid #e0e0e0",
|
|
456
|
+
"border-radius": "6px",
|
|
457
|
+
"padding": "8px",
|
|
458
|
+
}
|
|
459
|
+
):
|
|
460
|
+
console_entries = console_ref.current.get_entries()
|
|
461
|
+
|
|
462
|
+
# Display history entries with auto-scrolling
|
|
463
|
+
with solara.v.ScrollYTransition(group=True):
|
|
464
|
+
for entry in console_entries:
|
|
465
|
+
with solara.Div():
|
|
466
|
+
command_html = format_command_html(entry)
|
|
467
|
+
output_html = format_output_html(entry)
|
|
468
|
+
solara.Markdown(command_html + output_html)
|
|
469
|
+
|
|
470
|
+
# Input row that adapts to content above it
|
|
471
|
+
with solara.Row(
|
|
472
|
+
style={"align-items": "center", "margin": "0", "width": "94.5%"}
|
|
473
|
+
):
|
|
474
|
+
solara.Text(">>> ", style={"color": "#0066cc"})
|
|
475
|
+
ConsoleInput(
|
|
476
|
+
on_submit=handle_code_execution, on_up=handle_up, on_down=handle_down
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
solara.Markdown(
|
|
480
|
+
"*Type 'tips' for usage instructions.*",
|
|
481
|
+
style="font-size: 0.8em; color: #666;",
|
|
482
|
+
)
|