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
9 changes: 7 additions & 2 deletions apps/desktop/src/components/interactive-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface InteractiveButtonProps {
children: ReactNode;
onClick?: () => void;
onCmdClick?: () => void;
onShiftClick?: () => void;
onMouseDown?: (e: MouseEvent<HTMLElement>) => void;
contextMenu?: MenuItemDef[];
className?: string;
Expand All @@ -20,6 +21,7 @@ export function InteractiveButton({
children,
onClick,
onCmdClick,
onShiftClick,
onMouseDown,
contextMenu,
className,
Expand All @@ -34,14 +36,17 @@ export function InteractiveButton({
return;
}

if (e.metaKey || e.ctrlKey) {
if (e.shiftKey) {
e.preventDefault();
onShiftClick?.();
} else if (e.metaKey || e.ctrlKey) {
e.preventDefault();
onCmdClick?.();
} else {
onClick?.();
}
},
[onClick, onCmdClick, disabled],
[onClick, onCmdClick, onShiftClick, disabled],
);

const Element = asChild ? "div" : "button";
Expand Down
17 changes: 16 additions & 1 deletion apps/desktop/src/components/main/body/sessions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export const TabItemNote: TabItem<Extract<Tab, { type: "sessions" }>> = ({
const isEnhancing = useIsSessionEnhancing(tab.id);
const isActive = sessionMode === "active" || sessionMode === "finalizing";
const isFinalizing = sessionMode === "finalizing";
const showSpinner = !tab.active && (isFinalizing || isEnhancing);
const isBatching = sessionMode === "running_batch";
const showSpinner =
!tab.active && (isFinalizing || isEnhancing || isBatching);

const showCloseConfirmation =
pendingCloseConfirmationTab?.type === "sessions" &&
Expand Down Expand Up @@ -101,11 +103,24 @@ export function TabContentNote({
tab: Extract<Tab, { type: "sessions" }>;
}) {
const listenerStatus = useListener((state) => state.live.status);
const sessionMode = useListener((state) => state.getSessionMode(tab.id));
const updateSessionTabState = useTabs((state) => state.updateSessionTabState);
const { conn } = useSTTConnection();
const startListening = useStartListening(tab.id);
const hasAttemptedAutoStart = useRef(false);

useEffect(() => {
if (
sessionMode === "running_batch" &&
tab.state.view?.type !== "transcript"
) {
updateSessionTabState(tab, {
...tab.state,
view: { type: "transcript" },
});
}
}, [sessionMode, tab, updateSessionTabState]);

useEffect(() => {
if (!tab.state.autoStart) {
hasAttemptedAutoStart.current = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ import * as main from "../../../../../../store/tinybase/store/main";
import { useTabs } from "../../../../../../store/zustand/tabs";
import { useUndoDelete } from "../../../../../../store/zustand/undo-delete";

const UNDO_TIMEOUT_MS = 5000;

export function DeleteNote({ sessionId }: { sessionId: string }) {
const store = main.UI.useStore(main.STORE_ID);
const indexes = main.UI.useIndexes(main.STORE_ID);
const invalidateResource = useTabs((state) => state.invalidateResource);
const { setDeletedSession, setTimeoutId, clear } = useUndoDelete();
const addDeletion = useUndoDelete((state) => state.addDeletion);

const handleDeleteNote = useCallback(() => {
if (!store) {
Expand All @@ -34,27 +32,14 @@ export function DeleteNote({ sessionId }: { sessionId: string }) {
void deleteSessionCascade(store, indexes, sessionId);

if (capturedData) {
setDeletedSession(capturedData);

const timeoutId = setTimeout(() => {
clear();
}, UNDO_TIMEOUT_MS);
setTimeoutId(timeoutId);
addDeletion(capturedData);
}

void analyticsCommands.event({
event: "session_deleted",
includes_recording: true,
});
}, [
store,
indexes,
sessionId,
invalidateResource,
setDeletedSession,
setTimeoutId,
clear,
]);
}, [store, indexes, sessionId, invalidateResource, addDeletion]);

return (
<DropdownMenuItem
Expand Down
105 changes: 96 additions & 9 deletions apps/desktop/src/components/main/sidebar/timeline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import { cn, startOfDay } from "@hypr/utils";

import { useConfigValue } from "../../../../config/use-config";
import { useNativeContextMenu } from "../../../../hooks/useNativeContextMenu";
import {
captureSessionData,
deleteSessionCascade,
} from "../../../../store/tinybase/store/deleteSession";
import * as main from "../../../../store/tinybase/store/main";
import { useTabs } from "../../../../store/zustand/tabs";
import { useTimelineSelection } from "../../../../store/zustand/timeline-selection";
import { useUndoDelete } from "../../../../store/zustand/undo-delete";
import {
buildTimelineBuckets,
calculateIndicatorIndex,
Expand Down Expand Up @@ -63,6 +69,22 @@ export function TimelineView() {
return session?.event_id ? String(session.event_id) : undefined;
}, [selectedSessionId, store]);

const selectedIds = useTimelineSelection((s) => s.selectedIds);
const clearSelection = useTimelineSelection((s) => s.clear);
const indexes = main.UI.useIndexes(main.STORE_ID);
const invalidateResource = useTabs((state) => state.invalidateResource);
const addDeletion = useUndoDelete((state) => state.addDeletion);

const flatItemKeys = useMemo(() => {
const keys: string[] = [];
for (const bucket of buckets) {
for (const item of bucket.items) {
keys.push(`${item.type}-${item.id}`);
}
}
return keys;
}, [buckets]);

const {
containerRef,
isAnchorVisible: isTodayVisible,
Expand Down Expand Up @@ -106,15 +128,66 @@ export function TimelineView() {
setShowIgnored((prev) => !prev);
}, []);

const handleDeleteSelected = useCallback(() => {
if (!store || !indexes) {
return;
}

const sessionIds = selectedIds
.filter((key) => key.startsWith("session-"))
.map((key) => key.replace("session-", ""));

for (const sessionId of sessionIds) {
const capturedData = captureSessionData(store, indexes, sessionId);

invalidateResource("sessions", sessionId);
void deleteSessionCascade(store, indexes, sessionId);

if (capturedData) {
addDeletion(capturedData);
}
}

clearSelection();
}, [
store,
indexes,
selectedIds,
invalidateResource,
addDeletion,
clearSelection,
]);

const sessionCount = useMemo(
() => selectedIds.filter((key) => key.startsWith("session-")).length,
[selectedIds],
);

const contextMenuItems = useMemo(
() => [
{
id: "toggle-ignored",
text: showIgnored ? "Hide Ignored Events" : "Show Ignored Events",
action: toggleShowIgnored,
},
() =>
selectedIds.length > 0
? [
{
id: "delete-selected",
text: `Delete Selected (${sessionCount})`,
action: handleDeleteSelected,
disabled: sessionCount === 0,
},
]
: [
{
id: "toggle-ignored",
text: showIgnored ? "Hide Ignored Events" : "Show Ignored Events",
action: toggleShowIgnored,
},
],
[
selectedIds,
sessionCount,
handleDeleteSelected,
showIgnored,
toggleShowIgnored,
],
[showIgnored, toggleShowIgnored],
);

const showContextMenu = useNativeContextMenu(contextMenuItems);
Expand Down Expand Up @@ -157,20 +230,25 @@ export function TimelineView() {
selectedSessionId={selectedSessionId}
selectedEventId={selectedEventId}
timezone={timezone}
selectedIds={selectedIds}
flatItemKeys={flatItemKeys}
/>
) : (
bucket.items.map((item) => {
const itemKey = `${item.type}-${item.id}`;
const selected =
item.type === "session"
? item.id === selectedSessionId
: item.id === selectedEventId;
return (
<TimelineItemComponent
key={`${item.type}-${item.id}`}
key={itemKey}
item={item}
precision={bucket.precision}
selected={selected}
timezone={timezone}
multiSelected={selectedIds.includes(itemKey)}
flatItemKeys={flatItemKeys}
/>
);
})
Expand Down Expand Up @@ -217,13 +295,17 @@ function TodayBucket({
selectedSessionId,
selectedEventId,
timezone,
selectedIds,
flatItemKeys,
}: {
items: TimelineItem[];
precision: TimelinePrecision;
registerIndicator: (node: HTMLDivElement | null) => void;
selectedSessionId: string | undefined;
selectedEventId: string | undefined;
timezone?: string;
selectedIds: string[];
flatItemKeys: string[];
}) {
const currentTimeMs = useCurrentTimeMs();

Expand Down Expand Up @@ -267,18 +349,21 @@ function TodayBucket({
);
}

const itemKey = `${entry.item.type}-${entry.item.id}`;
const selected =
entry.item.type === "session"
? entry.item.id === selectedSessionId
: entry.item.id === selectedEventId;

nodes.push(
<TimelineItemComponent
key={`${entry.item.type}-${entry.item.id}`}
key={itemKey}
item={entry.item}
precision={precision}
selected={selected}
timezone={timezone}
multiSelected={selectedIds.includes(itemKey)}
flatItemKeys={flatItemKeys}
/>,
);
});
Expand All @@ -301,6 +386,8 @@ function TodayBucket({
selectedSessionId,
selectedEventId,
timezone,
selectedIds,
flatItemKeys,
]);

return renderedEntries;
Expand Down
Loading
Loading