discussion history improvemnts
This commit is contained in:
150
gui.py
150
gui.py
@@ -287,36 +287,38 @@ class ConfirmDialog:
|
||||
DISC_ROLES = ["User", "AI", "Vendor API", "System"]
|
||||
|
||||
|
||||
def _parse_history_entries(history: list[str]) -> list[dict]:
|
||||
def _parse_history_entries(history: list[str], roles: list[str] | None = None) -> list[dict]:
|
||||
"""
|
||||
Convert the raw TOML string array into a flat list of {role, content} dicts.
|
||||
Convert the raw TOML string array into a flat list of {role, content, collapsed} dicts.
|
||||
Each TOML string is one excerpt (may contain multiple role blocks separated
|
||||
by lines like "Role:" or "[Role]"). We detect the common patterns:
|
||||
"User:\n..." "AI:\n..." "[User]\n..." "[AI]\n..."
|
||||
and split accordingly. Unrecognised text becomes a User entry.
|
||||
roles: list of known role strings (defaults to module DISC_ROLES if None).
|
||||
"""
|
||||
import re
|
||||
known = roles if roles is not None else DISC_ROLES
|
||||
entries: list[dict] = []
|
||||
role_pattern = re.compile(
|
||||
r'^(?:\[)?(' + '|'.join(re.escape(r) for r in DISC_ROLES) + r')(?:\])?:?\s*$',
|
||||
r'^(?:\[)?(' + '|'.join(re.escape(r) for r in known) + r')(?:\])?:?\s*$',
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
for excerpt in history:
|
||||
# Find all role header positions
|
||||
splits = [(m.start(), m.end(), m.group(1).capitalize()) for m in role_pattern.finditer(excerpt)]
|
||||
splits = [(m.start(), m.end(), m.group(1)) for m in role_pattern.finditer(excerpt)]
|
||||
if not splits:
|
||||
# No role headers found - treat whole excerpt as User content
|
||||
text = excerpt.strip()
|
||||
if text:
|
||||
entries.append({"role": "User", "content": text})
|
||||
entries.append({"role": "User", "content": text, "collapsed": False})
|
||||
continue
|
||||
# Extract content between headers
|
||||
for idx, (start, end, role) in enumerate(splits):
|
||||
next_start = splits[idx + 1][0] if idx + 1 < len(splits) else len(excerpt)
|
||||
content = excerpt[end:next_start].strip()
|
||||
# Normalise role capitalisation to match DISC_ROLES
|
||||
matched = next((r for r in DISC_ROLES if r.lower() == role.lower()), role)
|
||||
entries.append({"role": matched, "content": content})
|
||||
# Normalise role capitalisation to match known list
|
||||
matched = next((r for r in known if r.lower() == role.lower()), role)
|
||||
entries.append({"role": matched, "content": content, "collapsed": False})
|
||||
return entries
|
||||
|
||||
class App:
|
||||
@@ -330,7 +332,10 @@ class App:
|
||||
self.config.get("discussion", {}).get("history", [])
|
||||
)
|
||||
|
||||
self.disc_entries: list[dict] = _parse_history_entries(self.history)
|
||||
self.disc_roles: list[str] = list(
|
||||
self.config.get("discussion", {}).get("roles", list(DISC_ROLES))
|
||||
)
|
||||
self.disc_entries: list[dict] = _parse_history_entries(self.history, self.disc_roles)
|
||||
|
||||
ai_cfg = self.config.get("ai", {})
|
||||
self.current_provider: str = ai_cfg.get("provider", "gemini")
|
||||
@@ -455,7 +460,7 @@ class App:
|
||||
if dpg.does_item_exist(tag):
|
||||
entry["content"] = dpg.get_value(tag)
|
||||
self.history = self._disc_serialize()
|
||||
self.config["discussion"] = {"history": self.history}
|
||||
self.config["discussion"] = {"history": self.history, "roles": self.disc_roles}
|
||||
self.config["ai"] = {
|
||||
"provider": self.current_provider,
|
||||
"model": self.current_model,
|
||||
@@ -711,32 +716,47 @@ class App:
|
||||
return
|
||||
dpg.delete_item("disc_scroll", children_only=True)
|
||||
for i, entry in enumerate(self.disc_entries):
|
||||
collapsed = entry.get("collapsed", False)
|
||||
preview = entry["content"].replace("\n", " ")[:60]
|
||||
if len(entry["content"]) > 60:
|
||||
preview += "..."
|
||||
with dpg.group(parent="disc_scroll"):
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(
|
||||
tag=f"disc_toggle_{i}",
|
||||
label="+" if collapsed else "-",
|
||||
width=24,
|
||||
callback=self._make_disc_toggle_cb(i),
|
||||
)
|
||||
dpg.add_combo(
|
||||
tag=f"disc_role_{i}",
|
||||
items=DISC_ROLES,
|
||||
items=self.disc_roles,
|
||||
default_value=entry["role"],
|
||||
width=120,
|
||||
width=160,
|
||||
callback=self._make_disc_role_cb(i),
|
||||
)
|
||||
dpg.add_button(
|
||||
label="Insert Before",
|
||||
callback=self._make_disc_insert_cb(i),
|
||||
if collapsed:
|
||||
dpg.add_button(
|
||||
label="Ins",
|
||||
width=36,
|
||||
callback=self._make_disc_insert_cb(i),
|
||||
)
|
||||
dpg.add_button(
|
||||
label="Del",
|
||||
width=36,
|
||||
callback=self._make_disc_remove_cb(i),
|
||||
)
|
||||
dpg.add_text(preview, color=(160, 160, 150))
|
||||
with dpg.group(tag=f"disc_body_{i}", show=not collapsed):
|
||||
dpg.add_input_text(
|
||||
tag=f"disc_content_{i}",
|
||||
default_value=entry["content"],
|
||||
multiline=True,
|
||||
width=-1,
|
||||
height=100,
|
||||
callback=self._make_disc_content_cb(i),
|
||||
on_enter=False,
|
||||
)
|
||||
dpg.add_button(
|
||||
label="Remove",
|
||||
callback=self._make_disc_remove_cb(i),
|
||||
)
|
||||
dpg.add_input_text(
|
||||
tag=f"disc_content_{i}",
|
||||
default_value=entry["content"],
|
||||
multiline=True,
|
||||
width=-1,
|
||||
height=100,
|
||||
callback=self._make_disc_content_cb(i),
|
||||
on_enter=False,
|
||||
)
|
||||
dpg.add_separator()
|
||||
|
||||
def _make_disc_role_cb(self, idx: int):
|
||||
@@ -764,8 +784,62 @@ class App:
|
||||
self._rebuild_disc_list()
|
||||
return cb
|
||||
|
||||
def _make_disc_toggle_cb(self, idx: int):
|
||||
def cb():
|
||||
if idx < len(self.disc_entries):
|
||||
tag = f"disc_content_{idx}"
|
||||
if dpg.does_item_exist(tag):
|
||||
self.disc_entries[idx]["content"] = dpg.get_value(tag)
|
||||
self.disc_entries[idx]["collapsed"] = not self.disc_entries[idx].get("collapsed", False)
|
||||
self._rebuild_disc_list()
|
||||
return cb
|
||||
|
||||
def cb_disc_collapse_all(self):
|
||||
for i, entry in enumerate(self.disc_entries):
|
||||
tag = f"disc_content_{i}"
|
||||
if dpg.does_item_exist(tag):
|
||||
entry["content"] = dpg.get_value(tag)
|
||||
entry["collapsed"] = True
|
||||
self._rebuild_disc_list()
|
||||
|
||||
def cb_disc_expand_all(self):
|
||||
for entry in self.disc_entries:
|
||||
entry["collapsed"] = False
|
||||
self._rebuild_disc_list()
|
||||
|
||||
def _rebuild_disc_roles_list(self):
|
||||
if not dpg.does_item_exist("disc_roles_scroll"):
|
||||
return
|
||||
dpg.delete_item("disc_roles_scroll", children_only=True)
|
||||
for i, role in enumerate(self.disc_roles):
|
||||
with dpg.group(horizontal=True, parent="disc_roles_scroll"):
|
||||
dpg.add_button(
|
||||
label="x", width=24,
|
||||
callback=self._make_disc_remove_role_cb(i),
|
||||
)
|
||||
dpg.add_text(role)
|
||||
|
||||
def _make_disc_remove_role_cb(self, idx: int):
|
||||
def cb():
|
||||
if idx < len(self.disc_roles):
|
||||
self.disc_roles.pop(idx)
|
||||
self._rebuild_disc_roles_list()
|
||||
self._rebuild_disc_list()
|
||||
return cb
|
||||
|
||||
def cb_disc_add_role(self):
|
||||
if not dpg.does_item_exist("disc_new_role_input"):
|
||||
return
|
||||
name = dpg.get_value("disc_new_role_input").strip()
|
||||
if name and name not in self.disc_roles:
|
||||
self.disc_roles.append(name)
|
||||
dpg.set_value("disc_new_role_input", "")
|
||||
self._rebuild_disc_roles_list()
|
||||
self._rebuild_disc_list()
|
||||
|
||||
def cb_disc_append_entry(self):
|
||||
self.disc_entries.append({"role": "User", "content": ""})
|
||||
default_role = self.disc_roles[0] if self.disc_roles else "User"
|
||||
self.disc_entries.append({"role": default_role, "content": "", "collapsed": False})
|
||||
self._rebuild_disc_list()
|
||||
|
||||
def cb_disc_clear(self):
|
||||
@@ -785,13 +859,13 @@ class App:
|
||||
def cb_append_message_to_history(self):
|
||||
msg = dpg.get_value("ai_input")
|
||||
if msg:
|
||||
self.disc_entries.append({"role": "User", "content": msg})
|
||||
self.disc_entries.append({"role": "User", "content": msg, "collapsed": False})
|
||||
self._rebuild_disc_list()
|
||||
|
||||
def cb_append_response_to_history(self):
|
||||
resp = self.ai_response
|
||||
if resp:
|
||||
self.disc_entries.append({"role": "AI", "content": resp})
|
||||
self.disc_entries.append({"role": "AI", "content": resp, "collapsed": False})
|
||||
self._rebuild_disc_list()
|
||||
|
||||
|
||||
@@ -971,9 +1045,22 @@ class App:
|
||||
):
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(label="+ Entry", callback=self.cb_disc_append_entry)
|
||||
dpg.add_button(label="-All", callback=self.cb_disc_collapse_all)
|
||||
dpg.add_button(label="+All", callback=self.cb_disc_expand_all)
|
||||
dpg.add_button(label="Clear All", callback=self.cb_disc_clear)
|
||||
dpg.add_button(label="Save", callback=self.cb_disc_save)
|
||||
dpg.add_separator()
|
||||
with dpg.collapsing_header(label="Roles", default_open=False):
|
||||
with dpg.child_window(tag="disc_roles_scroll", height=96, border=True):
|
||||
pass
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_input_text(
|
||||
tag="disc_new_role_input",
|
||||
hint="New role name",
|
||||
width=-72,
|
||||
)
|
||||
dpg.add_button(label="Add", callback=self.cb_disc_add_role)
|
||||
dpg.add_separator()
|
||||
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
||||
pass
|
||||
|
||||
@@ -1100,6 +1187,7 @@ class App:
|
||||
self._build_ui()
|
||||
theme.load_from_config(self.config)
|
||||
self._rebuild_disc_list()
|
||||
self._rebuild_disc_roles_list()
|
||||
self._fetch_models(self.current_provider)
|
||||
|
||||
while dpg.is_dearpygui_running():
|
||||
|
||||
Reference in New Issue
Block a user