Commit graph

112 commits

Author SHA1 Message Date
Vadym Samoilenko
802c004ca4 feat(i18n): full EN/UK/RU coverage — app pages, landing, AppLayout switcher
- Add LanguageSwitcher to AppLayout header (all authenticated pages)
- Fix Pricing tooltip: remove nested TooltipProvider (broken hover popup)
- Landing: FAQ, HowItWorks, Comparison, Testimonials, FeatureGrid, Footer
- App pages: Dashboard, Admin, MyUsage, Billing
- Toast messages: FocusGroups, SyntheticUsers, FocusGroupSession (28 toasts)
- New namespaces: faq, how_it_works, comparison, testimonials, features,
  footer, dashboard, admin, usage, billing, focus_groups, synthetic_users,
  focus_group_session — 130+ keys across EN/UK/RU

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:23:36 +01:00
Vadym Samoilenko
183a3cb2ff feat: full report export — LLM executive summary + transcript + themes
GET /api/focus-groups/{id}/report/download generates a markdown report
with AI-written executive summary, key themes with quotes, and full
transcript. Frontend adds "Export Full Report" button to ThemesPanel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:08:40 +01:00
Vadym Samoilenko
ef7ac32a2b fix: audit — crash fixes, SSRF, API key leak, whitelist merge, stale closure
Frontend:
- SetupTab: restore missing Input import (runtime crash on render)
- SetupTab: wrap onSubmit in event adapter — button outside <form> passed undefined
- Pricing: load() → useCallback, add to useEffect deps (stale closure)
- Pricing: credits_label hardcoded string → t() interpolation

Backend:
- admin/ai-config/test: remove endpoint/api_key from request body (SSRF fix)
  — endpoint and key now read exclusively from DB or env vars
- admin/ai-config/test: str(exc) → sanitized message, full exc logged server-side only
  (prevents API key leak via OpenAI SDK error context)
- admin/ai-config PUT: whitelist allowed fields on provider/model merge
  (prevents injection of arbitrary keys into DB document)
- admin/ai-config PUT: validate active_provider/model are non-empty strings
- admin.py: move deferred imports (time, os, AsyncOpenAI) to module level
- billing.py: guard get_json() with or {} (prevents AttributeError on empty body)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:51:56 +01:00
Vadym Samoilenko
de990fb486 fix: remove unused eslint-disable, fix AIConfigTab updateProvider type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:34:00 +01:00
Vadym Samoilenko
d05480610d feat(admin): AI model catalog — provider config, model routing, test connection
- app_settings.py: add ai_providers[], active_provider, active_main/mini_model to DEFAULTS
- admin.py: GET/PUT /api/admin/ai-config (API key masked on read, preserved if not updated)
           POST /api/admin/ai-config/test (latency + connection check)
- AIConfigTab.tsx: provider dropdown, endpoint/key fields, models table with role+enabled toggles,
                   main/mini routing selects, "Test connection" with live latency feedback
- Admin.tsx: add "AI Config" tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:32:23 +01:00
Vadym Samoilenko
14f63a3e0c feat(seo): react-helmet-async, JSON-LD, robots.txt, sitemap, llms.txt
- HelmetProvider wraps App; PageMeta component for per-page title/description/OG/noindex
- Index.tsx: Organization + SoftwareApplication JSON-LD structured data
- Login, Register: noindex meta
- public/robots.txt: Allow /, Disallow app routes, Sitemap pointer
- public/sitemap.xml: static sitemap for landing sections
- public/llms.txt: Cohorta description for ChatGPT Search / Perplexity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:29:42 +01:00
Vadym Samoilenko
31d25a9293 feat(i18n): add EN/uk/ru support with language switcher in navbar
- Install i18next, react-i18next, i18next-browser-languagedetector
- src/i18n/index.ts: init with localStorage → navigator detection, fallback EN
- Locales: en/uk/ru common.json covering nav, hero, pricing, auth namespaces
- LanguageSwitcher component (EN/UA/RU pill buttons) — desktop header + mobile menu
- Hero, Pricing, Header: all public-facing strings wrapped in t()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:27:37 +01:00
Vadym Samoilenko
b457afdfa6 feat(focus-groups): redesign setup form — text rewrite + accordion for AI settings
- All copy updated: "Session name", "What are you researching?", "Key topics to explore",
  "Session length", "Thinking depth", "Response length", "Attach materials (optional)"
