meltygui 0.1.0__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.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""File-tree I/O and metadata adaptation for mutable dictionary values.
|
|
2
|
+
|
|
3
|
+
These operations do not own windows, pollers, or render state. The core host
|
|
4
|
+
coordinates when disk snapshots and edits pass through them.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from meltygui.core.conversion.bubbling import install_bubbling
|
|
11
|
+
|
|
12
|
+
def _scan(folder):
|
|
13
|
+
"""Disk → the held shape: {name: Path} for files, {name: {…}} for dirs."""
|
|
14
|
+
out = {}
|
|
15
|
+
try:
|
|
16
|
+
children = sorted(folder.iterdir())
|
|
17
|
+
except OSError:
|
|
18
|
+
return out
|
|
19
|
+
for p in children:
|
|
20
|
+
if p.name.startswith(".") or p.name == "__pycache__":
|
|
21
|
+
continue
|
|
22
|
+
out[p.name] = _scan(p) if p.is_dir() else p
|
|
23
|
+
return out
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _create(path, value, pending):
|
|
28
|
+
"""A key the user ADDED → put it on disk. A held dict → mkdir (its children
|
|
29
|
+
create themselves from the same plan); a held str → a file with those
|
|
30
|
+
contents; a held Path that still exists elsewhere → a MOVE (a dragged or
|
|
31
|
+
renamed key is the old Path re-appearing under a new name). A STALE held
|
|
32
|
+
Path (its file already gone) pairs by basename to a pending delete — the
|
|
33
|
+
two halves of a move whose delete side was planned first (e.g. undoing a
|
|
34
|
+
cross-folder drag re-inserts the OLD Path while the file sits at the NEW
|
|
35
|
+
one) — and moves that file instead of writing an empty husk."""
|
|
36
|
+
try:
|
|
37
|
+
if isinstance(value, dict):
|
|
38
|
+
path.mkdir(exist_ok=True)
|
|
39
|
+
return
|
|
40
|
+
if isinstance(value, Path):
|
|
41
|
+
if value.exists() and value != path:
|
|
42
|
+
value.replace(path) # rename() refuses an existing target on Windows
|
|
43
|
+
pending.discard(value)
|
|
44
|
+
return
|
|
45
|
+
if not path.exists():
|
|
46
|
+
# value == path covers undo: the OLD Path re-inserted at its
|
|
47
|
+
# old home while the file sits at the move's target.
|
|
48
|
+
twin = next((p for p in sorted(pending)
|
|
49
|
+
if p.name == value.name and p.is_file()), None)
|
|
50
|
+
if twin is not None:
|
|
51
|
+
twin.replace(path)
|
|
52
|
+
pending.discard(twin)
|
|
53
|
+
return
|
|
54
|
+
if not path.exists():
|
|
55
|
+
path.write_text(value if isinstance(value, str) else "")
|
|
56
|
+
except OSError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _delete(path):
|
|
62
|
+
"""A key the user DELETED → remove from disk (rmtree for a folder).
|
|
63
|
+
Toggles.FileSafety.block_file_delete gates ALL disk deletes (read live);
|
|
64
|
+
the poller re-discovers the surviving file and restores its key."""
|
|
65
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
66
|
+
if Toggles.FileSafety.block_file_delete:
|
|
67
|
+
print(f"[folder_files] delete blocked (Toggles.FileSafety.block_file_delete): {path}")
|
|
68
|
+
return
|
|
69
|
+
try:
|
|
70
|
+
shutil.rmtree(path) if path.is_dir() else path.unlink(missing_ok=True)
|
|
71
|
+
except OSError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _reconcile(store, disk, folder, seen):
|
|
77
|
+
"""Mirror `store` (the held tree) ⇄ `disk` (the poller's snapshot of
|
|
78
|
+
`folder`), in place, in two phases. Phase 1 (_collect) walks the whole
|
|
79
|
+
tree gathering creates and deletes, stamping every planned effect straight
|
|
80
|
+
into `disk` so the snapshot never lags our own writes — the poller only
|
|
81
|
+
ever reports EXTERNAL changes. Phase 2 executes EVERY create before ANY
|
|
82
|
+
delete, tree-wide: a file dragged between folders is renamed (a MOVE)
|
|
83
|
+
before the old key's delete fires no matter which folder the walk visits
|
|
84
|
+
first. (The old per-folder ordering destroyed the file's content whenever
|
|
85
|
+
the source folder reconciled before the target.) A delete consumed by a
|
|
86
|
+
move pairing is skipped; the rest run last, so a moved folder's old
|
|
87
|
+
skeleton is removed only after its children have been renamed out."""
|
|
88
|
+
creates, deletes = [], []
|
|
89
|
+
_collect(store, disk, folder, seen, creates, deletes)
|
|
90
|
+
pending = set(deletes)
|
|
91
|
+
for path, value in creates:
|
|
92
|
+
_create(path, value, pending)
|
|
93
|
+
for path in deletes:
|
|
94
|
+
if path in pending:
|
|
95
|
+
_delete(path)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _collect(store, disk, folder, seen, creates, deletes):
|
|
100
|
+
"""Phase 1 of _reconcile: the recursive walk. A path the store has never
|
|
101
|
+
SEEN is disk's to give (new on disk → hold it); a path it HAS seen is the
|
|
102
|
+
user's to take away (key deleted → plan a delete, key added → plan a
|
|
103
|
+
create). Store/disk/seen bookkeeping happens here — only the disk side
|
|
104
|
+
effects are deferred to the plan."""
|
|
105
|
+
for name, value in list(store.items()):
|
|
106
|
+
if name == "__overrides__": # view metadata, not a file
|
|
107
|
+
continue
|
|
108
|
+
if name not in disk and (folder / name) not in seen: # user added
|
|
109
|
+
creates.append((folder / name, value))
|
|
110
|
+
disk[name] = {} if isinstance(value, dict) else folder / name
|
|
111
|
+
if not isinstance(value, dict):
|
|
112
|
+
store[name] = disk[name]
|
|
113
|
+
for name in sorted(set(disk) | set(store)):
|
|
114
|
+
if name == "__overrides__":
|
|
115
|
+
continue
|
|
116
|
+
path = folder / name
|
|
117
|
+
if name not in store:
|
|
118
|
+
if path in seen: # user deleted
|
|
119
|
+
deletes.append(path)
|
|
120
|
+
disk.pop(name)
|
|
121
|
+
seen.discard(path)
|
|
122
|
+
continue
|
|
123
|
+
store[name] = {} if isinstance(disk[name], dict) else disk[name] # new on disk
|
|
124
|
+
elif name not in disk: # vanished from disk
|
|
125
|
+
store.pop(name)
|
|
126
|
+
seen.discard(path)
|
|
127
|
+
continue
|
|
128
|
+
seen.add(path)
|
|
129
|
+
if isinstance(store[name], dict):
|
|
130
|
+
sub = disk[name] if isinstance(disk[name], dict) else {}
|
|
131
|
+
_collect(store[name], sub, path, seen, creates, deletes)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _file_meta(root=None):
|
|
136
|
+
"""The shared path→params store (file_meta.file_meta_store()) — what
|
|
137
|
+
AppModel.file_meta_collection.file_meta is too. `root` is accepted for
|
|
138
|
+
the on_load callers and ignored: the store exists before any model."""
|
|
139
|
+
from meltygui.models.file_meta import file_meta_store
|
|
140
|
+
return file_meta_store()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _apply_meta(tree, folder, meta):
|
|
145
|
+
"""Meta → tree, recursively: stamp __overrides__ entries and sort keys by
|
|
146
|
+
stored `order`. All writes are inbound state, not user edits — dunder-key
|
|
147
|
+
stores are raw (no dirty mark) and reorders use raw dict ops — so applying
|
|
148
|
+
never dirties the host or triggers a save. Returns True if anything
|
|
149
|
+
changed (caller invalidates the subtree so cached rows repaint)."""
|
|
150
|
+
changed = False
|
|
151
|
+
names = [n for n in tree if n != "__overrides__"]
|
|
152
|
+
desired, orders = {}, {}
|
|
153
|
+
for n in names:
|
|
154
|
+
entry = meta.get(str(folder / n))
|
|
155
|
+
if not isinstance(entry, dict):
|
|
156
|
+
continue
|
|
157
|
+
params = {k: v for k, v in entry.items()
|
|
158
|
+
if k not in ("order", "project", "environment") and not (isinstance(k, str) and k.startswith("__"))
|
|
159
|
+
# unpainted (alpha-0) tint: no override, the row keeps its own
|
|
160
|
+
and not (k == "tint" and isinstance(v, (tuple, list))
|
|
161
|
+
and len(v) >= 4 and not v[3])}
|
|
162
|
+
if params:
|
|
163
|
+
desired[f"__{n}__"] = params
|
|
164
|
+
if isinstance(entry.get("order"), (int, float)):
|
|
165
|
+
orders[n] = entry["order"]
|
|
166
|
+
current = tree.get("__overrides__")
|
|
167
|
+
if desired:
|
|
168
|
+
if current != desired:
|
|
169
|
+
# Wrap entries in the host's bubbling for the raw dunder store,
|
|
170
|
+
# or later UI edits to an existing entry would show but not save
|
|
171
|
+
# (see _LazyOverrideEntry's docstring).
|
|
172
|
+
broot = getattr(tree, "_bubble_root", None)
|
|
173
|
+
if broot is not None:
|
|
174
|
+
desired = install_bubbling(desired, broot)
|
|
175
|
+
tree["__overrides__"] = desired
|
|
176
|
+
changed = True
|
|
177
|
+
elif isinstance(current, dict) and current:
|
|
178
|
+
dict.pop(tree, "__overrides__", None)
|
|
179
|
+
changed = True
|
|
180
|
+
if orders:
|
|
181
|
+
want = sorted(names, key=lambda n: (orders.get(n, float("inf")), n))
|
|
182
|
+
if names != want:
|
|
183
|
+
for n in want:
|
|
184
|
+
dict.__setitem__(tree, n, dict.pop(tree, n))
|
|
185
|
+
changed = True
|
|
186
|
+
for n in names:
|
|
187
|
+
child = tree.get(n)
|
|
188
|
+
if isinstance(child, dict):
|
|
189
|
+
changed |= _apply_meta(child, folder / n, meta)
|
|
190
|
+
return changed
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _collect_meta(tree, folder, meta):
|
|
195
|
+
"""Tree → meta, recursively: read each child's __overrides__ params back
|
|
196
|
+
into the persisted store, and capture drag reordering as `order` stamps.
|
|
197
|
+
Order is stamped only once a folder's key order diverges from the natural
|
|
198
|
+
sorted order (or was stamped before) — an untouched folder saves nothing."""
|
|
199
|
+
names = [n for n in tree if n != "__overrides__"]
|
|
200
|
+
ovs = tree.get("__overrides__")
|
|
201
|
+
ovs = ovs if isinstance(ovs, dict) else {}
|
|
202
|
+
stamp_order = (names != sorted(names)
|
|
203
|
+
or any(isinstance(meta.get(str(folder / n)), dict)
|
|
204
|
+
and "order" in meta[str(folder / n)] for n in names))
|
|
205
|
+
for i, n in enumerate(names):
|
|
206
|
+
path = str(folder / n)
|
|
207
|
+
entry_src = ovs.get(f"__{n}__")
|
|
208
|
+
entry = {k: v for k, v in entry_src.items()
|
|
209
|
+
if not (isinstance(k, str) and k.startswith("__"))} \
|
|
210
|
+
if isinstance(entry_src, dict) else {}
|
|
211
|
+
old = meta.get(path)
|
|
212
|
+
if stamp_order:
|
|
213
|
+
entry["order"] = i
|
|
214
|
+
elif isinstance(old, dict) and "order" in old:
|
|
215
|
+
entry["order"] = old["order"]
|
|
216
|
+
if entry:
|
|
217
|
+
if old != entry:
|
|
218
|
+
from meltygui.models.file_meta import FileMeta
|
|
219
|
+
meta[path] = FileMeta(entry)
|
|
220
|
+
elif old is not None:
|
|
221
|
+
meta.pop(path, None)
|
|
222
|
+
child = tree.get(n)
|
|
223
|
+
if isinstance(child, dict):
|
|
224
|
+
_collect_meta(child, folder / n, meta)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def initialize_file_metadata(vis, root):
|
|
228
|
+
skip_suffixes = {".pyc"}
|
|
229
|
+
from meltygui.models.file_meta import FileMeta
|
|
230
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
231
|
+
meta = _file_meta(root)
|
|
232
|
+
if meta is None:
|
|
233
|
+
return
|
|
234
|
+
# Upgrade entries deserialized as plain dicts (older saves / from_dict)
|
|
235
|
+
# to FileMeta, keeping their stored values.
|
|
236
|
+
# Retroactive (09-02): stored tints that were never a user's pick - the
|
|
237
|
+
# old bluish-grey class default, the tab tint's fallback colour (which got
|
|
238
|
+
# written back onto 184 files), or an alpha-0 default — are dropped, so
|
|
239
|
+
# those files read as unpainted (FileMeta.tint, black background).
|
|
240
|
+
unpainted = {tuple(round(c, 3) for c in FileMeta._LEGACY_DEFAULT_TINT[:3]),
|
|
241
|
+
tuple(round(c, 3) for c in Toggles.CodeEditor.tab_tint_fallback[:3])}
|
|
242
|
+
for key, entry in list(meta.items()):
|
|
243
|
+
if isinstance(entry, dict) and not isinstance(entry, FileMeta):
|
|
244
|
+
meta[key] = FileMeta(entry)
|
|
245
|
+
stored = dict.get(meta[key], "tint") if isinstance(meta[key], dict) else None
|
|
246
|
+
if stored is None:
|
|
247
|
+
continue
|
|
248
|
+
if (FileMeta.painted_tint({"tint": stored}) is None
|
|
249
|
+
or tuple(round(c, 3) for c in stored[:3]) in unpainted):
|
|
250
|
+
dict.pop(meta[key], "tint", None)
|
|
251
|
+
meta.touch(key) # raw dict op: tell the shared store
|
|
252
|
+
from meltygui.core.runtime.paths import PACKAGE_ROOT
|
|
253
|
+
module_root = PACKAGE_ROOT # .../src
|
|
254
|
+
for p in module_root.rglob("*"):
|
|
255
|
+
rel = p.relative_to(module_root).parts
|
|
256
|
+
if any(part == "__pycache__" or part.startswith(".") for part in rel):
|
|
257
|
+
continue
|
|
258
|
+
if p.suffix in skip_suffixes:
|
|
259
|
+
continue
|
|
260
|
+
meta.setdefault(str(p), FileMeta())
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def list_directory(directory, show_hidden=False):
|
|
265
|
+
"""The rows of `directory`: [(Path, is_dir)] — folders first, then files,
|
|
266
|
+
each case-insensitive by name. An unreadable directory lists empty."""
|
|
267
|
+
try:
|
|
268
|
+
entries = list(os.scandir(directory))
|
|
269
|
+
except OSError:
|
|
270
|
+
return []
|
|
271
|
+
folders, files = [], []
|
|
272
|
+
for entry in entries:
|
|
273
|
+
if not show_hidden and entry.name.startswith("."):
|
|
274
|
+
continue
|
|
275
|
+
try:
|
|
276
|
+
is_dir = entry.is_dir()
|
|
277
|
+
except OSError:
|
|
278
|
+
is_dir = False
|
|
279
|
+
(folders if is_dir else files).append(entry.name)
|
|
280
|
+
key = str.casefold
|
|
281
|
+
return ([(Path(directory) / n, True) for n in sorted(folders, key=key)]
|
|
282
|
+
+ [(Path(directory) / n, False) for n in sorted(files, key=key)])
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _dir_mtime_ns(directory):
|
|
286
|
+
try:
|
|
287
|
+
return os.stat(directory).st_mtime_ns
|
|
288
|
+
except OSError:
|
|
289
|
+
return -1
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import pwd
|
|
6
|
+
import subprocess
|
|
7
|
+
import getpass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_name(self, input_str):
|
|
11
|
+
"""
|
|
12
|
+
Convert various string formats to properly capitalized space-separated string.
|
|
13
|
+
Examples:
|
|
14
|
+
test_string -> Test String
|
|
15
|
+
testString -> Test String
|
|
16
|
+
TestString -> Test String
|
|
17
|
+
test-string -> Test String
|
|
18
|
+
"""
|
|
19
|
+
# First replace any hyphens with underscores for consistent handling
|
|
20
|
+
input_str = input_str.replace('-', '_')
|
|
21
|
+
|
|
22
|
+
# Split on underscores if they exist
|
|
23
|
+
if '_' in input_str:
|
|
24
|
+
words = input_str.split('_')
|
|
25
|
+
else:
|
|
26
|
+
# Handle camelCase by adding space before capital letters
|
|
27
|
+
words = []
|
|
28
|
+
current_word = input_str[0]
|
|
29
|
+
for char in input_str[1:]:
|
|
30
|
+
if char.isupper():
|
|
31
|
+
words.append(current_word)
|
|
32
|
+
current_word = char
|
|
33
|
+
else:
|
|
34
|
+
current_word += char
|
|
35
|
+
words.append(current_word)
|
|
36
|
+
|
|
37
|
+
# Capitalize each word and join with spaces
|
|
38
|
+
return ' '.join(word.capitalize() for word in words)
|
|
39
|
+
|
|
40
|
+
def create_models_folder(password_callback=None, subfolder="latent-descent"):
|
|
41
|
+
"""
|
|
42
|
+
Creates a folder at /models and a subfolder inside it, both owned by the current user in Linux.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
password_callback (callable, optional): A function that returns the sudo password.
|
|
46
|
+
If None, will use getpass to prompt the user.
|
|
47
|
+
subfolder (str, optional): Name of the subfolder to create inside /models.
|
|
48
|
+
Defaults to "sub".
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
bool: True if successful, False otherwise
|
|
52
|
+
"""
|
|
53
|
+
# Check if both directories already exist
|
|
54
|
+
if os.path.exists('/models') and os.path.exists(f'/models/{subfolder}'):
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Get current username
|
|
59
|
+
current_user = pwd.getpwuid(os.getuid()).pw_name
|
|
60
|
+
|
|
61
|
+
# Get password using the callback or default to getpass
|
|
62
|
+
if password_callback is None:
|
|
63
|
+
password = getpass.getpass("Enter sudo password: ")
|
|
64
|
+
else:
|
|
65
|
+
password = password_callback()
|
|
66
|
+
|
|
67
|
+
# Create the commands to run
|
|
68
|
+
commands = [
|
|
69
|
+
["mkdir", "-p", f"/models/{subfolder}"], # -p will create parent dir if needed
|
|
70
|
+
["chown", f"{current_user}:{current_user}", "/models"],
|
|
71
|
+
["chown", f"{current_user}:{current_user}", f"/models/{subfolder}"],
|
|
72
|
+
["chmod", "755", "/models"],
|
|
73
|
+
["chmod", "755", f"/models/{subfolder}"]
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Run each command with sudo using the provided password
|
|
77
|
+
for cmd in commands:
|
|
78
|
+
process = subprocess.Popen(
|
|
79
|
+
["sudo", "-S"] + cmd,
|
|
80
|
+
stdin=subprocess.PIPE,
|
|
81
|
+
stdout=subprocess.PIPE,
|
|
82
|
+
stderr=subprocess.PIPE,
|
|
83
|
+
universal_newlines=True
|
|
84
|
+
)
|
|
85
|
+
stdout, stderr = process.communicate(input=password + "\n")
|
|
86
|
+
|
|
87
|
+
if process.returncode != 0:
|
|
88
|
+
print(f"Command failed: sudo {' '.join(cmd)}")
|
|
89
|
+
print(f"Error: {stderr}")
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
print(f"Successfully created /models directory and /{subfolder} subdirectory owned by {current_user}")
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"Unexpected error: {e}")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def print_ascii_tensor(tensors, border=True, indices=None, spacing=2, names=None):
|
|
100
|
+
"""
|
|
101
|
+
Prints an ASCII representation of one or more PyTorch tensors horizontally.
|
|
102
|
+
- Zeros are replaced with '#' symbols
|
|
103
|
+
- Single-digit numbers are shown as is
|
|
104
|
+
- Multi-digit positive numbers are shown as '+'
|
|
105
|
+
- Multi-digit negative numbers are shown as '-'
|
|
106
|
+
|
|
107
|
+
For tensors with more than 2 dimensions, this function will use the last two dimensions
|
|
108
|
+
by default, or you can specify which indices to use for higher dimensions.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
tensors (torch.Tensor or list): A PyTorch tensor or list of tensors
|
|
112
|
+
border (bool): Whether to add an ASCII border around each tensor (default: True)
|
|
113
|
+
indices (tuple or None): Specific indices to use for dimensions beyond the last two.
|
|
114
|
+
For a 4D tensor, this would be a tuple of 2 indices.
|
|
115
|
+
spacing (int): Number of spaces between tensors (default: 2)
|
|
116
|
+
names (list or None): Optional list of names for each tensor. If provided, must match
|
|
117
|
+
the number of tensors. Names will be displayed above each tensor.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> x = torch.tensor([[1, 0, 0], [1, 1, 0], [1, 1, 1]])
|
|
121
|
+
>>> y = torch.tensor([[0, 0, 2], [0, 3, 2], [4, 3, 2]])
|
|
122
|
+
>>> print_ascii_tensor([x, y], names=["Identity", "Values"])
|
|
123
|
+
Identity Values
|
|
124
|
+
+-----+ +-----+
|
|
125
|
+
| 1 # # | | # # 2 |
|
|
126
|
+
| 1 1 # | | # 3 2 |
|
|
127
|
+
| 1 1 1 | | 4 3 2 |
|
|
128
|
+
+-----+ +-----+
|
|
129
|
+
"""
|
|
130
|
+
# Convert single tensor to list for uniform processing
|
|
131
|
+
if isinstance(tensors, torch.Tensor):
|
|
132
|
+
tensors = [tensors]
|
|
133
|
+
|
|
134
|
+
# Validate names if provided
|
|
135
|
+
if names is not None:
|
|
136
|
+
if len(names) != len(tensors):
|
|
137
|
+
raise ValueError(f"Number of names ({len(names)}) doesn't match number of tensors ({len(tensors)})")
|
|
138
|
+
|
|
139
|
+
# Process each tensor into a list of string rows
|
|
140
|
+
all_tensor_rows = []
|
|
141
|
+
max_heights = []
|
|
142
|
+
tensor_widths = []
|
|
143
|
+
|
|
144
|
+
for tensor_idx, tensor in enumerate(tensors):
|
|
145
|
+
# Handle tensors with more than 2 dimensions
|
|
146
|
+
tensor_dim = tensor.dim()
|
|
147
|
+
if tensor_dim > 2:
|
|
148
|
+
# For tensors with more than 2 dimensions, extract the 2D slice to display
|
|
149
|
+
if indices is None:
|
|
150
|
+
# Default: use first indices for all but the last two dimensions
|
|
151
|
+
slice_indices = tuple([0] * (tensor_dim - 2))
|
|
152
|
+
else:
|
|
153
|
+
# Use provided indices
|
|
154
|
+
if len(indices) != tensor_dim - 2:
|
|
155
|
+
raise ValueError(f"Expected {tensor_dim - 2} indices but got {len(indices)}")
|
|
156
|
+
slice_indices = indices
|
|
157
|
+
|
|
158
|
+
# Extract the 2D slice from the tensor
|
|
159
|
+
tensor_slice = tensor
|
|
160
|
+
for idx in slice_indices:
|
|
161
|
+
tensor_slice = tensor_slice[idx]
|
|
162
|
+
|
|
163
|
+
tensor = tensor_slice
|
|
164
|
+
|
|
165
|
+
# Ensure the tensor is 2D at this point
|
|
166
|
+
if tensor.dim() != 2:
|
|
167
|
+
raise ValueError("Each tensor must have at least 2 dimensions")
|
|
168
|
+
|
|
169
|
+
# Convert tensor to CPU and get its values as a numpy array
|
|
170
|
+
tensor_np = tensor.cpu().numpy()
|
|
171
|
+
|
|
172
|
+
# Convert tensor to string representation
|
|
173
|
+
rows = []
|
|
174
|
+
max_width = 0
|
|
175
|
+
|
|
176
|
+
for row in tensor_np:
|
|
177
|
+
row_str = ""
|
|
178
|
+
for val in row:
|
|
179
|
+
if val == 0:
|
|
180
|
+
row_str += "0 "
|
|
181
|
+
else:
|
|
182
|
+
# Convert to integer if it's a whole number
|
|
183
|
+
if float(val).is_integer():
|
|
184
|
+
val = int(val)
|
|
185
|
+
|
|
186
|
+
# Display single-digit numbers as is, use symbols for multi-digit numbers
|
|
187
|
+
if -9 <= val <= 9:
|
|
188
|
+
row_str += f"{val} "
|
|
189
|
+
elif val > 0:
|
|
190
|
+
row_str += "+ "
|
|
191
|
+
else: # val < 0
|
|
192
|
+
row_str += "- "
|
|
193
|
+
|
|
194
|
+
rows.append(row_str.rstrip()) # Remove trailing space
|
|
195
|
+
max_width = max(max_width, len(row_str.rstrip()))
|
|
196
|
+
|
|
197
|
+
# Add border if needed
|
|
198
|
+
tensor_rows = []
|
|
199
|
+
if border:
|
|
200
|
+
# Create top border
|
|
201
|
+
border_line = "+" + "-" * (max_width + 2) + "+"
|
|
202
|
+
tensor_rows.append(border_line)
|
|
203
|
+
|
|
204
|
+
# Add each row with side borders
|
|
205
|
+
for row in rows:
|
|
206
|
+
# Calculate padding to ensure the right border aligns perfectly
|
|
207
|
+
padding = max_width - len(row)
|
|
208
|
+
tensor_rows.append(f"| {row}{' ' * padding} |")
|
|
209
|
+
|
|
210
|
+
# Create bottom border
|
|
211
|
+
tensor_rows.append(border_line)
|
|
212
|
+
else:
|
|
213
|
+
# Use rows without border
|
|
214
|
+
tensor_rows = rows
|
|
215
|
+
|
|
216
|
+
all_tensor_rows.append(tensor_rows)
|
|
217
|
+
max_heights.append(len(tensor_rows))
|
|
218
|
+
|
|
219
|
+
# Record width of this tensor's rows
|
|
220
|
+
if len(tensor_rows) > 0:
|
|
221
|
+
tensor_widths.append(len(tensor_rows[0]))
|
|
222
|
+
else:
|
|
223
|
+
tensor_widths.append(0)
|
|
224
|
+
|
|
225
|
+
# Print tensor names if provided
|
|
226
|
+
if names is not None:
|
|
227
|
+
name_line = ""
|
|
228
|
+
for tensor_idx, name in enumerate(names):
|
|
229
|
+
# Center name over tensor width
|
|
230
|
+
tensor_width = tensor_widths[tensor_idx]
|
|
231
|
+
|
|
232
|
+
# If name is longer than tensor, truncate or allow overflow
|
|
233
|
+
if len(name) > tensor_width:
|
|
234
|
+
# Let's allow overflow for readability
|
|
235
|
+
centered_name = name
|
|
236
|
+
else:
|
|
237
|
+
# Center the name
|
|
238
|
+
padding = (tensor_width - len(name)) // 2
|
|
239
|
+
centered_name = " " * padding + name
|
|
240
|
+
|
|
241
|
+
name_line += centered_name
|
|
242
|
+
|
|
243
|
+
# Add spacing between tensors (except after the last one)
|
|
244
|
+
if tensor_idx < len(tensors) - 1:
|
|
245
|
+
name_line += " " * spacing
|
|
246
|
+
|
|
247
|
+
print(name_line)
|
|
248
|
+
|
|
249
|
+
# Find the maximum height across all tensors
|
|
250
|
+
max_height = max(max_heights)
|
|
251
|
+
|
|
252
|
+
# Print all tensors horizontally
|
|
253
|
+
for row_idx in range(max_height):
|
|
254
|
+
row_str = ""
|
|
255
|
+
for tensor_idx, tensor_rows in enumerate(all_tensor_rows):
|
|
256
|
+
# If this tensor has fewer rows than the max height, print empty space
|
|
257
|
+
if row_idx < len(tensor_rows):
|
|
258
|
+
row_str += tensor_rows[row_idx]
|
|
259
|
+
else:
|
|
260
|
+
# Add empty space for the width of this tensor's representation
|
|
261
|
+
if len(tensor_rows) > 0: # Make sure tensor has at least one row
|
|
262
|
+
row_str += " " * len(tensor_rows[0])
|
|
263
|
+
|
|
264
|
+
# Add spacing between tensors (except after the last one)
|
|
265
|
+
if tensor_idx < len(all_tensor_rows) - 1:
|
|
266
|
+
row_str += " " * spacing
|
|
267
|
+
|
|
268
|
+
print(row_str)
|
|
269
|
+
print("\n")
|
|
270
|
+
|
|
271
|
+
def split_string_by_token(tokenizer, text):
|
|
272
|
+
if tokenizer is None:
|
|
273
|
+
return text
|
|
274
|
+
|
|
275
|
+
encoding = tokenizer(text, return_tensors="pt", return_attention_mask=True,
|
|
276
|
+
add_special_tokens=False)
|
|
277
|
+
input_ids = encoding['input_ids']
|
|
278
|
+
# iterate over input ids
|
|
279
|
+
sequence_length = input_ids.shape[1]
|
|
280
|
+
batch_size = input_ids.shape[0]
|
|
281
|
+
batches = []
|
|
282
|
+
for b in range(batch_size):
|
|
283
|
+
decoded_tokens = []
|
|
284
|
+
for s in range(sequence_length):
|
|
285
|
+
token = input_ids[b, s]
|
|
286
|
+
token_text = tokenizer.decode(token)
|
|
287
|
+
decoded_tokens.append(token_text)
|
|
288
|
+
batches.append(decoded_tokens)
|
|
289
|
+
return batches
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def format_time(seconds):
|
|
293
|
+
"""
|
|
294
|
+
Convert seconds to a human-readable time string with associated color.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
seconds (float): Time in seconds
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
tuple: (formatted_time_string, color_tuple)
|
|
301
|
+
where color_tuple is (r, g, b) values from 0-1
|
|
302
|
+
"""
|
|
303
|
+
# Handle negative time
|
|
304
|
+
if seconds is None:
|
|
305
|
+
return "Unknown time", (1.0, 0.0, 0.0)
|
|
306
|
+
|
|
307
|
+
if seconds < 0:
|
|
308
|
+
time_str, color = format_time(-seconds)
|
|
309
|
+
return f"-{time_str}", color
|
|
310
|
+
|
|
311
|
+
# Very small time periods - golden yellow (1.0, 0.84, 0)
|
|
312
|
+
golden_yellow = (1.0, 0.84, 0.0)
|
|
313
|
+
if seconds < 1:
|
|
314
|
+
return f"0 seconds", golden_yellow
|
|
315
|
+
|
|
316
|
+
# Seconds - golden yellow (1.0, 0.84, 0)
|
|
317
|
+
if seconds < 60:
|
|
318
|
+
return f"{seconds:.0f} seconds", golden_yellow
|
|
319
|
+
|
|
320
|
+
# Minutes - warm red (0.86, 0.24, 0.2)
|
|
321
|
+
warm_red = (0.96, 0.44, 0.4)
|
|
322
|
+
minutes = seconds / 60
|
|
323
|
+
if minutes < 60:
|
|
324
|
+
return f"{minutes:.1f} minutes", warm_red
|
|
325
|
+
|
|
326
|
+
# Hours - greenish (0.29, 0.71, 0.31)
|
|
327
|
+
greenish = (0.29, 0.71, 0.31)
|
|
328
|
+
hours = minutes / 60
|
|
329
|
+
if hours < 24:
|
|
330
|
+
return f"{hours:.1f} hours", greenish
|
|
331
|
+
|
|
332
|
+
# Days - greenish (0.29, 0.71, 0.31)
|
|
333
|
+
days = hours / 24
|
|
334
|
+
if days < 7:
|
|
335
|
+
return f"{days:.1f} days", greenish
|
|
336
|
+
|
|
337
|
+
# Weeks - greenish (0.29, 0.71, 0.31)
|
|
338
|
+
weeks = days / 7
|
|
339
|
+
if weeks < 4.35: # Approximate weeks in a month
|
|
340
|
+
return f"{weeks:.1f} weeks", greenish
|
|
341
|
+
|
|
342
|
+
# Months - greenish (0.29, 0.71, 0.31)
|
|
343
|
+
months = days / 30.44 # Average days in a month
|
|
344
|
+
if months < 12:
|
|
345
|
+
return f"{months:.1f} months", greenish
|
|
346
|
+
|
|
347
|
+
# Years - yellow orange (0.94, 0.59, 0.2)
|
|
348
|
+
yellow_orange = (0.94, 0.59, 0.2)
|
|
349
|
+
years = days / 365.25
|
|
350
|
+
if years < 10:
|
|
351
|
+
return f"{years:.1f} years", yellow_orange
|
|
352
|
+
|
|
353
|
+
# Decades - dark bluish grey (0.27, 0.35, 0.43)
|
|
354
|
+
dark_bluish_grey = (0.27, 0.35, 0.43)
|
|
355
|
+
if years < 100:
|
|
356
|
+
return f"{years:.1f} years", dark_bluish_grey
|
|
357
|
+
|
|
358
|
+
if years < 1_000:
|
|
359
|
+
return f"{years:.1f} years", dark_bluish_grey
|
|
360
|
+
|
|
361
|
+
if years < 1_000_000:
|
|
362
|
+
return f"{years / 1_000:.1f} thousand years", dark_bluish_grey
|
|
363
|
+
|
|
364
|
+
# Millions of years - dark red (0.55, 0.12, 0.12)
|
|
365
|
+
dark_red = (0.55, 0.12, 0.12)
|
|
366
|
+
if years < 1_000_000_000:
|
|
367
|
+
return f"{years / 1_000_000:.1f} million years", dark_red
|
|
368
|
+
|
|
369
|
+
# Billions of years and beyond - blue (0.12, 0.31, 0.71)
|
|
370
|
+
blue = (0.12, 0.31, 0.71)
|
|
371
|
+
|
|
372
|
+
# Use descriptive strings instead of numerical values for billion+ years
|
|
373
|
+
if years < 4.5e9: # Age of Earth ~4.5 billion years
|
|
374
|
+
return "Age of the Earth", blue
|
|
375
|
+
|
|
376
|
+
if years < 5e9: # Approximate time until Sun begins to expand significantly
|
|
377
|
+
return "Time until the Sun begins expanding", blue
|
|
378
|
+
|
|
379
|
+
if years < 7.6e9: # Time until Sun engulfs Earth's orbit
|
|
380
|
+
return "Time until the Sun engulfs Earth", blue
|
|
381
|
+
|
|
382
|
+
if years < 1e14: # Time until all stars burn out
|
|
383
|
+
return "Era of stellar extinction", blue
|
|
384
|
+
|
|
385
|
+
if years < 1e40: # Deep time
|
|
386
|
+
return "Approaching heat death of the universe", blue
|
|
387
|
+
|
|
388
|
+
return "Beyond heat death of the universe", blue
|
|
389
|
+
|
|
390
|
+
|