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,352 @@
|
|
|
1
|
+
"""Dict-first UI contract. Reconcile once AFTER an edit, not inside pop/del.
|
|
2
|
+
|
|
3
|
+
Backends publish inbound changes on the owner thread via drain(). The UI
|
|
4
|
+
only mutates dicts; no RPC or remote lifecycle vocabulary crosses this boundary.
|
|
5
|
+
"""
|
|
6
|
+
import queue
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from meltygui.chat.messages import Message
|
|
11
|
+
from meltygui.chat.messages import UserMessage
|
|
12
|
+
from meltygui.chat.messages import user_message
|
|
13
|
+
from meltygui.chat.messages import input_text
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def writer_conflict(error):
|
|
17
|
+
message = str(error or "").lower()
|
|
18
|
+
return ("active writer" in message or
|
|
19
|
+
("session" in message and "already in use" in message))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def epoch_seconds(value):
|
|
23
|
+
"""A provider's timestamp as epoch seconds: seconds or milliseconds
|
|
24
|
+
(anything past 1e11 is milliseconds), an ISO-8601 string, or 0 when
|
|
25
|
+
absent / unreadable."""
|
|
26
|
+
if value is None or value == "":
|
|
27
|
+
return 0.0
|
|
28
|
+
if isinstance(value, str):
|
|
29
|
+
try:
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
32
|
+
if parsed.tzinfo is None:
|
|
33
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
34
|
+
return parsed.timestamp()
|
|
35
|
+
except ValueError:
|
|
36
|
+
return 0.0
|
|
37
|
+
try:
|
|
38
|
+
number = float(value)
|
|
39
|
+
except (TypeError, ValueError):
|
|
40
|
+
return 0.0
|
|
41
|
+
return number / 1000.0 if number > 1e11 else number
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def apply_queue_edit(chat, operation, value=None):
|
|
45
|
+
"""Queue edits are idempotent so a reconnect can safely replay them."""
|
|
46
|
+
queued = chat.setdefault("queued_messages", {})
|
|
47
|
+
if operation == "queue_message":
|
|
48
|
+
identifier, message, interrupt = value
|
|
49
|
+
if identifier in queued or identifier in chat["messages"] or identifier in chat.sent:
|
|
50
|
+
return
|
|
51
|
+
if interrupt:
|
|
52
|
+
chat["queue_paused"] = False
|
|
53
|
+
busy = chat["running"] or chat.turn_id or "send" in chat.inflight
|
|
54
|
+
if busy:
|
|
55
|
+
chat["queued_messages"] = {identifier: message, **queued}
|
|
56
|
+
chat["running"] = False
|
|
57
|
+
else:
|
|
58
|
+
chat["messages"][identifier] = message
|
|
59
|
+
chat.scanned = -1
|
|
60
|
+
if chat.get("retryable_error"):
|
|
61
|
+
chat.error = None
|
|
62
|
+
chat["retryable_error"] = False
|
|
63
|
+
else:
|
|
64
|
+
queued[identifier] = message
|
|
65
|
+
elif operation == "send_queued":
|
|
66
|
+
message = queued.pop(value, None)
|
|
67
|
+
if message is not None:
|
|
68
|
+
apply_queue_edit(chat, "queue_message", (value, message, True))
|
|
69
|
+
elif operation == "cancel_queued":
|
|
70
|
+
queued.pop(value, None)
|
|
71
|
+
elif operation == "resume_queue":
|
|
72
|
+
chat["queue_paused"] = False
|
|
73
|
+
elif operation == "stop_queue":
|
|
74
|
+
chat["queue_paused"] = True
|
|
75
|
+
chat["running"] = False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Chat(dict):
|
|
79
|
+
"""``updated`` is the conversation's last activity as epoch seconds (0 =
|
|
80
|
+
unknown): the provider's listing stamps it, a backend bumps it as a turn
|
|
81
|
+
streams, the proxy bumps it on send. The sidebar's age filter reads it."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, value=None, remote_id=None):
|
|
84
|
+
super().__init__(title="New conversation", project="", messages={}, requests={}, running=False,
|
|
85
|
+
updated=0.0)
|
|
86
|
+
self.update(value or {})
|
|
87
|
+
self.remote_id = remote_id
|
|
88
|
+
self.applied_tint = None
|
|
89
|
+
self.loaded = remote_id is None
|
|
90
|
+
self.loading = False
|
|
91
|
+
self.refreshing = False # re-reading the session another client is writing
|
|
92
|
+
self.inflight = set()
|
|
93
|
+
self.sent = set()
|
|
94
|
+
self.saved_title = self["title"]
|
|
95
|
+
self.turn_id = None
|
|
96
|
+
self.resumed = remote_id is None
|
|
97
|
+
self.error = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ChatProxy(dict):
|
|
101
|
+
def _queue_edit(self, key, operation, value=None):
|
|
102
|
+
apply_queue_edit(self[key], operation, value)
|
|
103
|
+
self.revision += 1
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
def queue_message(self, key, message, interrupt=False):
|
|
107
|
+
import uuid
|
|
108
|
+
return self._queue_edit(key, "queue_message", (str(uuid.uuid4()), message, interrupt))
|
|
109
|
+
|
|
110
|
+
def stop_chat(self, key):
|
|
111
|
+
self._queue_edit(key, "stop_queue")
|
|
112
|
+
|
|
113
|
+
def cancel_queued(self, key, identifier):
|
|
114
|
+
self._queue_edit(key, "cancel_queued", identifier)
|
|
115
|
+
|
|
116
|
+
def resume_queue(self, key):
|
|
117
|
+
self._queue_edit(key, "resume_queue")
|
|
118
|
+
|
|
119
|
+
def send_queued(self, key, identifier):
|
|
120
|
+
return self._queue_edit(key, "send_queued", identifier)
|
|
121
|
+
|
|
122
|
+
def __init__(self, account_id, metadata=None, wake=None):
|
|
123
|
+
super().__init__()
|
|
124
|
+
self.session_version = 3
|
|
125
|
+
self.account_id = account_id
|
|
126
|
+
from meltygui.chat.metadata import shared_metadata
|
|
127
|
+
self.metadata = metadata if metadata is not None else shared_metadata()
|
|
128
|
+
self.projects = self.metadata.account(account_id)["projects"]
|
|
129
|
+
self.wake = wake or (lambda: None)
|
|
130
|
+
self.events = queue.Queue()
|
|
131
|
+
self.jobs = queue.Queue()
|
|
132
|
+
self.closed = False
|
|
133
|
+
self.loading = True
|
|
134
|
+
self.revision = 0
|
|
135
|
+
self.error = None
|
|
136
|
+
self.known = {}
|
|
137
|
+
self.applied_order = None
|
|
138
|
+
self.refreshed = 0.0 # when the window last asked for `refresh`
|
|
139
|
+
self._applied_revision = -1
|
|
140
|
+
self.worker = threading.Thread(target=self._work, daemon=True, name="chat-backend")
|
|
141
|
+
self.worker.start()
|
|
142
|
+
|
|
143
|
+
def __setitem__(self, key, value):
|
|
144
|
+
chat = value if isinstance(value, Chat) else Chat(value)
|
|
145
|
+
chat.metadata = self.metadata.conversation(self.account_id, chat["project"], key)
|
|
146
|
+
super().__setitem__(key, chat)
|
|
147
|
+
|
|
148
|
+
def __getitem__(self, key):
|
|
149
|
+
chat = super().__getitem__(key)
|
|
150
|
+
self.active_key = key
|
|
151
|
+
self._load_history(key, chat)
|
|
152
|
+
return chat
|
|
153
|
+
|
|
154
|
+
def _load_history(self, key, chat):
|
|
155
|
+
operation = getattr(chat, "pending_history", None)
|
|
156
|
+
if ((not chat.loaded or operation) and not chat.loading and not chat.refreshing
|
|
157
|
+
and not chat["running"] and "send" not in chat.inflight):
|
|
158
|
+
chat.loading = not chat.loaded
|
|
159
|
+
chat.refreshing = chat.loaded
|
|
160
|
+
chat.pending_history = None
|
|
161
|
+
self.submit(operation or "hydrate", key)
|
|
162
|
+
|
|
163
|
+
def refresh_history(self, key, operation="hydrate"):
|
|
164
|
+
"""Keep inactive transcripts cached; refresh them when displayed again."""
|
|
165
|
+
chat = dict.get(self, key)
|
|
166
|
+
if chat is not None and chat.loaded:
|
|
167
|
+
chat.pending_history = operation
|
|
168
|
+
if getattr(self, "active_key", None) == key:
|
|
169
|
+
self._load_history(key, chat)
|
|
170
|
+
|
|
171
|
+
def publish(self, kind, value):
|
|
172
|
+
if not self.closed:
|
|
173
|
+
needs_wake = self.events.empty()
|
|
174
|
+
self.events.put((kind, value))
|
|
175
|
+
if needs_wake:
|
|
176
|
+
self.wake()
|
|
177
|
+
|
|
178
|
+
def submit(self, operation, *args):
|
|
179
|
+
if not self.closed:
|
|
180
|
+
self.jobs.put((operation, args))
|
|
181
|
+
|
|
182
|
+
def _work(self):
|
|
183
|
+
try:
|
|
184
|
+
self.connect()
|
|
185
|
+
while not self.closed:
|
|
186
|
+
try:
|
|
187
|
+
job = self.jobs.get(timeout=5.0)
|
|
188
|
+
except queue.Empty:
|
|
189
|
+
self.refresh()
|
|
190
|
+
continue
|
|
191
|
+
if job is None:
|
|
192
|
+
break
|
|
193
|
+
operation, args = job
|
|
194
|
+
try:
|
|
195
|
+
self.execute(operation, *args)
|
|
196
|
+
except Exception as error:
|
|
197
|
+
self.publish("failure", (operation, args, str(error)))
|
|
198
|
+
except Exception as error:
|
|
199
|
+
self.publish("failure", ("connect", (), str(error)))
|
|
200
|
+
finally:
|
|
201
|
+
self.disconnect()
|
|
202
|
+
|
|
203
|
+
def drain(self):
|
|
204
|
+
changed = False
|
|
205
|
+
while True:
|
|
206
|
+
try:
|
|
207
|
+
kind, value = self.events.get_nowait()
|
|
208
|
+
except queue.Empty:
|
|
209
|
+
break
|
|
210
|
+
self.receive(kind, value)
|
|
211
|
+
changed = True
|
|
212
|
+
if changed:
|
|
213
|
+
self.revision += 1
|
|
214
|
+
return changed
|
|
215
|
+
|
|
216
|
+
def reconcile(self):
|
|
217
|
+
"""A pop+insert is order-only. A missing key at this boundary is removal."""
|
|
218
|
+
# Normalize dict.update/setdefault insertions before mirroring them.
|
|
219
|
+
for key, value in list(self.items()):
|
|
220
|
+
if not isinstance(value, Chat):
|
|
221
|
+
self[key] = value
|
|
222
|
+
# The metadata passes walk every conversation: only if the set,
|
|
223
|
+
# the order, an event batch or a tint override changed since last time.
|
|
224
|
+
keys = list(self)
|
|
225
|
+
if (keys != self.applied_order or self.revision != self._applied_revision or self._tints_changed()):
|
|
226
|
+
self.metadata.collect(self.account_id, self)
|
|
227
|
+
self.metadata.apply(self.account_id, self)
|
|
228
|
+
self._applied_revision = self.revision
|
|
229
|
+
for key, chat in list(self.known.items()):
|
|
230
|
+
if key not in self and "archive" not in chat.inflight and "create" not in chat.inflight:
|
|
231
|
+
chat.inflight.add("archive")
|
|
232
|
+
if chat.remote_id:
|
|
233
|
+
self.submit("archive", key, chat.remote_id)
|
|
234
|
+
else:
|
|
235
|
+
self.known.pop(key, None)
|
|
236
|
+
for key, value in list(self.items()):
|
|
237
|
+
if not isinstance(value, Chat):
|
|
238
|
+
self[key] = value
|
|
239
|
+
chat = dict.__getitem__(self, key)
|
|
240
|
+
self.known[key] = chat
|
|
241
|
+
if chat.error:
|
|
242
|
+
continue
|
|
243
|
+
if not chat.remote_id:
|
|
244
|
+
if "create" not in chat.inflight:
|
|
245
|
+
chat.inflight.add("create")
|
|
246
|
+
chat["updated"] = chat["updated"] or time.time()
|
|
247
|
+
self.submit("create", key, chat["project"], chat["title"])
|
|
248
|
+
continue
|
|
249
|
+
if chat["title"] != chat.saved_title and "rename" not in chat.inflight:
|
|
250
|
+
chat.inflight.add("rename")
|
|
251
|
+
self.submit("rename", key, chat.remote_id, chat["title"])
|
|
252
|
+
if not chat["running"] and "send" in chat.inflight:
|
|
253
|
+
chat.inflight.add("interrupt_requested")
|
|
254
|
+
if not chat["running"] and chat.turn_id and "interrupt" not in chat.inflight:
|
|
255
|
+
chat.inflight.add("interrupt")
|
|
256
|
+
self.submit("interrupt", key, chat.remote_id, chat.turn_id)
|
|
257
|
+
if (chat.loaded and not chat["running"] and not chat.turn_id and "send" not in chat.inflight
|
|
258
|
+
and not chat.get("queue_paused") and not chat.get("locked") and not chat.get("external_busy")
|
|
259
|
+
and chat.get("queued_messages")
|
|
260
|
+
and not any(identifier not in chat.sent and message.get("role") == "user"
|
|
261
|
+
for identifier, message in chat["messages"].items())):
|
|
262
|
+
identifier = next(iter(chat["queued_messages"]))
|
|
263
|
+
chat["messages"][identifier] = chat["queued_messages"].pop(identifier)
|
|
264
|
+
self.revision += 1
|
|
265
|
+
if (chat.loaded and not chat["running"] and not chat.turn_id and "send" not in chat.inflight
|
|
266
|
+
and (len(chat["messages"]), len(chat.sent)) != getattr(chat, "scanned", None)):
|
|
267
|
+
# A prompt can remain after the previous send was acknowledged,
|
|
268
|
+
# even if the transcript did not grow in the meantime.
|
|
269
|
+
chat.scanned = (len(chat["messages"]), len(chat.sent))
|
|
270
|
+
for message_id, message in chat["messages"].items():
|
|
271
|
+
# Adopt dict-inserted prompts from UI callers at the model boundary.
|
|
272
|
+
if not isinstance(message, Message) and message.get("role") == "user":
|
|
273
|
+
message = chat["messages"][message_id] = (UserMessage("userMessage", content=message["content"])
|
|
274
|
+
if "content" in message else user_message(message.get("text", "")))
|
|
275
|
+
if message_id not in chat.sent and message.get("role") == "user":
|
|
276
|
+
chat.inflight.add("send")
|
|
277
|
+
chat["running"] = True
|
|
278
|
+
chat["updated"] = time.time()
|
|
279
|
+
chat["last_user_at"] = chat["updated"]
|
|
280
|
+
self.submit("send", key, chat.remote_id, message_id, input_text(message), chat.resumed)
|
|
281
|
+
break
|
|
282
|
+
for request_id, request in chat["requests"].items():
|
|
283
|
+
if "answer" in request and not request.get("submitted"):
|
|
284
|
+
request["submitted"] = True
|
|
285
|
+
self.submit("answer", key, request_id, request["answer"])
|
|
286
|
+
|
|
287
|
+
def _tints_changed(self):
|
|
288
|
+
"""A tint edited on a chat's metadata entry (the sidebar's chip) or
|
|
289
|
+
pushed through its __overrides__ since the last apply."""
|
|
290
|
+
for chat in self.values():
|
|
291
|
+
applied = getattr(chat, "applied_tint", None)
|
|
292
|
+
if chat.get("__overrides__", {}).get("tint") != applied:
|
|
293
|
+
return True
|
|
294
|
+
entry = getattr(chat, "metadata", None)
|
|
295
|
+
if entry is not None and entry.get("tint") != applied:
|
|
296
|
+
return True
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
def fork(self, key):
|
|
300
|
+
import uuid
|
|
301
|
+
source = self[key]
|
|
302
|
+
if not source.remote_id:
|
|
303
|
+
return None
|
|
304
|
+
new_key = str(uuid.uuid4())
|
|
305
|
+
self[new_key] = {"title": source["title"] + " (fork)", "project": source["project"],
|
|
306
|
+
"created_at": time.time(), "updated": time.time()}
|
|
307
|
+
chat = dict.__getitem__(self, new_key)
|
|
308
|
+
chat.loaded, chat.loading = False, True
|
|
309
|
+
chat.inflight.add("create")
|
|
310
|
+
self.known[new_key] = chat
|
|
311
|
+
for field in ("model", "permissions", "effort", "model_explicit", "model_selected_at", "permissions_selected_at",
|
|
312
|
+
"effort_selected_at", "service_tier", "service_tier_selected_at"):
|
|
313
|
+
if field in source.metadata:
|
|
314
|
+
chat.metadata[field] = source.metadata[field]
|
|
315
|
+
if "codex_settings" in source:
|
|
316
|
+
chat["codex_settings"] = dict(source["codex_settings"])
|
|
317
|
+
chat["codex_settings_at"] = source.get("codex_settings_at", 0)
|
|
318
|
+
self.submit("fork", new_key, source.remote_id, source["project"], chat["title"])
|
|
319
|
+
return new_key
|
|
320
|
+
|
|
321
|
+
def finish_fork(self, key, remote_id):
|
|
322
|
+
chat = self.known.get(key)
|
|
323
|
+
if chat is not None:
|
|
324
|
+
chat.remote_id = remote_id
|
|
325
|
+
chat.metadata["remote_id"] = remote_id
|
|
326
|
+
chat.inflight.discard("create")
|
|
327
|
+
chat.loading = False
|
|
328
|
+
chat.fresh = False
|
|
329
|
+
chat.resumed = False
|
|
330
|
+
|
|
331
|
+
def refresh(self):
|
|
332
|
+
"""Look for activity outside this window — a session another client
|
|
333
|
+
(a terminal) is writing — and publish it: a chat's ``updated`` moves,
|
|
334
|
+
a new session is listed. The window asks every few seconds. Default:
|
|
335
|
+
nothing to look at."""
|
|
336
|
+
|
|
337
|
+
def close(self):
|
|
338
|
+
self.closed = True
|
|
339
|
+
self.jobs.put(None)
|
|
340
|
+
# Transport closes on the worker after its bounded in-flight call.
|
|
341
|
+
|
|
342
|
+
def connect(self):
|
|
343
|
+
raise NotImplementedError
|
|
344
|
+
|
|
345
|
+
def execute(self, operation, *args):
|
|
346
|
+
raise NotImplementedError
|
|
347
|
+
|
|
348
|
+
def receive(self, kind, value):
|
|
349
|
+
raise NotImplementedError
|
|
350
|
+
|
|
351
|
+
def disconnect(self):
|
|
352
|
+
pass
|