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
2 changes: 1 addition & 1 deletion src/components/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ export default function ControlPanel() {
className="h-7 px-2.5 pl-1.5 gap-1"
>
<ChevronLeft size={14} strokeWidth={1.8} />
Back to notes
{t("controlPanel.backToNotes")}
</Button>
</div>
)}
Expand Down
19 changes: 2 additions & 17 deletions src/components/OnboardingFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
const { t } = useTranslation();
const { isSignedIn } = useAuth();

// Max valid step index dynamically determined based on auth state
// Signed-in users: 3 steps (Welcome, Setup, Activation) - index 0-2
// Non-signed-in users: 4 steps (Welcome, Setup, Permissions, Activation) - index 0-3
const getMaxStep = () => (isSignedIn ? 2 : 3);

const [currentStep, setCurrentStep, removeCurrentStep] = useLocalStorage(
Expand Down Expand Up @@ -350,11 +347,7 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
);

case 1: // Setup - Choose Mode & Configure (merged with permissions for signed-in users)
// Simplified path for signed-in users (cloud-first) with permissions
if (isSignedIn && !skipAuth) {
const platform = permissionsHook.pasteToolsInfo?.platform;
const isMacOS = platform === "darwin";

return (
<div className="space-y-6">
<div className="text-center">
Expand Down Expand Up @@ -589,11 +582,7 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
case 1:
// For signed-in users: Setup step includes permissions
if (isSignedIn && !skipAuth) {
return areRequiredPermissionsMet(
permissionsHook.micPermissionGranted,
permissionsHook.accessibilityPermissionGranted,
permissionsHook.pasteToolsInfo?.platform
);
return areRequiredPermissionsMet(permissionsHook.micPermissionGranted);
}

// For non-signed-in users: Setup - check if configuration is complete
Expand Down Expand Up @@ -622,11 +611,7 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
}

// For non-signed-in users, this is permissions step
return areRequiredPermissionsMet(
permissionsHook.micPermissionGranted,
permissionsHook.accessibilityPermissionGranted,
permissionsHook.pasteToolsInfo?.platform
);
return areRequiredPermissionsMet(permissionsHook.micPermissionGranted);
}
case 3:
return hotkey.trim() !== ""; // Activation step for non-signed-in users
Expand Down
7 changes: 2 additions & 5 deletions src/components/PermissionsGate.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type React from "react";
import { useTranslation } from "react-i18next";
import { Shield, Check } from "lucide-react";
import { Card, CardContent } from "./ui/card";
Expand All @@ -22,11 +23,7 @@ export default function PermissionsGate({ onComplete }: PermissionsGateProps) {

const requiredMet =
permissions.pasteToolsInfo !== null &&
areRequiredPermissionsMet(
permissions.micPermissionGranted,
permissions.accessibilityPermissionGranted,
permissions.pasteToolsInfo?.platform
);
areRequiredPermissionsMet(permissions.micPermissionGranted);

return (
<div
Expand Down
48 changes: 19 additions & 29 deletions src/components/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import PromptStudio from "./ui/PromptStudio";
import ReasoningModelSelector from "./ReasoningModelSelector";
import { HotkeyInput } from "./ui/HotkeyInput";
import { useHotkeyRegistration } from "../hooks/useHotkeyRegistration";
import { getValidationMessage, normalizeHotkey } from "../utils/hotkeyValidator";
import { validateHotkeyForSlot } from "../utils/hotkeyValidation";
import { getPlatform, getCachedPlatform } from "../utils/platform";
import { getDefaultHotkey, formatHotkeyLabel } from "../utils/hotkeys";
import { ActivationModeSelector } from "./ui/ActivationModeSelector";
Expand Down Expand Up @@ -888,38 +888,28 @@ export default function SettingsPage({ activeSection = "general" }: SettingsPage
});

const validateDictationHotkey = useCallback(
(hotkey: string) => {
const formatError = getValidationMessage(hotkey, getPlatform());
if (formatError) return formatError;
const platform = getPlatform();
const normalized = normalizeHotkey(hotkey, platform);
if (meetingKey && normalizeHotkey(meetingKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", {
slot: t("settingsPage.general.meetingHotkey.title"),
});
}
if (agentKey && normalizeHotkey(agentKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", { slot: t("agentMode.settings.hotkey") });
}
return null;
},
(hotkey: string) =>
validateHotkeyForSlot(
hotkey,
{
"settingsPage.general.meetingHotkey.title": meetingKey,
"agentMode.settings.hotkey": agentKey,
},
t
),
[meetingKey, agentKey, t]
);

const validateMeetingHotkey = useCallback(
(hotkey: string) => {
const formatError = getValidationMessage(hotkey, getPlatform());
if (formatError) return formatError;
const platform = getPlatform();
const normalized = normalizeHotkey(hotkey, platform);
if (dictationKey && normalizeHotkey(dictationKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", { slot: t("settingsPage.general.hotkey.title") });
}
if (agentKey && normalizeHotkey(agentKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", { slot: t("agentMode.settings.hotkey") });
}
return null;
},
(hotkey: string) =>
validateHotkeyForSlot(
hotkey,
{
"settingsPage.general.hotkey.title": dictationKey,
"agentMode.settings.hotkey": agentKey,
},
t
),
[dictationKey, agentKey, t]
);

Expand Down
28 changes: 5 additions & 23 deletions src/components/notes/NoteEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { ActionProcessingState } from "../../hooks/useActionProcessing";
import ActionProcessingOverlay from "./ActionProcessingOverlay";
import DictationWidget from "./DictationWidget";
import { normalizeDbDate } from "../../utils/dateFormatting";
import { parseTranscriptSegments } from "../../utils/parseTranscriptSegments";

function formatNoteDate(dateStr: string): string {
const date = normalizeDbDate(dateStr);
Expand Down Expand Up @@ -97,23 +98,7 @@ export default function NoteEditor({

const displaySegments = useMemo<TranscriptSegment[]>(() => {
if (meetingSegments && meetingSegments.length > 0) return meetingSegments;
const raw = note.transcript || "";
if (raw.startsWith("[")) {
try {
const parsed = JSON.parse(raw) as Array<{
text: string;
source: "mic" | "system";
timestamp?: number;
}>;
return parsed.map((s, i) => ({
id: `stored-${i}`,
text: s.text,
source: s.source,
timestamp: s.timestamp,
}));
} catch {}
}
return [];
return parseTranscriptSegments(note.transcript || "");
}, [meetingSegments, note.transcript]);

const hasChatSegments = displaySegments.length > 0;
Expand Down Expand Up @@ -193,11 +178,6 @@ export default function NoteEditor({
document.execCommand("insertText", false, text);
}, []);

const prevMeetingRecRef = useRef(false);
useEffect(() => {
prevMeetingRecRef.current = !!isMeetingRecording;
}, [isMeetingRecording]);

// Auto-switch to transcript view after recording stops and transcript is ready
const prevRecordingRef = useRef(false);
const pendingTranscriptSwitchRef = useRef(false);
Expand Down Expand Up @@ -390,7 +370,9 @@ export default function NoteEditor({
isRecording={isRecording || !!isMeetingRecording}
isProcessing={isProcessing}
onStart={onStartRecording}
onStop={isMeetingRecording ? onStopMeetingRecording! : onStopRecording}
onStop={
isMeetingRecording ? (onStopMeetingRecording ?? onStopRecording) : onStopRecording
}
actionPicker={isMeetingRecording ? undefined : actionPicker}
/>
</div>
Expand Down
23 changes: 10 additions & 13 deletions src/components/notes/PersonalNotesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useNoteDragAndDrop } from "../../hooks/useNoteDragAndDrop";
import { cn } from "../lib/utils";
import { MEETINGS_FOLDER_NAME } from "./shared";
import logger from "../../utils/logger";
import { parseTranscriptSegments } from "../../utils/parseTranscriptSegments";
import {
useNotes,
useActiveNoteId,
Expand Down Expand Up @@ -715,19 +716,15 @@ export default function PersonalNotesView({
let formattedTranscript = "";
let isMeetingNote = false;
if (rawTranscript) {
try {
const segments = JSON.parse(rawTranscript) as Array<{
text: string;
source: string;
}>;
if (Array.isArray(segments) && segments.length > 0 && segments[0].source) {
isMeetingNote = true;
formattedTranscript = segments
.map((s) => `${s.source === "mic" ? "You" : "Them"}: ${s.text}`)
.join("\n");
}
} catch {
// Not JSON segments — use raw transcript as-is
const segments = parseTranscriptSegments(rawTranscript);
if (segments.length > 0) {
isMeetingNote = true;
formattedTranscript = segments
.map(
(s) =>
`${s.source === "mic" ? t("notes.speaker.you") : t("notes.speaker.them")}: ${s.text}`
)
.join("\n");
}
if (!formattedTranscript) {
formattedTranscript = rawTranscript;
Expand Down
27 changes: 10 additions & 17 deletions src/components/settings/AgentModeSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { HotkeyInput } from "../ui/HotkeyInput";
import { Toggle } from "../ui/toggle";
import { SettingsRow, SettingsPanel, SettingsPanelRow, SectionHeader } from "../ui/SettingsSection";
import ReasoningModelSelector from "../ReasoningModelSelector";
import { getValidationMessage, normalizeHotkey } from "../../utils/hotkeyValidator";
import { getPlatform } from "../../utils/platform";
import { validateHotkeyForSlot } from "../../utils/hotkeyValidation";

export default function AgentModeSettings() {
const { t } = useTranslation();
Expand Down Expand Up @@ -45,21 +44,15 @@ export default function AgentModeSettings() {
const isCustomMode = cloudAgentMode === "byok";

const validateAgentHotkey = useCallback(
(hotkey: string) => {
const formatError = getValidationMessage(hotkey, getPlatform());
if (formatError) return formatError;
const platform = getPlatform();
const normalized = normalizeHotkey(hotkey, platform);
if (dictationKey && normalizeHotkey(dictationKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", { slot: t("settingsPage.general.hotkey.title") });
}
if (meetingKey && normalizeHotkey(meetingKey, platform) === normalized) {
return t("hotkey.errors.slotConflict", {
slot: t("settingsPage.general.meetingHotkey.title"),
});
}
return null;
},
(hotkey: string) =>
validateHotkeyForSlot(
hotkey,
{
"settingsPage.general.hotkey.title": dictationKey,
"settingsPage.general.meetingHotkey.title": meetingKey,
},
t
),
[dictationKey, meetingKey, t]
);

Expand Down
10 changes: 0 additions & 10 deletions src/components/ui/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { cn } from "../lib/utils";
interface RichTextEditorProps {
value: string;
onChange?: (value: string) => void;
onSelect?: () => void;
onBlur?: () => void;
placeholder?: string;
className?: string;
disabled?: boolean;
Expand All @@ -21,8 +19,6 @@ interface RichTextEditorProps {
export function RichTextEditor({
value,
onChange,
onSelect,
onBlur,
placeholder,
className,
disabled,
Expand Down Expand Up @@ -59,12 +55,6 @@ export function RichTextEditor({
internalValueRef.current = md;
onChange?.(md);
},
onSelectionUpdate: () => {
onSelect?.();
},
onBlur: () => {
onBlur?.();
},
editorProps: {
attributes: {
class: "rich-text-editor-content",
Expand Down
72 changes: 0 additions & 72 deletions src/helpers/audioManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -815,78 +815,6 @@ registerProcessor("pcm-streaming-processor", PCMStreamingProcessor);
return apiKey;
}

async optimizeAudio(audioBlob) {
return new Promise((resolve) => {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const reader = new FileReader();

reader.onload = async () => {
try {
const arrayBuffer = reader.result;
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

// Convert to 16kHz mono for smaller size and faster upload
const sampleRate = 16000;
const channels = 1;
const length = Math.floor(audioBuffer.duration * sampleRate);
const offlineContext = new OfflineAudioContext(channels, length, sampleRate);

const source = offlineContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(offlineContext.destination);
source.start();

const renderedBuffer = await offlineContext.startRendering();
const wavBlob = this.audioBufferToWav(renderedBuffer);
resolve(wavBlob);
} catch (error) {
// If optimization fails, use original
resolve(audioBlob);
}
};

reader.onerror = () => resolve(audioBlob);
reader.readAsArrayBuffer(audioBlob);
});
}

audioBufferToWav(buffer) {
const length = buffer.length;
const arrayBuffer = new ArrayBuffer(44 + length * 2);
const view = new DataView(arrayBuffer);
const sampleRate = buffer.sampleRate;
const channelData = buffer.getChannelData(0);

const writeString = (offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};

writeString(0, "RIFF");
view.setUint32(4, 36 + length * 2, true);
writeString(8, "WAVE");
writeString(12, "fmt ");
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 1, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(36, "data");
view.setUint32(40, length * 2, true);

let offset = 44;
for (let i = 0; i < length; i++) {
const sample = Math.max(-1, Math.min(1, channelData[i]));
view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
offset += 2;
}

return new Blob([arrayBuffer], { type: "audio/wav" });
}

async processWithReasoningModel(text, model, agentName) {
logger.logReasoning("CALLING_REASONING_SERVICE", {
model,
Expand Down
Loading
Loading