Skip to content

Commit 80ba5b4

Browse files
latest fix to the faculty componenent
1 parent 782435a commit 80ba5b4

4 files changed

Lines changed: 374 additions & 63 deletions

File tree

backend/agents/facultyAgent.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ TONE:
77
- Encouraging, professional, and peer-to-peer.
88
- Never use "Bad" or "Wrong."
99
- Use "Opportunity for clarity," "Consider diversifying," or "Would benefit from."
10+
- Be specific enough that an instructor could make edits immediately.
11+
- Prefer evidence and timestamps over generic teaching advice.
1012
1113
METRIC PILLARS (Score each 0-100):
1214
1. Clarity: Is the logic linear? Are terms defined?
@@ -36,33 +38,63 @@ ${fullTranscript}
3638
Return a JSON object with this exact schema:
3739
{
3840
"overallScore": number (0-100),
41+
"executiveSummary": "string — 2-3 sentence private coaching summary for the instructor",
42+
"strongestMoment": {
43+
"title": "string — strongest teaching move",
44+
"description": "string — why this worked and what to keep doing",
45+
"timestamp": number
46+
},
3947
"topPriority": {
4048
"title": "string — short title of the issue",
4149
"description": "string — detailed explanation with suggested fix",
42-
"timestamp": number
50+
"timestamp": number,
51+
"impact": "string (High|Medium|Low)",
52+
"effort": "string (Quick fix|Moderate|Redesign)"
4353
},
4454
"dimensions": [
4555
{
4656
"name": "string (Clarity|Accessibility|Equity|Pacing)",
4757
"score": number (0-100),
4858
"feedback": "string — 2-3 sentence summary of findings for this dimension",
49-
"suggestions": ["string"]
59+
"keepDoing": ["string — positive practice to preserve"],
60+
"improveBeforePublishing": ["string — concrete improvement"],
61+
"suggestions": ["string — backwards-compatible summary suggestions"],
62+
"evidence": [
63+
{
64+
"timestamp": number,
65+
"quoteOrMoment": "string — concise reference to what happened",
66+
"whyItMatters": "string — pedagogical significance"
67+
}
68+
]
5069
}
5170
],
5271
"timestampedSuggestions": [
5372
{
5473
"timestamp": number (seconds into the lecture),
5574
"note": "string — what the issue is and how to fix it",
56-
"type": "string (positive|improvement)"
75+
"type": "string (positive|improvement)",
76+
"impact": "string (High|Medium|Low)",
77+
"effort": "string (Quick fix|Moderate|Redesign)",
78+
"suggestedRewrite": "string — if improvement, write the exact sentence(s) the instructor could say instead; if positive, describe how to reuse this move"
79+
}
80+
],
81+
"publishingChecklist": [
82+
{
83+
"item": "string — concrete action before publishing",
84+
"timestamp": number,
85+
"impact": "string (High|Medium|Low)"
5786
}
5887
]
5988
}
6089
6190
RULES:
6291
1. "topPriority" must be the single most impactful change the instructor can make.
63-
2. Provide 3-5 specific "timestampedSuggestions" for specific moments in the transcript.
92+
2. Provide 4-6 specific "timestampedSuggestions" for specific moments in the transcript.
6493
3. Frame all suggestions as a coach helping a colleague. Use "positive" type for things done well, "improvement" for suggested changes.
6594
4. Every dimension MUST have "Clarity", "Accessibility", "Equity", or "Pacing" as its name.
95+
5. For Accessibility, explicitly check whether visual references, formulas, acronyms, transitions, and examples are understandable to an audio-only learner.
96+
6. Evidence entries must cite real timestamps from the transcript markers.
97+
7. Include 4-6 publishingChecklist items, ordered by practical priority.
6698
`;
6799

68100
try {

frontend/app/audit/[jobId]/page.tsx

Lines changed: 236 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

33
import { useState, useEffect } from "react"
4+
import type { ReactNode } from "react"
45
import { useParams } from "next/navigation"
56
import Link from "next/link"
67
import { FoxMascot } from "@/components/fox-mascot"
@@ -16,7 +17,11 @@ import {
1617
Lightbulb,
1718
Play,
1819
Loader2,
19-
AlertCircle
20+
AlertCircle,
21+
CheckCircle2,
22+
Wrench,
23+
ClipboardCheck,
24+
Sparkles
2025
} from "lucide-react"
2126

2227
function ScoreGauge({ score }: { score: number }) {
@@ -66,6 +71,25 @@ const dimensionMeta: Record<string, { icon: any; color: string; hex: string }> =
6671
"Pacing": { icon: Clock, color: "duo-purple", hex: "#CE82FF" },
6772
}
6873

74+
function EvidenceLink({ videoId, timestamp, children, className = "" }: {
75+
videoId: string
76+
timestamp: number
77+
children: ReactNode
78+
className?: string
79+
}) {
80+
return (
81+
<a
82+
href={`https://www.youtube.com/watch?v=${videoId}&t=${Math.floor(timestamp || 0)}`}
83+
target="_blank"
84+
rel="noopener noreferrer"
85+
className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-black transition-colors ${className}`}
86+
>
87+
<Play className="w-3 h-3 fill-current" />
88+
{children}
89+
</a>
90+
)
91+
}
92+
6993
export default function AuditPage() {
7094
const params = useParams()
7195
const jobId = params.jobId as string
@@ -125,7 +149,15 @@ export default function AuditPage() {
125149
}
126150

127151
const { result, videoMeta } = data
128-
const { overallScore, topPriority, dimensions, timestampedSuggestions } = result
152+
const {
153+
overallScore,
154+
executiveSummary,
155+
strongestMoment,
156+
topPriority,
157+
dimensions,
158+
timestampedSuggestions,
159+
publishingChecklist
160+
} = result
129161

130162
return (
131163
<div className="min-h-screen bg-white">
@@ -154,6 +186,16 @@ export default function AuditPage() {
154186
<p className="text-duo-text-muted font-semibold">
155187
{videoMeta.author}
156188
</p>
189+
{executiveSummary && (
190+
<div className="mt-4 max-w-3xl rounded-2xl border-2 border-duo-border bg-duo-surface/40 p-4">
191+
<p className="mb-2 text-xs font-black uppercase tracking-wider text-duo-text-muted">
192+
Private Coaching Summary
193+
</p>
194+
<MarkdownContent className="text-sm font-semibold leading-relaxed text-duo-text">
195+
{executiveSummary}
196+
</MarkdownContent>
197+
</div>
198+
)}
157199
</div>
158200

159201
{/* Overall Score & Top Priority */}
@@ -178,6 +220,50 @@ export default function AuditPage() {
178220
<MarkdownContent className="text-duo-text-muted text-sm font-semibold">
179221
{topPriority.description}
180222
</MarkdownContent>
223+
<div className="mt-3 flex flex-wrap items-center gap-2">
224+
{topPriority.timestamp !== undefined && (
225+
<EvidenceLink
226+
videoId={videoMeta.videoId}
227+
timestamp={topPriority.timestamp}
228+
className="bg-duo-orange/10 text-duo-orange hover:bg-duo-orange hover:text-white"
229+
>
230+
{formatTime(topPriority.timestamp)}
231+
</EvidenceLink>
232+
)}
233+
{topPriority.impact && (
234+
<span className="rounded-full bg-duo-red/10 px-3 py-1 text-xs font-black text-duo-red">
235+
{topPriority.impact} impact
236+
</span>
237+
)}
238+
{topPriority.effort && (
239+
<span className="rounded-full bg-duo-blue/10 px-3 py-1 text-xs font-black text-duo-blue">
240+
{topPriority.effort}
241+
</span>
242+
)}
243+
</div>
244+
</div>
245+
</div>
246+
</div>
247+
)}
248+
249+
{strongestMoment && (
250+
<div className="card-duo p-4 border-l-4 border-duo-green">
251+
<div className="flex items-start gap-3">
252+
<Sparkles className="w-5 h-5 text-duo-green flex-shrink-0 mt-0.5" />
253+
<div className="flex-1">
254+
<h3 className="font-bold text-duo-text mb-1">{strongestMoment.title}</h3>
255+
<MarkdownContent className="text-duo-text-muted text-sm font-semibold">
256+
{strongestMoment.description}
257+
</MarkdownContent>
258+
{strongestMoment.timestamp !== undefined && (
259+
<EvidenceLink
260+
videoId={videoMeta.videoId}
261+
timestamp={strongestMoment.timestamp}
262+
className="mt-3 bg-duo-green/10 text-duo-green hover:bg-duo-green hover:text-white"
263+
>
264+
Keep this move at {formatTime(strongestMoment.timestamp)}
265+
</EvidenceLink>
266+
)}
181267
</div>
182268
</div>
183269
</div>
@@ -208,16 +294,77 @@ export default function AuditPage() {
208294
{dim.feedback}
209295
</MarkdownContent>
210296

211-
{dim.suggestions && dim.suggestions.length > 0 && (
212-
<div className="space-y-2">
213-
{dim.suggestions.map((suggestion: string, idx: number) => (
214-
<div key={idx} className="flex items-start gap-2 text-sm">
215-
<Lightbulb className="w-4 h-4 text-duo-yellow flex-shrink-0 mt-0.5" />
216-
<MarkdownContent className="text-duo-text font-semibold">{suggestion}</MarkdownContent>
297+
<div className="grid grid-cols-1 gap-3">
298+
{dim.keepDoing && dim.keepDoing.length > 0 && (
299+
<div className="rounded-2xl bg-duo-green/5 p-3">
300+
<div className="mb-2 flex items-center gap-2">
301+
<CheckCircle2 className="w-4 h-4 text-duo-green" />
302+
<span className="text-xs font-black uppercase tracking-wider text-duo-green">Keep Doing</span>
217303
</div>
218-
))}
219-
</div>
220-
)}
304+
<div className="space-y-2">
305+
{dim.keepDoing.map((item: string, idx: number) => (
306+
<MarkdownContent key={idx} className="text-sm font-semibold text-duo-text">
307+
{item}
308+
</MarkdownContent>
309+
))}
310+
</div>
311+
</div>
312+
)}
313+
314+
{dim.improveBeforePublishing && dim.improveBeforePublishing.length > 0 && (
315+
<div className="rounded-2xl bg-duo-orange/5 p-3">
316+
<div className="mb-2 flex items-center gap-2">
317+
<Wrench className="w-4 h-4 text-duo-orange" />
318+
<span className="text-xs font-black uppercase tracking-wider text-duo-orange">Improve Before Publishing</span>
319+
</div>
320+
<div className="space-y-2">
321+
{dim.improveBeforePublishing.map((item: string, idx: number) => (
322+
<MarkdownContent key={idx} className="text-sm font-semibold text-duo-text">
323+
{item}
324+
</MarkdownContent>
325+
))}
326+
</div>
327+
</div>
328+
)}
329+
330+
{(!dim.keepDoing && !dim.improveBeforePublishing) && dim.suggestions && dim.suggestions.length > 0 && (
331+
<div className="space-y-2">
332+
{dim.suggestions.map((suggestion: string, idx: number) => (
333+
<div key={idx} className="flex items-start gap-2 text-sm">
334+
<Lightbulb className="w-4 h-4 text-duo-yellow flex-shrink-0 mt-0.5" />
335+
<MarkdownContent className="text-duo-text font-semibold">{suggestion}</MarkdownContent>
336+
</div>
337+
))}
338+
</div>
339+
)}
340+
341+
{dim.evidence && dim.evidence.length > 0 && (
342+
<div className="mt-1 rounded-2xl border border-duo-border bg-white p-3">
343+
<p className="mb-3 text-xs font-black uppercase tracking-wider text-duo-text-muted">
344+
Evidence
345+
</p>
346+
<div className="space-y-3">
347+
{dim.evidence.map((item: any, idx: number) => (
348+
<div key={idx} className="border-t border-duo-border pt-3 first:border-t-0 first:pt-0">
349+
<EvidenceLink
350+
videoId={videoMeta.videoId}
351+
timestamp={item.timestamp}
352+
className="mb-2 bg-duo-blue/10 text-duo-blue hover:bg-duo-blue hover:text-white"
353+
>
354+
{formatTime(item.timestamp)}
355+
</EvidenceLink>
356+
<MarkdownContent className="text-sm font-black text-duo-text">
357+
{item.quoteOrMoment}
358+
</MarkdownContent>
359+
<MarkdownContent className="mt-1 text-xs font-semibold text-duo-text-muted">
360+
{item.whyItMatters}
361+
</MarkdownContent>
362+
</div>
363+
))}
364+
</div>
365+
</div>
366+
)}
367+
</div>
221368
</div>
222369
)
223370
})}
@@ -239,22 +386,91 @@ export default function AuditPage() {
239386
: "bg-white border-duo-yellow/20"
240387
}`}
241388
>
242-
<a
243-
href={item.youtubeLink || `https://www.youtube.com/watch?v=${videoMeta.videoId}&t=${Math.floor(item.timestamp)}`}
244-
target="_blank"
245-
rel="noopener noreferrer"
246-
className={`flex-shrink-0 px-4 py-2 rounded-2xl text-sm font-black flex items-center gap-2 border-b-4 ${
389+
<EvidenceLink
390+
videoId={videoMeta.videoId}
391+
timestamp={item.timestamp}
392+
className={`flex-shrink-0 border-b-4 px-4 py-2 text-sm ${
247393
item.type === "positive"
248394
? "bg-duo-green text-white border-duo-green-dark"
249395
: "bg-duo-yellow text-duo-text border-duo-yellow-dark"
250396
}`}
251397
>
252-
<Play className="w-4 h-4 fill-current" />
253398
{formatTime(item.timestamp)}
254-
</a>
255-
<MarkdownContent className="text-duo-text font-bold text-lg flex-1">
256-
{item.note}
399+
</EvidenceLink>
400+
<div className="flex-1">
401+
<div className="mb-2 flex flex-wrap gap-2">
402+
{item.impact && (
403+
<span className="rounded-full bg-duo-red/10 px-3 py-1 text-xs font-black text-duo-red">
404+
{item.impact} impact
405+
</span>
406+
)}
407+
{item.effort && (
408+
<span className="rounded-full bg-duo-blue/10 px-3 py-1 text-xs font-black text-duo-blue">
409+
{item.effort}
410+
</span>
411+
)}
412+
</div>
413+
<MarkdownContent className="text-duo-text font-bold text-lg">
414+
{item.note}
415+
</MarkdownContent>
416+
{item.suggestedRewrite && (
417+
<div className="mt-4 rounded-2xl border-2 border-duo-border bg-duo-surface/60 p-4">
418+
<p className="mb-2 text-xs font-black uppercase tracking-wider text-duo-text-muted">
419+
{item.type === "positive" ? "Reuse This Move" : "Suggested Rewrite"}
420+
</p>
421+
<MarkdownContent className="text-sm font-semibold leading-relaxed text-duo-text">
422+
{item.suggestedRewrite}
423+
</MarkdownContent>
424+
</div>
425+
)}
426+
</div>
427+
</div>
428+
))}
429+
</div>
430+
</div>
431+
)}
432+
433+
{/* Publishing Checklist */}
434+
{publishingChecklist && publishingChecklist.length > 0 && (
435+
<div className="card-duo p-8 mb-12">
436+
<div className="mb-6 flex items-center gap-3">
437+
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-duo-green/10">
438+
<ClipboardCheck className="h-5 w-5 text-duo-green" />
439+
</div>
440+
<div>
441+
<h2 className="text-base font-black uppercase tracking-wider text-duo-text">
442+
Publishing Checklist
443+
</h2>
444+
<p className="text-sm font-semibold text-duo-text-muted">
445+
The fastest edits to make before students see this lecture.
446+
</p>
447+
</div>
448+
</div>
449+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
450+
{publishingChecklist.map((item: any, index: number) => (
451+
<div key={index} className="rounded-2xl border-2 border-duo-border bg-duo-surface/30 p-4">
452+
<div className="mb-3 flex items-center justify-between gap-3">
453+
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-duo-green text-xs font-black text-white">
454+
{index + 1}
455+
</span>
456+
{item.timestamp !== undefined && (
457+
<EvidenceLink
458+
videoId={videoMeta.videoId}
459+
timestamp={item.timestamp}
460+
className="bg-white text-duo-blue hover:bg-duo-blue hover:text-white"
461+
>
462+
{formatTime(item.timestamp)}
463+
</EvidenceLink>
464+
)}
465+
</div>
466+
<MarkdownContent className="text-sm font-bold text-duo-text">
467+
{item.item}
257468
</MarkdownContent>
469+
{item.impact && (
470+
<span className="mt-3 inline-flex rounded-full bg-duo-red/10 px-3 py-1 text-xs font-black text-duo-red">
471+
{item.impact} impact
472+
</span>
473+
)}
258474
</div>
259475
))}
260476
</div>

0 commit comments

Comments
 (0)