Skip to content

Commit 604aef4

Browse files
committed
Add AI-powered title emoji helper
1 parent 42b6bd9 commit 604aef4

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

components/PromptEditor.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({ documentNode, onSave, o
8383
const [isDirty, setIsDirty] = useState(false);
8484
const [isSaving, setIsSaving] = useState(false);
8585
const [isGeneratingTitle, setIsGeneratingTitle] = useState(false);
86+
const [isGeneratingEmoji, setIsGeneratingEmoji] = useState(false);
8687
const [isCopied, setIsCopied] = useState(false);
8788
const [viewMode, setViewMode] = useState<ViewMode>(resolveDefaultViewMode(documentNode.default_view_mode, documentNode.language_hint));
8889
const [splitSize, setSplitSize] = useState(50);
@@ -389,6 +390,31 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({ documentNode, onSave, o
389390
}
390391
};
391392

393+
const handleAddEmojiToTitle = async () => {
394+
if (!settings.llmProviderUrl || !settings.llmModelName || !title.trim()) return;
395+
setIsGeneratingEmoji(true);
396+
setError(null);
397+
addLog('INFO', `Attempting to generate emoji for title "${title}".`);
398+
try {
399+
const emoji = await llmService.generateEmojiForTitle(title, settings, addLog);
400+
setTitle((currentTitle) => {
401+
const baseTitle = currentTitle.trim();
402+
const emojiPrefixRegex = /^[\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji}\ufe0f]+\s*/u;
403+
const strippedTitle = baseTitle.replace(emojiPrefixRegex, '').trim();
404+
if (!strippedTitle) {
405+
return `${emoji}`;
406+
}
407+
return `${emoji} ${strippedTitle}`;
408+
});
409+
} catch (err) {
410+
const message = err instanceof Error ? err.message : 'Unknown error';
411+
setError(`Could not generate emoji: ${message}`);
412+
addLog('ERROR', `Could not generate emoji: ${message}`);
413+
} finally {
414+
setIsGeneratingEmoji(false);
415+
}
416+
};
417+
392418
const acceptRefinement = () => {
393419
if (refinedContent) {
394420
setContent(refinedContent);
@@ -527,6 +553,23 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({ documentNode, onSave, o
527553
<div className="flex justify-between items-center px-4 h-7 gap-4 border-b border-border-color flex-shrink-0 bg-secondary">
528554
<div className="flex items-center gap-3 flex-1 min-w-0">
529555
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Document Title" disabled={isGeneratingTitle} className="bg-transparent text-base font-semibold text-text-main focus:outline-none w-full truncate"/>
556+
{supportsAiTools && (
557+
<IconButton
558+
onClick={handleAddEmojiToTitle}
559+
disabled={
560+
isGeneratingEmoji ||
561+
!title.trim() ||
562+
!settings.llmProviderUrl ||
563+
!settings.llmModelName
564+
}
565+
tooltip="Add Emoji to Title"
566+
size="xs"
567+
variant="ghost"
568+
className="flex-shrink-0"
569+
>
570+
{isGeneratingEmoji ? <Spinner /> : <span className="text-base">😊</span>}
571+
</IconButton>
572+
)}
530573
{supportsAiTools && (
531574
<IconButton onClick={handleGenerateTitle} disabled={isGeneratingTitle || !content.trim() || !settings.llmProviderUrl} tooltip="Regenerate Title with AI" size="xs" variant="ghost" className="flex-shrink-0">
532575
{isGeneratingTitle ? <Spinner /> : <RefreshIcon className="w-4 h-4 text-primary" />}

services/llmService.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ const createBody = (apiType: 'ollama' | 'openai' | 'unknown', model: string, con
3131
});
3232
};
3333

34+
const extractEmoji = (raw: string): string | null => {
35+
const trimmed = raw.trim();
36+
if (!trimmed) return null;
37+
const chars = Array.from(trimmed);
38+
const emojiRegex = /\p{Extended_Pictographic}/u;
39+
for (const char of chars) {
40+
if (emojiRegex.test(char)) {
41+
return char;
42+
}
43+
}
44+
return null;
45+
};
46+
3447
const makeLLMRequest = async (
3548
metaPrompt: string,
3649
settings: Settings,
@@ -114,4 +127,18 @@ Title:`;
114127
addLog('INFO', `Successfully generated and cleaned title: "${cleanedTitle}"`);
115128
return cleanedTitle;
116129
},
130+
generateEmojiForTitle: async (title: string, settings: Settings, addLog: (level: LogLevel, message: string) => void): Promise<string> => {
131+
const metaPrompt = `You will be given the title of a document. Respond with exactly one emoji that best represents the theme or mood of that title. Do not include any additional words, punctuation, or explanation. If no emoji is appropriate, respond with a single page icon emoji.
132+
133+
Title:
134+
---
135+
${title}
136+
---
137+
Emoji:`;
138+
139+
const result = await makeLLMRequest(metaPrompt, settings, addLog);
140+
const emoji = extractEmoji(result) ?? '📄';
141+
addLog('INFO', `Generated emoji "${emoji}" for title "${title}"`);
142+
return emoji;
143+
},
117144
};

0 commit comments

Comments
 (0)