fix(panels): eliminate getSession() from transcription store and cabinets panel
Same GoTrueClient lock contention fix as CCFilesPanel: - transcriptionStore: add _accessToken/_userId state + setAuthInfo() action; replace all 6 getSession() calls (startSession, flushCanvasEvents, loadSessions, loadKeywordWatches, addKeywordWatch, deleteKeywordWatch, checkSegmentForKeywords) with stored values — zero getSession() calls remain in the store - CCTranscriptionPanel: destructure accessToken from useAuth; sync both values into store via setAuthInfo() on every auth change; gate loadSessions on authUser.id - CCCabinetsPanel: same pattern as CCFilesPanel — useAuth for token, useCallback on apiFetch/loadCabinets, gate effect on authUser.id Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -130,6 +130,11 @@ interface TranscriptionState {
|
||||
keywordWatches: KeywordWatch[];
|
||||
keywordMatches: KeywordMatch[];
|
||||
|
||||
// Auth (set by panel via setAuthInfo after SIGNED_IN)
|
||||
_accessToken: string | null;
|
||||
_userId: string | null;
|
||||
setAuthInfo: (token: string | null, userId: string | null) => void;
|
||||
|
||||
// Actions
|
||||
startSession: (timetableTag?: TimetablePeriod) => Promise<void>;
|
||||
stopSession: () => Promise<void>;
|
||||
@@ -167,6 +172,8 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
isRecording: false,
|
||||
isConnecting: false,
|
||||
activeSession: null,
|
||||
_accessToken: null,
|
||||
_userId: null,
|
||||
completedSegments: [],
|
||||
serverWindow: [],
|
||||
currentSegment: null,
|
||||
@@ -195,19 +202,23 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
set({ timetableContext: context });
|
||||
},
|
||||
|
||||
setAuthInfo: (token: string | null, userId: string | null) => {
|
||||
set({ _accessToken: token, _userId: userId });
|
||||
},
|
||||
|
||||
startSession: async (timetableTag?: TimetablePeriod) => {
|
||||
set({ isRecording: true, isConnecting: false, elapsedSeconds: 0, timetableContext: timetableTag || null });
|
||||
|
||||
// Create session in Supabase
|
||||
try {
|
||||
const { data: sessionData_auth } = await supabase.auth.getSession();
|
||||
if (!sessionData_auth.session?.user) {
|
||||
const { _accessToken: token, _userId: userId } = get();
|
||||
if (!token || !userId) {
|
||||
console.error('No authenticated user');
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionData = {
|
||||
user_id: sessionData_auth.session.user.id,
|
||||
user_id: userId,
|
||||
title: timetableTag?.event_label || 'Untitled Session',
|
||||
canvas_type: 'teaching-canvas',
|
||||
timetable_period_id: timetableTag?.period_id || null,
|
||||
@@ -465,7 +476,7 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
for (const event of eventsToFlush) {
|
||||
await supabase.from('canvas_events').insert({
|
||||
session_id: activeSession?.id || null,
|
||||
user_id: (await supabase.auth.getSession()).data.session?.user?.id || '',
|
||||
user_id: get()._userId || '',
|
||||
timestamp: new Date().toISOString(),
|
||||
session_elapsed_seconds: event.sessionElapsedSeconds || null,
|
||||
event_type: event.eventType,
|
||||
@@ -484,13 +495,13 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
|
||||
loadSessions: async (): Promise<TranscriptionSession[]> => {
|
||||
try {
|
||||
const { data: sessionData_auth } = await supabase.auth.getSession();
|
||||
if (!sessionData_auth.session?.user) return [];
|
||||
const { _userId: userId } = get();
|
||||
if (!userId) return [];
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('transcription_sessions')
|
||||
.select('*')
|
||||
.eq('user_id', sessionData_auth.session.user.id)
|
||||
.eq('user_id', userId)
|
||||
.order('started_at', { ascending: false })
|
||||
.limit(50);
|
||||
|
||||
@@ -584,11 +595,11 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
|
||||
loadKeywordWatches: async () => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) return;
|
||||
const { _accessToken: token } = get();
|
||||
if (!token) return;
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
|
||||
const response = await fetch(`${apiBaseUrl}/transcribe/keywords`, {
|
||||
headers: { 'Authorization': `Bearer ${session.access_token}` },
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const watches = await response.json();
|
||||
@@ -600,17 +611,17 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
|
||||
addKeywordWatch: async (keyword: string) => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token || !session.user) return;
|
||||
const { _accessToken: token, _userId: userId } = get();
|
||||
if (!token || !userId) return;
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
|
||||
const response = await fetch(`${apiBaseUrl}/transcribe/keywords`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: session.user.id,
|
||||
user_id: userId,
|
||||
keyword: keyword.trim(),
|
||||
match_type: 'contains',
|
||||
action: 'alert',
|
||||
@@ -626,12 +637,12 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
|
||||
deleteKeywordWatch: async (watchId: string) => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) return;
|
||||
const { _accessToken: token } = get();
|
||||
if (!token) return;
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
|
||||
await fetch(`${apiBaseUrl}/transcribe/keywords/${watchId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${session.access_token}` },
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
});
|
||||
set((state) => ({ keywordWatches: state.keywordWatches.filter((w) => w.id !== watchId) }));
|
||||
} catch (error) {
|
||||
@@ -667,13 +678,13 @@ export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
|
||||
if (activeSession) {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
const { _accessToken: _kwToken } = get();
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
|
||||
await fetch(`${apiBaseUrl}/transcribe/keywords/events`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(session?.access_token ? { 'Authorization': `Bearer ${session.access_token}` } : {}),
|
||||
...(_kwToken ? { 'Authorization': `Bearer ${_kwToken}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: activeSession.id,
|
||||
|
||||
Reference in New Issue
Block a user