In-Chat Image Generation (B2C)¶
B2C users get an Image chip in the AI-tutor chat. With the chip active, the tutor can generate an educational image (diagram, illustration, visualization) directly in the conversation — the orchestrator LLM writes the image prompt from chat context, the chatbot's media specialist renders it, and the image appears inline as a card. Pro users generate without limits; free users spend from their existing shared 10-generations/month pool.
Why this exists¶
- Who asked: Founder decision (2026-07-05, staging bug-bash) — self-initiated hypothesis, flagged as such. Trigger observation: a B2C learner account asked the tutor "can you generate me an image of a tiger running in the forest" and the tutor could only reply with a prompt to paste into some other product.
- User pain: A learner asking for a visual mid-study-session is ejected from the product at the exact moment of engagement — "use this prompt in an image generator" breaks the one-tutor promise.
- Cost of not doing it: Learners bounce to ChatGPT/Gemini for visuals mid-session; Pro loses a tangible, demo-able perk. Not acute today (small B2C base) — hence a metered free taste rather than a hard paywall.
- Validated or guess: A guess with one observed data point. Mitigant: marginal cost is low — the chatbot media specialist already existed for the trainer image tool, and pool metering caps free-tier spend.
- Success signal: Free users who generate images upgrade to Pro at a higher rate than those who don't; repeat usage across ≥2 sessions. Sean Ellis counterfactual: if the chip is removed a week after shipping, do image-users complain specifically? If not, it becomes Pro-only or gets cut.
How it works¶
User flow¶
- B2C user (learner or trainer) sees the Image chip in the chat input's mode row (driven by
available_modesfrom/auth/me, capabilityimage.create). - With the chip selected, the message goes through the normal chat stream with
learning_mode="image"; the webapp addsimage_generationto the chatbot'stools_enabledand, for free users, decrements the shared monthly pool (refunded if the turn produces no image). - The chatbot orchestrator's
generate_imagetool writes a full prompt from conversation context and calls the media toolkit (Imagen → Gemini → DALL-E fallback chain, same as the trainer tool). The asset comes back on animageSSE side-channel event as base64. - The webapp SSE relay intercepts the
imageevent: uploads bytes to GCS, writes aMediaGenerationrecord, and re-emits the event with a signed URL — the browser never receives base64. - The chat thread renders an image card (image + one-line caption); the image URL is persisted on the assistant message's
extra_dataso history reloads re-render it.
Gating (follows the standard B2C two-tier model)¶
| Plan | Behavior |
|---|---|
| B2C Free | Image generations decrement the shared 10/month generation pool (same pool as presentations, research, exam prep, recap). Pool exhausted → standard quota_exceeded SSE → upgrade CTA. |
| B2C Pro | Unlimited (pool bypass, same as every pool feature). |
| B2B (all roles) | Chip not offered. Trainers keep the existing standalone image tool (/ai-teacher/generate-image, daily GenerationQuota). |
Free metering is deliberately the shared pool, not a separate image counter and not a daily allowance: the B2C free tier retired per-feature limits in favor of one pool, and the pool pill ("X of 10 generations left") already communicates state. No schema change — QuotaFeature.IMAGE joins _POOL_FEATURES and the existing generations_used counter covers it.
Non-goals¶
- Video/audio in chat. Video costs ~5× per generation and takes minutes (Veo long-running operation) — wrong shape for a chat turn. Both stay reachable only via the
/agents/mediarunner (trainer surfaces). - A separate image pool or daily allowance. Rejected to keep one free-tier metering primitive (see Rejected alternatives).
- B2B learner access. Institutional learner media policy is an org-admin conversation, not a default.
- Replacing the trainer image tool.
/ai-teacher/generate-image(form-based, daily quota) is untouched.
Rejected alternatives¶
- "1 free image per day" (original proposal): would reintroduce the retired per-feature-limit pattern, needs a new
UserQuotacounter + daily reset semantics + parallel UI copy ("1 left today" next to "X of 10 this month"). Rejected for the shared pool. - Presentation-style dedicated endpoint (
/images/generate-streamshort-circuiting chat): loses the conversational prompt (the LLM refining "the tiger from the story we just read" into a full prompt) and adds a third dispatch path. The chat-relay interception pattern (question_bank/lesson_plan artifacts) already exists. - Metering via the daily
GenerationQuotasystem (image_daily): would double-gate B2C users across two quota systems with different periods. Chat images are governed by the B2C pool only;MediaGenerationrows are still written for history/cost tracking.
Where it lives¶
- Chatbot (
kwiloai_chatbot):src/agent/specialists/media/orchestrator_tool.py(thegenerate_imagepremium tool), registry +PremiumToolenum entries,imageSSE emission inorchestrator/runtime.py. - Webapp backend:
core/authorization/capabilities.py(IMAGE_CREATE),core/authorization/modes.py(imagemode, B2C pool),services/chatbot_client.py(image→image_generationtool mapping),services/b2c_quota.py(QuotaFeature.IMAGE),api/v1/ai_tutor.py(pool charge/refund +imageSSE interception + persistence). - Webapp frontend: Image chip in
services/ai-tutor/index.tsmode registries, icon map,imageevent handling inuseChatHandlers, inline image card inMessageBubble.
Changelog¶
- 2026-07-05 — Initial doc + implementation (chatbot + webapp PRs). Deploy order: chatbot first (webapp sending an unknown tool name would 422).