Files
ObsiGate/backend/tools/labels.py
T
bruno c5c225a68e
CI / lint (push) Successful in 2m36s
CI / security (push) Successful in 1m49s
CI / test (push) Successful in 4m33s
CI / build (push) Successful in 2m39s
CI / e2e (push) Successful in 15m45s
feat: completude de l'editeur Excel - mise en forme, sortie, concurrence, cache, outils IA #156
L'editeur lisait les styles mais ne les ecrivait pas, exportait la feuille
entiere et laissait le dernier-ecrivain gagner entre processus.

- A8 : bouton Mise en forme (gras/italique/souligne, alignements, couleurs via
  selecteur natif, formats de nombre, fusion, volets figes, largeur/hauteur) et
  nouvelle route PUT .../xlsx/style (verrou, backup, swap atomique, garde de
  perte, If-Match) ; A9 : decision "pas de moteur de formule" annoncee dans
  l'UI ; A10 : undo/redo unifie, piles par fichier conservees au re-rendu
- A11 : export de la selection + Markdown/HTML/impression et recherche sur
  toutes les feuilles ; A12 : concurrence optimiste (If-Match -> 409 reparable,
  retry qui relit) ; A13 : cache des metadonnees par (chemin, mtime, taille)
- A14 : outils IA .xlsm/.csv + search_workbook, analyze_range,
  edit_xlsx_structure
- tests : test_xlsx_styles.py (17), test_spreadsheet_tools.py (34),
  TestOptimisticConcurrency/TestMetaCache, JSDOM xlsx-viewer 107/107
2026-09-30 07:04:47 -04:00

109 lines
4.3 KiB
Python

"""Human-readable labels for tool calls (Notion-style “steps” section).
The assistant's conversation window shows a collapsible « N steps » block
above each answer. Raw tool names (``search_fulltext``) are developer-centric;
this module maps every registered tool to an i18n message key plus the
argument worth surfacing (path, query, …), so the UI can render sentences like
« Recherche dans le vault : pizza » or « Fichier lu : notes/x.md ».
The label KEY is emitted with each ``tool`` SSE event; the client resolves it
against its locale dictionaries (``ai.step.<key>``). Unknown tools fall back to
a generic key with the raw name prettified, so a new tool never breaks the UI.
"""
from __future__ import annotations
from typing import Any
# tool name -> (message-key, primary argument name or None)
# NOTE: argument names MUST match the pydantic input models in
# backend/tools/schemas.py (verified by tests/test_tool_labels.py).
_STEP_LABELS: dict[str, tuple[str, str | None]] = {
"list_vaults": ("vaults", None),
"list_directory": ("directory", "path"),
"list_all_files": ("files", "dir"),
"read_file": ("file_read", "path"),
"read_file_raw": ("file_read", "path"),
"get_backlinks": ("backlinks", "path"),
"list_backups": ("backups", "path"),
"diff_backup": ("backup_diff", "path"),
"get_graph": ("graph", None),
"search_fulltext": ("search", "q"),
"search_advanced": ("search", "q"),
"search_paths": ("search_paths", "q"),
"list_tags": ("tags", None),
"suggest_tags": ("tags_suggest", "q"),
"list_recent": ("recent", None),
"create_file": ("file_create", "path"),
"create_directory": ("dir_create", "path"),
"edit_file": ("file_edit", "path"),
"append_to_file": ("file_append", "path"),
"rename_file": ("file_rename", "path"),
"rename_directory": ("dir_rename", "path"),
"move_path": ("move", "source_path"),
"replace_in_files": ("replace", "find"),
"delete_file": ("file_delete", "path"),
"delete_directory": ("dir_delete", "path"),
"restore_backup": ("backup_restore", "path"),
"web_search": ("web_search", "query"),
"fetch_url": ("fetch_url", "url"),
"crawl_site": ("crawl", "url"),
"git_list_repos": ("git_repos", "provider"),
"git_search_issues": ("git_issues", "query"),
"git_get_file": ("git_file", "path"),
"create_xlsx": ("xlsx_create", "path"),
"list_xlsx_sheets": ("xlsx_sheets", "path"),
"xlsx_to_markdown": ("xlsx_read", "path"),
"update_xlsx_cells": ("xlsx_update", "path"),
"append_xlsx_rows": ("xlsx_append", "path"),
"search_workbook": ("xlsx_search", "query"),
"analyze_range": ("xlsx_analyze", "path"),
"edit_xlsx_structure": ("xlsx_structure", "path"),
"create_docx": ("docx_create", "path"),
"create_csv": ("csv_create", "path"),
"create_pdf": ("pdf_create", "path"),
}
GENERIC_KEY = "generic"
def _prettify(name: str) -> str:
return name.replace("_", " ").strip()
def _primary_value(arguments: dict[str, Any], arg: str | None) -> str | None:
if not arg:
return None
value = arguments.get(arg)
if isinstance(value, str) and value.strip():
return value.strip()
return None
def tool_step_label(name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any]:
"""Return ``{key, params}`` describing one tool call in human terms.
``key`` resolves client-side to ``ai.step.<key>``; ``params`` carries the
optional ``{value}`` placeholder (path, query, …). For an unknown tool the
generic key is used with the prettified name as the value.
"""
arguments = arguments or {}
key, arg = _STEP_LABELS.get(name, (GENERIC_KEY, None))
if key == GENERIC_KEY:
return {"key": GENERIC_KEY, "params": {"tool": _prettify(name)}}
value = _primary_value(arguments, arg)
params: dict[str, str] = {}
if value is not None:
params["value"] = value
return {"key": key, "params": params}
def thought_step_label(text: str) -> dict[str, Any]:
"""Step descriptor for one intermediate reasoning note of the model.
The note is shown expanded under a “Thought” sub-section in the UI, so it
is kept long enough to be readable (truncation is the safety net).
"""
cleaned = " ".join((text or "").split())
return {"key": "thought", "params": {"value": cleaned[:1200]}}