feat: Added collapsible PersonalityProfile component to CreatorDetail p…

- "frontend/src/components/PersonalityProfile.tsx"
- "frontend/src/api/creators.ts"
- "frontend/src/pages/CreatorDetail.tsx"
- "frontend/src/App.css"

GSD-Task: S06/T03
This commit is contained in:
jlightner 2026-04-04 08:31:37 +00:00
parent 15299232a8
commit 73736295c1
5 changed files with 327 additions and 1 deletions

View file

@ -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 {

View file

@ -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<string, number>;
personality_profile: PersonalityProfile | null;
}
export interface CreatorListParams {

View file

@ -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 (
<section className="personality-profile">
<button
className="personality-profile__toggle"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}
>
<h2 className="personality-profile__heading">Personality Profile</h2>
<span
className={`personality-profile__chevron${expanded ? " personality-profile__chevron--open" : ""}`}
aria-hidden="true"
>
</span>
</button>
<div
className="personality-profile__collapse"
style={{
gridTemplateRows: expanded ? "1fr" : "0fr",
}}
>
<div className="personality-profile__inner">
{/* Summary */}
<p className="personality-profile__summary">{profile.summary}</p>
<div className="personality-profile__cards">
{/* Teaching Style */}
<div className="personality-profile__card">
<h3 className="personality-profile__card-title">Teaching Style</h3>
<dl className="personality-profile__dl">
<dt>Formality</dt>
<dd>{profile.tone.formality}</dd>
<dt>Energy</dt>
<dd>{profile.tone.energy}</dd>
<dt>Teaching style</dt>
<dd>{profile.tone.teaching_style}</dd>
<dt>Humor</dt>
<dd>{profile.tone.humor_frequency}</dd>
</dl>
{profile.tone.descriptors.length > 0 && (
<div className="personality-profile__pills">
{profile.tone.descriptors.map((d) => (
<span key={d} className="pill pill--tag">
{d}
</span>
))}
</div>
)}
</div>
{/* Vocabulary */}
<div className="personality-profile__card">
<h3 className="personality-profile__card-title">Vocabulary</h3>
<dl className="personality-profile__dl">
<dt>Jargon level</dt>
<dd>{profile.vocabulary.technical_jargon_level}</dd>
{profile.vocabulary.filler_words.length > 0 && (
<>
<dt>Filler words</dt>
<dd>{profile.vocabulary.filler_words.join(", ")}</dd>
</>
)}
</dl>
{profile.vocabulary.signature_phrases.length > 0 && (
<>
<span className="personality-profile__pill-label">
Signature phrases
</span>
<div className="personality-profile__pills">
{profile.vocabulary.signature_phrases.map((p) => (
<span key={p} className="pill pill--tag">
"{p}"
</span>
))}
</div>
</>
)}
{profile.vocabulary.distinctive_terms.length > 0 && (
<>
<span className="personality-profile__pill-label">
Distinctive terms
</span>
<div className="personality-profile__pills">
{profile.vocabulary.distinctive_terms.map((t) => (
<span key={t} className="pill pill--tag">
{t}
</span>
))}
</div>
</>
)}
</div>
{/* Style */}
<div className="personality-profile__card">
<h3 className="personality-profile__card-title">Style</h3>
<dl className="personality-profile__dl">
<dt>Explanation approach</dt>
<dd>{profile.style_markers.explanation_approach}</dd>
<dt>Audience engagement</dt>
<dd>{profile.style_markers.audience_engagement}</dd>
<dt>Self-references</dt>
<dd>{profile.style_markers.self_references_frequency}</dd>
</dl>
<div className="personality-profile__booleans">
<span
className={`personality-profile__bool${profile.style_markers.uses_analogies ? " personality-profile__bool--yes" : ""}`}
>
{profile.style_markers.uses_analogies ? "✓" : "✗"} Uses
analogies
</span>
<span
className={`personality-profile__bool${profile.style_markers.uses_sound_words ? " personality-profile__bool--yes" : ""}`}
>
{profile.style_markers.uses_sound_words ? "✓" : "✗"} Uses
sound words
</span>
</div>
</div>
</div>
{/* Metadata */}
<div className="personality-profile__meta">
Based on {profile.transcript_sample_size} transcript
{profile.transcript_sample_size !== 1 ? "s" : ""} · extracted{" "}
{new Date(profile.extracted_at).toLocaleDateString()}
</div>
</div>
</div>
</section>
);
}

View file

@ -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() {
)}
</div>
{/* Personality Profile */}
<PersonalityProfile profile={creator.personality_profile ?? null} />
{/* Technique pages */}
<section className="creator-techniques">
<div className="creator-techniques__header">

View file

@ -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"}
{"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"}