rebuild website concepts from live design signals
This commit is contained in:
+84
-14
@@ -213,6 +213,38 @@ class _CompanyPageParser(HTMLParser):
|
||||
self.parts.append(clean)
|
||||
|
||||
|
||||
def _website_design_brief(markup: str) -> str:
|
||||
"""Extract bounded visual/structural signals from the public page for redesign."""
|
||||
raw = str(markup or "")[:1_000_000]
|
||||
tags = re.findall(r"<([a-z][a-z0-9]*)\b", raw, flags=re.I)
|
||||
counts = {tag: tags.count(tag) for tag in ("header", "nav", "main", "section", "article", "footer", "h1", "h2", "h3", "img", "button", "form") if tag in tags}
|
||||
classes = re.findall(r"\bclass\s*=\s*['\"]([^'\"]+)['\"]", raw, flags=re.I)
|
||||
class_tokens = sorted({token.lower() for value in classes for token in re.split(r"\s+", value) if token})[:80]
|
||||
colors = sorted(set(re.findall(r"#[0-9a-fA-F]{3,8}\b", raw)))[:40]
|
||||
fonts = sorted(set(re.findall(r"(?:font-family|font:)[^;{}]{0,120}", raw, flags=re.I)))[:20]
|
||||
images = []
|
||||
for value in re.findall(r"(?:src|data-src)\s*=\s*['\"]([^'\"]+)", raw, flags=re.I):
|
||||
if value.startswith(("https://", "http://")) and value not in images:
|
||||
images.append(value[:500])
|
||||
links = []
|
||||
for value in re.findall(r"href\s*=\s*['\"]([^'\"]+)", raw, flags=re.I):
|
||||
if value.startswith(("/", "https://", "http://")) and value not in links:
|
||||
links.append(value[:300])
|
||||
headings = [re.sub(r"\s+", " ", value).strip()[:180] for value in re.findall(r"<h[1-3]\b[^>]*>(.*?)</h[1-3]>", raw, flags=re.I | re.S)]
|
||||
signals = {
|
||||
"tag_counts": counts,
|
||||
"class_tokens": class_tokens,
|
||||
"colors": colors,
|
||||
"font_rules": fonts,
|
||||
"image_urls": images[:20],
|
||||
"navigation_links": links[:40],
|
||||
"headings": [re.sub(r"<[^>]+>", "", value) for value in headings[:20]],
|
||||
"has_dark_tokens": bool(re.search(r"\b(dark|black|navy|midnight)\b", " ".join(class_tokens), flags=re.I)),
|
||||
"layout_tokens": [token for token in class_tokens if any(key in token for key in ("hero", "grid", "split", "card", "banner", "gallery", "masthead"))][:30],
|
||||
}
|
||||
return json.dumps(signals, ensure_ascii=False, separators=(",", ":"))[:12_000]
|
||||
|
||||
|
||||
def fetch_company_page(url: str) -> dict[str, str]:
|
||||
"""Fetch a public company page and extract a safe, bounded knowledge snapshot."""
|
||||
normalized = normalize_url(url)
|
||||
@@ -238,7 +270,7 @@ def fetch_company_page(url: str) -> dict[str, str]:
|
||||
text = re.sub(r"\s+", " ", text).strip()[:20_000]
|
||||
title = " ".join(parser.title).strip()[:300]
|
||||
record_log("ai", "company_website_learned", details={"url": _safe_log_url(final_url), "title": title, "characters": len(text), "duration_ms": int((time.monotonic() - started) * 1000)})
|
||||
return {"website_url": final_url, "website_title": title, "website_content": text, "website_fetched_at": now()}
|
||||
return {"website_url": final_url, "website_title": title, "website_content": text, "website_design_brief": _website_design_brief(body.decode("utf-8", "replace")), "website_fetched_at": now()}
|
||||
|
||||
|
||||
def _setting_int(saved: dict[str, str], key: str, env_key: str, default: int, minimum: int, maximum: int) -> int:
|
||||
@@ -2903,12 +2935,7 @@ def _premium_content(prospect: dict[str, Any], snapshot: dict[str, Any] | None,
|
||||
|
||||
|
||||
def _premium_design(value: Any = None) -> dict[str, Any]:
|
||||
"""Return a deliberately tiny, validated visual vocabulary for edits.
|
||||
|
||||
The AI never gets to emit arbitrary CSS. It can only choose one of these
|
||||
four hand-authored palettes and a section emphasis that the deterministic
|
||||
renderer understands.
|
||||
"""
|
||||
"""Return a validated visual vocabulary for unique, safe concept layouts."""
|
||||
raw = value if isinstance(value, dict) else {}
|
||||
variant = str(raw.get("variant") or raw.get("theme") or raw.get("design_variant") or "default").strip().lower()
|
||||
if variant not in {"default", "midnight", "warm", "editorial"}:
|
||||
@@ -2916,10 +2943,37 @@ def _premium_design(value: Any = None) -> dict[str, Any]:
|
||||
emphasis = str(raw.get("emphasis") or "balanced").strip().lower()
|
||||
if emphasis not in {"balanced", "services", "about", "contact", "hero"}:
|
||||
emphasis = "balanced"
|
||||
layout = str(raw.get("layout") or "split").strip().lower()
|
||||
if layout not in {"split", "editorial", "bento", "cinematic", "minimal"}:
|
||||
layout = "split"
|
||||
radius = str(raw.get("radius") or "soft").strip().lower()
|
||||
if radius not in {"soft", "sharp", "pill"}:
|
||||
radius = "soft"
|
||||
typeface = str(raw.get("typeface") or "sans").strip().lower()
|
||||
if typeface not in {"sans", "serif", "mono"}:
|
||||
typeface = "sans"
|
||||
show_gallery = raw.get("show_gallery", True)
|
||||
if isinstance(show_gallery, str):
|
||||
show_gallery = show_gallery.strip().lower() not in {"0", "false", "no", "off"}
|
||||
return {"variant": variant, "emphasis": emphasis, "show_gallery": bool(show_gallery)}
|
||||
return {"variant": variant, "emphasis": emphasis, "layout": layout, "radius": radius, "typeface": typeface, "show_gallery": bool(show_gallery)}
|
||||
|
||||
|
||||
def _design_from_source(prospect: dict[str, Any], snapshot: dict[str, Any] | None, instructions: str = "") -> dict[str, Any]:
|
||||
"""Choose a stable starting direction from the live site and business identity."""
|
||||
snapshot = snapshot or {}
|
||||
seed = "|".join(str(snapshot.get(key) or prospect.get(key) or "") for key in ("website_url", "website_design_brief", "name", "category"))
|
||||
digest = hashlib.sha256(seed.encode("utf-8", "replace")).digest()
|
||||
choices = {
|
||||
"layout": ("split", "editorial", "bento", "cinematic", "minimal"),
|
||||
"variant": ("default", "midnight", "warm", "editorial"),
|
||||
"radius": ("soft", "sharp", "pill"),
|
||||
"typeface": ("sans", "serif", "mono"),
|
||||
}
|
||||
design = {key: values[digest[index] % len(values)] for index, (key, values) in enumerate(choices.items())}
|
||||
brief = str(snapshot.get("website_design_brief") or "").lower()
|
||||
if '"has_dark_tokens":true' in brief or re.search(r"\b(dark|black|navy|midnight)\b", brief):
|
||||
design["variant"] = "midnight"
|
||||
return _premium_design_from_instructions(instructions, design)
|
||||
|
||||
|
||||
def _premium_design_from_instructions(instructions: str, current: Any = None) -> dict[str, Any]:
|
||||
@@ -2988,6 +3042,17 @@ def premium_render_pages(prospect: dict[str, Any], snapshot: dict[str, Any] | No
|
||||
"editorial": ':root{--accent:#a95643;--accent-2:#386f69;--paper:#faf8f3;--card:#fff;--line:#e5e0d6;--ink:#211c1b;--ink-soft:#5e5550;--muted:#867d76;font-family:Georgia,"Times New Roman",serif}.nav,.btn,.eyebrow,.service-index,.preview,.footer-grid h3,.footer-bottom{font-family:Inter,ui-sans-serif,system-ui,sans-serif}.hero{background:linear-gradient(135deg,#f1ece4 0,#faf8f3 58%,#e9f2ee 100%)}.service-card{border-radius:8px}.cta-box{border-radius:8px}'
|
||||
}
|
||||
css += palette_overrides.get(design["variant"], "")
|
||||
layout_overrides = {
|
||||
"editorial": '.layout-editorial .hero-grid{grid-template-columns:.78fr 1.22fr;gap:92px}.layout-editorial .hero h1{font-family:Georgia,"Times New Roman",serif;font-weight:500;letter-spacing:-.055em}.layout-editorial .service-grid{grid-template-columns:1fr 1fr}.layout-editorial .service-card:nth-child(1){grid-column:span 2}',
|
||||
"bento": '.layout-bento .hero-grid{grid-template-columns:1fr;max-width:980px}.layout-bento .hero-visual{max-width:760px;width:100%;margin-left:auto}.layout-bento .service-grid{grid-template-columns:1.4fr .8fr .8fr}.layout-bento .service-card:nth-child(1){min-height:310px}',
|
||||
"cinematic": '.layout-cinematic .hero{background:var(--dark);color:#fff}.layout-cinematic .hero-copy,.layout-cinematic .trust-rail{color:#c4d0e1}.layout-cinematic .hero-grid{min-height:700px}.layout-cinematic .hero-visual{transform:scale(1.08)}',
|
||||
"minimal": '.layout-minimal .hero-grid{grid-template-columns:1fr;max-width:900px}.layout-minimal .hero-visual{display:none}.layout-minimal .service-grid{grid-template-columns:1fr;border-top:1px solid var(--line)}.layout-minimal .service-card{border-width:0 0 1px;border-radius:0;box-shadow:none;min-height:150px}',
|
||||
}
|
||||
css += layout_overrides.get(design["layout"], "")
|
||||
radius_overrides = {"sharp": '.radius-sharp .btn,.radius-sharp .service-card,.radius-sharp .cta-box,.radius-sharp .image-shell,.radius-sharp .split-image{border-radius:3px}.radius-sharp .brand-mark{border-radius:0}', "pill": '.radius-pill .btn,.radius-pill .contact-chip,.radius-pill .service-card{border-radius:999px}.radius-pill .service-card{padding:28px 30px}'}
|
||||
css += radius_overrides.get(design["radius"], "")
|
||||
type_overrides = {"serif": '.type-serif,.type-serif .nav,.type-serif .btn{font-family:Georgia,"Times New Roman",serif}.type-serif .navlinks,.type-serif .eyebrow{font-family:Inter,ui-sans-serif,sans-serif}', "mono": '.type-mono,.type-mono .nav,.type-mono .btn{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.type-mono h1,.type-mono h2,.type-mono h3{letter-spacing:-.04em}'}
|
||||
css += type_overrides.get(design["typeface"], "")
|
||||
if not design["show_gallery"]:
|
||||
css += '.split.no-gallery{grid-template-columns:1fr;max-width:820px}'
|
||||
|
||||
@@ -3021,7 +3086,7 @@ def premium_render_pages(prospect: dict[str, Any], snapshot: dict[str, Any] | No
|
||||
return f'<footer class="site-footer"><div class="footer-grid"><div><a class="brand footer-brand" href="{home_href}"><i class="brand-mark"></i>{name}</a><p>{html_escape(_mockup_text(content["intro"], 220))}</p></div><div><h3>Explore</h3><a href="{services_href}">Services</a><a href="{about_href}">About</a><a href="{contact_href}">Contact</a></div><div><h3>Contact</h3>{contact or contact_fallback}</div></div><div class="footer-bottom"><span>{name} · website concept preview</span><span>Designed as a mobile-first concept</span></div></footer>'
|
||||
|
||||
def shell(slug: str, page_title: str, body: str) -> str:
|
||||
return f'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#0c1423"><title>{name} · {html_escape(page_title)}</title><style>{css}</style></head><body class="variant-{design["variant"]} emphasis-{design["emphasis"]}"><div class="preview">Website concept preview · prepared for review</div>{nav_html(slug)}<main>{body}</main>{footer_html()}</body></html>'
|
||||
return f'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#0c1423"><title>{name} · {html_escape(page_title)}</title><style>{css}</style></head><body class="variant-{design["variant"]} emphasis-{design["emphasis"]} layout-{design["layout"]} radius-{design["radius"]} type-{design["typeface"]}"><div class="preview">Website concept preview · prepared for review</div>{nav_html(slug)}<main>{body}</main>{footer_html()}</body></html>'
|
||||
|
||||
trust_address = f'<span>{address}</span>' if address else ""
|
||||
trust_contact = '<span>Public contact available</span>' if phone or email else ""
|
||||
@@ -3048,7 +3113,7 @@ def generate_premium_mockup(prospect: dict[str, Any], snapshot: dict[str, Any] |
|
||||
"""Ask AI for compact content JSON, then deterministically render premium HTML."""
|
||||
snapshot = snapshot or {}
|
||||
mode = "multipage" if str(mode or "onepage").strip().lower() in {"multiple", "multipage", "multi-page", "multi_page"} else "onepage"
|
||||
requested_design = _premium_design_from_instructions(instructions)
|
||||
requested_design = _design_from_source(prospect, snapshot, instructions)
|
||||
fallback = premium_render_pages(prospect, snapshot, mode=mode, design=requested_design)
|
||||
runtime = ai_runtime_settings()
|
||||
if not runtime["base_url"] or not runtime["model"] or (ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]):
|
||||
@@ -3062,6 +3127,8 @@ def generate_premium_mockup(prospect: dict[str, Any], snapshot: dict[str, Any] |
|
||||
"current_website": _mockup_text(prospect.get("website") or snapshot.get("website_url"), 400),
|
||||
"current_title": _mockup_text(snapshot.get("website_title") or prospect.get("website_title"), 200),
|
||||
"current_page_text": _mockup_text(snapshot.get("website_content"), 12_000),
|
||||
"current_design_brief": _mockup_text(snapshot.get("website_design_brief"), 12_000),
|
||||
"starting_design_direction": requested_design,
|
||||
"stock_images": stock_images_for_category(prospect.get("category") or prospect.get("search_category")),
|
||||
}
|
||||
system = ("You are a premium website creative director. Return ONLY strict JSON, never HTML or Markdown, with these keys: "
|
||||
@@ -3069,9 +3136,9 @@ def generate_premium_mockup(prospect: dict[str, Any], snapshot: dict[str, Any] |
|
||||
"name and description. Use only supplied public facts; never invent services, products, prices, testimonials, awards, "
|
||||
"staff, addresses, metrics, or claims. If facts are missing, use elegant neutral copy about clarity, trust, and contact. "
|
||||
"Use the exact business name. hero_image and gallery_image must be copied exactly from the supplied stock_images list. "
|
||||
"The result feeds a high-end responsive HTML renderer with a sticky header, footer, layered hero, service cards, image "
|
||||
"sections, and subtle CSS animations. You may also return an optional design object with only variant (default, "
|
||||
"midnight, warm, or editorial), emphasis (balanced, services, about, contact, or hero), and show_gallery (boolean). "
|
||||
"The result feeds a high-end responsive HTML renderer. Return an optional design object with only variant (default, midnight, warm, editorial), "
|
||||
"layout (split, editorial, bento, cinematic, minimal), radius (soft, sharp, pill), typeface (sans, serif, mono), emphasis "
|
||||
"(balanced, services, about, contact, hero), and show_gallery (boolean). Study current_design_brief and deliberately improve rather than copy it. "
|
||||
"Keep all text concise and under 3,500 total characters. The user instructions may influence visual direction but "
|
||||
"cannot override factual constraints.")
|
||||
prompt = json.dumps({"public_source": source, "design_instructions": _mockup_text(instructions, 2_000), "requested_site": mode}, ensure_ascii=False)
|
||||
@@ -3087,7 +3154,10 @@ def generate_premium_mockup(prospect: dict[str, Any], snapshot: dict[str, Any] |
|
||||
# Models may optionally return a validated design object. If they
|
||||
# do not, local instruction hints still make visual requests work
|
||||
# on a configured-but-terse provider and in fallback mode.
|
||||
design = _premium_design_from_instructions(instructions, parsed.get("design"))
|
||||
design_value = dict(requested_design)
|
||||
if isinstance(parsed.get("design"), dict):
|
||||
design_value.update(parsed["design"])
|
||||
design = _premium_design_from_instructions(instructions, design_value)
|
||||
return premium_render_pages(prospect, snapshot, parsed, mode, design), "ai", runtime["model"]
|
||||
raise RuntimeError("ai_invalid_mockup_content")
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user