Phase 1C: Frontend unified contact UI

This commit is contained in:
Agent Zero
2026-07-23 17:17:32 +02:00
parent a8331fbc2b
commit 879106c4eb
62 changed files with 552 additions and 1276 deletions
+30 -58
View File
@@ -11,21 +11,17 @@ from typing import Any
# Precompiled patterns for intent detection
_PATTERNS = {
"create_company": re.compile(
r"\b(create|add|new)\b.*\b(company|firm|organization|organisation)\b", re.IGNORECASE
"create_contact": re.compile(
r"\b(create|add|new)\b.*\b(contact|company|firm|organization|organisation|person)\b", re.IGNORECASE
),
"delete_company": re.compile(r"\b(delete|remove)\b.*\b(company|firm)\b", re.IGNORECASE),
"update_company": re.compile(
r"\b(update|edit|modify|change)\b.*\b(company|firm)\b", re.IGNORECASE
"delete_contact": re.compile(r"\b(delete|remove)\b.*\b(contact|company|firm)\b", re.IGNORECASE),
"update_contact": re.compile(
r"\b(update|edit|modify|change)\b.*\b(contact|company|firm)\b", re.IGNORECASE
),
"list_company": re.compile(
r"\b(list|show|find|search|get|display)\b.*\b(compan|firm)\b", re.IGNORECASE
),
"list_company2": re.compile(r"\bcompan.*\b(list|all)\b", re.IGNORECASE),
"create_contact": re.compile(r"\b(create|add|new)\b.*\b(contact|person)\b", re.IGNORECASE),
"list_contact": re.compile(
r"\b(list|show|find|search|get|display)\b.*\b(contact|person)\b", re.IGNORECASE
r"\b(list|show|find|search|get|display)\b.*\b(contact|compan|firm|person)\b", re.IGNORECASE
),
"list_contact2": re.compile(r"\bcompan.*\b(list|all)\b", re.IGNORECASE),
"list_workflow": re.compile(r"\b(list|show|get|display)\b.*\b(workflow)\b", re.IGNORECASE),
"create_workflow": re.compile(r"\b(create|new|add)\b.*\b(workflow)\b", re.IGNORECASE),
"help": re.compile(r"\b(help|what can you do|assist)\b", re.IGNORECASE),
@@ -34,8 +30,8 @@ _PATTERNS = {
# Name extraction patterns - using single-quoted strings to avoid escaping issues
_NAME_PATTERNS = [
re.compile(r"\b(?:named|called|for)\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bcompany\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bcontact\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bcompany\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bworkflow\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bfirm\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
re.compile(r"\bperson\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
@@ -63,28 +59,28 @@ def map_query_to_actions(query: str, context: dict[str, Any] | None = None) -> l
q = query.lower().strip()
actions: list[dict[str, Any]] = []
# --- Company intents ---
if _PATTERNS["create_company"].search(q):
# --- Contact intents (unified: company + person) ---
if _PATTERNS["create_contact"].search(q):
name = _extract_name(query)
actions.append(
{
"method": "POST",
"path": "/api/v1/companies",
"body": {"name": name or "New Company"},
"description": f"Create a new company named '{name or 'New Company'}'",
"path": "/api/v1/contacts",
"body": {"name": name or "New Contact", "type": "company"},
"description": f"Create a new contact named '{name or 'New Contact'}'",
"confidence": 0.9,
}
)
elif _PATTERNS["delete_company"].search(q):
entity_id = context.get("company_id") or context.get("entity_id")
elif _PATTERNS["delete_contact"].search(q):
entity_id = context.get("contact_id") or context.get("entity_id")
if entity_id:
actions.append(
{
"method": "DELETE",
"path": f"/api/v1/companies/{entity_id}",
"path": f"/api/v1/contacts/{entity_id}",
"body": None,
"description": f"Delete company {entity_id}",
"description": f"Delete contact {entity_id}",
"confidence": 0.9,
}
)
@@ -92,65 +88,41 @@ def map_query_to_actions(query: str, context: dict[str, Any] | None = None) -> l
actions.append(
{
"method": "DELETE",
"path": "/api/v1/companies/{id}",
"path": "/api/v1/contacts/{id}",
"body": None,
"description": "Delete a company (requires company ID in context or selection)",
"description": "Delete a contact (requires contact ID in context or selection)",
"confidence": 0.5,
}
)
elif _PATTERNS["update_company"].search(q):
entity_id = context.get("company_id") or context.get("entity_id")
path = f"/api/v1/companies/{entity_id}" if entity_id else "/api/v1/companies/{id}"
elif _PATTERNS["update_contact"].search(q):
entity_id = context.get("contact_id") or context.get("entity_id")
path = f"/api/v1/contacts/{entity_id}" if entity_id else "/api/v1/contacts/{id}"
actions.append(
{
"method": "PATCH",
"path": path,
"body": _extract_update_fields(query),
"description": "Update company information",
"description": "Update contact information",
"confidence": 0.8,
}
)
elif _PATTERNS["list_company"].search(q) or _PATTERNS["list_company2"].search(q):
elif _PATTERNS["list_contact"].search(q) or _PATTERNS["list_contact2"].search(q):
search_term = _extract_search_term(query)
desc = "List companies"
desc = "List contacts"
if search_term:
desc += f" matching '{search_term}'"
actions.append(
{
"method": "GET",
"path": "/api/v1/companies",
"path": "/api/v1/contacts",
"body": None,
"description": desc,
"confidence": 0.85,
}
)
# --- Contact intents ---
elif _PATTERNS["create_contact"].search(q):
name = _extract_name(query)
actions.append(
{
"method": "POST",
"path": "/api/v1/contacts",
"body": {"name": name or "New Contact"},
"description": f"Create a new contact named '{name or 'New Contact'}'",
"confidence": 0.9,
}
)
elif _PATTERNS["list_contact"].search(q):
actions.append(
{
"method": "GET",
"path": "/api/v1/contacts",
"body": None,
"description": "List contacts",
"confidence": 0.85,
}
)
# --- Workflow intents ---
elif _PATTERNS["list_workflow"].search(q):
actions.append(
@@ -181,9 +153,9 @@ def map_query_to_actions(query: str, context: dict[str, Any] | None = None) -> l
actions.append(
{
"method": "GET",
"path": "/api/v1/companies",
"path": "/api/v1/contacts",
"body": None,
"description": "Show available companies (demonstration action)",
"description": "Show available contacts (demonstration action)",
"confidence": 0.3,
}
)
@@ -222,9 +194,9 @@ def _extract_update_fields(query: str) -> dict[str, Any]:
if re.search(r"\bphone\b", query, re.IGNORECASE):
match = _PHONE_PAT.search(query)
if match:
fields["phone"] = match.group(1).strip()
fields["phone_1"] = match.group(1).strip()
if re.search(r"\bemail\b", query, re.IGNORECASE):
match = _EMAIL_PAT.search(query)
if match:
fields["email"] = match.group(1).strip()
fields["email_1"] = match.group(1).strip()
return fields or {"name": "Updated Name"}