- llm_model, reasoning_effort, verbosity moved into collapsible "Advanced settings" accordion
- Word-count indicators now float inside textareas (absolute overlay) instead of below
- Removed duplicate amber text blocks and redundant FormDescriptions
- FocusGroupModerator header: "AI Focus Group Moderator" → "Set Up Your Research Session"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:22:53 +01:00
Vadym Samoilenko
c8d4591117 feat: form redesign — Synthetic Users (Epic 3)
AI Recruiter form:
- Word-count indicator floats inside textarea corner (absolute overlay)
- Model dropdown: compact label "Model", removed description, shorter option text
- personaCount: removed description, label → "Number of personas"

Manual Creation form:
- Behavioral Attributes (4): Slider → Input[type=number] inline with label+% suffix
- OCEAN traits (5): Slider → Input[type=number] with description below
- userCount header: +/- buttons → single Input[type=number] (w-16, centered)
- TabsList: sticky top-0 z-10 so tabs stay visible while scrolling
- Removed unused Slider and Sliders imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:18:32 +01:00
Vadym Samoilenko
d679691cc3 feat: OG image, meta tags, dynamic pricing from admin
Epic 1 — OG-image & SEO base:
- Replace wrong og-image.png with branded 1200×630 Cohorta design
- index.html: full title, og:type/url/image dimensions, twitter:card, canonical

Epic 2 — Pricing from admin panel:
- Pricing.tsx: remove hardcoded DEFAULT_PACKS; add loading skeleton and error+retry state
- Features list and personas/sessions counts computed from API credits/costs
- billing.py /packs: also returns persona_cost and run_cost for frontend math
- app_settings.py: add popular:True to pro pack default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:11:42 +01:00
Vadym Samoilenko
3ec9aeaf42 fix: page titles invisible on dark bg — text-slate-900 → text-foreground
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:12:14 +01:00
Vadym Samoilenko
f4dc3074c0 feat: compass/crosshair SVG decorations across landing page, fix email from noreply→hello
- CompassBg: 8 compass marks scattered across full page height with brand amber
  accent on inner ring, faded crosshair lines, cardinal ticks — z-index 0 (behind content)
- Index.tsx: CompassBg rendered as absolute layer inside relative wrapper
- email_service: EMAIL_FROM default hello@ (noreply triggers spam filters)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:06:42 +01:00
Vadym Samoilenko
969198adde fix: logo SVG in navbar, scroll-to-top at root level, threshold 100px
- Header: replace banner PNG with Logo SVG mark (44px) + Cohorta wordmark — crisp at all sizes
- ScrollToTop: moved from PublicLayout to App.tsx root — works on all pages,
  avoids position:fixed breakage from framer-motion transform ancestors
