Commit graph

114 commits

Author SHA1 Message Date
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
8c5146022a deploy.sh: run seed_model_pricing.py after container startup
Without this step the model_pricing collection is empty and all LLM
costs are recorded as $0. Seed is idempotent — safe on every deploy.
Also wait for backend container readiness before running migrations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:10:12 +01:00
Vadym Samoilenko
3e9ccafad2 Add LLM usage tracking infrastructure (Phases A-C)
- Model renames: gpt-5.2 → gpt-5.4-2026-03-05, gemini-3-pro-preview → gemini-3.1-pro-preview; retire gpt-4.1 via alias fallback
- New: llm_usage_context.py (ContextVar-based attribution), model_pricing.py (tiered pricing + 60s cache), usage_event.py (append-only telemetry), quota.py (user/FG quota enforcement with 80% warning)
- Wire _record_usage into all 3 LLM methods; set_llm_context at every service entry point
- Fix admin_required decorator (was sync, never awaited User.find_by_id); add active_required and with_user_context decorators
- Inject user_id into ContextVar from JWT on every authenticated request
- Add DB indexes for usage_events, model_pricing, users collections
- Seed script for model pricing (gpt-5.4 single-tier, gemini-3.1 two-tier 200k threshold)
- Fix parse_json_response NameError (logger undefined at module level)
- 70 passing tests: conftest.py with sys.modules stubs, test_usage_infrastructure.py (52 tests), rewrite stale test_llm_service.py (18 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:08:27 +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
7e72d07329 Fix AI loop hanging: add asyncio.wait_for timeouts on LLM calls
The autonomous conversation loop could hang indefinitely because
self.response_timeout=30 was defined but never used in wait_for().

- autonomous_conversation_controller: wrap generate_persona_response()
  with asyncio.wait_for(timeout=120s); 30s was too short for production
  LLMs, raised to 120s; TimeoutError returns an error dict so the loop
  can continue or count toward consecutive_silence limit
- conversation_decision_service: add asyncio.wait_for(timeout=60s)
  around LLMService.generate_content() for the decision call; add
  asyncio import and explicit TimeoutError handling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:17:36 +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
33272cc677 Allow document uploads (PDF, DOCX, TXT, etc.) as focus group assets
- Expand allowed file types from images-only to also include:
  PDF, DOCX, DOC, TXT, MD, CSV, XLSX, XLS, PPTX, PPT, RTF
- validate_asset_file: skip PIL validation for non-image files; 50MB limit for docs / 10MB for images
- Correct MIME type detection for document extensions
- Store asset_type: "document"|"image" in metadata
- ImageDescriptionService: text files → LLM summary; binary docs → label; images → existing multimodal flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 17:08:30 +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
f0876649e0 Add SVG favicon and update page title to Semblance
- favicon.svg: indigo/purple gradient rounded square with 3 persona silhouettes (focus group concept)
- index.html: link SVG favicon (with ICO fallback), set title to Semblance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:47:59 +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
Vadym Samoilenko
c7034634e3 Fix all async LLM routes: bypass GCP 30s load balancer timeout
Convert 6 synchronous LLM routes to async 202+WebSocket pattern:
- generate-response (focus_group_ai): persona chat response
- generate-key-themes (focus_group_ai): discussion analysis
- modify-with-ai (personas): AI persona modification
- export-profile (personas): markdown profile export
- describe-asset (focus_groups): image AI description

Each route now returns 202 + task_id immediately, runs LLM in
asyncio background task, delivers result via WebSocket task_completed
event. Frontend listeners updated to wait for ws:task_completed
instead of HTTP response body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:56:54 +00:00
Vadym Samoilenko
f4a587c4f7 Fix 500: add current_app import to focus_groups route
Missing import caused NameError when starting background discussion
guide generation task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:35:29 +00:00
Vadym Samoilenko
d8a5d6643f Fix discussion guide 504: async flow + WebSocket delivery
- Backend: /generate-discussion-guide now returns task_id immediately (202)
  and runs generation as a background asyncio task, delivering the guide
  via WebSocket task_completed event (bypasses GCP LB 30s timeout)
- Frontend: useDiscussionGuideGeneration awaits ws:task_completed event
  to resolve the guide Promise instead of waiting on the HTTP response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:32:08 +00:00
Vadym Samoilenko
6917518d11 Fix NameError: _fg_logger undefined in update_focus_group route
_fg_logger was used but never defined, causing a NameError on every
PUT /focus-groups/:id request that included llm_model (i.e. all autosave
and handleSubmit updates) — resulting in a 500 Internal Server Error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:26:08 +00:00
Vadym Samoilenko
f359157949 Fix focus group create: 500 on update + 400 on autosave
- FocusGroup.update: use matched_count > 0 instead of modified_count > 0
  so updates succeed even when data is unchanged (was returning 500)
- useFocusGroupAutoSave: skip save if name is empty (not all-fields-empty)
  preventing 400 Bad Request when autosave fires before name is filled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:20:40 +00:00
Vadym Samoilenko
770bdee829 Remove console.log debug calls from frontend browser console
Removes all debug/verbose console.log calls across frontend to prevent
sensitive data exposure (session IDs, tokens) and reduce console noise.
Keeps only console.error and console.warn for genuine errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:30:56 +00:00
Vadym Samoilenko
f60d86e8cb Fix task_completed WebSocket payload too large
Don't send full persona objects in WS event — only send counts.
Frontend navigates to list page where personas load from API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:11:14 +00:00
Vadym Samoilenko
aa4090888d Fix persona generation 504: async flow + remove debug logging
- Backend: /generate-personas-full now returns task_id immediately (202)
  and runs generation as a background asyncio task, delivering results
  via WebSocket task_completed event (bypasses GCP LB 30s timeout)
- Frontend: AIRecruiter listens for ws:task_completed to process personas
  instead of awaiting the long HTTP response
- Remove 53 debug console.log calls from websocketServiceNew.ts including
  session_id exposure and a self-test emit that was firing fake events
- Remove debug logs from WebSocketContextNew, AIRecruiter, personaGenerator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:00:07 +00:00
Vadym Samoilenko
c00728f375 Fix Gemini LLM AssertionError: force httpx transport over aiohttp
google-genai SDK uses aiohttp when it's available in the environment
(installed via llama-index-core), causing AssertionError (connector is None)
on async requests. Pass httpx_async_client in HttpOptions to bypass aiohttp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:05:27 +00:00
Vadym Samoilenko
2c6505f472 Switch to loginRedirect — popup blocked by server COOP header
Server-level COOP prevents parent from monitoring popup.location,
so loginPopup never resolves in parent. Redirect flow is the correct
solution: page navigates to Microsoft, returns with #code, and
handleRedirectPromise exchanges the idToken with backend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:44:19 +00:00
Vadym Samoilenko
3943be1c51 Fix MSAL popup loading full app — skip React render when in popup context
MSAL parent polls popup URL for auth code (same-origin), so the popup
doesn't need to render anything. Prevents app from opening inside popup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:35:25 +00:00
Vadym Samoilenko
2154ce946d Add COOP header to Apache config — fix MSAL popup window.closed blocking
same-origin-allow-popups allows the Microsoft auth popup to communicate
back to the parent window, which is required for loginPopup to work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:25:00 +00:00
Vadym Samoilenko
361ef7bb6a Revert to loginPopup — COOP warning was non-blocking, popup worked before
The real issue was the wrong domain (oliver.solution vs oliver.solutions).
Now that Azure has the correct redirect URI configured, popup flow works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:18:12 +00:00
Vadym Samoilenko
c00116eea4 Remove manual PKCE params from loginRequest — MSAL handles PKCE internally
Passing code_challenge_method without code_challenge causes AADSTS90014.
MSAL generates and sends PKCE parameters automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:05:08 +00:00
Vadym Samoilenko
7a7d3a90ac Switch MSAL from loginPopup to loginRedirect — fix COOP blocking
Popup-based auth fails in production due to Cross-Origin-Opener-Policy
headers blocking window.closed calls. Redirect flow avoids this entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:03:16 +00:00
Vadym Samoilenko
7f0df54de3 Fix domain typo: oliver.solution → oliver.solutions across all files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:40:00 +00:00
Vadym Samoilenko
cf698e1e87 Remove create_default_user call from run.py (method removed in security remediation) 2026-03-20 13:34:26 +00:00
Vadym Samoilenko
05d7ea68e2 Fix make_serializable import — move to utils/__init__.py (was shadowed by utils/ package) 2026-03-20 13:33:11 +00:00
Vadym Samoilenko
d1788a4017 Fix .dockerignore — exclude *.txt but keep requirements.txt 2026-03-20 13:28:32 +00:00
Vadym Samoilenko
b481f616cb Single deploy.sh for server — frontend build via Node Docker container
- deploy.sh runs entirely on server, no local Mac needed
- docker-compose: add 'frontend' service (node:20-alpine, profile=build)
  builds frontend and copies dist/ to /var/www/html/semblance
- Remove server-deploy.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:26:39 +00:00
Vadym Samoilenko
f63608cff0 Add server-deploy.sh for running directly on server 2026-03-20 13:24:52 +00:00
Vadym Samoilenko
4f8a952e30 Match MSAL redirect URI to Entra registration (trailing slash) 2026-03-20 13:21:58 +00:00
Vadym Samoilenko
4a6b4d6fe0 Dockerize backend — replace systemd service with docker-compose
- Add backend/Dockerfile (python:3.12-slim)
- Add docker-compose.yml (backend :5137 + mongo:7)
- Add backend/.dockerignore
- Rewrite deploy.sh: build frontend locally, rsync dist/, docker compose up --build
- Remove semblance.service (no longer needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:21:34 +00:00
Vadym Samoilenko
8d532a50f0 Add VITE_MSAL_TENANT_ID and VITE_MSAL_CLIENT_ID to .env.production 2026-03-20 13:17:26 +00:00
Vadym Samoilenko
42a8f25510 Adapt deploy.sh for optical-dev server infrastructure
- Build frontend locally (Node not installed on server)
- rsync dist/ to server instead of building remotely
- Change PYTHON_CMD to python3 (server has 3.12, not 3.13)
- Add step to start MongoDB in Docker (mongo:7, not installed natively)
- Add step to inject Apache proxy config for /semblance_back/ and /semblance/
- SSH_HOST=optical-dev (uses ~/.ssh/config alias)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:59:32 +00:00
Vadym Samoilenko
bb4dca0fe8 Update production URL to optical-dev.oliver.solution
Replace ai-sandbox.oliver.solutions with optical-dev.oliver.solution
across all config, env, docs, and source files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:55:45 +00:00
Vadym Samoilenko
3e1865edbd Apply Jintech security audit remediation (sprint 3) — 87/92 findings fixed
- Fix missing await on FocusGroup.get_messages() (N-L1)
- Replace time.sleep with asyncio.sleep in key_theme_service and focus_group_service (N-P10)
- Replace flask import with quart in focus_groups.py (N-S3)
- Add logger.error before all 500 returns in focus_groups.py (N-P6)
- Add logging to silent except blocks across routes (N-M10, N-M11)
- Add @rate_limit to 6 remaining AI endpoints (N-H4)
- Add --confirm flag to populate scripts before delete_many (S-H2)
- Remove hardcoded Azure ID fallbacks from msal_service.py and msalConfig.ts (A-M2, F-H4)
- Centralize make_serializable() in utils.py, remove duplicates from 3 route files (N-P7)
- Replace all datetime.utcnow() with datetime.now(timezone.utc) across entire backend (M-L2)
- AuthContext.tsx: only mark token validated on 200 success, not on non-401 errors (F-H2)
- Rename authType → auth_type in auth.py (N-S4)
- Add security_report.md and security_report.pdf with full 92-finding status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:51:18 +00:00
Vadym Samoilenko
bf5e74fe49 Revert sudo on frontend deploy steps — fix ownership on server instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 17:42:10 +00:00
Vadym Samoilenko
75b97f8c88 Fix permission denied errors in deploy script by using sudo for frontend file operations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 17:41:06 +00:00
michael
c7ff1755ee Add architecture document generator and PDF
Create comprehensive technical architecture document (PDF) with 11
chapters covering system architecture, frontend/backend design, data
model, auth, WebSocket communication, LLM integration, and core
feature flows. Includes 11 Mermaid diagrams rendered as PNGs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 11:39:50 -06:00
michael
33fb25467e Update README with comprehensive project documentation
Rewrites the outdated README to accurately reflect the current state of the
project: corrects framework references (Quart/Hypercorn instead of Flask/Gunicorn),
documents all major features (autonomous AI conversations, multi-model LLM support,
WebSocket communication, Microsoft SSO, theme extraction), updates the project
structure and tech stack, adds architecture overview, environment configuration
table, and deployment instructions using deploy.sh/systemd.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:50:29 -06:00
michael
b1be8f8c38 Add model alias for legacy gpt-5 database entries
Focus groups created before the gpt-5.2 rename have llm_model='gpt-5'
stored in MongoDB. Without an alias, the backend falls through to the
Gemini provider and fails with an aiohttp AssertionError.

Adds MODEL_ALIASES mapping and _resolve_model() helper so gpt-5 is
transparently resolved to gpt-5.2. Also updates all llm_model checks
to accept both values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 12:06:10 -06:00
Michael Clervi
82b06c5ea2 added dist to gitignore 2026-02-11 17:28:15 +00:00
Michael Clervi
923b0f46c5 stop tracking dist/index.html 2026-02-11 17:27:06 +00:00
michael
b0445de18b Update GPT-5 to GPT-5.2 and lower default reasoning effort to low
Swap model ID from gpt-5 to gpt-5.2 across all backend services,
frontend components, and documentation. Change default reasoning
effort from medium to low for faster responses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:24:01 -06:00
michael
d4ce5d99bc Fix audience_brief and research_objective dropped in Stage 2 persona generation
Stage 2 (detailed persona generation) was ignoring the audience brief and
research objective, causing the LLM to guess research context from demographics
alone. Now passes both values through to generate_persona() in all three
endpoints (generate-personas-full, complete-and-save-persona, complete-persona)
and auto-generates prompt customization via customize_persona_prompt() when
they are provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 08:40:17 -06:00