Skip to content

Commit 03f7280

Browse files
Add title generation button and improve auto-title generation (#2708)
* refactor(title-generation): switch to text streaming for title generation * feat(ui): add reveal animation for generated session titles * feat(sessions): add title generation button and improve auto-title generation
1 parent 1879acb commit 03f7280

File tree

3 files changed

+61
-28
lines changed

3 files changed

+61
-28
lines changed

apps/desktop/src/components/main/body/sessions/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { commands as miscCommands } from "@hypr/plugin-misc";
77

88
import AudioPlayer from "../../../../contexts/audio-player";
99
import { useListener } from "../../../../contexts/listener";
10+
import { useAutoTitle } from "../../../../hooks/useAutoTitle";
1011
import { useIsSessionEnhancing } from "../../../../hooks/useEnhancedNotes";
1112
import { useStartListening } from "../../../../hooks/useStartListening";
1213
import { useSTTConnection } from "../../../../hooks/useSTTConnection";
@@ -146,6 +147,8 @@ function TabContentNoteInner({
146147
editor: import("@hypr/tiptap/editor").TiptapEditor | null;
147148
}>(null);
148149

150+
const { generateTitle } = useAutoTitle(tab);
151+
149152
const focusTitle = React.useCallback(() => {
150153
titleInputRef.current?.focus();
151154
}, []);
@@ -168,6 +171,7 @@ function TabContentNoteInner({
168171
ref={titleInputRef}
169172
tab={tab}
170173
onNavigateToEditor={focusEditor}
174+
onGenerateTitle={generateTitle}
171175
/>
172176
</div>
173177
<div className="mt-2 px-2 flex-1 min-h-0">

apps/desktop/src/components/main/body/sessions/title-input.tsx

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { usePrevious } from "@uidotdev/usehooks";
2+
import { SparklesIcon } from "lucide-react";
23
import { forwardRef, useEffect, useRef, useState } from "react";
34

45
import { cn } from "@hypr/utils";
@@ -12,8 +13,9 @@ export const TitleInput = forwardRef<
1213
{
1314
tab: Extract<Tab, { type: "sessions" }>;
1415
onNavigateToEditor?: () => void;
16+
onGenerateTitle?: () => void;
1517
}
16-
>(({ tab, onNavigateToEditor }, ref) => {
18+
>(({ tab, onNavigateToEditor, onGenerateTitle }, ref) => {
1719
const {
1820
id: sessionId,
1921
state: { view },
@@ -23,15 +25,19 @@ export const TitleInput = forwardRef<
2325
const wasGenerating = usePrevious(isGenerating);
2426
const [showRevealAnimation, setShowRevealAnimation] = useState(false);
2527

28+
const editorId = view ? "active" : "inactive";
29+
const internalRef = useRef<HTMLInputElement>(null);
30+
const inputRef = (ref as React.RefObject<HTMLInputElement>) || internalRef;
31+
2632
useEffect(() => {
27-
if (wasGenerating && !isGenerating && title) {
33+
if (wasGenerating && !isGenerating) {
2834
setShowRevealAnimation(true);
2935
const timer = setTimeout(() => {
3036
setShowRevealAnimation(false);
3137
}, 1000);
3238
return () => clearTimeout(timer);
3339
}
34-
}, [wasGenerating, isGenerating, title]);
40+
}, [wasGenerating, isGenerating]);
3541

3642
const handleEditTitle = main.UI.useSetPartialRowCallback(
3743
"sessions",
@@ -41,10 +47,6 @@ export const TitleInput = forwardRef<
4147
main.STORE_ID,
4248
);
4349

44-
const editorId = view ? "active" : "inactive";
45-
const internalRef = useRef<HTMLInputElement>(null);
46-
const inputRef = (ref as React.RefObject<HTMLInputElement>) || internalRef;
47-
4850
useEffect(() => {
4951
const handleMoveToTitlePosition = (e: Event) => {
5052
const customEvent = e as CustomEvent<{ pixelWidth: number }>;
@@ -172,20 +174,42 @@ export const TitleInput = forwardRef<
172174
);
173175
}
174176

177+
const hasTitle = Boolean(title?.trim());
178+
const showButton = hasTitle && onGenerateTitle;
179+
175180
return (
176-
<input
177-
ref={inputRef}
178-
id={`title-input-${sessionId}-${editorId}`}
179-
placeholder="Untitled"
180-
type="text"
181-
onChange={(e) => handleEditTitle(e.target.value)}
182-
onKeyDown={handleKeyDown}
183-
value={title ?? ""}
184-
className={cn([
185-
"w-full transition-opacity duration-200",
186-
"border-none bg-transparent focus:outline-none",
187-
"text-xl font-semibold placeholder:text-muted-foreground",
188-
])}
189-
/>
181+
<div className="flex items-center gap-2 w-full">
182+
<input
183+
ref={inputRef}
184+
id={`title-input-${sessionId}-${editorId}`}
185+
placeholder="Untitled"
186+
type="text"
187+
onChange={(e) => handleEditTitle(e.target.value)}
188+
onKeyDown={handleKeyDown}
189+
value={title ?? ""}
190+
className={cn([
191+
"flex-1 min-w-0 transition-opacity duration-200",
192+
"border-none bg-transparent focus:outline-none",
193+
"text-xl font-semibold placeholder:text-muted-foreground",
194+
])}
195+
/>
196+
{showButton && (
197+
<button
198+
onClick={(e) => {
199+
e.preventDefault();
200+
e.stopPropagation();
201+
onGenerateTitle?.();
202+
}}
203+
onMouseDown={(e) => e.preventDefault()}
204+
className={cn([
205+
"shrink-0 p-1",
206+
"text-muted-foreground hover:text-foreground",
207+
"opacity-50 hover:opacity-100 transition-opacity",
208+
])}
209+
>
210+
<SparklesIcon className="w-4 h-4" />
211+
</button>
212+
)}
213+
</div>
190214
);
191215
});

apps/desktop/src/hooks/useAutoTitle.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,7 @@ export function useAutoTitle(tab: Extract<Tab, { type: "sessions" }>) {
4646
},
4747
});
4848

49-
const attemptGenerateTitle = useCallback(() => {
50-
const trimmedTitle = title?.trim();
51-
if (trimmedTitle) {
52-
return;
53-
}
54-
49+
const generateTitle = useCallback(() => {
5550
if (!model) {
5651
return;
5752
}
@@ -60,7 +55,16 @@ export function useAutoTitle(tab: Extract<Tab, { type: "sessions" }>) {
6055
model,
6156
args: { sessionId },
6257
});
63-
}, [title, model, titleTask, sessionId]);
58+
}, [model, titleTask, sessionId]);
59+
60+
const attemptGenerateTitle = useCallback(() => {
61+
const trimmedTitle = title?.trim();
62+
if (trimmedTitle) {
63+
return;
64+
}
65+
66+
generateTitle();
67+
}, [title, generateTitle]);
6468

6569
const hasGeneratedForContentRef = useRef<string | null>(null);
6670

@@ -86,5 +90,6 @@ export function useAutoTitle(tab: Extract<Tab, { type: "sessions" }>) {
8690

8791
return {
8892
isGenerating: titleTask.isGenerating,
93+
generateTitle,
8994
};
9095
}

0 commit comments

Comments
 (0)