fix(gui): Resolve Markdown squashing, MiniMax compression error, and UI import issues
- Modularize discussion entry rendering to src/discussion_entry_renderer.py to fix layout squashing. - Fix MiniMax compression routing with robust case-insensitive check and synced base URL. - Implement src/ui_shared.py to resolve circular imports and consolidate shared UI helpers. - Finalize Structural File Editor integration and state unification.
This commit is contained in:
+5
-121
@@ -35,6 +35,7 @@ from src import ai_client
|
||||
from src import aggregate
|
||||
from src import api_hooks
|
||||
from src import app_controller
|
||||
from src import ui_shared
|
||||
from src import bg_shader
|
||||
from src import cost_tracker
|
||||
from src import history
|
||||
@@ -3112,129 +3113,12 @@ def render_discussion_hub(app: App) -> None:
|
||||
return
|
||||
|
||||
def render_discussion_entry(app: App, entry: dict, index: int) -> None:
|
||||
with imscope.id(f"disc_{index}"):
|
||||
role = entry.get("role", "User")
|
||||
# Subtle tints: User(Blue), AI(Green), Vendor(Orange), System(Dark)
|
||||
bg_col = vec4(40, 50, 70, 0.25) if role == "User" else vec4(45, 60, 50, 0.25) if role == "AI" else vec4(60, 50, 40, 0.25) if role == "Vendor API" else vec4(30, 30, 30, 0.2)
|
||||
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
p_min = imgui.get_cursor_screen_pos()
|
||||
full_width = imgui.get_content_region_avail().x
|
||||
|
||||
draw_list.channels_split(2)
|
||||
draw_list.channels_set_current(1) # Foreground
|
||||
|
||||
imgui.begin_group()
|
||||
collapsed, read_mode = entry.get("collapsed", False), entry.get("read_mode", False)
|
||||
if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed
|
||||
imgui.same_line(); render_text_viewer(app, f"Entry #{index+1}", entry["content"], id_suffix=f"disc_btn_{index}"); imgui.same_line(); imgui.set_next_item_width(120)
|
||||
if imgui.begin_combo("##role", entry["role"]):
|
||||
for r in app.disc_roles:
|
||||
if imgui.selectable(r, r == entry["role"])[0]: entry["role"] = r
|
||||
imgui.end_combo()
|
||||
if not collapsed:
|
||||
imgui.same_line()
|
||||
if imgui.button("[Edit]" if read_mode else "[Read]"): entry["read_mode"] = not read_mode
|
||||
ts_str = entry.get("ts", "")
|
||||
usage = entry.get("usage", {})
|
||||
if ts_str or usage:
|
||||
imgui.same_line()
|
||||
if ts_str:
|
||||
imgui.text_colored(vec4(120, 120, 100), str(ts_str))
|
||||
e_dt = project_manager.parse_ts(ts_str)
|
||||
if e_dt:
|
||||
e_unix, next_unix = e_dt.timestamp(), float('inf')
|
||||
if index + 1 < len(app.disc_entries):
|
||||
n_ts = app.disc_entries[index+1].get("ts", ""); n_dt = project_manager.parse_ts(n_ts)
|
||||
if n_dt: next_unix = n_dt.timestamp()
|
||||
injected = [f for f in app.files if hasattr(f, 'injected_at') and f.injected_at and e_unix <= f.injected_at < next_unix]
|
||||
if injected:
|
||||
imgui.same_line(); imgui.text_colored(vec4(100, 255, 100), f"[{len(injected)}+]")
|
||||
if imgui.is_item_hovered(): imgui.set_tooltip("Files injected at this point:\n" + "\n".join([f.path for f in injected]))
|
||||
if usage:
|
||||
inp = usage.get("input_tokens", 0)
|
||||
out = usage.get("output_tokens", 0)
|
||||
cache = usage.get("cache_read_input_tokens", 0)
|
||||
usage_str = f" in:{inp} out:{out}"
|
||||
if cache: usage_str += f" cache:{cache}"
|
||||
imgui.same_line()
|
||||
imgui.text_colored(vec4(100, 150, 180), usage_str)
|
||||
if collapsed:
|
||||
imgui.same_line()
|
||||
if imgui.button("Ins"): app.disc_entries.insert(index, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
|
||||
imgui.same_line();
|
||||
if imgui.button("Del"):
|
||||
if entry in app.disc_entries: app.disc_entries.remove(entry)
|
||||
draw_list.channels_merge() # Must merge before return
|
||||
return
|
||||
imgui.same_line()
|
||||
if imgui.button("Branch"): app._branch_discussion(index)
|
||||
imgui.same_line(); preview = entry["content"].replace("\n", " ")[:60]
|
||||
if len(entry["content"]) > 60: preview += "..."
|
||||
if not preview.strip() and entry.get("thinking_segments"):
|
||||
preview = entry["thinking_segments"][0]["content"].replace("\n", " ")[:60]
|
||||
if len(entry["thinking_segments"][0]["content"]) > 60: preview += "..."
|
||||
imgui.text_colored(vec4(160, 160, 150), preview)
|
||||
if not collapsed:
|
||||
thinking_segments, has_content = entry.get("thinking_segments", []), bool(entry.get("content", "").strip())
|
||||
if thinking_segments: render_thinking_trace(app, entry, thinking_segments, index, is_standalone=not has_content)
|
||||
if read_mode: render_discussion_entry_read_mode(app, entry, index)
|
||||
else:
|
||||
if not (bool(thinking_segments) and not has_content): ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150))
|
||||
imgui.end_group()
|
||||
|
||||
draw_list.channels_set_current(0) # Background
|
||||
p_max = imgui.get_item_rect_max()
|
||||
p_max.x = p_min.x + full_width
|
||||
draw_list.add_rect_filled(p_min, p_max, imgui.get_color_u32(bg_col), 4.0)
|
||||
draw_list.channels_merge()
|
||||
imgui.separator()
|
||||
from src import discussion_entry_renderer
|
||||
discussion_entry_renderer.render_discussion_entry(app, entry, index)
|
||||
|
||||
def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None:
|
||||
content = entry["content"]
|
||||
if not content.strip(): return
|
||||
if '## Retrieved Context' in content:
|
||||
rag_match = re.search(r'## Retrieved Context\n\n([\s\S]*?)(?=\n\n#|\Z)', content)
|
||||
if rag_match:
|
||||
rag_section = rag_match.group(1)
|
||||
if imgui.collapsing_header('Retrieved Context'):
|
||||
chunks = re.finditer(r'### Chunk (\d+) \(Source: (.*?)\)\n([\s\S]*?)(?=\n### Chunk|\Z)', rag_section)
|
||||
for chunk_match in chunks:
|
||||
idx, path, chunk_content = chunk_match.group(1), chunk_match.group(2), chunk_match.group(3)
|
||||
if imgui.collapsing_header(f'Chunk {idx}: {path}'):
|
||||
if imgui.button(f'[Source]##rag_{index}_{idx}'):
|
||||
res = mcp_client.read_file(path)
|
||||
if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'); app.show_windows["Text Viewer"] = True
|
||||
imgui.text_unformatted(chunk_content)
|
||||
content = content[:rag_match.start()] + content[rag_match.end():]
|
||||
pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?")
|
||||
matches, is_nerv = list(pattern.finditer(content)), theme.is_nerv_active()
|
||||
if not matches:
|
||||
with imscope.child(f"read_content_{index}", size_x=0, size_y=0, flags=imgui.WindowFlags_.no_scroll_with_mouse | imgui.WindowFlags_.always_auto_resize):
|
||||
with theme.ai_text_style():
|
||||
markdown_helper.render(content, context_id=f'disc_{index}')
|
||||
else:
|
||||
with imscope.child(f"read_content_{index}", size_x=0, size_y=400, flags=True):
|
||||
last_idx = 0
|
||||
for m_idx, match in enumerate(matches):
|
||||
before = content[last_idx:match.start()]
|
||||
if before:
|
||||
with theme.ai_text_style():
|
||||
markdown_helper.render(before, context_id=f'disc_{index}_b_{m_idx}')
|
||||
header_text, path, code_block = match.group(0).split("\n")[0].strip(), match.group(2), match.group(4)
|
||||
if imgui.collapsing_header(header_text):
|
||||
if imgui.button(f"[Source]##{index}_{match.start()}"):
|
||||
res = mcp_client.read_file(path)
|
||||
if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'); app.show_windows["Text Viewer"] = True
|
||||
if code_block:
|
||||
with theme.ai_text_style():
|
||||
markdown_helper.render(code_block, context_id=f'disc_{index}_c_{m_idx}')
|
||||
last_idx = match.end()
|
||||
after = content[last_idx:]
|
||||
if after:
|
||||
with theme.ai_text_style():
|
||||
markdown_helper.render(after, context_id=f'disc_{index}_a')
|
||||
if app.ui_word_wrap: imgui.pop_text_wrap_pos()
|
||||
from src import discussion_entry_renderer
|
||||
discussion_entry_renderer.render_discussion_entry_read_mode(app, entry, index)
|
||||
|
||||
def render_history_window(app: App) -> None:
|
||||
if not app.show_windows.get('Undo/Redo History', False):
|
||||
|
||||
Reference in New Issue
Block a user