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