feat(ui): gemini-style top-anchored scroll, centered chat layout and task drawer sync (closes #49)
This commit is contained in:
@@ -10,16 +10,24 @@ AI-CONTEXT-ANCHORS:
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Tuple, Dict
|
||||
from typing import Tuple, Dict, List, Set
|
||||
from core.connection import get_connection
|
||||
|
||||
|
||||
# ANCHOR[PROMPT_DIFF_BUILDER]
|
||||
def build_prompt_diff(action: str, section_id: int, item_id: int, content: str = "", prompt_name: str = "main_agent") -> Tuple[str, str]:
|
||||
"""
|
||||
Возвращает кортеж (merged_draft_text, diff_html_for_ui).
|
||||
"""
|
||||
def build_prompt_diff(
|
||||
action: str,
|
||||
section_id: int = None,
|
||||
item_id: int = None,
|
||||
content: str = "",
|
||||
delete_nodes: List[Tuple[int, int]] = None,
|
||||
prompt_name: str = "main_agent"
|
||||
) -> Tuple[str, str]:
|
||||
act = (action or "ADD").upper()
|
||||
nodes_to_delete: Set[Tuple[int, int]] = set()
|
||||
|
||||
if delete_nodes:
|
||||
nodes_to_delete.update(delete_nodes)
|
||||
elif act == "DELETE" and section_id is not None and item_id is not None:
|
||||
nodes_to_delete.add((section_id, item_id))
|
||||
|
||||
with get_connection(row_factory=True) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -32,46 +40,40 @@ def build_prompt_diff(action: str, section_id: int, item_id: int, content: str =
|
||||
existing_nodes = cursor.fetchall()
|
||||
|
||||
nodes_dict = {(r["section_id"], r["item_id"]): r["content"] for r in existing_nodes}
|
||||
nodes_dict_for_draft = {k: v for k, v in nodes_dict.items() if k not in nodes_to_delete}
|
||||
|
||||
# Формируем словарь для чистого текста
|
||||
nodes_dict_for_draft = {k: v for k, v in nodes_dict.items() if k != (section_id, item_id)} if act == "DELETE" else dict(nodes_dict)
|
||||
if act != "DELETE":
|
||||
if act not in ["DELETE", "BATCH_DELETE"] and section_id is not None and item_id is not None:
|
||||
nodes_dict_for_draft[(section_id, item_id)] = content
|
||||
|
||||
draft_lines = []
|
||||
curr_sec = None
|
||||
for (s_id, i_id), txt in sorted(nodes_dict_for_draft.items()):
|
||||
if i_id == 0:
|
||||
if curr_sec is not None:
|
||||
draft_lines.append("")
|
||||
if curr_sec is not None: draft_lines.append("")
|
||||
draft_lines.append(f"{s_id}. {txt}")
|
||||
curr_sec = s_id
|
||||
else:
|
||||
draft_lines.append(f" {s_id}.{i_id}. {txt}")
|
||||
merged_prompt = "\n".join(draft_lines)
|
||||
|
||||
# Формируем HTML Diff
|
||||
diff_lines = []
|
||||
curr_sec = None
|
||||
display_nodes = dict(nodes_dict)
|
||||
if act != "DELETE":
|
||||
if act not in ["DELETE", "BATCH_DELETE"] and section_id is not None and item_id is not None:
|
||||
display_nodes[(section_id, item_id)] = content
|
||||
|
||||
for (s_id, i_id), txt in sorted(display_nodes.items()):
|
||||
if i_id == 0:
|
||||
if curr_sec is not None:
|
||||
diff_lines.append("")
|
||||
if curr_sec is not None: diff_lines.append("")
|
||||
diff_lines.append(f"{s_id}. {txt}")
|
||||
curr_sec = s_id
|
||||
else:
|
||||
if s_id == section_id and i_id == item_id:
|
||||
if act == "DELETE":
|
||||
line_str = f' <span class="line-through text-rose-600 font-bold bg-rose-50 px-1.5 py-0.5 rounded border border-rose-300 opacity-80">{s_id}.{i_id}. {txt} [УДАЛЕНИЕ]</span>'
|
||||
else:
|
||||
line_str = f' <span class="text-rose-600 font-bold bg-rose-50 px-1.5 py-0.5 rounded border border-rose-300">{s_id}.{i_id}. {txt}</span>'
|
||||
if (s_id, i_id) in nodes_to_delete:
|
||||
line_str = f' <span class="line-through text-rose-600 font-bold bg-rose-50 px-1.5 py-0.5 rounded border border-rose-300 opacity-80">{s_id}.{i_id}. {txt} [УДАЛЕНИЕ]</span>'
|
||||
elif act == "ADD" and s_id == section_id and i_id == item_id:
|
||||
line_str = f' <span class="text-rose-600 font-bold bg-rose-50 px-1.5 py-0.5 rounded border border-rose-300">{s_id}.{i_id}. {txt}</span>'
|
||||
else:
|
||||
line_str = f" {s_id}.{i_id}. {txt}"
|
||||
diff_lines.append(line_str)
|
||||
|
||||
diff_html = "\n".join(diff_lines)
|
||||
return merged_prompt, diff_html
|
||||
return merged_prompt, "\n".join(diff_lines)
|
||||
@@ -7,7 +7,7 @@ ROLE: Единый доменный сервис управления систе
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
from typing import Tuple, Dict, Any
|
||||
from typing import Tuple, Dict, Any, List, Optional
|
||||
from .repository import repo_get_active_prompt, repo_apply_prompt_action, repo_save_full_prompt
|
||||
from .diff_engine import build_prompt_diff
|
||||
|
||||
@@ -22,16 +22,28 @@ def apply_prompt_action(action: str, section_id: int, item_id: int, content: str
|
||||
repo_apply_prompt_action(action, section_id, item_id, content)
|
||||
|
||||
|
||||
def save_full_prompt_draft(draft_text: str) -> None:
|
||||
def save_full_prompt_draft(draft_text: str, prompt_name: str = "main_agent") -> None:
|
||||
"""Сохранить полный черновик промпта."""
|
||||
repo_save_full_prompt(draft_text)
|
||||
repo_save_full_prompt(draft_text, prompt_name=prompt_name)
|
||||
|
||||
|
||||
def create_prompt_preview(action: str, section_id: int, item_id: int, content: str = "") -> Tuple[str, str, str]:
|
||||
def create_prompt_preview(
|
||||
action: str,
|
||||
section_id: Optional[int] = None,
|
||||
item_id: Optional[int] = None,
|
||||
content: str = "",
|
||||
delete_nodes: Optional[List[Tuple[int, int]]] = None
|
||||
) -> Tuple[str, str, str]:
|
||||
"""
|
||||
Формирует черновик и diff.
|
||||
Возвращает (merged_draft, diff_html, baseline_prompt).
|
||||
"""
|
||||
baseline = repo_get_active_prompt()
|
||||
draft, diff_html = build_prompt_diff(action, section_id, item_id, content)
|
||||
draft, diff_html = build_prompt_diff(
|
||||
action=action,
|
||||
section_id=section_id,
|
||||
item_id=item_id,
|
||||
content=content,
|
||||
delete_nodes=delete_nodes
|
||||
)
|
||||
return draft, diff_html, baseline
|
||||
Reference in New Issue
Block a user