meltygui 0.1.0__py3-none-any.whl → 0.1.2__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 +15 -2
- meltygui/accounts/internet_accounts.py +27 -17
- meltygui/chat/__init__.py +26 -10
- meltygui/chat/__main__.py +5 -0
- meltygui/chat/assets/source-icons/LICENSE +21 -0
- meltygui/chat/assets/source-icons/README.md +4 -0
- meltygui/chat/assets/source-icons/claude.png +0 -0
- meltygui/chat/assets/source-icons/claude.svg +1 -0
- meltygui/chat/assets/source-icons/codex.png +0 -0
- meltygui/chat/assets/source-icons/codex.svg +1 -0
- meltygui/chat/auto_titles.py +79 -0
- meltygui/chat/backends.py +1 -1
- meltygui/chat/chat_interface.py +49 -22
- meltygui/chat/chat_service.py +517 -0
- meltygui/chat/claude_code.py +1054 -0
- meltygui/chat/codex_proxy.py +11 -0
- meltygui/chat/codex_settings.py +26 -0
- meltygui/chat/main.py +202 -0
- meltygui/chat/metadata.py +4 -1
- meltygui/code/chain_converters.py +2 -2
- meltygui/code/codec_registry.py +40 -0
- meltygui/code/core_syntax.py +15 -0
- meltygui/code/file_converters.py +40 -2
- meltygui/code/live_view.py +9 -2
- meltygui/code/melty_scan.py +257 -5
- meltygui/code/new_codecs.py +69 -32
- meltygui/code/new_converters.py +19 -2
- meltygui/core/README.md +8 -5
- meltygui/core/automation/input_recording_core.py +60 -0
- meltygui/core/automation/input_replay.py +180 -0
- meltygui/core/automation/mcp_server.py +5 -4
- meltygui/core/automation/orchestration_core.py +6 -4
- meltygui/core/cache/invalidation_decoration.py +24 -0
- meltygui/core/cache/invalidation_tracker.py +39 -0
- meltygui/core/cache/mask_batch.py +176 -0
- meltygui/core/cache/parameter_dependencies.py +52 -0
- meltygui/core/cache/tile_cache.py +535 -442
- meltygui/core/cache/tile_marks.py +96 -0
- meltygui/core/conversion/dict_conversion.py +3 -0
- meltygui/core/conversion/load_save_v2.py +47 -21
- meltygui/core/core_render.py +165 -339
- meltygui/core/definition_hotswap.py +54 -0
- meltygui/core/diagnostics/edge_motion_guard.py +621 -0
- meltygui/core/diagnostics/fps_counter.py +13 -27
- meltygui/core/diagnostics/geometry_jitter.py +208 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +9 -3
- meltygui/core/diagnostics/resize_trace.py +4 -2
- meltygui/core/files/file_explorer_core.py +45 -0
- meltygui/core/files/file_tree_core.py +1 -1
- meltygui/core/files/path_icons.py +76 -0
- meltygui/core/graphics/cuda_context_core.py +6 -0
- meltygui/core/graphics/cuda_interop_core.py +32 -0
- meltygui/core/graphics/overlay_renderer.py +31 -7
- meltygui/core/input/drag_drop_core.py +16 -6
- meltygui/core/input/input_handler.py +71 -23
- meltygui/core/input/pynput_backend.py +6 -3
- meltygui/core/layout/column_core.py +137 -34
- meltygui/core/layout/dropdown_core.py +3 -2
- meltygui/core/layout/edge_constraints.py +6 -0
- meltygui/core/layout/header_runtime.py +1 -1
- meltygui/core/layout/layout_core.py +1 -1
- meltygui/core/layout/tile_links.py +180 -0
- meltygui/core/layout/tile_manager_core.py +221 -44
- meltygui/core/layout/tile_resize.py +54 -0
- meltygui/core/melty.py +336 -86
- meltygui/core/module_map.json +22 -3
- meltygui/core/rendering/fast_view.py +538 -0
- meltygui/core/rendering/injected_state.py +62 -0
- meltygui/core/rendering/mode.py +305 -259
- meltygui/core/rendering/modes.py +0 -5
- meltygui/core/rendering/overlay.py +353 -0
- meltygui/core/rendering/parameter_core.py +4 -0
- meltygui/core/rendering/render_dispatch.py +13 -46
- meltygui/core/rendering/render_funcs.py +0 -1
- meltygui/core/rendering/view_identity.py +199 -0
- meltygui/core/runtime/app.py +211 -30
- meltygui/core/runtime/app_settings.py +183 -0
- meltygui/core/runtime/background.py +10 -1
- meltygui/core/runtime/launch_override.py +223 -0
- meltygui/core/runtime/toggles.py +69 -7
- meltygui/core/styling/fonts.py +1 -1
- meltygui/core/styling/warm_start.py +32 -0
- meltygui/core/windowing/dock_core.py +3 -3
- meltygui/core/windowing/geometry_feed.py +97 -5
- meltygui/core/windowing/glfw_utils.py +113 -27
- meltygui/core/windowing/os_frame.py +59 -11
- meltygui/core/windowing/surface.py +67 -3
- meltygui/core/windowing/titlebar.py +122 -39
- meltygui/core/windowing/window_visibility.py +30 -0
- meltygui/editor/file_header.py +1 -1
- meltygui/editor/live_view_views.py +1 -1
- meltygui/editor/pending_save.py +34 -3
- meltygui/editor/source_preview.py +17 -1
- meltygui/editor/text_editor.py +11 -4
- meltygui/editor/usage_picker.py +2 -2
- meltygui/examples/settings_demo.py +28 -0
- meltygui/examples/tile_manager_demo.py +19 -4
- meltygui/examples/voxel_transfer_paths.py +213 -0
- meltygui/files/fast_file_explorer.py +4 -6
- meltygui/graphics/__init__.py +1 -2
- meltygui/model/breakpoint_model.py +67 -0
- meltygui/model/chat_folder_model.py +163 -0
- meltygui/model/chat_model.py +98 -6
- meltygui/model/code_dict_model.py +870 -0
- meltygui/model/cuda_buffer_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +1 -1
- meltygui/model/cuda_texture_model.py +80 -1
- meltygui/model/dropdown_model.py +16 -1
- meltygui/model/file_location_model.py +118 -0
- meltygui/model/folder_icon_model.py +348 -0
- meltygui/model/format_model.py +4 -3
- meltygui/model/input_recording_model.py +418 -0
- meltygui/model/lut_model.py +113 -3
- meltygui/model/source_snapshot_model.py +288 -0
- meltygui/model/tensor_model.py +12 -1
- meltygui/model/texture_model.py +113 -9
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +29 -10
- meltygui/model/trace_report_model.py +38 -1
- meltygui/models/file_meta.py +96 -9
- meltygui/state/chat_state.py +21 -1
- meltygui/state/core_undo.py +0 -4
- meltygui/state/file_state.py +6 -2
- meltygui/state/new_core_model.py +109 -63
- meltygui/state/path_icon_state.py +23 -0
- meltygui/state/texture_state.py +21 -0
- meltygui/state/trace_state.py +4 -1
- meltygui/state/view_reference.py +20 -0
- meltygui/view/account_view.py +1 -1
- meltygui/view/action_view.py +1 -1
- meltygui/view/chat_decoration_view.py +1 -1
- meltygui/view/chat_sidebar_view.py +440 -0
- meltygui/view/chat_source_icon_view.py +48 -0
- meltygui/view/chat_view.py +183 -291
- meltygui/view/code_view.py +62 -69
- meltygui/view/collection_view.py +524 -197
- meltygui/view/color_view.py +218 -3
- meltygui/view/control_view.py +64 -16
- meltygui/view/decoration_view.py +5 -4
- meltygui/view/dropdown_view.py +305 -24
- meltygui/view/file_icon_view.py +60 -0
- meltygui/view/file_view.py +344 -112
- meltygui/view/graph_view.py +3 -0
- meltygui/view/header_view.py +78 -64
- meltygui/view/input_recording_view.py +50 -0
- meltygui/view/inspection_view.py +81 -12
- meltygui/view/layout_view.py +3 -3
- meltygui/view/menu_view.py +18 -2
- meltygui/view/orchestration_view.py +1 -1
- meltygui/view/path_icon_view.py +105 -0
- meltygui/view/text_view.py +89 -21
- meltygui/view/texture_view.py +198 -434
- meltygui/view/tile_view.py +299 -21
- meltygui/view/trace_view.py +59 -11
- meltygui/view/voxel_cuda_view.py +15 -7
- meltygui/view/voxel_view.py +18 -14
- {meltygui-0.1.0.dist-info → meltygui-0.1.2.dist-info}/METADATA +6 -4
- {meltygui-0.1.0.dist-info → meltygui-0.1.2.dist-info}/RECORD +160 -115
- meltygui/graphics/texture_manager.py +0 -170
- {meltygui-0.1.0.dist-info → meltygui-0.1.2.dist-info}/WHEEL +0 -0
- {meltygui-0.1.0.dist-info → meltygui-0.1.2.dist-info}/licenses/LICENSE +0 -0
meltygui/__init__.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"""MeltyGUI: immediate-mode apps, live code editing, HDR and tensor views.
|
|
2
2
|
|
|
3
3
|
Install with ``uv pip install meltygui``; define windows with ``@glfw_window``.
|
|
4
|
-
|
|
4
|
+
Importing this package does not start a GUI, and it stays light: the views
|
|
5
|
+
below import on first use, the render libraries (imgui, numpy, OpenGL) and
|
|
6
|
+
the GL side of the runtime load on the boot's import thread while the first
|
|
7
|
+
``@glfw_window`` opens the display, and the code-editing stack (libcst) loads
|
|
8
|
+
with the first code edit. core/runtime/app.py describes the boot sequence;
|
|
9
|
+
tests/test_startup_imports.py keeps the import graph that shape.
|
|
5
10
|
"""
|
|
6
11
|
import os
|
|
7
12
|
import sys
|
|
@@ -23,9 +28,13 @@ from meltygui.core.runtime.app import pressed
|
|
|
23
28
|
from meltygui.core.runtime.app import content_size
|
|
24
29
|
from meltygui.core.runtime.app import mark
|
|
25
30
|
from meltygui.core.runtime.app import persisted
|
|
31
|
+
from meltygui.core.runtime.app import after_first_frame
|
|
26
32
|
|
|
27
33
|
if TYPE_CHECKING: # IDE / type checkers only; never executed
|
|
34
|
+
from meltygui.state.new_core_model import DrawState
|
|
28
35
|
from meltygui.view.text_view import draw_text
|
|
36
|
+
from meltygui.model.tile_model import multi_instance
|
|
37
|
+
from meltygui.view.chat_view import draw_claude_chat
|
|
29
38
|
from meltygui.view.texture_view import draw_texture
|
|
30
39
|
from meltygui.core.rendering.render_dispatch import draw_any
|
|
31
40
|
from meltygui.view.control_view import draw_button
|
|
@@ -50,6 +59,7 @@ if TYPE_CHECKING: # IDE / type checkers only; never executed
|
|
|
50
59
|
|
|
51
60
|
_NCV = 'meltygui.core.rendering.render_dispatch'
|
|
52
61
|
_VIEWS = {
|
|
62
|
+
'DrawState': ('meltygui.state.new_core_model', 'DrawState'),
|
|
53
63
|
'draw_voxels': ('meltygui.view.voxel_view', 'draw_voxels'),
|
|
54
64
|
'draw_voxels_opengl': ('meltygui.view.voxel_view', 'draw_voxels_opengl'),
|
|
55
65
|
'draw_voxels_cuda': ('meltygui.view.voxel_view', 'draw_voxels_cuda'),
|
|
@@ -57,6 +67,8 @@ _VIEWS = {
|
|
|
57
67
|
'draw_tensor_error': ('meltygui.view.tensor_view', 'draw_tensor_error'),
|
|
58
68
|
'draw_line_graph': ('meltygui.view.graph_view', 'draw_line_graph'),
|
|
59
69
|
'render_func': ('meltygui.core.core_render', 'render_func'),
|
|
70
|
+
'multi_instance': ('meltygui.model.tile_model', 'multi_instance'),
|
|
71
|
+
'draw_claude_chat': ('meltygui.view.chat_view', 'draw_claude_chat'),
|
|
60
72
|
'draw_text': ('meltygui.view.text_view', 'draw_text'),
|
|
61
73
|
'draw_texture': ('meltygui.view.texture_view', 'draw_texture'),
|
|
62
74
|
'draw_any': (_NCV, 'draw_any'),
|
|
@@ -103,5 +115,6 @@ def __getattr__(name):
|
|
|
103
115
|
return value
|
|
104
116
|
|
|
105
117
|
|
|
106
|
-
__all__ = ['Style', 'boot', 'glfw_window', 'run', 'pressed', 'content_size', 'mark', 'persisted', '
|
|
118
|
+
__all__ = ['Style', 'boot', 'glfw_window', 'run', 'pressed', 'content_size', 'mark', 'persisted', 'after_first_frame',
|
|
119
|
+
'toggles', 'window_api', 'imgui',
|
|
107
120
|
*_VIEWS]
|
|
@@ -24,8 +24,8 @@ editable fields, its action buttons and any extra rows (a device-code card,
|
|
|
24
24
|
the Ollama model list). A kind with a `chat_label` appears in the Chat
|
|
25
25
|
window's provider dropdown; its conversations come from the ChatProxy
|
|
26
26
|
factory registered for the kind name with `register_chat_backend`
|
|
27
|
-
(chat/backends.py) —
|
|
28
|
-
|
|
27
|
+
(chat/backends.py) — Codex's and Claude Code's are registered below: the
|
|
28
|
+
detached chat service's mirror (chat/chat_service.py).
|
|
29
29
|
|
|
30
30
|
Layout: every row measures its buttons FIRST (`strip_layout`); if the text
|
|
31
31
|
would be left less than min_text_width, or the strip is wider than the row,
|
|
@@ -52,7 +52,7 @@ from meltygui.chat.backends import chat_backend
|
|
|
52
52
|
from meltygui.chat.backends import register_chat_backend
|
|
53
53
|
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
54
54
|
from meltygui.core.windowing.glfw_utils import request_render
|
|
55
|
-
from meltygui.core.cache.
|
|
55
|
+
from meltygui.core.cache.tile_marks import add_shadow
|
|
56
56
|
from meltygui.core.core_render import render_func
|
|
57
57
|
from meltygui.core.rendering.window_decoration import window
|
|
58
58
|
|
|
@@ -507,24 +507,23 @@ class AnthropicKind(AccountKind):
|
|
|
507
507
|
flow.submit_code(code)
|
|
508
508
|
|
|
509
509
|
def _owns_claude_login(self, account, login):
|
|
510
|
-
"""
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
first row sharing that path (the default account)."""
|
|
510
|
+
"""Match known identities even when only one row uses the login file.
|
|
511
|
+
|
|
512
|
+
Rows without a browser identity retain the default-file fallback;
|
|
513
|
+
a known different account must never inherit another account's usage.
|
|
514
|
+
"""
|
|
516
515
|
path = self._claude_code_login_path(account)
|
|
517
516
|
sharing = [entry for entry in accounts.of_kind(self.name)
|
|
518
517
|
if self._claude_code_login_path(entry) == path]
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
518
|
+
email = (login.get("email") or "").strip().lower()
|
|
519
|
+
identities = [((self.login_info(entry) or {}).get("email") or "").strip().lower()
|
|
520
|
+
for entry in sharing]
|
|
522
521
|
if email:
|
|
523
|
-
matching = [entry for entry in sharing
|
|
524
|
-
if ((self.login_info(entry) or {}).get("email") or "").lower() == email]
|
|
522
|
+
matching = [entry for entry, identity in zip(sharing, identities) if identity == email]
|
|
525
523
|
if matching:
|
|
526
524
|
return matching[0] is account
|
|
527
|
-
|
|
525
|
+
sharing = [entry for entry, identity in zip(sharing, identities) if not identity]
|
|
526
|
+
return bool(sharing) and sharing[0] is account
|
|
528
527
|
|
|
529
528
|
def refresh_all(self, account):
|
|
530
529
|
"""The Refresh button: re-read the logins and, for an open panel,
|
|
@@ -830,12 +829,23 @@ class AnthropicKind(AccountKind):
|
|
|
830
829
|
return out
|
|
831
830
|
|
|
832
831
|
|
|
832
|
+
# Both chat kinds mirror the detached chat service (chat/chat_service.py): the
|
|
833
|
+
# service owns the real backends (CodexChats, ClaudeCodeChats), so a turn
|
|
834
|
+
# outlives the window that sent it and every app shows the same conversations.
|
|
833
835
|
@register_chat_backend("codex")
|
|
834
836
|
def codex_chats(account, metadata=None, wake=None):
|
|
835
837
|
if account.get("_codex_signing_in") or account.get("_busy"):
|
|
836
838
|
return None
|
|
837
|
-
from meltygui.chat.
|
|
838
|
-
return
|
|
839
|
+
from meltygui.chat.chat_service import PersistentChats
|
|
840
|
+
return PersistentChats("codex", account, metadata, wake)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
@register_chat_backend("anthropic")
|
|
844
|
+
def claude_chats(account, metadata=None, wake=None):
|
|
845
|
+
if account.get("_busy"):
|
|
846
|
+
return None
|
|
847
|
+
from meltygui.chat.chat_service import PersistentChats
|
|
848
|
+
return PersistentChats("anthropic", account, metadata, wake)
|
|
839
849
|
|
|
840
850
|
|
|
841
851
|
@account_kind
|
meltygui/chat/__init__.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"""meltygui.chat — the code-assistant chat: the window, the data contract a
|
|
2
2
|
backend implements, and the registry that plugs a backend into a provider.
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
from meltygui.chat import draw_chat_interface, persistent_metadata
|
|
6
|
-
import melty_agents # registers the Claude Code backend
|
|
4
|
+
from meltygui.chat import draw_claude_chat
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
draw_tiles(..., multi_instance_renderers=(draw_claude_chat,)) # a tile, or
|
|
7
|
+
draw_claude_chat(None, width=w, height=h) # in a window body
|
|
8
|
+
|
|
9
|
+
`draw_claude_chat` is the chat ready for a window body or a tile (`python -m
|
|
10
|
+
meltygui.chat [PROJECT]` is it as the melty-claude app): a plain function, one
|
|
11
|
+
call of `draw_chat_interface` (the only render boundary) with folder tints from the file-meta store and its sidebar metadata kept between
|
|
12
|
+
runs. Its input value picks where new conversations start (`chat_project`).
|
|
13
|
+
Conversations run in the detached chat service (`chat_service.py`): a turn
|
|
14
|
+
outlives the window that sent it and every app shows the same live state.
|
|
15
|
+
Claude Code's backend (`claude_code.py`) needs the `meltygui[claude]` extra.
|
|
10
16
|
|
|
11
17
|
The window (`draw_chat_interface`, the studio's Chat playground) lists the
|
|
12
18
|
account kinds that carry a `chat_label` in a provider dropdown, the accounts
|
|
@@ -20,13 +26,15 @@ backend mirrors to its provider from a worker thread. `chat_proxy.py` is
|
|
|
20
26
|
the contract; `codex_proxy.py` in the same package is the reference
|
|
21
27
|
implementation, and its docstrings say what each operation and event means.
|
|
22
28
|
|
|
23
|
-
A backend registers a factory for an account kind name
|
|
29
|
+
A backend registers a factory for an account kind name
|
|
30
|
+
(`accounts/internet_accounts.py` registers the chat service's mirror for
|
|
31
|
+
"codex" and "anthropic"; the service builds `CodexChats` / `ClaudeCodeChats`):
|
|
24
32
|
|
|
25
33
|
from meltygui.chat import ChatProxy, Chat, messages, register_chat_backend
|
|
26
34
|
|
|
27
|
-
@register_chat_backend('
|
|
28
|
-
def
|
|
29
|
-
return
|
|
35
|
+
@register_chat_backend('my-kind')
|
|
36
|
+
def my_chats(account, metadata=None, wake=None):
|
|
37
|
+
return MyChats(account['id'], metadata, wake)
|
|
30
38
|
|
|
31
39
|
Pictures (`images`): an ImageReference in a message — a base64 payload, a
|
|
32
40
|
path or a data URL — is decoded once and drawn inline by the window, HDR
|
|
@@ -57,6 +65,10 @@ if TYPE_CHECKING: # IDE and type checkers only; never executed
|
|
|
57
65
|
from meltygui.chat.metadata import persistent_metadata
|
|
58
66
|
from meltygui.chat.metadata import shared_metadata
|
|
59
67
|
from meltygui.view.chat_view import draw_chat_interface
|
|
68
|
+
from meltygui.view.chat_view import draw_claude_chat
|
|
69
|
+
from meltygui.chat.chat_interface import stop_running
|
|
70
|
+
from meltygui.chat.chat_interface import disconnect_chats
|
|
71
|
+
from meltygui.model.chat_model import chat_project
|
|
60
72
|
from meltygui.state.chat_state import ChatInterfaceState
|
|
61
73
|
from meltygui.accounts.internet_accounts import accounts
|
|
62
74
|
from meltygui.accounts.internet_accounts import KINDS
|
|
@@ -68,7 +80,11 @@ _LAZY = {
|
|
|
68
80
|
'shared_metadata': ('meltygui.chat.metadata', 'shared_metadata'),
|
|
69
81
|
'persistent_metadata': ('meltygui.chat.metadata', 'persistent_metadata'),
|
|
70
82
|
'draw_chat_interface': ('meltygui.chat.chat_interface', 'draw_chat_interface'),
|
|
71
|
-
'
|
|
83
|
+
'draw_claude_chat': ('meltygui.view.chat_view', 'draw_claude_chat'),
|
|
84
|
+
'stop_running': ('meltygui.chat.chat_interface', 'stop_running'),
|
|
85
|
+
'disconnect_chats': ('meltygui.chat.chat_interface', 'disconnect_chats'),
|
|
86
|
+
'chat_project': ('meltygui.model.chat_model', 'chat_project'),
|
|
87
|
+
'ChatInterfaceState': ('meltygui.state.chat_state', 'ChatInterfaceState'),
|
|
72
88
|
'accounts': ('meltygui.accounts.internet_accounts', 'accounts'),
|
|
73
89
|
'KINDS': ('meltygui.accounts.internet_accounts', 'KINDS'),
|
|
74
90
|
'AccountKind': ('meltygui.accounts.internet_accounts', 'AccountKind'),
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 LobeHub
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Claude and Codex marks from https://github.com/lobehub/lobe-icons/tree/master/packages/static-svg/icons
|
|
2
|
+
(downloaded 2026-09-14). Upstream MIT license is included in LICENSE.
|
|
3
|
+
The SVG originals are retained; the white 96px PNGs were rendered with CairoSVG
|
|
4
|
+
for tinting by the sidebar. No network access or SVG renderer is needed at runtime.
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"></path></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"></path></svg>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Background conversation names matching ~/bin/claude-d-title."""
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import threading
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
TITLE_PROMPT = '''Name a chat session for the request below. Reply with ONLY a title of the form: file name, function or class name, short bug/task name (two-three lowercase words) — comma-separated, e.g. "text_editor.py, draw_text, missing labels". Use only file/function/class names actually mentioned in the request; OMIT any part you cannot identify (keep the others, no placeholders) — worst case just the bug name. No explanation. If the request is too vague to name (e.g. "continue", "yes", "fix it"), reply NONE.
|
|
11
|
+
|
|
12
|
+
Request: '''
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generate_title(prompt):
|
|
16
|
+
executable = shutil.which('claude') or str(Path.home() / '.local/bin/claude')
|
|
17
|
+
env = {k: v for k, v in os.environ.items()
|
|
18
|
+
if k not in ('TMUX', 'TMUX_PANE', 'CLAUDECODE', 'CLAUDE_CODE_ENTRYPOINT')}
|
|
19
|
+
env['CLAUDE_CODE_DISABLE_TERMINAL_TITLE'] = '1'
|
|
20
|
+
try:
|
|
21
|
+
result = subprocess.run(
|
|
22
|
+
[executable, '-p', '--model', 'haiku', '--output-format', 'text',
|
|
23
|
+
'--no-session-persistence', '--tools', '', '--strict-mcp-config',
|
|
24
|
+
'--settings', '{"disableAllHooks":true}'],
|
|
25
|
+
input=TITLE_PROMPT + prompt[:400], cwd='/', env=env,
|
|
26
|
+
capture_output=True, text=True, timeout=60)
|
|
27
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
28
|
+
return None
|
|
29
|
+
lines = result.stdout.strip().splitlines()
|
|
30
|
+
name = ' '.join(lines[-1].split()) if lines else ''
|
|
31
|
+
if (result.returncode == 0 and name != 'NONE' and len(name) <= 64
|
|
32
|
+
and re.fullmatch(r'[A-Za-z0-9_./-]+(?:,? [A-Za-z0-9_./-]+){0,6}', name)):
|
|
33
|
+
return name
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AutoTitles:
|
|
38
|
+
def __init__(self, lock, generate=generate_title):
|
|
39
|
+
self.lock = lock
|
|
40
|
+
self.generate = generate
|
|
41
|
+
self.pending = set()
|
|
42
|
+
|
|
43
|
+
def install(self, proxy):
|
|
44
|
+
submit = proxy.submit
|
|
45
|
+
|
|
46
|
+
def with_title(operation, *args):
|
|
47
|
+
submit(operation, *args)
|
|
48
|
+
if operation == 'send':
|
|
49
|
+
key, _, _, prompt, *_ = args
|
|
50
|
+
self.request(proxy, key, prompt)
|
|
51
|
+
|
|
52
|
+
proxy.submit = with_title
|
|
53
|
+
|
|
54
|
+
def request(self, proxy, key, prompt):
|
|
55
|
+
chat = dict.get(proxy, key)
|
|
56
|
+
if chat is None or not prompt.strip():
|
|
57
|
+
return
|
|
58
|
+
state = chat.metadata.get('auto_title')
|
|
59
|
+
if state is None and chat['title'] == 'New conversation':
|
|
60
|
+
state = chat.metadata['auto_title'] = 'retry'
|
|
61
|
+
if state != 'retry' or id(chat) in self.pending:
|
|
62
|
+
return
|
|
63
|
+
self.pending.add(id(chat))
|
|
64
|
+
threading.Thread(target=self.finish, args=(proxy, key, chat, prompt),
|
|
65
|
+
name='melty-auto-title', daemon=True).start()
|
|
66
|
+
|
|
67
|
+
def finish(self, proxy, key, chat, prompt):
|
|
68
|
+
try:
|
|
69
|
+
name = self.generate(prompt)
|
|
70
|
+
with self.lock:
|
|
71
|
+
if (name and dict.get(proxy, key) is chat
|
|
72
|
+
and chat.metadata.get('auto_title') == 'retry'):
|
|
73
|
+
chat['title'] = name
|
|
74
|
+
chat.metadata['auto_title'] = 'done'
|
|
75
|
+
proxy.revision += 1
|
|
76
|
+
proxy.reconcile() # The backend persists it as a normal rename.
|
|
77
|
+
finally:
|
|
78
|
+
with self.lock:
|
|
79
|
+
self.pending.discard(id(chat))
|
meltygui/chat/backends.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""The chat backend registry: account kind name -> ChatProxy factory.
|
|
2
2
|
|
|
3
3
|
A backend is a ChatProxy subclass living anywhere (the studio's Codex one,
|
|
4
|
-
an external package
|
|
4
|
+
an external package). Registering it against an
|
|
5
5
|
account kind gives that kind's accounts a conversation list in the Chat
|
|
6
6
|
window; nothing else in the UI knows which package supplied it.
|
|
7
7
|
|
meltygui/chat/chat_interface.py
CHANGED
|
@@ -48,7 +48,7 @@ from meltygui.core.rendering.window_decoration import window
|
|
|
48
48
|
from meltygui.view.collection_view import draw_tuple_fast
|
|
49
49
|
from meltygui.view.decoration_view import draw_bg
|
|
50
50
|
from meltygui.view.header_view import flat_button
|
|
51
|
-
from meltygui.core.cache.
|
|
51
|
+
from meltygui.core.cache.tile_marks import add_shadow
|
|
52
52
|
from meltygui.view.texture_view import draw_texture
|
|
53
53
|
import meltygui.accounts.internet_accounts as internet_accounts
|
|
54
54
|
|
|
@@ -165,7 +165,8 @@ from meltygui.view.chat_view import _scroll_position
|
|
|
165
165
|
|
|
166
166
|
|
|
167
167
|
@contextmanager
|
|
168
|
-
def _viewport(draw_state, state, key, width, height, content_height, follow=False
|
|
168
|
+
def _viewport(draw_state, state, key, width, height, content_height, follow=False,
|
|
169
|
+
scrollbar_overlays=None, stretch_scrollbar=False):
|
|
169
170
|
"""One clipped, independently scrolling region, owned by the window.
|
|
170
171
|
|
|
171
172
|
Only its visible footprint advances layout; content offsets never move
|
|
@@ -190,10 +191,16 @@ def _viewport(draw_state, state, key, width, height, content_height, follow=Fals
|
|
|
190
191
|
if drag is not None:
|
|
191
192
|
offset = _scroll_position(view, content_height, height,
|
|
192
193
|
drag_fraction=offset / maximum + drag.dy / travel)
|
|
194
|
+
if scrollbar_overlays is not None:
|
|
195
|
+
# Keep only prepared geometry; the overlay never lays out messages or
|
|
196
|
+
# handles input. The final pane can grow with the navigation's bottom.
|
|
197
|
+
scrollbar_overlays.append((y - draw_state.abs_top, height, content_height, offset,
|
|
198
|
+
draw_state.abs_left + draw_state.width - x - width,
|
|
199
|
+
draw_state.height if stretch_scrollbar else None))
|
|
193
200
|
Melty.push_clip(rect)
|
|
194
201
|
try:
|
|
195
202
|
yield x, y - offset, Melty.get_clip_rect()
|
|
196
|
-
if maximum > 0 and travel > 0:
|
|
203
|
+
if scrollbar_overlays is None and maximum > 0 and travel > 0:
|
|
197
204
|
thumb_y = y + travel * offset / maximum
|
|
198
205
|
add_shadow((x + width - bar_width, thumb_y, bar_width, thumb_height),
|
|
199
206
|
offset=Toggles.Chat.shadow_offset, corner_radius=3)
|
|
@@ -361,10 +368,11 @@ def chat_sources(accounts):
|
|
|
361
368
|
|
|
362
369
|
|
|
363
370
|
def conversation_source_tag(kind, chat):
|
|
371
|
+
"""A sidebar row's tag text where the provider has no bundled mark: a chat
|
|
372
|
+
glyph, plus a lock while another writer owns the conversation."""
|
|
364
373
|
from meltygui.chat.chat_proxy import writer_conflict
|
|
365
|
-
label = "Claude" if kind.name == "anthropic" else kind.chat_label
|
|
366
374
|
locked = chat.get("locked", writer_conflict(getattr(chat, "error", None)))
|
|
367
|
-
return
|
|
375
|
+
return "\uf075 \uf023" if locked else "\uf075"
|
|
368
376
|
|
|
369
377
|
|
|
370
378
|
def source_initials(label):
|
|
@@ -402,16 +410,14 @@ def pick_source(state, account_id, kinds, additive=False):
|
|
|
402
410
|
|
|
403
411
|
|
|
404
412
|
def apply_folder_shortcuts(state, events):
|
|
405
|
-
"""
|
|
406
|
-
|
|
413
|
+
"""Ctrl+Shift+= / Ctrl+Shift+- fold the hovered sidebar column: queued
|
|
414
|
+
here, applied by the sidebar once it can hit-test this frame's columns."""
|
|
415
|
+
state.hovered_folder_action = None
|
|
407
416
|
for key, mods in events:
|
|
408
417
|
if mods & (glfw.MOD_CONTROL | glfw.MOD_SHIFT) == (glfw.MOD_CONTROL | glfw.MOD_SHIFT):
|
|
409
418
|
if key in (glfw.KEY_EQUAL, glfw.KEY_KP_ADD, glfw.KEY_MINUS, glfw.KEY_KP_SUBTRACT):
|
|
410
|
-
state.
|
|
411
|
-
|
|
412
|
-
state.revision += 1
|
|
413
|
-
changed = True
|
|
414
|
-
return changed
|
|
419
|
+
state.hovered_folder_action = key in (glfw.KEY_EQUAL, glfw.KEY_KP_ADD)
|
|
420
|
+
return False
|
|
415
421
|
|
|
416
422
|
|
|
417
423
|
def _code_background(x, y, width, height, color, shadow=None):
|
|
@@ -565,11 +571,13 @@ def transcript_entries(messages, state, key):
|
|
|
565
571
|
|
|
566
572
|
|
|
567
573
|
def chat_activity(chat, provider):
|
|
568
|
-
"""Describe the latest reported work; never infer work from idle history.
|
|
569
|
-
|
|
570
|
-
|
|
574
|
+
"""Describe the latest reported work; never infer work from idle history.
|
|
575
|
+
An answered request can remain until the backend acknowledges it."""
|
|
576
|
+
from meltygui.model.chat_model import pending_requests
|
|
577
|
+
if pending_requests(chat):
|
|
578
|
+
return "Answer the request below to continue"
|
|
571
579
|
if not chat["running"]:
|
|
572
|
-
return "Working in another session" if chat.get("external_busy") else "
|
|
580
|
+
return "Working in another session" if chat.get("external_busy") else "Starting the next step…"
|
|
573
581
|
for message in reversed(chat["messages"].values()):
|
|
574
582
|
if isinstance(message, UserMessage):
|
|
575
583
|
break
|
|
@@ -591,11 +599,6 @@ def chat_activity(chat, provider):
|
|
|
591
599
|
from meltygui.core.services.chat_core import _cleanup_chat
|
|
592
600
|
|
|
593
601
|
|
|
594
|
-
def navigation_heading_control(pane, draw_state, state, x, y, width, height):
|
|
595
|
-
"""App extension: draw a trailing heading control; return changed, width used."""
|
|
596
|
-
return False, 0
|
|
597
|
-
|
|
598
|
-
|
|
599
602
|
def navigation_row_sizes(state, edges, opened, top, height, minimum):
|
|
600
603
|
"""Remember expanded sizes; redistribute space only when a section toggles.
|
|
601
604
|
|
|
@@ -666,8 +669,9 @@ def is_new_chat(chat):
|
|
|
666
669
|
|
|
667
670
|
def chat_project_choices(state, proxies, current, *defaults):
|
|
668
671
|
"""Use directory paths as labels so identically named folders stay distinct."""
|
|
672
|
+
from meltygui.model.chat_folder_model import folder_settings
|
|
669
673
|
projects = {current, *defaults, *state.projects.values(),
|
|
670
|
-
*
|
|
674
|
+
*folder_settings()["added_folders"]}
|
|
671
675
|
for proxy in proxies.values():
|
|
672
676
|
if proxy is not None:
|
|
673
677
|
projects.update(chat.get("project") for chat in dict.values(proxy))
|
|
@@ -730,3 +734,26 @@ def switch_new_chat_source(state, proxies, kinds, key, account_id, model):
|
|
|
730
734
|
|
|
731
735
|
from meltygui.view.chat_view import draw_chat_interface
|
|
732
736
|
draw_chat_interface = window(tint=(1.34, 1.62, 1.76), display_name='Chat', icon=f'\uf27a', initial={'width': 1100, 'height': 760})(draw_chat_interface)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _chat_proxies():
|
|
740
|
+
accounts = internet_accounts.accounts
|
|
741
|
+
for account in accounts.values():
|
|
742
|
+
proxy = account.get("_chat_proxy")
|
|
743
|
+
if proxy is not None and not proxy.closed:
|
|
744
|
+
yield proxy
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def stop_running():
|
|
748
|
+
"""Interrupt every running turn (an app's Escape)."""
|
|
749
|
+
for proxy in _chat_proxies():
|
|
750
|
+
for key, chat in proxy.items():
|
|
751
|
+
if chat["running"]:
|
|
752
|
+
proxy.stop_chat(key)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def disconnect_chats():
|
|
756
|
+
"""Detach this process from the chat service without interrupting its
|
|
757
|
+
conversations (an app's exit)."""
|
|
758
|
+
for proxy in _chat_proxies():
|
|
759
|
+
proxy.close()
|