- ScrollToTop: threshold 100px → appears sooner after scroll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:00:34 +01:00
Vadym Samoilenko
4e910704bc fix: billing idempotency atomic, logo in navbar, dark bg on app pages, Home btn, resend-verification
- billing: atomic upsert-based idempotency (fixes TOCTOU + crash-between-ops race)
- billing: payment_id uses `or` to handle explicit null payment_intent
- Header: logo h-[44px] contained within navbar frame, remove md overflow
- Header: Home button scrolls to top when already on /
- Header: useMemo limelightItems to prevent useLayoutEffect thrash on scroll
- Header: remove dead scrolled ternary (py-2 : py-2)
- Hero: remove md:pt-[176px] gap (logo no longer overflows)
- LimelightNav: clearTimeout cleanup, remove items from effect deps
- SyntheticUsers/FocusGroups: bg-slate-50 → bg-background (dark theme fix)
- api.ts + Dashboard: resendVerification passes user email (fixes 400 error)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:55:06 +01:00
Vadym Samoilenko
c0df44a049 fix: responsive navbar logo, AppLayout SVG logo, landing prices from API
- Header: logo h-48px mobile / h-168px desktop, md:self-start overflow
- PublicLayout: main pt-80px to match new header height
- Hero: -mt-80px cancels PublicLayout pt, responsive inner pt
- AppLayout: replace PNG logo (black box) with SVG mark + text
- billing.py: add public GET /billing/packs endpoint
- api.ts + Pricing.tsx: fetch packs from API, fallback to defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:38:44 +01:00
Vadym Samoilenko
614493b58e fix: navbar pill fixed 64px, logo top-anchored overflows below frame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:31:01 +01:00
Vadym Samoilenko
bce8b4e3db fix: logo 4x larger (168px), remove hero min-h-screen gap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:25:44 +01:00
Vadym Samoilenko
c0536df9e2 feat: logo banner in navbar, 45px hero gap, navbar sized to logo
- Move cohorta-banner.png into navbar left slot (height 42px, w-auto)
- Navbar py-2/py-1.5 sized to logo — no left spacer needed
- PublicLayout main: pt-20 → pt-[58px] to match new navbar height
- Hero: mt/pt offsets updated to 58px; content starts 45px below navbar
- Hero: banner block removed (now lives in navbar)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:22:27 +01:00
Vadym Samoilenko
31391bbae4 fix: hero justify-start, banner 400px height, pt-3 top gap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:19:05 +01:00
Vadym Samoilenko
5c61ee9d36 fix: banner full hero width above grid, scroll-to-top threshold 200px
- Banner moved outside the grid to full max-w-7xl width — naturally ~240px tall at desktop
- Removed banner from left column (no longer constrained to 50% column width)
- ScrollToTop visibility threshold: 400px → 200px (appears after ~1 screen scroll)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:13:14 +01:00
Vadym Samoilenko
b923605ea7 fix: UK London branding, restore wavy bg, limelight scroll-tracking, banner size, scroll-to-top btn
- Replace all EU-hosted/Lisbon/GDPR-safe text → UK hosted/London/GDPR compliant (Footer, FinalCTA, LegalStub, About)
- Hero: remove GridGlowCanvas, restore wavy-lines + glow-orb background
- Hero: banner full column width, height clamp(120px, 14vw, 200px)
- Header: LimelightNav now tracks scroll position — detects #product / #pricing sections, updates activeIndex in real time
- Header: whitespace-nowrap on Log in + Get started buttons
- Add ScrollToTop floating button — appears after 400px scroll, framer-motion fade+scale, fixed bottom-right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:08:52 +01:00
Vadym Samoilenko
f66d726e07 feat: required Terms + Data Processing consent on register, consent timestamps in admin
- Register form: two required checkboxes (Terms of Service / Privacy Policy + UK GDPR data processing)
- Zod schema uses z.literal(true) — form won't submit until both are checked
- Backend: validates accept_terms + accept_data_processing flags (400 if missing)
- User.save() writes created_at, consent_terms_at, consent_data_processing_at to MongoDB
- Admin UsersTab: Registered column, email verified badge, consent timestamps in edit dialog
- Fix: EU-hosted → UK hosted badge in register form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:06:43 +01:00
Vadym Samoilenko
fe59c42f0a feat: limelight navbar, grid-glow hero bg, UK hosted trust badges
- Remove logo mark from navbar; center LimelightNav with sliding orange limelight indicator
- Replace static hero bg (wavy lines + glow-orb) with animated GridGlowCanvas (grid + drifting amber glows)
- Hero banner moved into left content column, sized proportionally (clamp 72-120px)
- Hero trust line replaced with pill badges: UK hosted · GDPR compliant · Results in 5 min
- Remove TrustBar ("Built on enterprise infrastructure") section entirely
- Fix Bulk Export card: .PDF → .JSON (matches actual export formats: markdown/csv/json)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:02:33 +01:00
Vadym Samoilenko
83924901e6 fix: hero banner no bg, bigger image clamp(200,28vw,400px), full width 2026-05-23 21:48:32 +01:00
Vadym Samoilenko
5c2ba7503e feat: full-width hero banner with transparent logo on dark bg 2026-05-23 21:42:59 +01:00
Vadym Samoilenko
37f3cf1704 fix: remove dark bg from logo PNG (Pillow alpha), drop mix-blend-mode 2026-05-23 21:32:56 +01:00
Vadym Samoilenko
2d6c715c5b feat: header mark-only icon, logo banner in hero via screen blend, favicon update
- Header nav: SVG C-mark only (no PNG box, no dark bg)
- Hero: full logo PNG banner below nav with mix-blend-mode:screen (removes dark bg)
- favicon.svg: updated C-mark colours to match rebrand

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:31:12 +01:00
Vadym Samoilenko
aa2c66dbe9 fix: logo height sm 28→70px (×2.5) 2026-05-23 21:27:36 +01:00
Vadym Samoilenko
b7065cfe78 fix: correct emails, AImpress LTD links, cookie banner, wavy bg, credits math
- All hello@cohorta.ai-impress.comhello@ai-impress.com
- All AImpress LTD instances now link to https://ai-impress.com/
- Cookie consent banner (bottom sheet, accept/decline, localStorage)
- Hero background: grid → wavy SVG lines
- Fix credits math: 50cr = 5 personas × 2cr + 1 session × 40cr (not 25 personas)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:20:43 +01:00
Vadym Samoilenko
868df66f76 feat: replace SVG logo with PNG lockup (Cohorta copy.png) in horizontal variant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:08:42 +01:00
Vadym Samoilenko
93bc2f953d fix: smaller header logo (sm size) + remove stale .ico favicon fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:06:31 +01:00
Vadym Samoilenko
4c307bf00d feat: increase trial credits from 10 to 50 on signup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:05:13 +01:00
Vadym Samoilenko
a9a5fff659 feat: full visual rebrand + landing redesign + auth page refresh + email fix
- Landing: extract 513-line monolith into 12 focused section components
  (Hero, StatsBand, FeatureGrid, HowItWorks, LivePreview, Comparison,
  UseCases, Testimonials, Pricing, FAQ, FinalCTA, TrustBar)
