Ruperth commited on
Commit
73c3af5
·
1 Parent(s): 04f07f8

feat: pin team-posted comments to the top of the watch page

Browse files

Posting a comment now sends the random team-member name and the active video id to the API so the comment is persisted under that video. The page refetches only user_comment rows for the current video which guarantees the team posts stay above the YouTube thread on reload. Optimistic session items are deduped against the refreshed Supabase rows to avoid a flicker.

frontend/src/pages/WatchPage.tsx CHANGED
@@ -51,9 +51,13 @@ export function WatchPage() {
51
  const { result, loading, error } = useDebouncedPredict(draft, threshold);
52
 
53
  const refreshRecent = useCallback(async (videoId?: string) => {
 
 
 
 
54
  setRecentLoading(true);
55
  try {
56
- const res = await listPredictions(videoId, 20);
57
  setRecentActivity(Array.isArray(res?.predictions) ? res.predictions : []);
58
  } catch {
59
  // Degrade gracefully if endpoint is missing or DB not configured
@@ -86,8 +90,12 @@ export function WatchPage() {
86
  if (!text || posting) return;
87
  setPosting(true);
88
  try {
89
- const analysis = result ?? (await predict(text, threshold));
90
  const author = randomTeamMember();
 
 
 
 
 
91
  const item: CommentItem = {
92
  id: newId(),
93
  user: author,
@@ -112,7 +120,7 @@ export function WatchPage() {
112
  } finally {
113
  setPosting(false);
114
  }
115
- }, [draft, posting, result, threshold, addHubEntry, refreshRecent, activeVideo?.id, t]);
116
 
117
  const loadVideo = async (video: SuggestedVideo) => {
118
  setActiveVideo(video);
@@ -286,26 +294,36 @@ export function WatchPage() {
286
  )}
287
 
288
  <div className="comment-list">
289
- {/* Local Supabase comments — always at the top (newest first) */}
290
- {recentActivity.map((rec, idx) => (
291
- <CommentRow
292
- key={`recent-${rec.id ?? idx}`}
293
- comment={{
294
- id: `recent-${rec.id ?? idx}`,
295
- user: randomUsername(`supa-${rec.id ?? idx}`),
296
- text: truncate(rec.text, 140),
297
- time: relativeTime(rec.created_at),
298
- is_toxic: rec.is_toxic,
299
- probability: rec.probability,
300
- labels: rec.labels ?? [],
301
- source: "recent",
302
- }}
303
- />
304
- ))}
305
- {/* Current session (just posted) */}
306
- {[...sessionComments].reverse().map((c) => (
307
- <CommentRow key={c.id} comment={c} />
308
- ))}
 
 
 
 
 
 
 
 
 
 
309
  {/* YouTube fetched comments — below */}
310
  {youtubeComments.map((c) => (
311
  <CommentRow key={c.id} comment={c} />
 
51
  const { result, loading, error } = useDebouncedPredict(draft, threshold);
52
 
53
  const refreshRecent = useCallback(async (videoId?: string) => {
54
+ if (!videoId) {
55
+ setRecentActivity([]);
56
+ return;
57
+ }
58
  setRecentLoading(true);
59
  try {
60
+ const res = await listPredictions(videoId, 200, "user_comment");
61
  setRecentActivity(Array.isArray(res?.predictions) ? res.predictions : []);
62
  } catch {
63
  // Degrade gracefully if endpoint is missing or DB not configured
 
90
  if (!text || posting) return;
91
  setPosting(true);
92
  try {
 
93
  const author = randomTeamMember();
94
+ const analysis = await predict(text, threshold, {
95
+ videoId: activeVideo?.id,
96
+ author,
97
+ persist: true,
98
+ });
99
  const item: CommentItem = {
100
  id: newId(),
101
  user: author,
 
120
  } finally {
121
  setPosting(false);
122
  }
123
+ }, [draft, posting, threshold, addHubEntry, refreshRecent, activeVideo?.id, t]);
124
 
125
  const loadVideo = async (video: SuggestedVideo) => {
126
  setActiveVideo(video);
 
294
  )}
295
 
296
  <div className="comment-list">
297
+ {/* Persisted user comments for this video — always at the top (newest first) */}
298
+ {recentActivity
299
+ .filter((rec) => rec.source === "user_comment")
300
+ .map((rec, idx) => (
301
+ <CommentRow
302
+ key={`recent-${rec.id ?? idx}`}
303
+ comment={{
304
+ id: `recent-${rec.id ?? idx}`,
305
+ user: rec.author ?? randomUsername(`supa-${rec.id ?? idx}`),
306
+ text: truncate(rec.text, 140),
307
+ time: relativeTime(rec.created_at),
308
+ is_toxic: rec.is_toxic,
309
+ probability: rec.probability,
310
+ labels: rec.labels ?? [],
311
+ source: "recent",
312
+ }}
313
+ />
314
+ ))}
315
+ {/* Optimistic local additions until refresh from Supabase completes */}
316
+ {[...sessionComments]
317
+ .reverse()
318
+ .filter(
319
+ (c) =>
320
+ !recentActivity.some(
321
+ (r) => r.text === c.text && r.author === c.user,
322
+ ),
323
+ )
324
+ .map((c) => (
325
+ <CommentRow key={c.id} comment={c} />
326
+ ))}
327
  {/* YouTube fetched comments — below */}
328
  {youtubeComments.map((c) => (
329
  <CommentRow key={c.id} comment={c} />
frontend/src/types/api.ts CHANGED
@@ -54,6 +54,8 @@ export type PredictionRecord = {
54
  video_id?: string | null;
55
  created_at: string;
56
  labels?: string[];
 
 
57
  };
58
 
59
  export type PredictionsListResponse = {
 
54
  video_id?: string | null;
55
  created_at: string;
56
  labels?: string[];
57
+ source?: string | null;
58
+ author?: string | null;
59
  };
60
 
61
  export type PredictionsListResponse = {