Fix new-question landing and question-language HITL
Publish documentation / publish (push) Successful in 34s
Publish documentation / publish (push) Successful in 34s
Reset the activity panel when starting a new question so the landing navigation is restored. Detect and persist the original question language, pass it through runtime and widget descriptors, and scope HITL controls to that language. Validated with gate, session, backend and frontend tests, TypeScript checks, Ruff and strict docs build. Rebuilt and restarted local core/frontend; both healthy and serving HTTP successfully.
This commit is contained in:
@@ -178,7 +178,8 @@ def list_cmd(
|
||||
def new_cmd(
|
||||
question: str = typer.Argument(..., help="La domanda in linguaggio naturale."),
|
||||
interaction_language: str = typer.Option(
|
||||
None, "--interaction-language", help="BCP-47 interaction language; defaults to workspace language."
|
||||
None, "--interaction-language",
|
||||
help="BCP-47 fallback for ambiguous questions; otherwise use the question's language.",
|
||||
),
|
||||
provider: str = typer.Option(None, "--provider", help="Provider LLM (es. zai, anthropic)."),
|
||||
model: str = typer.Option(None, "--model", help="Modello LLM (es. glm-5.2)."),
|
||||
@@ -193,10 +194,11 @@ def new_cmd(
|
||||
from tht.session.store import _extract_name, new_session_manifest, render_question_md
|
||||
|
||||
cfg = _load_config_or_exit(config)
|
||||
from tht.session.language import validate_language_tag
|
||||
from tht.session.language import question_interaction_language
|
||||
|
||||
try:
|
||||
language = validate_language_tag(
|
||||
language = question_interaction_language(
|
||||
question,
|
||||
interaction_language if interaction_language is not None else getattr(cfg, "language", "en")
|
||||
)
|
||||
except ValueError as exc:
|
||||
@@ -209,7 +211,7 @@ def new_cmd(
|
||||
repository.create(manifest)
|
||||
repository.write_artifact(manifest.id, "question", render_question_md(question))
|
||||
if json_out:
|
||||
typer.echo(json.dumps({"id": manifest.id}, ensure_ascii=False))
|
||||
typer.echo(json.dumps({"id": manifest.id, "interaction_language": language}, ensure_ascii=False))
|
||||
return
|
||||
typer.secho(f"OK: sessione creata ({manifest.id})", fg=typer.colors.GREEN)
|
||||
typer.echo(manifest.id)
|
||||
@@ -384,7 +386,7 @@ def ensure_interaction_language_cmd(
|
||||
json_out: bool = typer.Option(False, "--json", help="Emit pristine JSON."),
|
||||
config: Path = CONFIG_OPT,
|
||||
) -> None:
|
||||
"""Pin a missing legacy language from the workspace; never override a saved value."""
|
||||
"""Pin a missing language from the question; never override a saved value."""
|
||||
cfg = _load_config_or_exit(config)
|
||||
manifest = ensure_session_language_or_exit(cfg, session_id)
|
||||
if json_out:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Session language policy, independent of workspace document content."""
|
||||
import re
|
||||
from functools import lru_cache
|
||||
|
||||
from langdetect import DetectorFactory, LangDetectException
|
||||
from langdetect.detector_factory import PROFILES_DIRECTORY
|
||||
|
||||
_LANGUAGE_TAG = re.compile(
|
||||
r"(?P<language>[A-Za-z]{2,8})(?:-(?P<script>[A-Za-z]{4}))?"
|
||||
@@ -21,3 +25,36 @@ def validate_language_tag(value: str) -> str:
|
||||
if match["region"]:
|
||||
parts.append(match["region"].upper())
|
||||
return "-".join(parts) + match["rest"].lower()
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _question_detector_factory() -> DetectorFactory:
|
||||
factory = DetectorFactory()
|
||||
factory.seed = 0
|
||||
factory.load_profile(PROFILES_DIRECTORY)
|
||||
return factory
|
||||
|
||||
|
||||
def question_interaction_language(question: str, fallback: str) -> str:
|
||||
"""Pin confidently detected prose language; short/ambiguous/code input uses the hint."""
|
||||
fallback = validate_language_tag(fallback)
|
||||
prose = re.sub(r"```[\s\S]*?```|`[^`]*`|\b\w*_\w*\b", " ", question)
|
||||
if re.match(
|
||||
r"\s*(?:SELECT\s+.+\s+FROM\b|WITH\s+\w+\s+AS\s*\(|INSERT\s+INTO\b|"
|
||||
r"UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|CREATE\s+TABLE\b)",
|
||||
prose, re.IGNORECASE | re.DOTALL,
|
||||
):
|
||||
return fallback
|
||||
if sum(character.isalpha() for character in prose) < 12:
|
||||
return fallback
|
||||
detector = _question_detector_factory().create()
|
||||
detector.append(prose)
|
||||
try:
|
||||
candidates = detector.get_probabilities()
|
||||
except LangDetectException:
|
||||
return fallback
|
||||
if not candidates or candidates[0].prob < 0.90:
|
||||
return fallback
|
||||
detected = validate_language_tag(candidates[0].lang)
|
||||
# Preserve a regional preference when it agrees with the detected language.
|
||||
return fallback if detected.split("-")[0] == fallback.split("-")[0] else detected
|
||||
|
||||
@@ -151,13 +151,13 @@ def new_session_manifest(
|
||||
|
||||
def pin_interaction_language(manifest: SessionManifest, workspace_language: str) -> bool:
|
||||
"""Apply legacy compatibility once, while the repository holds its writer lock."""
|
||||
from tht.session.language import validate_language_tag
|
||||
from tht.session.language import question_interaction_language
|
||||
|
||||
if manifest.interaction_language is not None:
|
||||
return False
|
||||
if manifest.status == "finalized" or manifest.archived:
|
||||
raise SessionError("Session is read-only (finalized or archived)")
|
||||
manifest.interaction_language = validate_language_tag(workspace_language)
|
||||
manifest.interaction_language = question_interaction_language(manifest.question, workspace_language)
|
||||
manifest.updated_at = datetime.now(UTC)
|
||||
manifest.updated_by = current_author()
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user