Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 5 additions & 34 deletions src/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,25 +321,6 @@ export class PostHogAPIClient {
return await response.json();
}

async getDesktopRecordingTranscript(recordingId: string) {
this.validateRecordingId(recordingId);
const teamId = await this.getTeamId();
const url = new URL(
`${this.api.baseUrl}/api/environments/${teamId}/desktop_recordings/${recordingId}/transcript/`,
);
const response = await this.api.fetcher.fetch({
method: "get",
url,
path: `/api/environments/${teamId}/desktop_recordings/${recordingId}/transcript/`,
});

if (!response.ok) {
throw new Error(`Failed to fetch transcript: ${response.statusText}`);
}

return await response.json();
}

async listDesktopRecordings(filters?: {
platform?: string;
status?: string;
Expand Down Expand Up @@ -405,28 +386,18 @@ export class PostHogAPIClient {
return data;
}

async updateDesktopRecordingTranscript(
async appendSegments(
recordingId: string,
updates: {
segments?: Array<{
timestamp_ms: number;
speaker: string | null;
text: string;
confidence: number | null;
is_final: boolean;
}>;
full_text?: string;
},
) {
segments: Array<Schemas.TranscriptSegment>,
): Promise<Schemas.DesktopRecording> {
this.validateRecordingId(recordingId);
const teamId = await this.getTeamId();

const data = await this.api.post(
//@ts-expect-error not in the generated client
"/api/environments/{project_id}/desktop_recordings/{id}/transcript/",
"/api/environments/{project_id}/desktop_recordings/{id}/append_segments/",
{
path: { project_id: teamId.toString(), id: recordingId },
body: updates as any,
body: { segments } as Schemas.AppendSegments,
},
);

Expand Down
74 changes: 58 additions & 16 deletions src/main/services/recallRecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ export function initializeRecallSDK(
posthogKey: string,
posthogHost: string,
) {
console.log("[Recall SDK] initializeRecallSDK called with:", {
recallApiUrl,
posthogHost,
hasKey: !!posthogKey,
});

if (sdkInitialized) {
console.warn("[Recall SDK] Already initialized, skipping");
return;
Expand All @@ -106,28 +112,17 @@ export function initializeRecallSDK(
return;
}

console.log("[Recall SDK] Initializing...");

sdkInitialized = true;
posthogClient = new PostHogAPIClient(posthogKey, posthogHost);

RecallAiSdk.init({
apiUrl: recallApiUrl,
acquirePermissionsOnStartup: [
"accessibility",
"screen-capture",
"microphone",
],
restartOnError: true,
});
console.log("[Recall SDK] Setting up event listeners...");

console.log("[Recall SDK] Ready. Listening for meetings...");
// IMPORTANT: Register ALL event listeners BEFORE calling init()
// This is required by Recall SDK

RecallAiSdk.addEventListener("permissions-granted", async () => {
console.log("[Recall SDK] Permissions granted");
});

RecallAiSdk.addEventListener("permission-status", async (evt) => {
console.log("[Recall SDK] Permission status:", evt.permission, evt.status);
if (evt.status === "denied" || evt.status === "error") {
console.warn(`[Recall SDK] Permission ${evt.permission}: ${evt.status}`);
}
Expand Down Expand Up @@ -385,6 +380,34 @@ export function initializeRecallSDK(
);
}
});

console.log("[Recall SDK] All event listeners registered");

// NOW initialize the SDK (after all event listeners are set up)
console.log("[Recall SDK] Initializing SDK...");
sdkInitialized = true;
posthogClient = new PostHogAPIClient(posthogKey, posthogHost);
console.log("[Recall SDK] PostHog client created");

try {
RecallAiSdk.init({
apiUrl: recallApiUrl,
acquirePermissionsOnStartup: [
"accessibility",
"screen-capture",
"microphone",
],
restartOnError: true,
});
console.log("[Recall SDK] RecallAiSdk.init() completed successfully");
} catch (error) {
console.error("[Recall SDK] Failed to initialize SDK:", error);
sdkInitialized = false;
posthogClient = null;
throw error;
}

console.log("[Recall SDK] ✓ Ready. Listening for meetings...");
}

export function requestRecallPermission(
Expand All @@ -398,18 +421,37 @@ export function shutdownRecallSDK() {
}

export function registerRecallIPCHandlers() {
console.log("[Recall SDK] Registering IPC handlers...");

ipcMain.handle(
"recall:initialize",
async (_event, recallApiUrl, posthogKey, posthogHost) => {
initializeRecallSDK(recallApiUrl, posthogKey, posthogHost);
console.log("[Recall SDK] IPC handler 'recall:initialize' called");
try {
initializeRecallSDK(recallApiUrl, posthogKey, posthogHost);
console.log("[Recall SDK] IPC handler 'recall:initialize' completed");
} catch (error) {
console.error(
"[Recall SDK] IPC handler 'recall:initialize' error:",
error,
);
throw error;
}
},
);

ipcMain.handle("recall:request-permission", async (_event, permission) => {
console.log(
"[Recall SDK] IPC handler 'recall:request-permission' called:",
permission,
);
requestRecallPermission(permission);
});

ipcMain.handle("recall:shutdown", async () => {
console.log("[Recall SDK] IPC handler 'recall:shutdown' called");
shutdownRecallSDK();
});

console.log("[Recall SDK] IPC handlers registered successfully");
}
41 changes: 28 additions & 13 deletions src/renderer/features/auth/stores/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ import {

const RECALL_API_URL = "https://us-west-2.recall.ai";

// Helper function to initialize Recall SDK
async function initializeRecallSDK(
accessToken: string,
apiHost: string,
): Promise<void> {
try {
console.log("[Auth] Calling recallInitialize with:", {
url: RECALL_API_URL,
host: apiHost,
hasToken: !!accessToken,
});
await window.electronAPI.recallInitialize(
RECALL_API_URL,
accessToken,
apiHost,
);
console.log("[Auth] recallInitialize completed successfully");
} catch (error) {
console.error("[Auth] Failed to initialize Recall SDK:", error);
// Don't throw - Recall SDK failure shouldn't block authentication
}
}

interface AuthState {
// OAuth state
oauthAccessToken: string | null;
Expand Down Expand Up @@ -137,19 +160,8 @@ export const useAuthStore = create<AuthState>()(
throw new Error("Failed to authenticate with PostHog");
}

try {
window.electronAPI
.recallInitialize(
RECALL_API_URL,
tokenResponse.access_token,
apiHost,
)
.catch((error) => {
console.error("[Auth] Failed to initialize Recall SDK:", error);
});
} catch (_error) {
throw new Error("Invalid API key or host");
}
// Initialize Recall SDK for meeting detection
await initializeRecallSDK(tokenResponse.access_token, apiHost);
},

refreshAccessToken: async () => {
Expand Down Expand Up @@ -332,6 +344,9 @@ export const useAuthStore = create<AuthState>()(
}
}

// Initialize Recall SDK for meeting detection (session restore)
await initializeRecallSDK(tokens.accessToken, apiHost);

return true;
} catch (error) {
console.error("Failed to validate OAuth session:", error);
Expand Down
18 changes: 5 additions & 13 deletions src/renderer/features/notetaker/components/RecordingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,12 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
const segments: TranscriptSegment[] =
recordingItem.type === "active"
? recordingItem.recording.segments || []
: (
recordingItem.recording.transcript_segments as Array<{
timestamp_ms: number;
speaker: string | null;
text: string;
confidence: number | null;
is_final: boolean;
}>
)?.map((seg) => ({
timestamp: seg.timestamp_ms,
speaker: seg.speaker,
: recordingItem.recording.transcript_segments?.map((seg) => ({
timestamp: seg.timestamp ?? 0,
speaker: seg.speaker ?? null,
text: seg.text,
confidence: seg.confidence,
is_final: seg.is_final,
confidence: seg.confidence ?? null,
is_final: seg.is_final ?? false,
})) || [];

useEffect(() => {
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/services/recordingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,16 @@ async function uploadPendingSegments(recordingId: string): Promise<void> {
throw new Error("PostHog client not initialized");
}

await client.updateDesktopRecordingTranscript(recordingId, {
segments: pendingSegments.map((seg) => ({
timestamp_ms: seg.timestamp,
await client.appendSegments(
recordingId,
pendingSegments.map((seg) => ({
timestamp: seg.timestamp,
speaker: seg.speaker,
text: seg.text,
confidence: seg.confidence,
is_final: seg.is_final,
})),
});
);

const newIndex =
recording.lastUploadedSegmentIndex + pendingSegments.length;
Expand Down