File size: 4,303 Bytes
15d68eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""
StyleAdvisor — recommends a heritage style based on user prompt.

Uses the free AMD Qwen/DeepSeek API. Falls back to a keyword heuristic
when the API is unavailable (deterministic, same prompt → same recommendation).
"""
from __future__ import annotations

import json
import logging
import re
from typing import Dict

from .base import AgentClient, AgentResponse
from config.styles import HERITAGE_STYLES, StyleSpec, get_style, list_styles

log = logging.getLogger(__name__)

SYSTEM_PROMPT = """You are a cultural art advisor specializing in Indian heritage painting traditions.

You recommend ONE of these five styles based on the user's text description:

- madhubani: Bihar folk art, geometric patterns, nature and mythology motifs.
- warli: Maharashtra tribal art, white-on-ochre, stick figures, dance and hunting scenes.
- pattachitra: Odisha scroll painting, mythological narratives (Jagannath, Krishna).
- mughal: Mughal court miniature, fine detail, gold leaf, elevated viewpoint.
- tanjore: Tamil Nadu devotional icon, gold leaf, frontal symmetry, deity portrait.

Respond with STRICT JSON only, no markdown:
{"style": "<one of madhubani|warli|pattachitra|mughal|tanjore>",
 "reason": "<one-sentence cultural rationale>",
 "confidence": <float 0..1>}"""


class StyleAdvisor:
    def __init__(self, client: AgentClient | None = None) -> None:
        self.client = client or AgentClient(temperature=0.3, max_tokens=400)

    def recommend(self, user_prompt: str) -> Dict:
        """Return {style, reason, confidence, source}."""
        if self.client.enabled:
            resp: AgentResponse = self.client.chat(
                system_prompt=SYSTEM_PROMPT,
                user_prompt=f"User prompt: {user_prompt!r}\n\nRecommend a style as JSON.",
            )
            if resp.ok:
                parsed = self._safe_parse(resp.content)
                if parsed and parsed.get("style") in HERITAGE_STYLES:
                    parsed["source"] = "amd_agent"
                    return parsed
                log.warning("Agent JSON parse failed: %s", resp.content[:200])

        # Fallback heuristic
        return self._heuristic_recommend(user_prompt)

    def _safe_parse(self, content: str) -> Dict | None:
        # Try strict JSON first
        try:
            return json.loads(content)
        except Exception:
            pass
        # Extract first {...} block
        m = re.search(r"\{[^{}]*\}", content, re.DOTALL)
        if m:
            try:
                return json.loads(m.group(0))
            except Exception:
                pass
        return None

    @staticmethod
    def _heuristic_recommend(user_prompt: str) -> Dict:
        """Deterministic keyword-based fallback."""
        text = user_prompt.lower()
        scores = {sid: 0 for sid in HERITAGE_STYLES}

        keyword_map = {
            "madhubani": ["nature", "tree", "fish", "peacock", "sun", "moon",
                          "banyan", "krishna", "woman", "folk", "village"],
            "warli": ["dance", "tribal", "hunter", "village", "rural", "stick",
                      "community", "wedding", "tarpa", "celebration"],
            "pattachitra": ["jagannath", "krishna", "mythology", "story", "scroll",
                            "odisha", "temple", "narrative", "rama", "vishnu"],
            "mughal": ["court", "king", "emperor", "palace", "battle", "garden",
                       "prince", "princess", "hunt", "persian", "mughal"],
            "tanjore": ["deity", "god", "goddess", "temple", "devotion", "krishna",
                        "shiva", "vishnu", "laxmi", "saraswati", "icon", "prayer"],
        }

        for sid, kws in keyword_map.items():
            for kw in kws:
                if kw in text:
                    scores[sid] += 1

        # Pick top; tie-break by canonical order (madhubani first)
        best = max(scores, key=lambda k: (scores[k], -list(scores).index(k)))
        if scores[best] == 0:
            best = "madhubani"  # safe default

        style: StyleSpec = get_style(best)
        return {
            "style": best,
            "reason": f"Heuristic match on cultural keywords ({style.display_name}).",
            "confidence": min(0.5 + scores[best] * 0.1, 0.9),
            "source": "heuristic_fallback",
        }