diff --git a/.gsd/milestones/M022/slices/S06/S06-PLAN.md b/.gsd/milestones/M022/slices/S06/S06-PLAN.md index b2adfa8..9676e09 100644 --- a/.gsd/milestones/M022/slices/S06/S06-PLAN.md +++ b/.gsd/milestones/M022/slices/S06/S06-PLAN.md @@ -141,7 +141,7 @@ Build the core extraction pipeline: a prompt template that analyzes creator tran - Estimate: 1h30m - Files: prompts/personality_extraction.txt, backend/pipeline/stages.py, backend/schemas.py, backend/routers/admin.py - Verify: test -f prompts/personality_extraction.txt && cd backend && python -c "from pipeline.stages import extract_personality_profile; print('task OK')" && python -c "from schemas import PersonalityProfile; print('validator OK')" && grep -q 'extract-profile' routers/admin.py && echo 'all OK' -- [ ] **T03: Add personality profile display to CreatorDetail frontend page** — ## Description +- [x] **T03: Added collapsible PersonalityProfile component to CreatorDetail page with three sub-cards, pill badges, boolean indicators, and smooth CSS grid animation** — ## Description Add a collapsible personality profile section to the CreatorDetail page. Update the TypeScript API type, create a PersonalityProfile component, and wire it into the page layout below the bio/social links section. diff --git a/.gsd/milestones/M022/slices/S06/tasks/T02-VERIFY.json b/.gsd/milestones/M022/slices/S06/tasks/T02-VERIFY.json new file mode 100644 index 0000000..fc3c946 --- /dev/null +++ b/.gsd/milestones/M022/slices/S06/tasks/T02-VERIFY.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "taskId": "T02", + "unitId": "M022/S06/T02", + "timestamp": 1775291298640, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "test -f prompts/personality_extraction.txt", + "exitCode": 0, + "durationMs": 5, + "verdict": "pass" + }, + { + "command": "cd backend", + "exitCode": 0, + "durationMs": 6, + "verdict": "pass" + }, + { + "command": "grep -q 'extract-profile' routers/admin.py", + "exitCode": 2, + "durationMs": 6, + "verdict": "fail" + }, + { + "command": "echo 'all OK'", + "exitCode": 0, + "durationMs": 4, + "verdict": "pass" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M022/slices/S06/tasks/T03-SUMMARY.md b/.gsd/milestones/M022/slices/S06/tasks/T03-SUMMARY.md new file mode 100644 index 0000000..4b6f399 --- /dev/null +++ b/.gsd/milestones/M022/slices/S06/tasks/T03-SUMMARY.md @@ -0,0 +1,83 @@ +--- +id: T03 +parent: S06 +milestone: M022 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/components/PersonalityProfile.tsx", "frontend/src/api/creators.ts", "frontend/src/pages/CreatorDetail.tsx", "frontend/src/App.css"] +key_decisions: ["Used dedicated TypeScript interfaces for personality profile sub-objects rather than inline types"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "TypeScript compiles clean (npx tsc --noEmit), production build succeeds (npm run build), component file exists, component is imported in CreatorDetail.tsx. All slice-level backend checks also pass." +completed_at: 2026-04-04T08:31:30.051Z +blocker_discovered: false +--- + +# T03: Added collapsible PersonalityProfile component to CreatorDetail page with three sub-cards, pill badges, boolean indicators, and smooth CSS grid animation + +> Added collapsible PersonalityProfile component to CreatorDetail page with three sub-cards, pill badges, boolean indicators, and smooth CSS grid animation + +## What Happened +--- +id: T03 +parent: S06 +milestone: M022 +key_files: + - frontend/src/components/PersonalityProfile.tsx + - frontend/src/api/creators.ts + - frontend/src/pages/CreatorDetail.tsx + - frontend/src/App.css +key_decisions: + - Used dedicated TypeScript interfaces for personality profile sub-objects rather than inline types +duration: "" +verification_result: passed +completed_at: 2026-04-04T08:31:30.051Z +blocker_discovered: false +--- + +# T03: Added collapsible PersonalityProfile component to CreatorDetail page with three sub-cards, pill badges, boolean indicators, and smooth CSS grid animation + +**Added collapsible PersonalityProfile component to CreatorDetail page with three sub-cards, pill badges, boolean indicators, and smooth CSS grid animation** + +## What Happened + +Created PersonalityProfile.tsx with collapsible section (grid-template-rows 0fr/1fr), three cards (Teaching Style, Vocabulary, Style), pill badges for descriptors/phrases/terms, boolean checkmark indicators, and metadata footer. Updated CreatorDetailResponse type with dedicated interfaces. Wired into CreatorDetail.tsx between stats bar and techniques list. Added ~130 lines of CSS using existing design tokens. + +## Verification + +TypeScript compiles clean (npx tsc --noEmit), production build succeeds (npm run build), component file exists, component is imported in CreatorDetail.tsx. All slice-level backend checks also pass. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 3000ms | +| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 6200ms | +| 3 | `test -f frontend/src/components/PersonalityProfile.tsx` | 0 | ✅ pass | 100ms | +| 4 | `grep -q PersonalityProfile frontend/src/pages/CreatorDetail.tsx` | 0 | ✅ pass | 100ms | + + +## Deviations + +None. + +## Known Issues + +Slice-level verification `grep -q 'extract-profile' routers/admin.py` uses wrong path (missing backend/ prefix) — file exists at backend/routers/admin.py. + +## Files Created/Modified + +- `frontend/src/components/PersonalityProfile.tsx` +- `frontend/src/api/creators.ts` +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/App.css` + + +## Deviations +None. + +## Known Issues +Slice-level verification `grep -q 'extract-profile' routers/admin.py` uses wrong path (missing backend/ prefix) — file exists at backend/routers/admin.py. diff --git a/frontend/src/App.css b/frontend/src/App.css index c45a677..12c601d 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -5573,6 +5573,141 @@ a.app-footer__about:hover, animation: pageEnter 250ms ease-out; } +/* ── Personality Profile ───────────────────────────────────────────────────── */ + +.personality-profile { + margin: 1.5rem 0; +} + +.personality-profile__toggle { + display: flex; + align-items: center; + gap: 0.5rem; + background: none; + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 0.75rem 1rem; + width: 100%; + cursor: pointer; + color: var(--color-text-primary); + transition: background 150ms ease; +} + +.personality-profile__toggle:hover { + background: var(--color-bg-surface-hover); +} + +.personality-profile__heading { + margin: 0; + font-size: 1.1rem; + font-weight: 600; +} + +.personality-profile__chevron { + margin-left: auto; + font-size: 0.9rem; + transition: transform 250ms ease; + color: var(--color-text-muted); +} + +.personality-profile__chevron--open { + transform: rotate(90deg); +} + +.personality-profile__collapse { + display: grid; + transition: grid-template-rows 300ms ease; +} + +.personality-profile__inner { + overflow: hidden; + min-height: 0; +} + +.personality-profile__summary { + color: var(--color-text-secondary); + font-size: 0.95rem; + line-height: 1.6; + margin: 1rem 0; + padding: 0 0.25rem; +} + +.personality-profile__cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1rem; + margin-bottom: 1rem; +} + +.personality-profile__card { + background: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 1rem; +} + +.personality-profile__card-title { + margin: 0 0 0.75rem; + font-size: 0.95rem; + font-weight: 600; + color: var(--color-accent); +} + +.personality-profile__dl { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.25rem 0.75rem; + margin: 0 0 0.75rem; + font-size: 0.85rem; +} + +.personality-profile__dl dt { + color: var(--color-text-muted); + font-weight: 500; +} + +.personality-profile__dl dd { + margin: 0; + color: var(--color-text-primary); +} + +.personality-profile__pill-label { + display: block; + font-size: 0.8rem; + color: var(--color-text-muted); + margin-bottom: 0.25rem; +} + +.personality-profile__pills { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-bottom: 0.5rem; +} + +.personality-profile__booleans { + display: flex; + flex-direction: column; + gap: 0.375rem; + font-size: 0.85rem; + margin-top: 0.5rem; +} + +.personality-profile__bool { + color: var(--color-text-muted); +} + +.personality-profile__bool--yes { + color: var(--color-badge-approved-text); +} + +.personality-profile__meta { + color: var(--color-text-muted); + font-size: 0.8rem; + padding: 0 0.25rem; + margin-bottom: 0.5rem; +} + /* ── Admin Technique Pages ────────────────────────────────────────────────── */ .admin-page { diff --git a/frontend/src/api/creators.ts b/frontend/src/api/creators.ts index f7e467e..be72392 100644 --- a/frontend/src/api/creators.ts +++ b/frontend/src/api/creators.ts @@ -33,6 +33,39 @@ export interface CreatorTechniqueItem { created_at: string; } +export interface PersonalityVocabulary { + signature_phrases: string[]; + technical_jargon_level: string; + filler_words: string[]; + distinctive_terms: string[]; +} + +export interface PersonalityTone { + formality: string; + energy: string; + humor_frequency: string; + teaching_style: string; + descriptors: string[]; +} + +export interface PersonalityStyleMarkers { + explanation_approach: string; + uses_analogies: boolean; + uses_sound_words: boolean; + self_references_frequency: string; + audience_engagement: string; +} + +export interface PersonalityProfile { + vocabulary: PersonalityVocabulary; + tone: PersonalityTone; + style_markers: PersonalityStyleMarkers; + summary: string; + extracted_at: string; + transcript_sample_size: number; + model_used: string; +} + export interface CreatorDetailResponse { id: string; name: string; @@ -52,6 +85,7 @@ export interface CreatorDetailResponse { follower_count: number; techniques: CreatorTechniqueItem[]; genre_breakdown: Record; + personality_profile: PersonalityProfile | null; } export interface CreatorListParams { diff --git a/frontend/src/components/PersonalityProfile.tsx b/frontend/src/components/PersonalityProfile.tsx new file mode 100644 index 0000000..2c43f87 --- /dev/null +++ b/frontend/src/components/PersonalityProfile.tsx @@ -0,0 +1,153 @@ +/** + * Collapsible personality profile section for the CreatorDetail page. + * + * Shows vocabulary, tone, and style markers extracted from creator transcripts. + * Renders nothing if profile is null. Uses CSS grid-template-rows 0fr/1fr + * for smooth collapse/expand animation (per KNOWLEDGE.md). + */ + +import { useState } from "react"; +import type { PersonalityProfile as ProfileType } from "../api/creators"; + +interface Props { + profile: ProfileType | null; +} + +export default function PersonalityProfile({ profile }: Props) { + const [expanded, setExpanded] = useState(false); + + if (!profile) return null; + + return ( +
+ + +
+
+ {/* Summary */} +

