This Platform
"You're looking at it." This portfolio site, DylanDocs, DylanBot, and DylanSupport are all one Phoenix application: github.com/dylanglover6/ChatSupportApp.
Why one app instead of three
A separate docs site or a separate chat service would fracture the design system and
make a persistent chat widget across every page much harder to pull off cleanly. One
Phoenix app, one root layout, one app.css: the whole thing (portfolio, docs, chat,
support desk) shares a single "Dark Arcade" design system and a single LiveView/PubSub
runtime.
Architecture
- KB / DylanDocs loader: loads these exact Markdown docs from
priv/kb/at runtime, parses simple frontmatter (title,slug,category,order,summary), renders CommonMark to HTML, and does keyword search over title/summary/body. No vector database, intentionally simple for an MVP. - Chat / AI: persists conversations and calls an LLM through one client module
boundary, the Claude API in production and a local Ollama model (
llama3.2) in development, with a deterministic fallback response whenever the live call fails, so the site never breaks just because a model is unavailable. - Agents / Tickets: mock support agents with shifts, specialties, and expertise levels; a rule-based assignment engine; and a fully simulated "send email" path with zero SMTP dependency (see the colophon for why that's a hard constraint, not a shortcut).
- Live chat takeover: Phoenix PubSub broadcasts an agent's messages into a visitor's chat widget in real time, and visitor replies back to the agent's screen, with no polling involved.
One AI boundary, two providers
Page-awareness and DylanDocs grounding are prompt engineering, not model capability, so
the AI client is a single module boundary with the provider chosen by config
(LLM_PROVIDER). Production runs the hosted Claude API (Haiku, cheap and quick for a
portfolio's traffic); local development runs a small Ollama model (llama3.2), which
is free, offline, and needs no API key. Because both sit behind the same boundary,
switching providers is a config change, not a rewrite, and a deterministic, KB-grounded
fallback covers any case where the live call fails.
Have a question about this page? Ask DylanBot →