- Auth pages: replace flat orange panel with animated live mock
  (real persona SVGs, typewriter messages, theme bars); Login label
  fixed to "Email or username"; Register wires ?plan= badge
- Brand: new Logo SVG (C-arc + 3 figures + wordmark/tagline), expanded
  palette tokens, fluid display type scale, framer-motion shared variants
- Header: scroll progress bar, removed non-functional language pill
- Footer: fixed all dead links, legal stubs, new logo
- Legal: /about /privacy /terms /cookies /gdpr real pages added
- Email: FROM_EMAIL default fixed to noreply@ai-impress.com (verified
  apex domain), HTML template rewritten to match new brand
- Tooling: Playwright screenshot script for visual self-check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:02:03 +01:00
Vadym Samoilenko
32d21d1260 feat: new logo mark, 8 persona avatars, remove unauthenticated billing API call
- Logo.tsx: C arc + 3 people silhouettes SVG mark matching brand design
- favicon.svg: updated to match new logo mark
- public/avatars/: 8 diverse persona SVGs (skin tones, hair styles, ages)
- Index.tsx: remove billingApi.getBalance() call on public landing page (was causing 401 console errors for anonymous visitors; pricing uses hardcoded defaults)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:15:33 +01:00
Vadym Samoilenko
b2a063b55f fix: move auth pages out of PublicLayout; fix nav links (remove Blog, anchor scroll to sections) 2026-05-23 19:57:09 +01:00
Vadym Samoilenko
9d2f1f2c7d feat: complete AIMPRESS visual rebrand — warm palette, new landing, real dashboard
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
- Replace cyan/violet design tokens with warm dark slate + orange (#E89B3C) palette
- Add Space Grotesk display font; new utilities: .outline-display, .orange-band, .corner-card, .persona-orb
- New brand components: Logo (hexagonal SVG), Header (pill nav + glass blur), Footer (4-col), PublicLayout, AppLayout, UserDropdown
- Rewrite Index.tsx as full sales funnel: Hero → Stats → Orange band → How it works → Pricing (API) → FAQ → Final CTA
- Rewrite Dashboard.tsx with real API data: credits balance, MTD spend, personas count, focus groups count, active tasks, recent transactions
- Rewrite auth pages (Login, Register, VerifyEmail, NotFound, Billing) with two-column orange-panel layout
- Replace hardcoded mock numbers in Dashboard with billingApi / personasApi / focusGroupsApi / usageApi calls
- Delete legacy components: Navigation.tsx, Hero.tsx, FeatureCard.tsx
- Add nested layout routing in App.tsx: PublicLayout for guests, AppLayout for protected routes
- Color sweep inner pages: replace all purple-500/600 with primary token
- Purge all semblance / Oliver / optical-dev references; rename semblance_app_documentation.md → cohorta_app_documentation.md; update backend scripts to cohorta_db

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 19:44:02 +01:00
Vadym Samoilenko
e01569c412 feat: commit all app changes — billing API, new auth, design overhaul
All checks were successful
Deploy to Production / deploy (push) Successful in 2m23s
Includes frontend redesign (Navigation, billingApi), backend updates
(auth routes, admin routes, LLM service refactor), MSAL removal,
and dependency updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:04:43 +01:00
Vadym Samoilenko
5491d2d73d Rebrand to Cohorta + full UI redesign + registration with email verification
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
- Complete dark-theme redesign inspired by ai-impress.com (navy + cyan + violet palette)
- New Syne display font + gradient logo mark + SVG favicon
- New Navigation: glass-morphism, gradient logo, Get Started CTA
- New Hero: animated glow orbs, mock focus-group chat UI, stats row
- New landing: Features grid, How-It-Works steps, CTA banner
- New Footer: AImpress LTD branding, © AImpress LTD. All rights reserved.
- New Login page: dark card, password visibility toggle, link to Register
- New Register page: full form, benefits row, 50 free credits pitch
- New VerifyEmail page: token verification flow with auto-redirect
- Backend: email_service.py using Resend API for verification emails
- Backend: /api/auth/register, /verify-email, /resend-verification endpoints
- User model: email_verified, email_verify_token, email_verify_expires fields
- Gitea Actions CI/CD: auto-deploy to aimpress server on push to main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:40:08 +01:00
Vadym Samoilenko
7b6a7c7347 Fix admin filters: ISO Z parsing crash + All time period returning month data
Two bugs caused filters to show 0 and period selector to have no effect:

1. Python < 3.11 can't parse JS toISOString() Z suffix — every request with a
   period filter threw ValueError → 500 → frontend received no data. Fixed with
   _parse_iso() helper that replaces Z with +00:00 before fromisoformat().

2. 'All time' sends no from/to params, but backend defaulted to _month_start()
   instead of omitting the ts filter. Fixed with _period_match() helper that
   returns {} (no filter) when both from and to are absent.

Also: stale _user_mtd_cost reference in get_user route replaced with
_user_period_cost(user_id, None, None); adminApi types updated with from/to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 19:17:57 +01:00
Vadym Samoilenko
57508e8e55 Add period selector to all cost-bearing admin tabs
- New usePeriod hook (day/week/month/all/custom presets) with from/to ISO string outputs
- New PeriodSelector component (button group + custom date inputs)
- UsersTab, UsageTab, FocusGroupsTab all wired up with period state
- Backend /admin/users and /admin/focus-groups now accept from/to query params
- MTD Cost column header now reflects selected period label (e.g. "Cost (MTD)")
- Logout clears local state only (no account sign-out)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 19:03:16 +01:00
Vadym Samoilenko
39ad2f00b5 Add back button to Admin page 2026-04-24 18:48:22 +01:00
Vadym Samoilenko
bc4138f332 Final pieces: decorators on LLM routes, usage self-service, billing page, WS events
Backend:
- @active_required + @with_user_context applied to all LLM-invoking routes
  in personas.py, focus_group_ai.py, ai_personas.py
- backend/app/routes/usage.py: GET /api/usage/me (MTD summary by feature),
  GET /api/usage/focus-groups/<id> (owner or admin)
- Registered usage_bp in app/__init__.py
- llm_service._record_usage now emits usage_update WS event to focus group room

Frontend:
- useMyUsage + useFocusGroupUsage hooks
- MyUsage.tsx: personal billing dashboard (cost cards + per-feature table)
- /billing route (ProtectedRoute) + Billing nav link
- FocusGroupSession: quota_warning amber banner with Progress bar,
  quota_exceeded + quota_warning WS events wired via websocketServiceNew

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:43:13 +01:00
Vadym Samoilenko
915c81b8f1 Complete phases D–G: quota enforcement, token invalidation, admin writes, backfill
Backend:
- token_version in JWT (bump_token_version, get_token_version on User model);
  jwt_required checks tv claim → 401 on mismatch; login routes embed version
- Quota pre-flight in all 3 LLM public methods (QuotaExceededError bubbles up)
- AI runner catches QuotaExceededError → sets status paused_quota + emits WS event
- Admin routes: POST /users (create), POST /users/<id>/reset-password,
  POST /pricing, GET /focus-groups with aggregated cost; PUT /users/<id>
  now bumps token_version on disable or role change
- backfill_usage.py: idempotent estimated-event generator for historical data,
  tiktoken for GPT models, char/3.8 for Gemini, --dry-run flag

Frontend:
- 402 interceptor dispatches quota_exceeded CustomEvent
- adminApi: createUser, resetPassword, createPricing, listFocusGroups
- UsersTab: New User dialog + Reset Password in edit dialog
- PricingTab: New Price dialog (model, provider, input/output/cached prices)
- FocusGroupsTab: focus groups table sorted by total cost
- Admin.tsx: 4th tab (Focus Groups)
- FocusGroupSession: admin-only cost badge + dismissable quota exceeded banner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:34:48 +01:00
Vadym Samoilenko
015e6cc5cc Add Phase D admin panel: user management + usage analytics
Backend: /api/admin/* blueprint with user CRUD (list, get, update,
disable/enable), usage summary aggregation (group by user/model/feature/
day/focus_group), usage event drill-down, and pricing list. Fixed
admin_required decorator (async-safe). Added find_all/count/update
helpers to User model.

Frontend: /admin page (AdminRoute guard, 3 tabs) — Users table with
search/filter/edit dialog, Usage tab with KPI cards + bar chart +
events table, Pricing tab showing active model rows with tier details.
Admin nav link visible only to admin role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:26:05 +01:00
Vadym Samoilenko
0bf6043fad Fix: task result not stored in useTaskPolling, causing false 'no personas' error
When a task completed, the result payload (personas_created, errors_count, etc.)
was discarded instead of being saved to state. AIRecruiter always read
generationState.result as undefined → count = 0 → showed error even when
the backend had successfully created all personas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:06:22 +01:00
Vadym Samoilenko
2e85fc1acc Fix root cause: naive vs aware datetime crash + stuck AI mode indicator
The autonomous loop was crashing on every decision with:
  TypeError: can't subtract offset-naive and offset-aware datetimes
because MongoDB stores created_at without timezone info but the code
compared it against datetime.now(timezone.utc).

- conversation_context_service: make created_at timezone-aware before
  subtraction (replace tzinfo=utc when naive)
- DiscussionPanel: fix sync effect — when server reports AI mode is
  inactive, always clear localAiModeActive regardless of its value,
  so the "AI is generating..." spinner doesn't get stuck when the
  backend fails/stops before the frontend has confirmed AI mode started

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:30:04 +00:00
Vadym Samoilenko
283b31e786 Fix AI mode: race condition, split-brain UI, and stuck local state
- Backend: set status to ai_mode in the route handler before submitting
  to AI runner, eliminating the race condition where frontend's immediate
  status poll read the old status
- Frontend: replace all raw isAiModeActive prop usages with
  effectiveAiModeActive in DiscussionPanel (13 locations) so ReasoningPanel,
  status text, loading indicator, and manual/AI controls all reflect the
  correct state instantly on Start AI Mode click
- Frontend: add useEffect to sync localAiModeActive back to null once
  the parent prop catches up, preventing permanent override after natural
  session end
- These fixes also unblock the 3-second AI message polling which was
  never activating due to isAiModeActive staying false

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:02:15 +00:00
Vadym Samoilenko
b4978989a5 Fix AI autonomous mode: cross-loop WebSocket emit + polling fallback
The AI Runner runs on a dedicated background thread with its own asyncio
event loop. When it emitted WebSocket events via sio.emit(), the call
happened on the wrong loop (AI Runner's vs ASGI/Quart's), causing silent
failures — messages were saved to MongoDB but never reached the frontend.

Additionally, the frontend HTTP polling fallback was never enabled when
WebSocket appeared connected, leaving no way to discover missed messages.

- websocket_manager_async.py: store ASGI main loop reference; detect
  cross-loop calls in emit_to_focus_group and use run_coroutine_threadsafe
  to schedule emits on the correct loop
- __init__.py: register the ASGI event loop with the WebSocket manager
  in before_serving hook
- FocusGroupSession.tsx: always poll fetchMessages every 3s during AI mode
  as a reliability fallback regardless of WebSocket status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:22:24 +00:00
Vadym Samoilenko
4b47b334d7 Fix data isolation + conversation/decision 500 errors
Data isolation:
- GET /tasks/<id>: verify requesting user owns the task (403 if not)
- DELETE /tasks/<id>: same ownership check
- GET /tasks/status: add @jwt_required()
- GET /personas/<id>: add ownership check (403 if created_by != user)
- GET /focus-groups/<id>: add ownership check
- GET /focus-groups/<id>/messages: add ownership check
- POST/DELETE /focus-groups/<id>/participants: add ownership check

Fix conversation/decision 500:
- Convert POST /conversation/decision to async 202+background (was synchronous LLM → timed out / LLM errors → 500)
- Frontend polls waitForTaskResult for decision result before calling generateResponseAsync
- GET /conversation/insights: return empty insights (200) on LLM error instead of 500

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 17:02:10 +00:00
Vadym Samoilenko
1b387daacf Migrate task result delivery from WebSocket to HTTP polling
Backend:
- task_manager.py: add result/error/completed_at storage, TTL sweeper (5min), store_task_result() helper
- tasks.py: add GET /<task_id> endpoint returning stored result; cancel route stores 'cancelled' status
- __init__.py: start TTL sweeper on app startup
- All 8 bg functions: store result before emitting lightweight WS hint (no payload data)

Frontend:
- src/lib/taskPolling.ts: waitForTaskResult() — polls GET /tasks/{id} every 2s, WS hint triggers immediate poll, 5min timeout
- src/hooks/useTaskPolling.ts: drop-in replacement for useCancellableGeneration using polling
- Migrate 6 Promise-based WS listeners → waitForTaskResult() in DiscussionPanel, FocusGroupSession (×2), PersonaProfile, PersonaModificationModal, useDiscussionGuideGeneration
- Migrate 3 hook-based consumers → useTaskPolling in AIRecruiter, SyntheticUsers, BulkExportProgressModal

Fixes WS Promise leak: polling survives disconnects, background tabs, page reloads.
WS events retained as zero-payload hints for near-zero latency when connected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:46:58 +00:00