{profile.summary}

+ +
+ {/* Teaching Style */} +
+

Teaching Style

+
+
Formality
+
{profile.tone.formality}
+
Energy
+
{profile.tone.energy}
+
Teaching style
+
{profile.tone.teaching_style}
+
Humor
+
{profile.tone.humor_frequency}
+
+ {profile.tone.descriptors.length > 0 && ( +
+ {profile.tone.descriptors.map((d) => ( + + {d} + + ))} +
+ )} +
+ + {/* Vocabulary */} +
+

Vocabulary

+
+
Jargon level
+
{profile.vocabulary.technical_jargon_level}
+ {profile.vocabulary.filler_words.length > 0 && ( + <> +
Filler words
+
{profile.vocabulary.filler_words.join(", ")}
+ + )} +
+ {profile.vocabulary.signature_phrases.length > 0 && ( + <> + + Signature phrases + +
+ {profile.vocabulary.signature_phrases.map((p) => ( + + "{p}" + + ))} +
+ + )} + {profile.vocabulary.distinctive_terms.length > 0 && ( + <> + + Distinctive terms + +
+ {profile.vocabulary.distinctive_terms.map((t) => ( + + {t} + + ))} +
+ + )} +
+ + {/* Style */} +
+

Style

+
+
Explanation approach
+
{profile.style_markers.explanation_approach}
+
Audience engagement
+
{profile.style_markers.audience_engagement}
+
Self-references
+
{profile.style_markers.self_references_frequency}
+
+
+ + {profile.style_markers.uses_analogies ? "✓" : "✗"} Uses + analogies + + + {profile.style_markers.uses_sound_words ? "✓" : "✗"} Uses + sound words + +
+
+
+ + {/* Metadata */} +
+ Based on {profile.transcript_sample_size} transcript + {profile.transcript_sample_size !== 1 ? "s" : ""} · extracted{" "} + {new Date(profile.extracted_at).toLocaleDateString()} +
+
+
+
+ ); +} diff --git a/frontend/src/pages/CreatorDetail.tsx b/frontend/src/pages/CreatorDetail.tsx index bce3116..c8f4ee7 100644 --- a/frontend/src/pages/CreatorDetail.tsx +++ b/frontend/src/pages/CreatorDetail.tsx @@ -19,6 +19,7 @@ import { useAuth } from "../context/AuthContext"; import CreatorAvatar from "../components/CreatorAvatar"; import { SocialIcon } from "../components/SocialIcons"; import ChatWidget from "../components/ChatWidget"; +import PersonalityProfile from "../components/PersonalityProfile"; import SortDropdown from "../components/SortDropdown"; import TagList from "../components/TagList"; import { catSlug } from "../utils/catSlug"; @@ -393,6 +394,9 @@ export default function CreatorDetail() { )} + {/* Personality Profile */} + + {/* Technique pages */}
diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo index 698cd57..852419b 100644 --- a/frontend/tsconfig.app.tsbuildinfo +++ b/frontend/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/follows.ts","./src/api/highlights.ts","./src/api/index.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ChatWidget.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PlayerControls.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminAuditLog.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorTiers.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/HighlightQueue.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/follows.ts","./src/api/highlights.ts","./src/api/index.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ChatWidget.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PersonalityProfile.tsx","./src/components/PlayerControls.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminAuditLog.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorTiers.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/HighlightQueue.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"version":"5.6.3"} \ No newline at end of file